Skip to content

Terminal Stylist Analysis: Console Output Patterns & Best Practices #15992

@github-actions

Description

@github-actions

Executive Summary

This analysis examines console output patterns across the gh-aw codebase, focusing on consistency, styling practices, and adherence to modern terminal UI best practices using the Charmbracelet ecosystem (Lipgloss and Huh).

Key Findings:

  • Well-architected console system with 17 specialized formatting functions
  • Excellent Lipgloss integration with adaptive theming and TTY-aware rendering
  • Consistent Huh usage across 13 interactive files with accessibility support
  • ⚠️ Mixed output patterns with 170+ files still using direct fmt.* calls
  • Centralized styling via pkg/styles/theme.go with 30+ pre-configured styles

🎨 Console Package Architecture

Available Formatting Functions (17 total)

Message Types

  • FormatSuccessMessage() → ✓ (green) - Operation completed successfully
  • FormatErrorMessage() → ✗ (red) - Operation failed
  • FormatInfoMessage() → ℹ (cyan) - Informational messages
  • FormatWarningMessage() → ⚠ (yellow) - Warnings and cautions

Contextual Formatters

  • FormatLocationMessage() → 📁 - File paths and locations
  • FormatCommandMessage() → ⚡ - Commands being executed
  • FormatProgressMessage() → 🔨 - Activity in progress
  • FormatPromptMessage() → ❓ - User input prompts
  • FormatCountMessage() → 📊 - Metrics and counts
  • FormatVerboseMessage() → 🔍 - Debug/verbose output

Structural Elements

  • FormatSectionHeader() - Section dividers
  • FormatListHeader() - List titles
  • FormatListItem() - Bullet point items
  • FormatBanner() - ASCII banners
  • FormatFileSize() - Human-readable sizes
  • FormatErrorWithSuggestions() - Errors with actionable fixes
  • FormatError() - IDE-parseable compiler errors

Design Principles

TTY-Aware Rendering:

// ✅ GOOD - Automatically adapts to terminal capabilities
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Compiled successfully"))
// Output (TTY): ✅ Compiled successfully (with color)
// Output (pipe): ✅ Compiled successfully (no ANSI codes)

Adaptive Theming:
All colors use lipgloss.AdaptiveColor for light/dark terminal backgrounds:

// From pkg/styles/theme.go
SuccessColor = lipgloss.AdaptiveColor{
    Light: "#43A047", // Darker green for light terminals
    Dark:  "#66BB6A", // Brighter green for dark terminals
}

📊 Current Usage Patterns

Files by Output Method

