Loading...
Loading...
Create new demo or utility modules following CLY project patterns. Use when adding TUI demonstration modules to modules/demo/ or utility modules to modules/, following Bubbletea/Bubbles conventions with proper Cobra CLI integration.
npx skill4agent add yurifrl/cly add-modulecmd.go✓ 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)pkg/| Location | Purpose | Example |
|---|---|---|
| Shared Lipgloss styles | Colors, borders, common styles |
| Common keybindings | Quit keys, navigation patterns |
| TUI utilities | Screen helpers, common components |
pkg/modules/demo/spinner/
├── cmd.go # Registration only
├── spinner.go # All logic here
└── (optional) # Helpers if needed, but keep in same packagemodules/demo/<name>/demomodules/<name>/references/bubbletea/examples/<name>/# For demo:
mkdir -p modules/demo/<name>
# For utility:
mkdir -p modules/<name>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.WithAltScreen()tea.WithMouseAllMotion()tea.WithReportFocus()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"
}modules/demo/cmd.goimport (
yourmodule "github.com/yurifrl/cly/modules/demo/your-module"
)
func init() {
// ... existing registrations
yourmodule.Register(DemoCmd)
}cmd/root.goimport (
"github.com/yurifrl/cly/modules/yourutil"
)
func init() {
// ... existing registrations
yourutil.Register(RootCmd)
}go buildgo run main.go --helpgo run main.go demo --helpgo run main.go <command>go run main.go demo <name>list-simple/package list_simplelistsimple "github.com/yurifrl/cly/modules/demo/list-simple"func main() {
s := spinner.New()
s.Spinner = spinner.Dot
m := model{spinner: s}
tea.NewProgram(m).Run()
}func initialModel() model {
s := spinner.New()
s.Spinner = spinner.Dot
return model{spinner: s}
}modules/demo/spinner/modules/demo/list-fancy/modules/uuid/modules/demo/chat/# Test compilation
go build
# View help
go run main.go --help
go run main.go demo --help
# Run demo
go run main.go demo <name>
# Run utility
go run main.go <name>
# Clean dependencies
go mod tidyinitialModelInitialModelmodules/demo/<name>/chatspinnertablelist-simplemodules/<name>/uuidreferences/bubbletea/examples/<component>/cp -r modules/demo/spinner modules/demo/<newname>modules/demo/cmd.gocp -r modules/uuid modules/<newname>cmd/root.gomodules/demo/<name>/cmd.gopackage <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
}modules/demo/<name>/<name>.gopackage <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"
}modules/demo/cmd.goimport (
// ...
yourmodule "github.com/yurifrl/cly/modules/demo/your-module"
)
func init() {
// ...
yourmodule.Register(DemoCmd)
}cmd/root.goimport (
// ...
"github.com/yurifrl/cly/modules/yourutil"
)
func init() {
uuid.Register(RootCmd)
demo.Register(RootCmd)
yourutil.Register(RootCmd) // Add here
}func run(cmd *cobra.Command, args []string) error {
p := tea.NewProgram(initialModel(), tea.WithAltScreen())
_, err := p.Run()
return err
}modules/demo/fullscreen/modules/demo/eyes/p := tea.NewProgram(initialModel(), tea.WithMouseAllMotion())modules/demo/mouse/p := tea.NewProgram(initialModel(), tea.WithReportFocus())modules/demo/focus-blur/p := tea.NewProgram(initialModel(), tea.WithFilter(filterFunc))modules/demo/prevent-quit/references/bubbletea/examples/modules/demo/| Demo | Shows | Reference |
|---|---|---|
| Animated loading | |
| Selection lists | |
| Data tables | |
| Single-line input | |
| Multi-line input | |
| Progress bars | |
| Demo | Shows | Reference |
|---|---|---|
| Textarea + Viewport | |
| File selection | |
| Complex forms | |
| Multiple panes | |
modules/demo/references/bubbletea/examples/<component>/main.gofmtoslogreferences/bubbletea/examples/spinner/main.gos := spinner.New()
s.Spinner = spinner.Dot
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
return model{spinner: s}func initialModel() model {
s := spinner.New()
s.Spinner = spinner.Dot
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
return model{spinner: s}
}list-simplecredit-card-formaltscreen-togglelist_simplecredit_card_formaltscreen_togglecmd.go<name>.gospinner.golist-simple.godelegate.gohelpers.gotypes.gogo buildgo run main.go --helpgo run main.go demo --helpgo run main.go <command>pkg/stylelist-simple/package list_simplefmtosloggo mod tidymodules/demo/list-fancy/
├── cmd.go # Command registration
├── list-fancy.go # Model and main logic
├── delegate.go # Custom item delegate
└── randomitems.go # Helper functionsvar 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
}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
}