add-module
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAdd Module Skill
模块添加技能
Automates creation of new modules in the CLY project following established patterns.
按照既定模式自动完成CLY项目中新模块的创建工作。
Module Isolation Principle (CRITICAL)
模块隔离原则(至关重要)
The Portability Test: Before creating a module, ask: "If I copy this module folder to a new repo, how hard would it be to make it work?"
The answer should be: trivially easy.
可移植性测试:创建模块前,请自问:“如果我将这个模块文件夹复制到新仓库中,让它正常工作有多难?”
答案应该是:极其简单。
Requirements for Isolation
隔离性要求
- Self-contained: All module logic lives within its directory
- Minimal dependencies: Only depend on stdlib, Bubbletea/Bubbles/Lipgloss, and Cobra
- No cross-module imports: Modules NEVER import from other modules
- Single registration point: Only touch parent's for registration
cmd.go - Own types: Define types locally, don't reach into other packages
- 自包含:所有模块逻辑都存放在其目录内
- 最小依赖:仅依赖标准库、Bubbletea/Bubbles/Lipgloss和Cobra
- 禁止跨模块导入:模块绝对不能从其他模块导入代码
- 单一注册点:仅需修改父级的完成注册
cmd.go - 本地类型定义:在本地定义类型,不要引用其他包的类型
What a Module Can Import
模块允许导入的内容
✓ Standard library (fmt, strings, etc.)
✓ github.com/charmbracelet/bubbletea
✓ github.com/charmbracelet/bubbles/*
✓ github.com/charmbracelet/lipgloss
✓ github.com/spf13/cobra
✓ github.com/yurifrl/cly/pkg/* (shared utilities - see below)
✗ github.com/yurifrl/cly/modules/* (NEVER)✓ 标准库(fmt、strings等)
✓ github.com/charmbracelet/bubbletea
✓ github.com/charmbracelet/bubbles/*
✓ github.com/charmbracelet/lipgloss
✓ github.com/spf13/cobra
✓ github.com/yurifrl/cly/pkg/* (共享工具类 - 见下文)
✗ github.com/yurifrl/cly/modules/* (绝对禁止)Avoiding Duplication (The Balance)
避免重复代码(平衡之道)
Isolation doesn't mean blind copy-paste. Use for genuinely shared code:
pkg/| Location | Purpose | Example |
|---|---|---|
| Shared Lipgloss styles | Colors, borders, common styles |
| Common keybindings | Quit keys, navigation patterns |
| TUI utilities | Screen helpers, common components |
Rule of Three: Only extract to when 3+ modules need the same code.
pkg/Duplication is OK when:
- Variations exist between modules
- Extraction would create tight coupling
Extract to pkg/ when:
- Exact same code in 3+ places
- Code is substantial and stable
- Changes should propagate everywhere
隔离性不意味着盲目复制粘贴。可将真正需要共享的代码放入目录:
pkg/| 位置 | 用途 | 示例 |
|---|---|---|
| 共享Lipgloss样式 | 颜色、边框、通用样式 |
| 通用按键绑定 | 退出键、导航模式 |
| TUI工具类 | 屏幕辅助函数、通用组件 |
三次原则:只有当3个及以上模块需要相同代码时,才将其提取到中。
pkg/以下情况允许重复代码:
- 模块之间存在功能差异
- 提取代码会导致强耦合
以下情况需提取到pkg/中:
- 3个及以上位置存在完全相同的代码
- 代码具备一定规模且稳定
- 代码变更需要同步到所有使用处
Module Directory = Complete Unit
模块目录 = 完整单元
modules/demo/spinner/
├── cmd.go # Registration only
├── spinner.go # All logic here
└── (optional) # Helpers if needed, but keep in same packageCopy this folder → paste in new project → change import path → works.
modules/demo/spinner/
├── cmd.go # 仅用于注册
├── spinner.go # 所有逻辑存放于此
└── (可选) # 辅助文件(如需),但需保持在同一个包内复制该文件夹 → 粘贴到新项目 → 修改导入路径 → 即可正常运行。
When to Use This Skill
何时使用本技能
- User wants to add a new demo module showcasing a Bubbletea component
- User wants to create a new utility command
- User mentions "create a module", "add a command", "new demo"
- 用户希望添加展示Bubbletea组件的新演示模块
- 用户希望创建新的实用工具命令
- 用户提到“创建模块”、“添加命令”、“新演示”等需求
Module Types
模块类型
Demo Modules (modules/demo/<name>/
)
modules/demo/<name>/演示模块(modules/demo/<name>/
)
modules/demo/<name>/Purpose: Showcase Charm UI components and patterns
Examples: chat, spinner, table, list-simple (48 total)
Parent: Registered under namespace
demo用途:展示Charm UI组件和模式
示例:chat、spinner、table、list-simple(共48个)
父级:注册在命名空间下
demoUtility Modules (modules/<name>/
)
modules/<name>/实用工具模块(modules/<name>/
)
modules/<name>/Purpose: Provide real functionality
Examples: uuid (UUID generator)
Parent: Registered directly under root command
用途:提供实际功能
示例:uuid(UUID生成器)
父级:直接注册在根命令下
Step-by-Step Workflow
分步工作流程
Determine Module Type
确定模块类型
Ask user if unclear:
- "Is this a demo (showcase component) or utility (real functionality)?"
若不确定,询问用户:
- “这是演示模块(展示组件)还是实用工具模块(提供实际功能)?”
Find Reference (for demos)
查找参考示例(针对演示模块)
- Check if component exists in
references/bubbletea/examples/<name>/ - Read the reference implementation
- Note initialization code in main() function
- 检查中是否存在对应组件
references/bubbletea/examples/<name>/ - 阅读参考实现代码
- 记录main()函数中的初始化代码
Create Directory Structure
创建目录结构
bash
undefinedbash
undefinedFor demo:
演示模块:
mkdir -p modules/demo/<name>
mkdir -p modules/demo/<name>
For utility:
实用工具模块:
mkdir -p modules/<name>
undefinedmkdir -p modules/<name>
undefinedCreate cmd.go (Command Registration)
创建cmd.go(命令注册)
Template for demos:
go
package <packagename>
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra"
)
func Register(parent *cobra.Command) {
cmd := &cobra.Command{
Use: "<name>",
Short: "<description>",
RunE: run,
}
parent.AddCommand(cmd)
}
func run(cmd *cobra.Command, args []string) error {
p := tea.NewProgram(initialModel())
if _, err := p.Run(); err != nil {
return err
}
return nil
}Add tea.Program options if needed:
- - For fullscreen demos
tea.WithAltScreen() - - For mouse tracking
tea.WithMouseAllMotion() - - For focus/blur events
tea.WithReportFocus()
演示模块模板:
go
package <packagename>
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra"
)
func Register(parent *cobra.Command) {
cmd := &cobra.Command{
Use: "<name>",
Short: "<description>",
RunE: run,
}
parent.AddCommand(cmd)
}
func run(cmd *cobra.Command, args []string) error {
p := tea.NewProgram(initialModel())
if _, err := p.Run(); err != nil {
return err
}
return nil
}如需添加tea.Program选项:
- - 用于全屏演示
tea.WithAltScreen() - - 用于鼠标追踪
tea.WithMouseAllMotion() - - 用于获取焦点/失焦事件
tea.WithReportFocus()
Create Implementation File
创建实现文件
Extract from reference:
- Copy type definitions (model struct, custom types)
- Copy Init(), Update(), View() methods
- Create initialModel() from main() function's initialization code
- Remove unused imports (fmt, os, log often unused after main() removal)
Template:
go
package <packagename>
import (
tea "github.com/charmbracelet/bubbletea"
// Component imports as needed
)
type model struct {
// State fields
}
func initialModel() model {
// Initialization from reference's main()
return model{}
}
func (m model) Init() tea.Cmd {
return nil
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q", "ctrl+c":
return m, tea.Quit
}
}
return m, nil
}
func (m model) View() string {
return "Your UI\n"
}从参考示例中提取内容:
- 复制类型定义(model结构体、自定义类型)
- 复制Init()、Update()、View()方法
- 从main()函数的初始化代码中创建initialModel()
- 移除未使用的导入(移除main()后,fmt、os、log通常不再被使用)
模板:
go
package <packagename>
import (
tea "github.com/charmbracelet/bubbletea"
// 根据需要导入组件
)
type model struct {
// 状态字段
}
func initialModel() model {
// 从参考示例的main()中提取初始化逻辑
return model{}
}
func (m model) Init() tea.Cmd {
return nil
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q", "ctrl+c":
return m, tea.Quit
}
}
return m, nil
}
func (m model) View() string {
return "Your UI\n"
}Register Module
注册模块
For demos - Edit :
modules/demo/cmd.gogo
import (
yourmodule "github.com/yurifrl/cly/modules/demo/your-module"
)
func init() {
// ... existing registrations
yourmodule.Register(DemoCmd)
}For utilities - Edit :
cmd/root.gogo
import (
"github.com/yurifrl/cly/modules/yourutil"
)
func init() {
// ... existing registrations
yourutil.Register(RootCmd)
}演示模块 - 编辑:
modules/demo/cmd.gogo
import (
yourmodule "github.com/yurifrl/cly/modules/demo/your-module"
)
func init() {
// ... 已有的注册代码
yourmodule.Register(DemoCmd)
}实用工具模块 - 编辑:
cmd/root.gogo
import (
"github.com/yurifrl/cly/modules/yourutil"
)
func init() {
// ... 已有的注册代码
yourutil.Register(RootCmd)
}Validation Checklist
验证检查清单
- Compiles:
go build - Shows in help: or
go run main.go --helpgo run main.go demo --help - Runs: (or
go run main.go <command>)go run main.go demo <name> - Quits cleanly with 'q' or Ctrl+C
- No unused imports
- 可编译:
go build - 显示在帮助中:或
go run main.go --helpgo run main.go demo --help - 可运行:(或
go run main.go <command>)go run main.go demo <name> - 可通过'q'或Ctrl+C正常退出
- 无未使用的导入
Common Patterns
通用模式
Package Naming
包命名
- Directory with hyphens:
list-simple/ - Package name with underscores:
package list_simple - Import alias:
listsimple "github.com/yurifrl/cly/modules/demo/list-simple"
- 目录名含连字符:
list-simple/ - 包名含下划线:
package list_simple - 导入别名:
listsimple "github.com/yurifrl/cly/modules/demo/list-simple"
Extracting initialModel()
提取initialModel()
In reference main():
go
func main() {
s := spinner.New()
s.Spinner = spinner.Dot
m := model{spinner: s}
tea.NewProgram(m).Run()
}Extract to:
go
func initialModel() model {
s := spinner.New()
s.Spinner = spinner.Dot
return model{spinner: s}
}参考示例的main()函数:
go
func main() {
s := spinner.New()
s.Spinner = spinner.Dot
m := model{spinner: s}
tea.NewProgram(m).Run()
}提取后:
go
func initialModel() model {
s := spinner.New()
s.Spinner = spinner.Dot
return model{spinner: s}
}Helper Functions
辅助函数
If reference has helpers (like getPackages(), filter(), etc.), copy them to the implementation file.
若参考示例中有辅助函数(如getPackages()、filter()等),将其复制到实现文件中。
Examples to Reference
参考示例
Simple: - Basic component
Complex: - Multiple files (delegate.go, randomitems.go)
Utility: - Real functionality with list UI
Advanced: - Multiple components (textarea + viewport)
modules/demo/spinner/modules/demo/list-fancy/modules/uuid/modules/demo/chat/简单示例: - 基础组件
复杂示例: - 多文件(delegate.go、randomitems.go)
实用工具示例: - 带列表UI的实际功能
高级示例: - 多组件(textarea + viewport)
modules/demo/spinner/modules/demo/list-fancy/modules/uuid/modules/demo/chat/Quick Reference Commands
快速参考命令
bash
undefinedbash
undefinedTest compilation
测试编译
go build
go build
View help
查看帮助
go run main.go --help
go run main.go demo --help
go run main.go --help
go run main.go demo --help
Run demo
运行演示模块
go run main.go demo <name>
go run main.go demo <name>
Run utility
运行实用工具模块
go run main.go <name>
go run main.go <name>
Clean dependencies
清理依赖
go mod tidy
undefinedgo mod tidy
undefinedBest Practices
最佳实践
Start from reference - All 48 Bubbletea examples available in references/
Copy existing module - Fastest way to get structure right
Test incrementally - Build and run after each file
Clean imports early - Remove fmt/os/log before testing
Follow naming - Hyphens in names, underscores in packages
从参考示例开始 - references/中提供了全部48个Bubbletea示例
复制现有模块 - 快速获取正确结构的最佳方式
增量测试 - 每完成一个文件就编译运行测试
尽早清理导入 - 测试前移除fmt/os/log等未使用的导入
遵循命名规范 - 名称用连字符,包名用下划线
Troubleshooting
故障排查
Build Errors
编译错误
- "undefined: initialModel" → Function not created or private (make sure it's , not
initialModel)InitialModel - "unused import" → Remove it from imports
- "package name mismatch" → Check hyphens vs underscores
- "undefined: initialModel" → 未创建该函数或函数为私有(确保是而非
initialModel)InitialModel - "unused import" → 从导入中移除该包
- "package name mismatch" → 检查连字符与下划线的对应关系
Runtime Errors
运行时错误
- "could not open TTY" → Normal in non-interactive shells, try in terminal
- Component not responding → Check Update() delegates to component's Update()
- Can't quit → Verify KeyMsg handling for "q" and "ctrl+c"
- "could not open TTY" → 在非交互式Shell中属于正常情况,请在终端中尝试运行
- 组件无响应 → 检查Update()是否委托给了组件的Update()
- 无法退出 → 验证KeyMsg是否处理了"q"和"ctrl+c"
Module Template
模块模板
Use this template to add new commands quickly.
使用本模板快速添加新命令。
Module Categories
模块分类
Demo Modules (UI Component Showcases)
演示模块(UI组件展示)
Location:
Purpose: Demonstrate Charm components and patterns
Examples: , , ,
modules/demo/<name>/chatspinnertablelist-simpleWhen to use: Showcasing UI components, TUI patterns, Bubbletea features
位置:
用途:演示Charm组件和模式
示例:、、、
modules/demo/<name>/chatspinnertablelist-simple适用场景:展示UI组件、TUI模式、Bubbletea特性
Utility Modules (Real Functionality)
实用工具模块(实际功能)
Location:
Purpose: Provide actual utility commands
Examples: (UUID generator)
modules/<name>/uuidWhen to use: Commands users will actually use for work
位置:
用途:提供实用命令
示例:(UUID生成器)
modules/<name>/uuid适用场景:用户实际工作中会用到的命令
Quick Steps
快速步骤
For Demo Modules
演示模块
- Find reference:
references/bubbletea/examples/<component>/ - Copy pattern:
cp -r modules/demo/spinner modules/demo/<newname> - Adapt implementation from reference
- Register in init()
modules/demo/cmd.go - Test
- 查找参考示例:
references/bubbletea/examples/<component>/ - 复制模板:
cp -r modules/demo/spinner modules/demo/<newname> - 根据参考示例调整实现代码
- 在的init()中注册
modules/demo/cmd.go - 测试
For Utility Modules
实用工具模块
- Copy pattern:
cp -r modules/uuid modules/<newname> - Implement functionality
- Register in init()
cmd/root.go - Test
- 复制模板:
cp -r modules/uuid modules/<newname> - 实现功能
- 在的init()中注册
cmd/root.go - 测试
Demo Module Template
演示模块模板
File: modules/demo/<name>/cmd.go
modules/demo/<name>/cmd.go文件:modules/demo/<name>/cmd.go
modules/demo/<name>/cmd.gogo
package <packagename> // Use underscores for hyphens: list_simple for list-simple
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra"
)
func Register(parent *cobra.Command) {
cmd := &cobra.Command{
Use: "<name>",
Short: "<short description>",
Long: "<detailed description>",
RunE: run,
}
parent.AddCommand(cmd)
}
func run(cmd *cobra.Command, args []string) error {
p := tea.NewProgram(initialModel())
if _, err := p.Run(); err != nil {
return err
}
return nil
}go
package <packagename> // 含连字符的目录对应下划线的包名:list-simple对应list_simple
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra"
)
func Register(parent *cobra.Command) {
cmd := &cobra.Command{
Use: "<name>",
Short: "<简短描述>",
Long: "<详细描述>",
RunE: run,
}
parent.AddCommand(cmd)
}
func run(cmd *cobra.Command, args []string) error {
p := tea.NewProgram(initialModel())
if _, err := p.Run(); err != nil {
return err
}
return nil
}File: modules/demo/<name>/<name>.go
modules/demo/<name>/<name>.go文件:modules/demo/<name>/<name>.go
modules/demo/<name>/<name>.gogo
package <packagename>
import (
tea "github.com/charmbracelet/bubbletea"
// Add component imports as needed:
// "github.com/charmbracelet/bubbles/spinner"
// "github.com/charmbracelet/bubbles/list"
// "github.com/charmbracelet/bubbles/table"
// "github.com/charmbracelet/lipgloss"
)
type model struct {
// Component state
quitting bool
err error
}
func initialModel() model {
// Initialize your model here
// Extract this from reference example's main() function
return model{}
}
func (m model) Init() tea.Cmd {
return nil
// Or return component's Init: spinner.Tick, textarea.Blink, etc.
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q", "ctrl+c":
m.quitting = true
return m, tea.Quit
}
}
return m, nil
}
func (m model) View() string {
if m.quitting {
return "Goodbye!\n"
}
return "Your UI here\nPress q to quit\n"
}go
package <packagename>
import (
tea "github.com/charmbracelet/bubbletea"
// 根据需要添加组件导入:
// "github.com/charmbracelet/bubbles/spinner"
// "github.com/charmbracelet/bubbles/list"
// "github.com/charmbracelet/bubbles/table"
// "github.com/charmbracelet/lipgloss"
)
type model struct {
// 组件状态
quitting bool
err error
}
func initialModel() model {
// 在此处初始化模型
// 从参考示例的main()函数中提取逻辑
return model{}
}
func (m model) Init() tea.Cmd {
return nil
// 或返回组件的Init:spinner.Tick、textarea.Blink等
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q", "ctrl+c":
m.quitting = true
return m, tea.Quit
}
}
return m, nil
}
func (m model) View() string {
if m.quitting {
return "Goodbye!\n"
}
return "Your UI here\nPress q to quit\n"
}Registration Patterns
注册模式
Demo Module Registration
演示模块注册
File:
modules/demo/cmd.gogo
import (
// ...
yourmodule "github.com/yurifrl/cly/modules/demo/your-module"
)
func init() {
// ...
yourmodule.Register(DemoCmd)
}文件:
modules/demo/cmd.gogo
import (
// ...
yourmodule "github.com/yurifrl/cly/modules/demo/your-module"
)
func init() {
// ...
yourmodule.Register(DemoCmd)
}Utility Module Registration
实用工具模块注册
File:
cmd/root.gogo
import (
// ...
"github.com/yurifrl/cly/modules/yourutil"
)
func init() {
uuid.Register(RootCmd)
demo.Register(RootCmd)
yourutil.Register(RootCmd) // Add here
}文件:
cmd/root.gogo
import (
// ...
"github.com/yurifrl/cly/modules/yourutil"
)
func init() {
uuid.Register(RootCmd)
demo.Register(RootCmd)
yourutil.Register(RootCmd) // 在此添加
}Bubbletea Program Options
Bubbletea Program选项
Some demos require special options when creating the tea.Program:
部分演示在创建tea.Program时需要特殊选项:
AltScreen (Fullscreen Mode)
AltScreen(全屏模式)
go
func run(cmd *cobra.Command, args []string) error {
p := tea.NewProgram(initialModel(), tea.WithAltScreen())
_, err := p.Run()
return err
}Use when: Demo should use alternate screen buffer (fullscreen, eyes, cellbuffer)
Examples: ,
modules/demo/fullscreen/modules/demo/eyes/go
func run(cmd *cobra.Command, args []string) error {
p := tea.NewProgram(initialModel(), tea.WithAltScreen())
_, err := p.Run()
return err
}适用场景:演示需要使用备用屏幕缓冲区(全屏、eyes、cellbuffer)
示例:、
modules/demo/fullscreen/modules/demo/eyes/Mouse Support
鼠标支持
go
p := tea.NewProgram(initialModel(), tea.WithMouseAllMotion())Use when: Demo needs mouse tracking
Example:
modules/demo/mouse/go
p := tea.NewProgram(initialModel(), tea.WithMouseAllMotion())适用场景:演示需要追踪鼠标
示例:
modules/demo/mouse/Focus Reporting
焦点报告
go
p := tea.NewProgram(initialModel(), tea.WithReportFocus())Use when: Demo needs to know when terminal gains/loses focus
Example:
modules/demo/focus-blur/go
p := tea.NewProgram(initialModel(), tea.WithReportFocus())适用场景:演示需要知道终端何时获取/失去焦点
示例:
modules/demo/focus-blur/Input Filtering
输入过滤
go
p := tea.NewProgram(initialModel(), tea.WithFilter(filterFunc))Use when: Need to intercept/modify messages before Update()
Example:
modules/demo/prevent-quit/go
p := tea.NewProgram(initialModel(), tea.WithFilter(filterFunc))适用场景:需要在Update()之前拦截/修改消息
示例:
modules/demo/prevent-quit/Reference Examples (48 Available)
参考示例(共48个)
All 48 Bubbletea examples are in and :
references/bubbletea/examples/modules/demo/全部48个Bubbletea示例位于和中:
references/bubbletea/examples/modules/demo/Core Components
核心组件
| Demo | Shows | Reference |
|---|---|---|
| Animated loading | |
| Selection lists | |
| Data tables | |
| Single-line input | |
| Multi-line input | |
| Progress bars | |
| 演示模块 | 展示内容 | 参考位置 |
|---|---|---|
| 动画加载效果 | |
| 选择列表 | |
| 数据表格 | |
| 单行输入框 | |
| 多行输入框 | |
| 进度条 | |
Advanced
高级示例
| Demo | Shows | Reference |
|---|---|---|
| Textarea + Viewport | |
| File selection | |
| Complex forms | |
| Multiple panes | |
All 48 examples are available - explore for implementations.
modules/demo/| 演示模块 | 展示内容 | 参考位置 |
|---|---|---|
| 文本框 + 视口 | |
| 文件选择器 | |
| 复杂表单 | |
| 多面板 | |
全部48个示例均可使用 - 可浏览查看实现代码。
modules/demo/Adapting Reference Examples
适配参考示例的步骤
Step-by-Step Process
分步流程
Find reference:
Read main() function: This has initialization code
Extract to initialModel(): Move setup from main() to initialModel()
Copy Model implementation: Copy type definitions, Init(), Update(), View()
Clean imports: Remove , , if unused
Create cmd.go: Use template above with Register() function
references/bubbletea/examples/<component>/main.gofmtoslog查找参考示例:
阅读main()函数:此处包含初始化代码
提取到initialModel():将main()中的设置逻辑移至initialModel()
复制Model实现:复制类型定义、Init()、Update()、View()
清理导入:若未使用则移除、、
创建cmd.go:使用上述模板编写Register()函数
references/bubbletea/examples/<component>/main.gofmtoslogExample: Adapting Spinner
示例:适配Spinner模块
Reference:
references/bubbletea/examples/spinner/main.goExtract this from main():
go
s := spinner.New()
s.Spinner = spinner.Dot
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
return model{spinner: s}Becomes initialModel():
go
func initialModel() model {
s := spinner.New()
s.Spinner = spinner.Dot
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
return model{spinner: s}
}参考示例:
references/bubbletea/examples/spinner/main.go从main()中提取以下内容:
go
s := spinner.New()
s.Spinner = spinner.Dot
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
return model{spinner: s}转换为initialModel():
go
func initialModel() model {
s := spinner.New()
s.Spinner = spinner.Dot
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
return model{spinner: s}
}Naming Conventions
命名规范
Command Names
命令名称
- Lowercase only
- Hyphens for multi-word: ,
list-simple,credit-card-formaltscreen-toggle
- 仅使用小写字母
- 多词名称使用连字符分隔:、
list-simple、credit-card-formaltscreen-toggle
Package Names
包名称
- Lowercase, no hyphens
- Use underscores: ,
list_simple,credit_card_formaltscreen_toggle - Go converts hyphens automatically during import
- 仅使用小写字母,禁止使用连字符
- 使用下划线分隔:、
list_simple、credit_card_formaltscreen_toggle - Go在导入时会自动转换连字符
File Names
文件名称
- - Always this name (command registration)
cmd.go - - Main implementation (e.g.,
<name>.go,spinner.go)list-simple.go - Additional files: ,
delegate.go,helpers.go(if needed)types.go
- - 固定名称(用于命令注册)
cmd.go - - 主实现文件(如
<name>.go、spinner.go)list-simple.go - 额外文件:、
delegate.go、helpers.go(如需拆分复杂逻辑)types.go
Checklist
检查清单
Before Implementation
实现前
- Decided: demo or utility module?
- Found reference example (if demo)
- Command name chosen (lowercase, hyphens if multi-word)
- 确定:是演示模块还是实用工具模块?
- 找到参考示例(针对演示模块)
- 选定命令名称(小写,多词用连字符)
During Implementation
实现中
- Created directory in correct location
- Created cmd.go with Register() function
- Created implementation file with initialModel()
- Package name matches conventions
- Imports are clean (no unused)
- 在正确位置创建目录
- 创建带Register()函数的cmd.go
- 创建带initialModel()的实现文件
- 包名称符合规范
- 导入列表已清理(无未使用的包)
After Implementation
实现后
- Registered in parent cmd.go init()
- Import added to parent cmd.go
- Compiles:
go build - Appears in help: or
go run main.go --helpgo run main.go demo --help - Runs:
go run main.go <command> - Quits cleanly with 'q' or Ctrl+C
- 在父级cmd.go的init()中完成注册
- 父级cmd.go中已添加对应导入
- 可编译:
go build - 显示在帮助中:或
go run main.go --helpgo run main.go demo --help - 可运行:
go run main.go <command> - 可通过'q'或Ctrl+C正常退出
Tips
技巧
Start with existing demos - 48 working examples to learn from
Copy working code - Don't reinvent, adapt from references
Test frequently - Build and run after each change
Keep it simple - Single file until complexity demands splitting
Use shared styles - Import for consistent theming
Follow the pattern - Look at 3-4 similar modules before starting
pkg/style从现有演示模块开始 - 48个可运行的示例可供学习
复制可运行代码 - 不要重复造轮子,从参考示例中适配
频繁测试 - 每完成一处修改就编译运行测试
保持简洁 - 除非复杂度要求,否则使用单一文件
使用共享样式 - 导入以保持主题一致性
遵循现有模式 - 开始前先查看3-4个同类模块
pkg/styleTroubleshooting
故障排查
"undefined: initialModel"
"undefined: initialModel"
- Make sure initialModel() function exists in implementation file
- Check it's exported (lowercase 'i' makes it package-private)
- 确保实现文件中存在initialModel()函数
- 检查函数是否为包级可见(小写的'i'表示私有函数)
"package name mismatch"
"package name mismatch"
- Directory name with hyphens → package name with underscores
- Example: →
list-simple/package list_simple
- 含连字符的目录名对应带下划线的包名
- 示例:→
list-simple/package list_simple
"unused import"
"unused import"
- Remove ,
fmt,osif not actually usedlog - Check your View() and Update() functions
- Common after removing main() function
- 若未实际使用,移除、
fmt、oslog - 检查View()和Update()函数
- 移除main()后经常会出现此类情况
"command not showing in help"
"command not showing in help"
- Verify Register() called in parent's init()
- Check import path is correct
- Run
go mod tidy
- 验证父级的init()中是否调用了Register()
- 检查导入路径是否正确
- 运行
go mod tidy
Advanced Patterns
高级模式
Multiple Files (Complex Modules)
多文件(复杂模块)
modules/demo/list-fancy/
├── cmd.go # Command registration
├── list-fancy.go # Model and main logic
├── delegate.go # Custom item delegate
└── randomitems.go # Helper functionsmodules/demo/list-fancy/
├── cmd.go # 命令注册
├── list-fancy.go # 模型和主逻辑
├── delegate.go # 自定义项委托
└── randomitems.go # 辅助函数With Flags
带参数的模块
go
var demoType string
func Register(parent *cobra.Command) {
cmd := &cobra.Command{
Use: "demo",
Short: "Demo with flag",
RunE: run,
}
cmd.Flags().StringVarP(&demoType, "type", "t", "default", "Demo type")
parent.AddCommand(cmd)
}
func run(cmd *cobra.Command, args []string) error {
// Use demoType variable in initialModel()
p := tea.NewProgram(initialModel(demoType))
_, err := p.Run()
return err
}go
var demoType string
func Register(parent *cobra.Command) {
cmd := &cobra.Command{
Use: "demo",
Short: "Demo with flag",
RunE: run,
}
cmd.Flags().StringVarP(&demoType, "type", "t", "default", "Demo type")
parent.AddCommand(cmd)
}
func run(cmd *cobra.Command, args []string) error {
// 在initialModel()中使用demoType变量
p := tea.NewProgram(initialModel(demoType))
_, err := p.Run()
return err
}With Required Args
带必填参数的模块
go
func Register(parent *cobra.Command) {
cmd := &cobra.Command{
Use: "download <url>",
Short: "Download with progress",
Args: cobra.ExactArgs(1), // Require 1 argument
RunE: run,
}
parent.AddCommand(cmd)
}
func run(cmd *cobra.Command, args []string) error {
url := args[0]
p := tea.NewProgram(initialModel(url))
_, err := p.Run()
return err
}go
func Register(parent *cobra.Command) {
cmd := &cobra.Command{
Use: "download <url>",
Short: "Download with progress",
Args: cobra.ExactArgs(1), // 必填1个参数
RunE: run,
}
parent.AddCommand(cmd)
}
func run(cmd *cobra.Command, args []string) error {
url := args[0]
p := tea.NewProgram(initialModel(url))
_, err := p.Run()
return err
}