Pattern Count Location Status
console.Format*() 100+ pkg/cli/, pkg/workflow/, pkg/parser/ ✅ Correct
Direct fmt.* 170+ Widespread ⚠️ Mixed quality
Lipgloss styling 5 core pkg/console/, pkg/styles/ ✅ Excellent
Huh forms 13 pkg/console/, pkg/cli/*interactive* ✅ Consistent

Console Formatter Adoption

High adoption areas (✅ Good):

  • pkg/cli/ - Commands properly use FormatSuccessMessage, FormatErrorMessage
  • pkg/workflow/ - Compilation output uses FormatInfoMessage, FormatWarningMessage
  • pkg/parser/ - Validation errors use FormatErrorWithSuggestions

Legacy fmt usage (⚠️ Opportunity):

  • pkg/logger/ - Debug output uses direct fmt.Fprintf(os.Stderr, ...)
  • Various utilities - Simple logging still uses fmt.Printf/fmt.Println
  • Test output - Intentionally uses raw fmt for test diagnostics

🎯 Lipgloss Integration Analysis

Centralized Theme System

pkg/styles/theme.go serves as the single source of truth:

  • 30+ pre-configured lipgloss.Style objects
  • Consistent spacing (1-2 char padding)
  • Border styles: Rounded for tables, Normal for info boxes
  • Adaptive colors for all text and backgrounds

Example: Table Styling

// From pkg/styles/theme.go
TableHeaderStyle = lipgloss.NewStyle().
    Bold(true).
    Foreground(lipgloss.AdaptiveColor{Light: "#1A1A1A", Dark: "#F8F8F2"}).
    Background(lipgloss.AdaptiveColor{Light: "#E0E0E0", Dark: "#44475A"}).
    Padding(0, 1)

Best Practices Observed

Excellent patterns found:

  1. TTY Detection - All styled output checks isTTY() before applying ANSI codes
  2. Accessibility Mode - Respects ACCESSIBLE env var, disables animations
  3. Layout Composition - Uses lipgloss.JoinHorizontal/Vertical for complex layouts
  4. Border Consistency - Rounded borders for data tables, subtle borders for info sections
  5. Zebra Striping - Alternating row colors for table readability

Example: Conditional Rendering

// From pkg/console/render.go
func applyStyle(style lipgloss.Style, text string) string {
    if !isTTY() {
        return text // No ANSI codes when piped
    }
    return style.Render(text)
}

Areas for Enhancement

⚠️ Manual ANSI sequences found:

  • A few utility functions still construct colors manually
  • Opportunity to migrate to Lipgloss for consistency

⚠️ Table rendering:

  • Some manual table formatting could use github.com/charmbracelet/lipgloss/table
  • Would improve consistency across different table outputs

🗳️ Huh Forms Analysis

Interactive Command Pattern

Consistent structure across 8 CLI interactive commands:

// From pkg/console/form.go - Reusable abstraction
func NewForm(groups ...*huh.Group) *huh.Form {
    return huh.NewForm(groups...).
        WithAccessible(IsAccessibleMode()). // ✅ Accessibility support
        WithTheme(GetHuhTheme())            // ✅ Consistent theming
}

Form Types Used

Command Fields Validation Notes
init Input, Select Workflow name, engine selection
add engine Select, Confirm Engine type, auth method
add auth Input, Select Secret name, type selection
add workflow Input, Confirm Workflow name, template
add orchestrator Select, MultiSelect Provider, features
add git Input, Select Remote, branch patterns

Best Practices Observed

Excellent patterns:

  1. Centralized form creation - pkg/console/form.go provides reusable builders
  2. Accessibility built-in - All forms use WithAccessible()
  3. Consistent theming - Single GetHuhTheme() function for uniform look
  4. Validation - All input fields have .Validate() callbacks
  5. Error handling - Forms return actionable errors on invalid input

Example: Input Validation

// From pkg/cli/add_interactive_workflow.go
huh.NewInput().
    Title("Workflow name").
    Validate(func(s string) error {
        if s == "" {
            return fmt.Errorf("workflow name cannot be empty")
        }
        if !isValidWorkflowName(s) {
            return fmt.Errorf("invalid characters in workflow name")
        }
        return nil
    })

Enhancement Opportunities

⚠️ Potential improvements:

  • Some simple prompts (pkg/console/input.go) could be migrated to Huh for consistency
  • Consider huh.FilePicker for file selection in add workflow --template
  • Multi-step flows could benefit from progress indicators between form groups

🔍 Anti-Patterns & Remediation

1. Direct fmt.Print* Usage

❌ Anti-pattern found in some files:

// From various utility files
fmt.Printf("Processing workflow...\n")
fmt.Println("Error: invalid configuration")

✅ Should be:

fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Processing workflow..."))
fmt.Fprintln(os.Stderr, console.FormatErrorMessage("Invalid configuration"))

Impact: Inconsistent styling, no TTY detection, no adaptive colors


2. Manual ANSI Escape Sequences

❌ Anti-pattern (rare, but found):

fmt.Printf("\033[31mError\033[0m: %s\n", msg)

✅ Should be:

fmt.Fprintln(os.Stderr, console.FormatErrorMessage(msg))
// Or use Lipgloss directly:
errorStyle := styles.ErrorStyle // from pkg/styles/theme.go
fmt.Fprintln(os.Stderr, errorStyle.Render("Error: " + msg))

Impact: Hardcoded colors, no theme support, breaks when piped


3. Stdout vs Stderr Confusion

❌ Anti-pattern found:

fmt.Println("Processing...") // Diagnostic output to stdout

✅ Should be:

fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Processing..."))

Impact: Mixes diagnostic output with structured data (JSON, hashes), breaks piping


4. Inconsistent Table Formatting

⚠️ Opportunity for standardization:
Some commands manually format tables with string padding/alignment. Could migrate to:

  • lipgloss/table package for consistent rendering
  • Centralized table builder in pkg/console/table.go (already exists!)

📋 Recommendations

Priority 1: High Impact, Low Effort

  1. Audit fmt.Printf/Println usage - Create linting rule to flag:

    • fmt.Printf → suggest fmt.Fprintf(os.Stderr, ...)
    • fmt.Println → suggest fmt.Fprintln(os.Stderr, console.Format*())
  2. Document output routing - Add to DEVGUIDE.md:

    **Output Routing:**
    - Diagnostic messages → stderr with console.Format*()
    - Structured data (JSON, hashes) → stdout
    - Never mix diagnostics on stdout
  3. Standardize error output - Ensure all error paths use FormatErrorMessage:

    # Find direct error printing
    grep -rn 'fmt.Printf.*error' pkg/ --include="*.go" | grep -v '_test.go'

Priority 2: Consistency Improvements

  1. Migrate manual tables - Replace string-based tables with pkg/console/table.go helpers

  2. Consolidate themes - Ensure all Lipgloss styles come from pkg/styles/theme.go

  3. Add linting - Consider golangci-lint custom rules:

    • Forbid \033[ ANSI sequences (use Lipgloss instead)
    • Flag fmt.Println in non-test files
    • Require console package import when using stderr output

Priority 3: Enhancement Opportunities

  1. Expand Huh usage - Migrate simple prompts to Huh forms:

    • pkg/console/input.go → use huh.NewInput()
    • File selection → use huh.FilePicker
  2. Table rendering library - Evaluate github.com/charmbracelet/lipgloss/table:

    • More consistent borders, padding, colors
    • Automatic row striping
    • Responsive width handling
  3. Progress indicators - For long operations:

    • Use console.NewSpinner() (already exists!)
    • Consider github.com/charmbracelet/bubbles/progress for longer tasks

🎯 Success Metrics

To measure console output quality over time:

Metric Current Target Check Command
Files using console.Format* ~100 150+ grep -r "console.Format" pkg/ | wc -l
Direct fmt.Println usage 170+ <50 grep -r "fmt.Println" pkg/ --include="*.go" | grep -v test | wc -l
Manual ANSI sequences ~5 0 grep -r "\\033\[" pkg/ --include="*.go" | wc -l
Huh form coverage 13 20+ grep -r "huh.New" pkg/ | wc -l
Lipgloss files 5 core 10+ grep -r "lipgloss.New" pkg/ --include="*.go" | wc -l

🏆 Exemplary Files

These files demonstrate excellent console output practices:

  1. pkg/console/console.go - Perfect use of Lipgloss, TTY detection, adaptive colors
  2. pkg/console/form.go - Reusable Huh form builders with accessibility
  3. pkg/styles/theme.go - Centralized theme management, comprehensive styling
  4. pkg/cli/compile_command.go - Proper console formatter usage throughout
  5. pkg/cli/init_interactive.go - Multi-step Huh forms with validation

Study these files when implementing new console output.


🔧 Quick Reference Card

Console Output Cheat Sheet

// ✅ CORRECT PATTERNS
import (
    "fmt"
    "os"
    "github.com/github/gh-aw/pkg/console"
)

// Success
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Operation completed"))

// Error
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))

// Info
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Processing..."))

// Warning
fmt.Fprintln(os.Stderr, console.FormatWarningMessage("File has uncommitted changes"))

// Structured output (stdout only)
fmt.Println(string(jsonBytes))  // JSON
fmt.Println(hash)                // Hashes

Interactive Forms Cheat Sheet

// ✅ CORRECT PATTERNS
import (
    "github.com/charmbracelet/huh"
    "github.com/github/gh-aw/pkg/console"
)

// Simple input
form := console.NewForm(
    huh.NewGroup(
        huh.NewInput().
            Title("Workflow name").
            Value(&workflowName).
            Validate(validateWorkflowName),
    ),
)
err := form.Run()

// Selection
form := console.NewForm(
    huh.NewGroup(
        huh.NewSelect[string]().
            Title("Choose engine").
            Options(
                huh.NewOption("Copilot", "copilot"),
                huh.NewOption("Claude", "claude"),
            ).
            Value(&engine),
    ),
)

Styling Cheat Sheet

// ✅ CORRECT PATTERNS
import (
    "github.com/github/gh-aw/pkg/styles"
    "github.com/charmbracelet/lipgloss"
)

// Use centralized styles
fmt.Fprintln(os.Stderr, styles.ErrorStyle.Render("Critical error"))
fmt.Fprintln(os.Stderr, styles.SuccessStyle.Render("All tests passed"))

// Build custom styles with adaptive colors
customStyle := lipgloss.NewStyle().
    Foreground(styles.SuccessColor).  // From theme.go
    Bold(true).
    Padding(0, 1)

// TTY-aware rendering
if console.IsTTY() {
    fmt.Fprintln(os.Stderr, customStyle.Render(text))
} else {
    fmt.Fprintln(os.Stderr, text)  // Plain text when piped
}

📚 Additional Resources


Analysis Date: 2026-02-15
Agent: Terminal Stylist
Repository: github/gh-aw
Files Analyzed: 250+ Go source files in pkg/

Next Steps: Review recommendations, prioritize based on team capacity, and consider adding linting rules to enforce console output best practices.


Note: This was intended to be a discussion, but discussions could not be created due to permissions issues. This issue was created as a fallback.

Tip: Discussion creation may fail if the specified category is not announcement-capable. Consider using the "Announcements" category or another announcement-capable category in your workflow configuration.

Generated by Terminal Stylist

  • expires on Feb 22, 2026, 10:27 PM UTC

Metadata

Metadata

Assignees

No one assigned

    Labels

    clidocumentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions