
Cli Skills
- 52 installs
- 835 repo stars
- Updated June 10, 2026
- llama-farm/llamafarm
Helps with ai & agent building tasks.
About
cli-skills is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- cli-skills
- AI & Agent Building
- AI-coding skill
Cli Skills by the numbers
- 52 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #7,142 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/llama-farm/llamafarm --skill cli-skillsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 52 |
|---|---|
| repo stars | ★ 835 |
| Last updated | June 10, 2026 |
| Repository | llama-farm/llamafarm ↗ |
What it does
Helps with ai & agent building tasks.
Files
CLI Skills for LlamaFarm
Framework-specific patterns for the LlamaFarm CLI. These guidelines extend the shared Go skills with Cobra, Bubbletea, and Lipgloss best practices.
Tech Stack
- Go 1.24+
- Cobra (CLI framework)
- Bubbletea (TUI framework)
- Lipgloss (terminal styling)
- Bubbles (TUI components)
Directory Structure
cli/
cmd/ # Cobra command implementations
config/ # Configuration types and loading
orchestrator/ # Service management and process control
utils/ # Shared utilities (HTTP, output, logging)
version/ # Version and upgrade handling
internal/ # Internal packages (not exported)
tui/ # Reusable TUI components
buildinfo/ # Build-time informationQuick Reference
Cobra Commands
- Use
RunEoverRunfor error handling - Register flags in
init()functions - Use persistent flags for shared options
- Validate arguments with
Argsfield
Bubbletea TUI
- Implement
Init(),Update(),View()interface - Use message types for state changes
- Return
tea.Cmdfor async operations - Keep state immutable in
Update()
Lipgloss Styling
- Define styles as package-level constants
- Use
lipgloss.NewStyle()for styling - Handle terminal width dynamically
- Support color themes via style variables
Shared Go Skills
This skill extends the base Go skills. See:
| Link | Description |
|---|---|
| go-skills/SKILL.md | Overview and quick reference |
| go-skills/patterns.md | Idiomatic Go patterns |
| go-skills/concurrency.md | Goroutines, channels, sync |
| go-skills/error-handling.md | Error wrapping, sentinels |
| go-skills/testing.md | Table-driven tests, mocks |
| go-skills/security.md | Input validation, secure coding |
CLI-Specific Checklists
| File | Description |
|---|---|
| cobra.md | Cobra command patterns, flags, validation |
| bubbletea.md | Bubbletea Model/Update/View patterns |
| performance.md | CLI-specific optimizations |
Key Patterns in This Codebase
Command Registration Pattern
var myCmd = &cobra.Command{
Use: "mycommand [args]",
Short: "Brief description",
Long: `Extended description with examples.`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// Implementation with error returns
return nil
},
}
func init() {
rootCmd.AddCommand(myCmd)
myCmd.Flags().StringVar(&flagVar, "flag", "default", "Flag description")
}TUI Model Pattern
type myModel struct {
viewport viewport.Model
textarea textarea.Model
width int
height int
err error
}
func (m myModel) Init() tea.Cmd {
return tea.Batch(m.textarea.Focus(), doAsyncWork())
}
func (m myModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c":
return m, tea.Quit
}
}
return m, tea.Batch(cmds...)
}
func (m myModel) View() string {
return lipgloss.JoinVertical(lipgloss.Left,
m.viewport.View(),
m.textarea.View(),
)
}Output API Pattern
// Use the output API for consistent messaging
utils.OutputInfo("Starting service %s...", serviceName)
utils.OutputSuccess("Service started successfully")
utils.OutputError("Failed to start service: %v", err)
utils.OutputProgress("Downloading model...")
utils.OutputWarning("Service already running")Service Orchestration Pattern
// Ensure services are running before operations
factory := GetServiceConfigFactory()
config := factory.ServerOnly(serverURL)
orchestrator.EnsureServicesOrExitWithConfig(config, "server")
// Or with multiple services
config := factory.RAGCommand(serverURL)
orchestrator.EnsureServicesOrExitWithConfig(config, "server", "rag", "universal-runtime")Guidelines
1. User Experience First: CLI should feel responsive and provide clear feedback 2. Graceful Degradation: Handle missing services, network errors, and timeouts gracefully 3. Consistent Output: Use the output API for all user-facing messages 4. Cross-Platform: Test on macOS, Linux, and Windows 5. Terminal Compatibility: Test with different terminal emulators and sizes
Bubbletea TUI Patterns
Best practices for building Terminal User Interfaces with Bubbletea and Lipgloss in LlamaFarm.
Checklist
1. Implement the Model Interface Correctly
Description: All Bubbletea models must implement Init(), Update(), and View() methods.
Search Pattern:
grep -rn "func (m.*) Init()" cli/ --include="*.go"
grep -rn "func (m.*) Update(msg tea.Msg)" cli/ --include="*.go"
grep -rn "func (m.*) View() string" cli/ --include="*.go"Pass Criteria: All three methods implemented with correct signatures.
Fail Criteria: Missing methods or incorrect signatures that prevent compilation.
Severity: High
Recommendation:
type myModel struct {
// State fields
width int
height int
err error
}
func (m myModel) Init() tea.Cmd {
// Return initial commands (can be nil)
return tea.Batch(doAsyncWork(), tea.EnterAltScreen)
}
func (m myModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Handle messages, return updated model and commands
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
}
return m, nil
}
func (m myModel) View() string {
// Return rendered string (never modify state here)
return "Hello, World!"
}---
2. Handle Window Size Messages
Description: Always handle tea.WindowSizeMsg to support terminal resizing.
Search Pattern:
grep -rn "tea.WindowSizeMsg" cli/ --include="*.go"Pass Criteria: All TUI models handle window size changes and adjust layouts accordingly.
Fail Criteria: Fixed-size layouts that break on terminal resize.
Severity: High
Recommendation:
func (m myModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
// Update child components
m.viewport.Width = msg.Width
m.viewport.Height = msg.Height - footerHeight - headerHeight
// Protect against negative dimensions
if m.viewport.Height < 1 {
m.viewport.Height = 1
}
m.textarea.SetWidth(msg.Width - 2)
}
return m, nil
}---
3. Use Message Types for State Changes
Description: Define custom message types for async operations and state updates.
Search Pattern:
grep -rn "type.*Msg struct" cli/ --include="*.go"Pass Criteria: Custom message types for each distinct state change or async result.
Fail Criteria: Using raw values or string messages for state changes.
Severity: Medium
Recommendation:
// Define message types
type responseMsg struct{ content string }
type errorMsg struct{ err error }
type streamDone struct{}
type serverHealthMsg struct{ health *HealthPayload }
// Use in Update
func (m myModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case responseMsg:
m.content = msg.content
case errorMsg:
m.err = msg.err
case streamDone:
m.loading = false
}
return m, nil
}
// Create commands that return messages
func fetchDataCmd() tea.Cmd {
return func() tea.Msg {
data, err := fetchData()
if err != nil {
return errorMsg{err: err}
}
return responseMsg{content: data}
}
}---
4. Use tea.Batch for Multiple Commands
Description: Combine multiple commands with tea.Batch when returning from Update.
Search Pattern:
grep -rn "tea.Batch" cli/ --include="*.go"Pass Criteria: Multiple concurrent commands combined with tea.Batch.
Fail Criteria: Returning only one command when multiple are needed.
Severity: Medium
Recommendation:
func (m myModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
// Update child components
var vpCmd tea.Cmd
m.viewport, vpCmd = m.viewport.Update(msg)
cmds = append(cmds, vpCmd)
var taCmd tea.Cmd
m.textarea, taCmd = m.textarea.Update(msg)
cmds = append(cmds, taCmd)
switch msg := msg.(type) {
case tea.KeyMsg:
if msg.String() == "enter" {
cmds = append(cmds, sendMessageCmd(m.textarea.Value()))
cmds = append(cmds, thinkingAnimationCmd())
}
}
return m, tea.Batch(cmds...)
}---
5. Keep View() Pure and Side-Effect Free
Description: The View() method should only render state, never modify it.
Search Pattern:
grep -rn "func (m.*) View()" cli/ --include="*.go" -A20Pass Criteria: View only reads from model fields, never assigns to them.
Fail Criteria: View modifies state, causing rendering bugs or infinite loops.
Severity: High
Recommendation:
// Good
func (m myModel) View() string {
var b strings.Builder
b.WriteString(m.header())
b.WriteString(m.viewport.View())
b.WriteString(m.footer())
return b.String()
}
// Avoid - modifying state in View
func (m myModel) View() string {
m.renderCount++ // BAD: modifying state
return fmt.Sprintf("Rendered %d times", m.renderCount)
}---
6. Use Lipgloss Styles Consistently
Description: Define styles as package-level variables or model fields for consistency.
Search Pattern:
grep -rn "lipgloss.NewStyle()" cli/ --include="*.go"Pass Criteria: Styles defined once and reused, not created in every render.
Fail Criteria: Creating new styles in every View() call, causing performance issues.
Severity: Medium
Recommendation:
// Define styles at package level or in model initialization
var (
headerStyle = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("86"))
errorStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("9"))
hintStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("240"))
)
// Use in View
func (m myModel) View() string {
return headerStyle.Render("Title") + "\n" +
m.content + "\n" +
hintStyle.Render("Press q to quit")
}---
7. Handle Keyboard Events Properly
Description: Use tea.KeyMsg with proper key string comparisons.
Search Pattern:
grep -rn 'msg.String() ==' cli/ --include="*.go"Pass Criteria: Key handling uses msg.String() for readable keys or msg.Type for special keys.
Fail Criteria: Inconsistent key handling that misses edge cases.
Severity: Medium
Recommendation:
func (m myModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "ctrl+t":
m.toggleMode()
case "enter":
return m, m.submitInput()
case "up":
m.navigateHistory(-1)
case "down":
m.navigateHistory(1)
case "esc":
if m.menuActive {
m.menuActive = false
} else if m.loading {
m.cancelOperation()
}
}
}
return m, nil
}---
8. Implement Cancellation Support
Description: Long-running operations should be cancellable via Escape or Ctrl+C.
Search Pattern:
grep -rn "Cancel()" cli/ --include="*.go"Pass Criteria: Streaming operations can be cancelled, UI provides feedback.
Fail Criteria: Operations run to completion with no way to stop them.
Severity: High
Recommendation:
type myModel struct {
cancelFunc context.CancelFunc
loading bool
}
func (m myModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if msg.String() == "esc" && m.loading {
if m.cancelFunc != nil {
m.cancelFunc()
}
m.loading = false
m.messages = append(m.messages, "Operation cancelled")
return m, nil
}
}
return m, nil
}
func (m *myModel) startAsyncOperation() tea.Cmd {
ctx, cancel := context.WithCancel(context.Background())
m.cancelFunc = cancel
m.loading = true
return func() tea.Msg {
result, err := doWork(ctx)
if err != nil {
return errorMsg{err: err}
}
return responseMsg{content: result}
}
}---
9. Use Viewport for Scrollable Content
Description: Use the viewport component for content that may exceed terminal height.
Search Pattern:
grep -rn "viewport.Model" cli/ --include="*.go"Pass Criteria: Long content uses viewport with proper height calculation.
Fail Criteria: Content overflow without scrolling capability.
Severity: Medium
Recommendation:
import "github.com/charmbracelet/bubbles/viewport"
type myModel struct {
viewport viewport.Model
ready bool
}
func newModel() myModel {
vp := viewport.New(80, 20)
vp.SetContent("Initial content")
return myModel{viewport: vp}
}
func (m myModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.WindowSizeMsg:
headerHeight := 3
footerHeight := 2
m.viewport.Width = msg.Width
m.viewport.Height = msg.Height - headerHeight - footerHeight
m.ready = true
}
m.viewport, cmd = m.viewport.Update(msg)
return m, cmd
}---
10. Handle Auto-Scrolling Correctly
Description: Auto-scroll to bottom for new content, but respect user scroll position.
Search Pattern:
grep -rn "GotoBottom\|AtBottom" cli/ --include="*.go"Pass Criteria: New content scrolls to bottom only if user was already at bottom.
Fail Criteria: Auto-scroll interrupts user reading previous content.
Severity: Medium
Recommendation:
func (m myModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case responseMsg:
// Check if user was at bottom before adding content
wasAtBottom := m.viewport.AtBottom()
// Add new content
m.content += msg.content
m.viewport.SetContent(m.content)
// Only auto-scroll if user was following along
if wasAtBottom || m.justStartedResponse {
m.viewport.GotoBottom()
}
}
return m, nil
}---
11. Use Spinner for Loading States
Description: Show spinner animation during async operations for user feedback.
Search Pattern:
grep -rn "spinner.Model" cli/ --include="*.go"Pass Criteria: Loading states show animated feedback.
Fail Criteria: UI appears frozen during long operations.
Severity: Low
Recommendation:
import "github.com/charmbracelet/bubbles/spinner"
type myModel struct {
spinner spinner.Model
loading bool
}
func newModel() myModel {
s := spinner.New()
s.Spinner = spinner.Dot
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
return myModel{spinner: s}
}
func (m myModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.loading {
var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
return m, cmd
}
return m, nil
}
func (m myModel) View() string {
if m.loading {
return m.spinner.View() + " Loading..."
}
return m.content
}---
12. Separate Overlay Components
Description: Overlay components (menus, dialogs) should be separate models for reusability.
Search Pattern:
grep -rn "type.*MenuModel\|type.*ToastModel" cli/ --include="*.go"Pass Criteria: Overlays are self-contained models with their own Update/View.
Fail Criteria: Overlay logic mixed into main model, making it complex.
Severity: Medium
Recommendation:
// internal/tui/toast.go
type ToastModel struct {
message string
visible bool
timestamp time.Time
}
func (m ToastModel) Update(msg tea.Msg) (ToastModel, tea.Cmd) {
switch msg := msg.(type) {
case ShowToastMsg:
m.message = msg.Message
m.visible = true
return m, tea.Tick(3*time.Second, func(t time.Time) tea.Msg {
return HideToastMsg{}
})
case HideToastMsg:
m.visible = false
}
return m, nil
}
// In main model
func (m myModel) View() string {
content := m.mainContent()
if toast := m.toast.View(); toast != "" {
content += "\n" + toast
}
return content
}Cobra Command Patterns
Best practices for building Cobra CLI commands in LlamaFarm.
Checklist
1. Use RunE for Error Handling
Description: Prefer RunE over Run to properly propagate errors up the command chain.
Search Pattern:
grep -rn "Run: func" cli/cmd/*.go | grep -v "RunE"Pass Criteria: Commands use RunE and return errors instead of calling os.Exit(1) directly.
Fail Criteria: Commands use Run and call os.Exit(1) for error handling.
Severity: Medium
Note: The root command (root.go) uses Run instead of RunE as an exception, since the root command itself performs no work and defers to subcommands.
Recommendation:
// Good
var myCmd = &cobra.Command{
Use: "mycommand",
RunE: func(cmd *cobra.Command, args []string) error {
if err := doWork(); err != nil {
return fmt.Errorf("failed to do work: %w", err)
}
return nil
},
}
// Avoid
var myCmd = &cobra.Command{
Use: "mycommand",
Run: func(cmd *cobra.Command, args []string) {
if err := doWork(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1) // Bypasses Cobra's error handling
}
},
}---
2. Use Args Validators
Description: Use Cobra's built-in argument validators instead of manual validation.
Search Pattern:
grep -rn "Args:" cli/cmd/*.goPass Criteria: Commands use appropriate Args validators like cobra.ExactArgs, cobra.MaximumNArgs, cobra.MinimumNArgs.
Fail Criteria: Manual argument count validation inside Run/RunE functions.
Severity: Low
Recommendation:
// Good
var myCmd = &cobra.Command{
Use: "mycommand <required-arg>",
Args: cobra.ExactArgs(1),
}
// For custom validation
var myCmd = &cobra.Command{
Use: "chat [namespace/project] \"input\"",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 && strings.Contains(args[0], "/") {
if strings.Count(args[0], "/") != 1 {
return fmt.Errorf("project must be in format 'namespace/project'")
}
}
return nil
},
}---
3. Register Flags in init()
Description: Register all flags in init() functions for predictable initialization order.
Search Pattern:
grep -rn "func init()" cli/cmd/*.go -A10 | grep "Flags()"Pass Criteria: All flag registration happens in init() functions.
Fail Criteria: Flags registered inside command Run functions or at arbitrary points.
Severity: Medium
Recommendation:
var myFlag string
var myCmd = &cobra.Command{
Use: "mycommand",
RunE: runMyCommand,
}
func init() {
rootCmd.AddCommand(myCmd)
myCmd.Flags().StringVar(&myFlag, "myflag", "default", "Description of flag")
myCmd.Flags().BoolVar(&verbose, "verbose", false, "Enable verbose output")
}---
4. Use Persistent Flags for Shared Options
Description: Use PersistentFlags() on parent commands for options shared by subcommands.
Search Pattern:
grep -rn "PersistentFlags()" cli/cmd/*.goPass Criteria: Global options like --debug, --server-url are persistent flags on root command.
Fail Criteria: Same flag defined on multiple commands instead of parent.
Severity: Low
Recommendation:
// In root.go
func init() {
rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Enable debug output")
rootCmd.PersistentFlags().StringVar(&serverURL, "server-url", "http://localhost:14345", "Server URL")
}
// Subcommands automatically inherit these flags---
5. Provide Comprehensive Help Text
Description: Commands should have clear Short, Long, and usage examples.
Search Pattern:
grep -rn "Long:" cli/cmd/*.go -A5Pass Criteria: Commands have Short description, Long description with examples, and clear Use pattern.
Fail Criteria: Missing or unhelpful descriptions.
Severity: Low
Recommendation:
var myCmd = &cobra.Command{
Use: "mycommand [flags] <required-arg>",
Short: "Brief one-line description",
Long: `Extended description explaining the command's purpose.
Examples:
# Basic usage
lf mycommand value
# With flags
lf mycommand --flag=option value
# Common use case
lf mycommand --verbose my-value`,
}---
6. Use Subcommand Hierarchy
Description: Group related commands under parent commands for better organization.
Search Pattern:
grep -rn "AddCommand" cli/cmd/*.goPass Criteria: Related commands grouped under logical parent commands (e.g., services start, services stop).
Fail Criteria: Flat command structure with many top-level commands.
Severity: Low
Recommendation:
// Parent command (no Run function needed)
var servicesCmd = &cobra.Command{
Use: "services",
Short: "Manage LlamaFarm services",
}
// Subcommands
var servicesStartCmd = &cobra.Command{
Use: "start [service-name]",
Short: "Start LlamaFarm services",
RunE: runServicesStart,
}
func init() {
rootCmd.AddCommand(servicesCmd)
servicesCmd.AddCommand(servicesStartCmd)
servicesCmd.AddCommand(servicesStopCmd)
servicesCmd.AddCommand(servicesStatusCmd)
}---
7. Handle PersistentPreRunE for Setup
Description: Use PersistentPreRunE for common setup that applies to all subcommands.
Search Pattern:
grep -rn "PersistentPreRunE" cli/cmd/*.goPass Criteria: Common initialization (like debug logging setup) in PersistentPreRunE on root.
Fail Criteria: Same setup code duplicated in multiple command RunE functions.
Severity: Medium
Recommendation:
var rootCmd = &cobra.Command{
Use: "lf",
Short: "LlamaFarm CLI",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if debug {
utils.InitDebugLogger("", true)
}
return nil
},
}---
8. Support JSON Output for Machine Readability
Description: Commands that output structured data should support --json flag.
Search Pattern:
grep -rn '"json"' cli/cmd/*.goPass Criteria: Status and list commands support --json for scripting and automation.
Fail Criteria: Only human-readable output, making scripting difficult.
Severity: Medium
Recommendation:
func init() {
myCmd.Flags().Bool("json", false, "Output in JSON format")
}
func runMyCommand(cmd *cobra.Command, args []string) error {
jsonOutput, _ := cmd.Flags().GetBool("json")
result := getResult()
if jsonOutput {
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
return encoder.Encode(result)
}
// Human-readable output
fmt.Printf("Result: %s\n", result.Name)
return nil
}---
9. Validate Flag Combinations
Description: Validate mutually exclusive or dependent flag combinations.
Search Pattern:
grep -rn "MarkFlagsMutuallyExclusive\|MarkFlagsRequiredTogether" cli/cmd/*.goPass Criteria: Conflicting flags are properly validated.
Fail Criteria: Invalid flag combinations lead to undefined behavior.
Severity: Medium
Recommendation:
func init() {
myCmd.Flags().StringVar(&inputFile, "file", "", "Input from file")
myCmd.Flags().StringVar(&inputText, "text", "", "Input as text")
// Cobra 1.5+ built-in validation
myCmd.MarkFlagsMutuallyExclusive("file", "text")
// Or manual validation in RunE
}
func runMyCommand(cmd *cobra.Command, args []string) error {
if inputFile != "" && inputText != "" {
return fmt.Errorf("specify either --file or --text, not both")
}
// ...
}---
10. Use Context for Cancellation
Description: Pass context through commands for proper cancellation support.
Search Pattern:
grep -rn "cmd.Context()" cli/cmd/*.goPass Criteria: Long-running commands use cmd.Context() for cancellation.
Fail Criteria: No cancellation support, leading to stuck processes on Ctrl+C.
Severity: High
Recommendation:
func runMyCommand(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
// Pass context to long-running operations
result, err := longRunningOperation(ctx)
if err != nil {
if errors.Is(err, context.Canceled) {
return fmt.Errorf("operation cancelled")
}
return err
}
return nil
}CLI Performance Optimizations
Best practices for building responsive and efficient CLI applications in LlamaFarm.
Checklist
1. Lazy Load Heavy Dependencies
Description: Defer loading of expensive resources until they are actually needed.
Search Pattern:
grep -rn "func init()" cli/ --include="*.go" -A10Pass Criteria: init() functions only register commands and flags, not load data.
Fail Criteria: Heavy operations (HTTP calls, file I/O) in init() slow down all commands.
Severity: High
Recommendation:
// Good - lazy loading
var configCache *Config
func getConfig() (*Config, error) {
if configCache != nil {
return configCache, nil
}
cfg, err := loadConfig()
if err != nil {
return nil, err
}
configCache = cfg
return configCache, nil
}
// Avoid - eager loading in init()
func init() {
config, _ = loadConfig() // Slows down all commands
}---
2. Use Connection Pooling for HTTP
Description: Reuse HTTP clients and connections across requests.
Search Pattern:
grep -rn "http.Client" cli/ --include="*.go"
grep -rn "GetHTTPClient" cli/ --include="*.go"Pass Criteria: Single HTTP client instance reused across requests.
Fail Criteria: Creating new http.Client for each request.
Severity: Medium
Recommendation:
// utils/httpclient.go
var httpClient *http.Client
var httpOnce sync.Once
func GetHTTPClient() *http.Client {
httpOnce.Do(func() {
httpClient = &http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
},
}
})
return httpClient
}
// Usage
resp, err := utils.GetHTTPClient().Do(req)---
3. Stream Large Responses
Description: Stream large API responses instead of buffering entire response in memory.
Search Pattern:
grep -rn "io.ReadAll" cli/ --include="*.go"Pass Criteria: Large responses (chat completions, file downloads) use streaming.
Fail Criteria: Reading entire response into memory before processing.
Severity: High
Recommendation:
// Good - streaming
func streamResponse(resp *http.Response, callback func(chunk string)) error {
reader := bufio.NewReader(resp.Body)
for {
line, err := reader.ReadString('\n')
if err == io.EOF {
break
}
if err != nil {
return err
}
callback(line)
}
return nil
}
// Avoid - buffering
func readResponse(resp *http.Response) (string, error) {
body, err := io.ReadAll(resp.Body) // May use excessive memory
return string(body), err
}---
4. Throttle Progress Updates
Description: Limit frequency of progress message updates to prevent UI flickering.
Search Pattern:
grep -rn "OutputProgress" cli/ --include="*.go"Pass Criteria: Progress updates throttled to reasonable interval (100-500ms).
Fail Criteria: Progress updates on every byte, causing performance issues.
Severity: Medium
Recommendation:
// utils/output.go - throttled progress
func sendConsolidatedProgressMessage(format string, args ...interface{}) {
content := fmt.Sprintf(format, args...)
outputManager.mu.Lock()
defer outputManager.mu.Unlock()
outputManager.lastProgressMessage = content
if !outputManager.progressMessageSent {
// Send immediately
sendToTUI(content)
outputManager.progressMessageSent = true
// Reset flag after delay
go func() {
time.Sleep(100 * time.Millisecond)
outputManager.mu.Lock()
outputManager.progressMessageSent = false
outputManager.mu.Unlock()
}()
}
}---
5. Cache Expensive Computations
Description: Cache results of expensive operations that don't change frequently.
Search Pattern:
grep -rn "Cache\|cache" cli/ --include="*.go"Pass Criteria: Model lists, configurations cached with appropriate TTL.
Fail Criteria: Repeated API calls for same data in short time period.
Severity: Medium
Recommendation:
type cachedResult struct {
data interface{}
timestamp time.Time
}
var cache = struct {
sync.RWMutex
items map[string]cachedResult
}{items: make(map[string]cachedResult)}
func getCached(key string, ttl time.Duration, fetch func() (interface{}, error)) (interface{}, error) {
cache.RLock()
if item, ok := cache.items[key]; ok && time.Since(item.timestamp) < ttl {
cache.RUnlock()
return item.data, nil
}
cache.RUnlock()
data, err := fetch()
if err != nil {
return nil, err
}
cache.Lock()
cache.items[key] = cachedResult{data: data, timestamp: time.Now()}
cache.Unlock()
return data, nil
}---
6. Minimize Render Calls
Description: Only re-render TUI when state actually changes.
Search Pattern:
grep -rn "SetContent\|GotoBottom" cli/ --include="*.go"Pass Criteria: Viewport content updated only when messages change.
Fail Criteria: Re-rendering on every Update call regardless of state change.
Severity: Medium
Recommendation:
// Use content hashing to detect changes
func computeTranscriptKey(m chatModel) string {
if len(m.messages) == 0 {
return "empty"
}
msg := m.messages[len(m.messages)-1]
h := fnv.New64a()
io.WriteString(h, msg.Role)
io.WriteString(h, msg.Content)
return fmt.Sprintf("%x", h.Sum64())
}
func computeTranscript(m chatModel) string {
key := computeTranscriptKey(m)
if lastTranscriptKey == key {
return m.transcript // Return cached
}
// Recompute only if changed
lastTranscriptKey = key
return renderMessages(m.messages)
}---
7. Use Goroutines for Parallel Operations
Description: Run independent operations concurrently to reduce total wait time.
Search Pattern:
grep -rn "go func" cli/ --include="*.go"Pass Criteria: Independent API calls run in parallel with proper synchronization.
Fail Criteria: Sequential operations that could be parallelized.
Severity: Medium
Recommendation:
func fetchProjectData(ns, proj string) (*ProjectData, error) {
var wg sync.WaitGroup
var models []ModelInfo
var databases *DatabasesResponse
var modelsErr, dbErr error
wg.Add(2)
go func() {
defer wg.Done()
models, modelsErr = fetchModels(ns, proj)
}()
go func() {
defer wg.Done()
databases, dbErr = fetchDatabases(ns, proj)
}()
wg.Wait()
if modelsErr != nil {
return nil, modelsErr
}
if dbErr != nil {
return nil, dbErr
}
return &ProjectData{Models: models, Databases: databases}, nil
}---
8. Efficient String Building
Description: Use strings.Builder for concatenating multiple strings.
Search Pattern:
grep -rn "strings.Builder" cli/ --include="*.go"Pass Criteria: String building uses strings.Builder or bytes.Buffer.
Fail Criteria: String concatenation with + in loops.
Severity: Low
Recommendation:
// Good
func renderMessages(messages []Message) string {
var b strings.Builder
for _, msg := range messages {
b.WriteString(formatMessage(msg))
b.WriteString("\n")
}
return b.String()
}
// Avoid
func renderMessages(messages []Message) string {
result := ""
for _, msg := range messages {
result += formatMessage(msg) + "\n" // Creates new string each iteration
}
return result
}---
9. Protect Against Negative Dimensions
Description: Guard against negative viewport dimensions that cause panics.
Search Pattern:
grep -rn "viewport.Height\|viewport.Width" cli/ --include="*.go"Pass Criteria: Dimension calculations check for and prevent negative values.
Fail Criteria: Negative dimensions passed to viewport causing slice bounds panic.
Severity: High
Recommendation:
func (m myModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
headerHeight := lipgloss.Height(m.renderHeader())
footerHeight := lipgloss.Height(m.renderFooter())
// CRITICAL: Prevent negative height
newHeight := msg.Height - headerHeight - footerHeight
if newHeight < 1 {
newHeight = 1
}
m.viewport.Height = newHeight
m.viewport.Width = msg.Width
// Also protect textarea width
newWidth := msg.Width - 2
if newWidth < 10 {
newWidth = 10
}
m.textarea.SetWidth(newWidth)
}
return m, nil
}---
10. Use Context Timeouts
Description: Set appropriate timeouts for all network operations.
Search Pattern:
grep -rn "context.WithTimeout" cli/ --include="*.go"Pass Criteria: All HTTP requests and long operations have timeouts.
Fail Criteria: Operations can hang indefinitely.
Severity: High
Recommendation:
func fetchWithTimeout(url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
resp, err := utils.GetHTTPClient().Do(req)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, fmt.Errorf("request timed out after 30s")
}
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}---
11. Avoid Blocking the Main Loop
Description: Never perform blocking operations in Update() - use commands instead.
Search Pattern:
grep -rn "func (m.*) Update" cli/ --include="*.go" -A30 | grep -E "http\.|os\.|io\."Pass Criteria: All I/O operations wrapped in tea.Cmd functions.
Fail Criteria: Direct HTTP calls or file operations in Update().
Severity: High
Recommendation:
// Good - async command
func (m myModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if msg.String() == "enter" {
return m, fetchDataCmd(m.input) // Returns immediately
}
case dataMsg:
m.data = msg.data // Handle result
}
return m, nil
}
func fetchDataCmd(input string) tea.Cmd {
return func() tea.Msg {
data, err := fetchFromAPI(input) // Runs in goroutine
if err != nil {
return errorMsg{err: err}
}
return dataMsg{data: data}
}
}
// Avoid - blocking in Update
func (m myModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if msg.String() == "enter" {
data, _ := fetchFromAPI(m.input) // BLOCKS UI
m.data = data
}
}
return m, nil
}---
12. Profile Before Optimizing
Description: Use Go's profiling tools to identify actual bottlenecks.
Search Pattern:
grep -rn "pprof\|runtime/pprof" cli/ --include="*.go"Pass Criteria: Performance-critical code paths are profiled and optimized based on data.
Fail Criteria: Premature optimization without profiling.
Severity: Low
Recommendation:
// Add profiling support for development
import _ "net/http/pprof"
func main() {
if os.Getenv("ENABLE_PPROF") == "1" {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
}
// ...
}
// Profile with:
// ENABLE_PPROF=1 lf chat
// go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30