Skip to content

Repository files navigation

Shotgun CLI

TestscodecovGo Report CardLicense: Shotgun Community

Generate focused, reviewable codebase context for AI coding workflows from a CLI or an interactive terminal UI. Shotgun applies Git-aware ignore rules, estimates context size, renders reusable prompt templates, and can send the result to Anthropic, OpenAI, or Google Gemini.

Install

Download a binary from the latest release, or build from source with Go 1.23 or newer:

git clone https://github.com/quantmind-br/shotgun-cli.git
cd shotgun-cli
make build
./build/shotgun-cli --help

Quick start

Launch the guided TUI:

shotgun-cli

Or generate a context file non-interactively:

shotgun-cli context generate \
--root ./my-project \
--output context.md \
--max-size 5MB

Configure Claude through the Anthropic provider and verify the setup:

shotgun-cli config set llm.provider anthropic
shotgun-cli config set llm.api-key "$ANTHROPIC_API_KEY"
shotgun-cli llm doctor

API keys stay in local configuration. Do not commit generated configuration or context files that may contain private source code.

Why Shotgun

Key Features and Capabilities

  • Interactive TUI Wizard: 5-step guided workflow using Bubble Tea framework for intuitive user interaction
  • CLI Commands: Programmatic interface for automation and scripting
  • Intelligent File Scanning: Recursive directory traversal with layered ignore rule processing
  • Template Management: Multi-source template loading with variable substitution
  • Context Optimization: Token estimation and size validation for LLM compatibility
  • AI Integration: Seamless integration with Google Gemini API via external tool execution
  • Cross-platform Support: Works across different operating systems with platform-specific optimizations

Common use cases

  • Code Review Automation: Generate comprehensive context for AI-powered code analysis
  • Documentation Generation: Create structured representations of codebases for AI-assisted documentation
  • Refactoring Assistance: Provide AI models with complete context for intelligent refactoring suggestions
  • Onboarding Tools: Help new developers understand complex codebase structures
  • Code Migration: Facilitate AI-assisted code migration between languages or frameworks

Project status

Shotgun is actively maintained. Releases include cross-platform binaries, and the test matrix covers Linux, macOS, and Windows. Bug reports and focused contributions are welcome; see CONTRIBUTING.md and SECURITY.md.

Acknowledgements and license

This project is based on glebkudr/shotgun_code, created by Gleb Kudryashov / Curly's Technology Tmi. The CLI-focused architecture and subsequent modifications in this repository are maintained by Diogo Soares Rodrigues.

The upstream project is distributed under the Shotgun Community License, which includes revenue and competitive-use restrictions. Those terms are preserved here. This is a source-available project, not an OSI-approved open-source license.

Table of Contents

Architecture

High-level Architecture Overview

Shotgun CLI implements a Clean Architecture/Hexagonal Architecture pattern with clear separation of concerns across three main layers:

  1. Presentation/Adapter Layer (cmd, internal/ui): Handles user interaction through CLI commands and interactive TUI wizard
  2. Application Layer (internal/app): Orchestrates business logic and provides a unified API (ContextService) for presentation layers
  3. Core/Domain Layer (internal/core): Contains pure business logic for context generation, file scanning, template management, and token estimation
  4. Infrastructure/Platform Layer (internal/platform): Implements external system integrations (LLM providers, http client, clipboard operations)

Technology Stack and Frameworks

  • Language: Go
  • CLI Framework: Cobra (github.com/spf13/cobra)
  • Configuration: Viper (github.com/spf13/viper)
  • TUI Framework: Bubble Tea (github.com/charmbracelet/bubbletea)
  • Logging: Zerolog (github.com/rs/zerolog)
  • Template Engine: Go standard library templates
  • Ignore Processing: go-gitignore (github.com/sabhiram/go-gitignore)

Component Relationships

graph TD
A[main.go] → B[cmd]
B → O[internal/app]
O → C[internal/core/context]
O → D[internal/core/scanner]
O → E[internal/core/template]
O → H[internal/platform/llm]
B → C
B → D
B → E
B → F[internal/core/ignore]
B → G[internal/core/tokens]
B → H
B → I[internal/platform/clipboard]
B → J[internal/ui/wizard]
B → K[internal/utils]
C → D
C → E
C → F
C → G
D → F
J → SC[internal/ui/scan_coordinator]
J → GC[internal/ui/generate_coordinator]
J → O
J → L[internal/ui/screens]
J → M[internal/ui/components]
SC → D
GC → C
L → M
L → N[internal/ui/styles]
M → N
style A fill:#e1f5fe
style B fill:#f3e5f5
style O fill:#fff9c4
style C fill:#e8f5e8
style D fill:#e8f5e8
style E fill:#e8f5e8
style F fill:#e8f5e8
style G fill:#e8f5e8
style H fill:#fff3e0
style I fill:#fff3e0
style J fill:#fce4ec
style K fill:#f5f5f5
Loading

Key Design Patterns

  • Command Pattern: CLI command structure in cmd package
  • Builder/Generator Pattern: Context generation in internal/core/context
  • Strategy Pattern: AI provider abstraction for multi-provider support
  • MVU Pattern: TUI state management with Bubble Tea
  • Template Method Pattern: Standardized template rendering process
  • Layered Architecture: Clear separation between presentation, business logic, and infrastructure
  • Factory Pattern: Template manager and scanner creation

Platform Layer Architecture

The Platform layer provides a standardized way for LLM providers to communicate with external APIs.

Shared HTTP Client

Most LLM providers use the shared JSONClient located in internal/platform/http/client.go. This client provides:

  • Standardized Requests: Using PostJSON() for consistent API interaction
  • Error Handling: Standardized HTTPError type that captures status codes and response bodies
  • Configuration: Unified timeout and base URL handling

LLM Provider Architecture

HTTP-based LLM providers use a shared BaseClient with the Strategy pattern:

┌─────────────────────────────────────────┐
│ BaseClient │
├─────────────────────────────────────────┤
│ - JSONClient │
│ - APIKey, Model, MaxTokens │
│ + Name(), IsAvailable(), IsConfigured() │
│ + ValidateConfig() │
│ + Send(ctx, content, sender) │
│ + SendWithProgress(...) │
└────────────────┬────────────────────────┘
│ embeds
┌────────────┼────────────┐
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌──────────┐
│ OpenAI │ │ Anthropic│ │ GeminiAPI│
└────────┘ └──────────┘ └──────────┘
(implements Sender interface)

Provider Implementation

By using the BaseClient and the shared client, LLM providers (OpenAI, Anthropic, GeminiAPI) only need to implement the Sender interface:

  1. Request Building: Constructing the provider-specific JSON request structure (BuildRequest)
  2. Response Mapping: Defining the target structure for JSON unmarshaling (NewResponse, ParseResponse)
  3. Endpoint & Headers: Providing the API path and required headers (GetEndpoint, GetHeaders)
  4. Provider Identity: Providing the display name (GetProviderName)

LLM Provider Configuration

shotgun-cli supports multiple LLM providers including OpenAI, Anthropic, and Google Gemini API.

LLM Diagnostic Commands

Shotgun CLI provides diagnostic commands to help troubleshoot and verify LLM provider configuration.

shotgun-cli llm status

Display the current LLM provider configuration and status.

shotgun-cli llm status

Output format:

=== LLM Configuration ===
Provider: anthropic
Model: claude-sonnet-4-20250514
Base URL: (default: https://api.anthropic.com)
API Key: sk-a...-key
Timeout: 300s
Status: Ready

The status command shows:

  • Provider: Currently selected LLM provider
  • Model: Model name configured for use
  • Base URL: API endpoint (shows default or custom URL)
  • API Key: Masked API key for security
  • Timeout: Request timeout in seconds
  • Status: One of Ready, Not configured, Not available, or Not ready

shotgun-cli llm doctor

Run diagnostics on the LLM provider configuration and provide specific guidance for fixing issues.

shotgun-cli llm doctor

Output format:

Running diagnostics for anthropic...
Checking provider... anthropic
Checking API key... configured
Checking model... claude-sonnet-4-20250514
Checking provider availability... OK
Checking provider configuration... OK
No issues found! anthropic is ready.

The doctor command checks:

  • Provider type is valid
  • API key is configured (if required)
  • Model is set
  • Provider availability
  • Provider configuration completeness

When issues are found, the doctor provides specific next steps for each provider:

  • OpenAI: API key setup link and configuration commands
  • Anthropic: API key setup link and configuration commands
  • Gemini: API key setup link and configuration commands

shotgun-cli llm list

List all supported LLM providers with descriptions and configuration information.

shotgun-cli llm list

Output format:

Supported LLM Providers:
openai - OpenAI (GPT-4o, GPT-4, o1, o3)
* anthropic - Anthropic (Claude 4, Claude 3.5)
gemini - Google Gemini (Gemini 2.5, Gemini 2.0)
Configure with:
shotgun-cli config set llm.provider <provider>
shotgun-cli config set llm.api-key <your-api-key>
For custom endpoints (OpenRouter, Azure, etc.):
shotgun-cli config set llm.base-url https://openrouter.ai/api/v1

The current provider is marked with * in the list.

Config Commands

Shotgun CLI provides a configuration system built on Viper that allows users to customize scanner behavior, LLM settings, and output preferences.

Configuration File Location

The configuration file is stored at:

  • Linux/macOS: $XDG_CONFIG_HOME/shotgun-cli/config.yaml (defaults to ~/.config/shotgun-cli/config.yaml)
  • Windows: %APPDATA%\shotgun-cli\config.yaml

Configuration Sources

Configuration values are loaded from multiple sources in order of priority (highest to lowest):

  1. Command-line flags: Highest priority, override all other sources
  2. Environment variables: Override config file values
  3. Config file: Persistent settings stored in config.yaml
  4. Defaults: Built-in default values used if no other source specifies a value

Interactive Configuration TUI

Launch the interactive configuration interface:

shotgun-cli config

This opens a full-screen TUI where you can:

  • Navigate between configuration categories (Scanner, Context, Template, Output, LLM Provider)
  • Edit values with real-time validation
  • Toggle boolean settings with Space
  • Select from dropdown options for enum values
  • Save all changes with Ctrl+S
  • See helpful descriptions for each setting

Keyboard Shortcuts:

KeyAction
Tab / Shift+TabNavigate between categories
Up/Down or j/kNavigate between fields
EnterEnter edit mode
EscExit edit mode / Cancel
SpaceToggle boolean fields
rReset field to default value
Ctrl+SSave all changes
F1Show help screen
q / Ctrl+QQuit (prompts to save if changes pending)

Features:

  • Real-time Validation: Invalid values are highlighted immediately with error messages
  • Type-aware Input: Integer fields only accept numbers, paths expand ~ automatically
  • Unsaved Changes Warning: Prompts before quitting if you have unsaved changes
  • Category Organization: Settings grouped logically for easy navigation

CLI Configuration Commands

For scripting or quick changes, use the CLI subcommands:

shotgun-cli config show

Display current configuration values with their sources.

shotgun-cli config show

Output format (human-readable):

scanner.max-files: 1000 (default)
scanner.max-file-size: 10MB (default)
llm.provider: anthropic (config file)
output.format: markdown (config file)

Output format (JSON):

shotgun-cli config show --format json

shotgun-cli config set <key> <value>

Set a configuration value. The value is validated and written to the config file.

# Set scanner max files
shotgun-cli config set scanner.max-files 5000
# Set LLM provider
shotgun-cli config set llm.provider openai
# Set API key
shotgun-cli config set llm.api-key sk-...

Validation: Values are validated before being saved. Invalid values will return an error:

$ shotgun-cli config set scanner.max-files invalid
Error: failed to parse integer value

Configuration Keys

Scanner Settings

KeyTypeDefaultDescription
scanner.max-filesint1000Maximum number of files to scan
scanner.max-file-sizesize10MBMaximum size per file (e.g., 10MB, 500KB)
scanner.respect-gitignorebooltrueRespect .gitignore files during scanning
scanner.skip-binarybooltrueSkip binary files during scanning
scanner.include-hiddenboolfalseInclude hidden files (starting with .)
scanner.include-ignoredboolfalseInclude git-ignored files
scanner.respect-shotgunignorebooltrueRespect .shotgunignore files

Context Settings

KeyTypeDefaultDescription
context.max-sizesize10MBMaximum size of generated context (e.g., 1MB, 500KB)
context.include-treebooltrueInclude file tree in context
context.include-summarybooltrueInclude file summary in context

Template Settings

KeyTypeDefaultDescription
template.custom-pathpath-Custom path to template directory

Output Settings

KeyTypeDefaultDescription
output.formatstringmarkdownOutput format: markdown or text
output.clipboardboolfalseCopy generated context to clipboard

LLM Provider Settings

KeyTypeDefaultDescription
llm.providerstring-LLM provider: openai, anthropic, gemini
llm.api-keystring-API key for the provider
llm.base-urlURL-Custom base URL for API requests
llm.modelstring-Model name to use
llm.timeoutint300Request timeout in seconds (1-3600)

Configuration Validation

The configuration system provides centralized validation through internal/config/validator.go. All values are validated before being saved to the configuration file.

Validation Functions

KeyValidatorRulesError Messages
scanner.max-filesvalidateIntValueInteger in 1-1,000,000, rejects size formats"expected a positive integer", "expected a number, got size format", "must be positive", "too large (max 1000000)"
scanner.max-file-sizevalidateSizeFormatSize format (KB/MB/GB/B) or plain number"expected size format (e.g., 1MB, 500KB)"
scanner.respect-gitignorevalidateBooleanValue"true" or "false" (case-insensitive)"expected 'true' or 'false'"
scanner.skip-binaryvalidateBooleanValue"true" or "false" (case-insensitive)"expected 'true' or 'false'"
scanner.include-hiddenvalidateBooleanValue"true" or "false" (case-insensitive)"expected 'true' or 'false'"
scanner.include-ignoredvalidateBooleanValue"true" or "false" (case-insensitive)"expected 'true' or 'false'"
scanner.respect-shotgunignorevalidateBooleanValue"true" or "false" (case-insensitive)"expected 'true' or 'false'"
context.max-sizevalidateSizeFormatSize format (KB/MB/GB/B) or plain number"expected size format (e.g., 1MB, 500KB)"
context.include-treevalidateBooleanValue"true" or "false" (case-insensitive)"expected 'true' or 'false'"
context.include-summaryvalidateBooleanValue"true" or "false" (case-insensitive)"expected 'true' or 'false'"
template.custom-pathvalidatePathValid path (empty allowed)"failed to expand home directory", "parent path exists but is not a directory"
output.formatvalidateEnumValue"markdown" or "text""expected one of: markdown, text"
output.clipboardvalidateBooleanValue"true" or "false" (case-insensitive)"expected 'true' or 'false'"
llm.providervalidateEnumValueopenai, anthropic, gemini"expected one of: openai, anthropic, gemini, got ''"
llm.api-keyNoneAny stringN/A
llm.base-urlvalidateURLEmpty or starts with http:// or https://"URL must start with http:// or https://"
llm.modelNoneAny string (provider-specific validation)N/A
llm.timeoutvalidateTimeoutValueInteger between 1 and 3600 seconds"timeout must be positive", "timeout too large (max 3600 seconds)"

Validation Rules Detail

Integer Validation (scanner.max-files):

  • Must be a valid integer format (e.g., 100, 5000)
  • Max-files: Must be within 1-1,000,000 (bounds declared in internal/config/metadata.go)
  • Max-files specifically rejects size formats like "10MB" or "1KB"

Size Format Validation (scanner.max-file-size, context.max-size):

  • Supports suffixes: KB, MB, GB, B
  • Also accepts plain numbers (bytes)
  • Examples: 100, 1KB, 10MB, 1GB, 500KB
  • Case-insensitive suffixes
  • Uses utils.ParseSize() for parsing

Boolean Validation (all *enabled, *include*, *skip*, *respect*, clipboard):

  • Only accepts: true or false
  • Case-insensitive: true, True, TRUE, false, False, FALSE
  • Does NOT accept: yes, no, 1, 0, on, off

String Enum Validation:

  • llm.provider: openai, anthropic, gemini
  • output.format: markdown, text

Path Validation (template.custom-path):

  • Empty string is allowed
  • Expands ~/ to home directory
  • Parent directory must exist or be creatable
  • Validates that existing parent is a directory

URL Validation (llm.base-url):

  • Empty string is allowed
  • Must start with http:// or https://
  • Basic URL format validation only

Timeout Validation (llm.timeout):

  • Must be a positive integer
  • Range: 1-3600 seconds (1 hour max)

Type Conversion

The ConvertValue() function converts validated string values to their appropriate Go types:

Input TypeConversionResult Type
Integer keysfmt.Sscanfint
Boolean keysstrings.ToLower == "true"bool
All other keysIdentitystring

Integer Keys: scanner.max-files, llm.timeout

Boolean Keys: All *enabled, *include*, *skip*, *respect*, clipboard keys

Testing

Configuration validation is tested in internal/config/validator_test.go:

  • TestIsValidKey: Valid key detection
  • TestValidKeys: No duplicates in valid keys list
  • TestValidateValue_*: Per-key validation tests (max-files, size format, boolean, provider, format, timeout, URL, path)
  • TestDeprecationMessage / TestDeprecatedKeysAreNotValid: retired keys report why they were removed
  • TestConvertValue_*: Type conversion tests
  • TestValidatePath_*: Path validation with existing files

All validation tests use t.Parallel() for efficient execution.

Testing

Configuration functions are tested in cmd/config_test.go:

  • TestShowCurrentConfig_*: Display configuration in various formats
  • TestSetConfigValue_*: Set and validate configuration values
  • TestGetDefaultConfigPath_*: Config file path resolution
  • TestGetConfigSource_*: Configuration source detection

CMD Helper Functions

The CMD package (cmd/) contains helper functions used throughout the CLI commands. This section documents key helper functions for formatting, display, and progress reporting.

Overview

CMD helper functions provide utility functionality for:

  • Duration Formatting: Converting time durations to human-readable strings
  • URL Display: Showing configuration URLs with appropriate defaults
  • Progress Reporting: Outputting progress in human or JSON format

Key Helper Functions

formatDuration(d time.Duration) string

Location: cmd/send.go

Purpose: Formats a duration for display in the CLI output.

Behavior:

  • Durations < 1 second: Returns milliseconds (e.g., "500ms")
  • Durations >= 1 second: Returns seconds with 1 decimal place (e.g., "1.5s")

Examples:

formatDuration(500*time.Millisecond) // "500ms"formatDuration(1500*time.Millisecond) // "1.5s"formatDuration(60*time.Second) // "60.0s"formatDuration(5*time.Minute) // "300.0s"

Use Case: Displaying API request duration after sending content to an LLM.

Tests: cmd/send_test.go - 13 test cases covering milliseconds, seconds, and minutes


displayURL(url string, provider llm.ProviderType) string

Location: cmd/llm.go

Purpose: Displays a base URL with appropriate default fallback for each provider.

Behavior:

  • If URL is empty and provider has a default BaseURL: Returns "(default: )"
  • If URL is empty and provider has no default BaseURL: Returns "(default)"
  • If URL is provided: Returns the URL as-is

Examples:

displayURL("", llm.ProviderOpenAI) // "(default: https://api.openai.com/v1)"displayURL("", llm.ProviderAnthropic) // "(default: https://api.anthropic.com)"displayURL("https://custom.proxy.com", llm.ProviderOpenAI) // "https://custom.proxy.com"

Use Case: Showing LLM endpoint configuration in status output.

Tests: cmd/llm_test.go - 10 test cases covering all providers and custom URLs


Progress Reporting Functions

renderProgressHuman(p ProgressOutput)

Location: cmd/context.go

Purpose: Renders progress output in human-readable format to stderr.

Behavior:

  • With Total > 0: Outputs [Stage] Message: Current/Total (Percent%)
  • Without Total: Outputs [Stage] Message

Output Format:

[scanning] Processing files: 50/100 (50.0%)
[generating] Creating context

Tests: cmd/context_test.go - Existing tests for various progress states


renderProgressJSON(p ProgressOutput)

Location: cmd/context.go

Purpose: Renders progress output as JSON to stderr (one line per event).

Behavior: Marshals the ProgressOutput struct to JSON and outputs to stderr.

Output Format:

{"timestamp":"2024-01-01T12:00:00Z","stage":"scanning","message":"Processing files","current":50,"total":100,"percent":50}

Use Case: Programmatic progress monitoring in CI/CD pipelines.

Tests: cmd/context_test.go - 4 test cases covering full progress, partial progress, and edge cases


renderProgress(mode ProgressMode, p ProgressOutput)

Location: cmd/context.go

Purpose: Routes progress output to the appropriate renderer based on mode.

Behavior:

  • ProgressHuman: Calls renderProgressHuman()
  • ProgressJSON: Calls renderProgressJSON()
  • ProgressNone: No output

Example:

renderProgress(ProgressHuman, progress) // Human-readable outputrenderProgress(ProgressJSON, progress) // JSON outputrenderProgress(ProgressNone, progress) // No output

Tests: cmd/context_test.go - 4 test cases covering all modes


ProgressOutput Struct

Location: cmd/context.go

typeProgressOutputstruct {
Timestampstring`json:"timestamp"`Stagestring`json:"stage"`Messagestring`json:"message"`Currentint64`json:"current,omitempty"`Totalint64`json:"total,omitempty"`Percentfloat64`json:"percent,omitempty"`
}

Testing

CMD helper functions are tested in:

  • cmd/send_test.go: TestFormatDuration
  • cmd/llm_test.go: TestDisplayURL
  • cmd/context_test.go: TestRenderProgressJSON, TestRenderProgress, and existing TestRenderProgressHuman_* tests

All tests use stdout/stderr capture to verify output format.

Cross-References

  • CMD Package: cmd/
  • LLM Types: internal/core/llm/
  • Context Commands: "Context Commands" section

TUI Wizard Usage

The TUI Wizard provides an interactive 5-step workflow for generating LLM-optimized codebase contexts. This section covers keyboard shortcuts, terminal requirements, and usage tips.

Terminal Requirements

Minimum terminal size: 40 columns x 10 rows

If your terminal window is too small, the wizard will display a warning overlay asking you to resize. The warning shows your current dimensions and the minimum required size.

Terminal too small
Current: 30x8
Required: 40x10
Please resize your terminal

Keyboard Shortcuts

Global Navigation

KeyAction
F1Toggle help screen
F7 / Ctrl+PPrevious step
F8 / Ctrl+NNext step
Ctrl+QQuit application

File Selection (Step 1)

KeyAction
↑/↓ or k/jNavigate up/down
←/→ or h/lCollapse/Expand directory
SpaceToggle selection (file or directory)
aSelect all visible files
ADeselect all visible files
iToggle showing ignored files
/Enter filter mode (fuzzy search)
Ctrl+CClear filter
F5Rescan directory

Filter Mode: When a filter is active, the status bar displays the match count in the format X/Y files (e.g., "12/45 files"), showing how many files match the filter out of the total available files.

Template Selection (Step 2)

KeyAction
↑/↓ or k/jNavigate templates
EnterSelect template
vView full template (opens modal)

Template Preview Modal:

KeyAction
j/kScroll up/down
PgUp/PgDownPage scroll
g/GJump to top/bottom
Esc/qClose modal

Text Input (Steps 3-4)

KeyAction
TypeEnter text
EnterNew line
BackspaceDelete character

Review (Step 5)

KeyAction
F8Generate context
cCopy to clipboard
F9Send to LLM (if configured)

Visual Feedback

  • Loading Spinner: During directory scanning, an animated spinner is displayed with "Scanning directory..." message
  • Progress Indicators: Progress bars show scan and generation progress with current/total counts
  • Filter Match Count: When filtering files, the stats bar shows "X/Y files" indicating matches vs total

TUI Wizard Helper Functions

The TUI Wizard (internal/ui/wizard.go) implements a 5-step interactive workflow using Bubble Tea. This section documents the key helper functions that power the wizard's internal operations.

Overview

The wizard follows the MVU (Model-View-Update) pattern from Bubble Tea, with helper functions handling specific aspects of the workflow:

  • State Management: Tracking the current step and wizard state
  • Scan Operations: File system scanning and tree building
  • Generation Operations: Context generation and file output
  • Message Handling: Processing Bubble Tea messages
  • Validation: Ensuring data integrity before state transitions

Composed Screen Model Architecture

The WizardModel uses composition to delegate screen-specific state to dedicated screen models:

┌─────────────────────────────────────────────┐
│ WizardModel │
│ (coordination + shared state only) │
├─────────────────────────────────────────────┤
│ Fields: step, width, height, progress │
│ rootPath, scanConfig, service │
│ scanCoordinator, generateCoordinator│
└─────────────┬───────────────────────────────┘
│ composes
┌─────────┼─────────┬──────────┬──────────┐
▼ ▼ ▼ ▼ ▼
┌────────┐┌────────┐┌────────┐┌────────┐┌────────┐
│FileSel ││TmplSel ││TaskInp ││RulesInp││Review │
│Model ││Model ││Model ││Model ││Model │
└────────┘└────────┘└────────┘└────────┘└────────┘
│ │ │ │ │
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
fileTree templates taskDesc rules summary
selections selected

Key Principles:

  1. Screen models own their state: File selections, template choice, task description, and rules are stored in their respective screen models, not in WizardModel.

  2. WizardModel delegates via accessor methods:

    • getSelectedFiles()fileSelection.GetSelections()
    • getSelectedTemplate()templateSelection.GetSelected()
    • getTaskDesc()taskInput.GetValue()
    • getRules()rulesInput.GetValue()
  3. Message routing: WizardModel.Update() routes messages to the appropriate screen model based on the current step.

  4. Single Responsibility: WizardModel handles step navigation and coordination; screen models handle their specific UI/state logic.

TUI Coordinator Pattern

The TUI Wizard uses the "Model of Models" pattern with dedicated coordinators for asynchronous operations. This separates the complex state management of scanning and generation from the main UI logic.

ScanCoordinator

Manages the file system scanning state machine in internal/ui/scan_coordinator.go.

  • Start: Initiates async scan with Start(rootPath, config)
  • Poll: Checks for progress updates via Poll()
  • Result: Returns (*scanner.FileNode, error) via Result()
  • State: Tracks started, done, and progress channels

GenerateCoordinator

Manages the context generation state machine in internal/ui/generate_coordinator.go.

  • Start: Initiates async generation with Start(config)
  • Poll: Checks for progress updates via Poll()
  • Result: Returns (string, error) via Result()
  • State: Tracks generation progress and content buffering

Message Flow

Both coordinators follow the Bubble Tea command pattern:

  1. Start: wizard.Update calls coordinator.Start() → returns tea.Cmd
  2. Poll: coordinator.Poll() checks channels and returns Batch(Msg, NextPoll)
  3. Progress: UI receives progress messages (ScanProgressMsg, GenerationProgressMsg)
  4. Completion: Coordinator signals completion, UI retrieves result via Result()

Message Handler Functions

handleTemplateMessage(msg tea.Msg) tea.Cmd

Purpose: Routes template-related messages to the template selection component.

Signature: func (m *WizardModel) handleTemplateMessage(msg tea.Msg) tea.Cmd

Behavior:

  • Only processes messages when in StepTemplateSelection
  • Delegates to templateSelection.HandleMessage() if component exists
  • Returns nil otherwise (ignores message)

Error Handling: No explicit errors; relies on template selection component.

Example:

cmd:=wizard.handleTemplateMessage(TemplateSelectedMsg{
Template: &template.Template{Name: "code-review"},
})

Related Tests: TestWizardHandleTemplateMessage, TestWizardHandleTemplateMessage_WrongStep, TestWizardHandleTemplateMessage_NilTemplateSelection, TestWizardHandleTemplateMessage_CorrectStepWithSelection

Testing Approach

The wizard helper functions are tested comprehensively in internal/ui/wizard_test.go:

  1. Unit Tests: Each helper function has dedicated tests covering success and error paths
  2. Table-Driven Tests: Complex functions use table-driven tests for multiple scenarios
  3. Parallel Execution: Tests use t.Parallel() for efficient execution
  4. State Validation: Tests verify internal state changes and message types
  5. Error Simulation: Tests simulate error conditions using mocked state

Coverage: The wizard package has comprehensive test coverage for all helper functions, including edge cases like nil state, empty content, and size validation.

Common Usage Patterns

Starting a Scan

// Initialize scan statemodel, cmd:=wizard.Update(startScanMsg{
rootPath: rootPath,
config: scanConfig,
})
// Process progress messagesmodel, _=wizard.Update(ScanProgressMsg{
Current: 50,
Total: 100,
Stage: "scanning",
})
// Complete scanmodel, _=wizard.Update(ScanCompleteMsg{
Tree: fileTree,
})

Starting a Generation

// Initialize generation statemodel, cmd:=wizard.Update(startGenerationMsg{
fileTree: wizard.fileTree,
selectedFiles: wizard.selectedFiles,
template: wizard.template,
taskDesc: wizard.taskDesc,
rules: wizard.rules,
rootPath: rootPath,
})
// Process progressmodel, _=wizard.Update(GenerationProgressMsg{
Stage: "render",
Message: "Rendering template",
})
// Complete generationmodel, _=wizard.Update(GenerationCompleteMsg{
Content: generatedContent,
FilePath: outputPath,
})

Cross-References

  • Test File: internal/ui/wizard_test.go
  • Wizard Implementation: internal/ui/wizard.go
  • Screen Components: internal/ui/screens/
  • Architecture docs: openwiki/architecture.md

TUI Wizard State Transitions

The TUI Wizard implements a state machine that guides users through a 5-step interactive workflow. This section documents the state transitions, message flows, and testing patterns used to ensure reliable wizard behavior.

Wizard State Machine

The wizard maintains a linear progression through five states:

StepConstantScreenPurpose
1StepFileSelectionFile SelectionSelect root directory and configure scan options
2StepTemplateSelectionTemplate SelectionChoose a prompt template for generation
3StepTaskInputTask InputDescribe the task/context for generation
4StepRulesInputRules Input(Optional) Add specific rules or constraints
5StepReviewReviewReview selections and trigger generation

State Transition Logic

Forward Movement: Users progress through steps using:

  • Enter: Confirm and move to next step
  • Tab: Navigate between interactive elements
  • Ctrl+N: Skip to next step (when applicable)

Backward Movement: Users can return to previous steps using:

  • Esc: Go back to previous step
  • Ctrl+B: Explicit back navigation

State Guards:

  • Cannot proceed from Step 1 without valid file selection
  • Cannot proceed from Step 2 without template selection
  • Cannot proceed from Step 3 with empty task description
  • Step 4 (Rules) is optional - can be skipped with empty rules
  • Step 5 requires successful scan completion before generation

Iterative Command Patterns

The wizard uses Bubble Tea's command pattern for asynchronous operations. Key iterative patterns include:

Scan Iterative Pattern

User Action → startScanMsg → scanner.Scan() (iterative)
↓
ScanProgressMsg → UI Update
↓
ScanCompleteMsg → finishScan() → Store Result

Components:

  • startScanMsg: Initiates scan with root path and config
  • scanner.Scan(): Returns tea.Cmd that yields progress messages
  • ScanProgressMsg: Updates UI with current/total file count
  • ScanCompleteMsg: Finalizes scan with file tree result
  • ScanErrorMsg: Handles scan failures

Generation Iterative Pattern

User Action → startGenerationMsg → context.Generate() (iterative)
↓
GenerationProgressMsg → UI Update
↓
GenerationCompleteMsg → finishGeneration() → Write File

Components:

  • startGenerationMsg: Initiates generation with template and context
  • context.Generate(): Returns tea.Cmd that yields progress messages
  • GenerationProgressMsg: Updates UI with generation stage
  • GenerationCompleteMsg: Finalizes generation with content and file path
  • GenerationErrorMsg: Handles generation failures

Message Types for State Transitions

MessageTypeSourceHandlerPurpose
startScanMsgInternalStep 1handleStartScanTrigger file scan
ScanProgressMsgInternalScannerhandleScanProgressUpdate scan UI
ScanCompleteMsgInternalScannerhandleScanCompleteStore scan results
ScanErrorMsgInternalScannerhandleScanErrorDisplay scan failure
startGenerationMsgInternalStep 5handleStartGenerationTrigger generation
GenerationProgressMsgInternalGeneratorhandleGenerationProgressUpdate generation UI
GenerationCompleteMsgInternalGeneratorhandleGenerationCompleteStore generation results
GenerationErrorMsgInternalGeneratorhandleGenerationErrorDisplay generation failure
StepBackMsgKey UserKeyboardUpdateNavigate to previous step
StepNextMsgKey UserKeyboardUpdateNavigate to next step
QuitMsgKey UserKeyboardUpdateExit wizard

Example Test Coverage Table

FunctionTest CoverageTest Cases
handleStartScan100%3 cases
finishScan100%3 cases (success, error, nil state)
handleStartGeneration100%4 cases
finishGeneration100%4 cases (success, empty, size error, nil)
validateContentSize100%10 cases (boundaries, errors, empty)
parseSize100%28 cases (valid, invalid, edge)
handleTemplateMessage100%3 cases (wrong step, nil, correct)

Testing Patterns

State-Based Testing

Tests verify wizard state at each transition point:

funcTestWizardStateTransitions(t*testing.T) {
wizard:=NewWizard("/tmp", &scanner.ScanConfig{}, nil)
// Initial staterequire.Equal(t, StepFileSelection, wizard.step)
// Transition to Step 2wizard.step=StepTemplateSelectionrequire.Equal(t, StepTemplateSelection, wizard.step)
}

Message-Driven Testing

Tests verify correct message handling for each state:

funcTestWizardHandleScanComplete(t*testing.T) {
wizard:=NewWizard("/tmp", &scanner.ScanConfig{}, nil)
wizard.scanState=&scanState{}
msg:=ScanCompleteMsg{
Tree: &scanner.FileNode{Name: "root", Path: "/tmp", IsDir: true},
}
cmd:=wizard.handleScanComplete(msg)
require.NotNil(t, cmd)
// Verify state updatedrequire.NotNil(t, wizard.fileTree)
}

Command Result Testing

Tests verify that commands return expected message types:

funcTestWizardFinishScan(t*testing.T) {
wizard:=NewWizard("/tmp", &scanner.ScanConfig{}, nil)
wizard.scanState=&scanState{
result: &scanner.FileNode{Name: "root"},
}
cmd:=wizard.finishScan()
msg:=cmd()
scanComplete, ok:=msg.(ScanCompleteMsg)
require.True(t, ok, "command should return ScanCompleteMsg")
require.NotNil(t, scanComplete.Tree)
}

Error Simulation

Tests verify error handling paths:

funcTestWizardFinishScan_WithError(t*testing.T) {
wizard:=NewWizard("/tmp", &scanner.ScanConfig{}, nil)
wizard.scanState=&scanState{
scanErr: errors.New("scan failed"),
}
cmd:=wizard.finishScan()
msg:=cmd()
scanErr, ok:=msg.(ScanErrorMsg)
require.True(t, ok, "command should return ScanErrorMsg on error")
require.Contains(t, scanErr.Error(), "scan failed")
}

Architecture Diagram

┌─────────────────────────────────────────────────────────────────┐
│ Wizard Model │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Step 1 │───▶│ Step 2 │───▶│ Step 3 │ │
│ │ File Select │ │ Template │ │ Task Input │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │
│ │ startScanMsg │ │
│ ▼ │ │
│ ┌──────────────┐ │ │
│ │ Scan Process │◀─────────────┐ │ │
│ │ (Iterative) │ │ │ │
│ └──────────────┘ │ │ │
│ │ │ │ │
│ │ ScanProgressMsg │ │ │
│ ▼ │ │ │
│ ┌──────────────┐ │ │ │
│ │ ScanComplete │───────────────▶│ │ │
│ └──────────────┘ │ │ │
│ │ │ │
│ │ ▼ │
│ │ ┌──────────────┐ │
│ │ │ Step 4 │ │
│ │ │ Rules Input │ │
│ │ └──────────────┘ │
│ │ │ │
│ │ │ (optional) │
│ │ ▼ │
│ │ ┌──────────────┐ │
│ │ │ Step 5 │ │
│ └───▶│ Review │ │
│ └──────────────┘ │
│ │ │
│ │ startGenerationMsg │
│ ▼ │
│ ┌──────────────┐ │
│ │ Generation │ │
│ │ (Iterative) │ │
│ └──────────────┘ │
│ │ │
│ │ GenerationComplete │
│ ▼ │
│ ┌──────────────┐ │
│ │ File Output │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

Cross-References

  • Wizard Implementation: internal/ui/wizard.go
  • Test Suite: internal/ui/wizard_test.go
  • Helper Functions: "TUI Wizard Helper Functions" section
  • Scanner Package: internal/scanner/
  • Context Package: internal/context/

TUI LLM Integration

The TUI provides integrated LLM functionality that allows users to send generated context to AI models directly from the wizard interface. This section documents the LLM integration architecture, configuration, and testing.

LLM Integration Architecture

The LLM integration follows a layered architecture where the UI delegates orchestration to the application layer:

┌─────────────────────────────────────────────────────────────────┐
│ TUI Wizard (Review Screen) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Generated Content ────▶ [Send to LLM] Button │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ handleSendToLLM() │ │
│ │ • Validates wizard state │ │
│ │ • Prepares LLMSendConfig │ │
│ │ • Calls svc.SendToLLMWithProgress() │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ app.ContextService │ │
│ │ • Orchestrates provider creation │ │
│ │ • Handles progress reporting via callback │ │
│ │ • Manages response saving │ │
│ │ • Returns LLMCompleteMsg or LLMErrorMsg │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Response File │
│ │
└─────────────────────────────────────────────────────────────────┘

LLM Provider Configuration

The wizard supports multiple LLM providers through a unified configuration:

ProviderConfig KeyRequired FieldsOptional Fields
OpenAIopenaiapi_keybase_url, model, timeout
Anthropicanthropicapi_keybase_url, model, timeout
Geminigeminiapi_keybase_url, model, timeout

Configuration Example:

llm:
provider: "anthropic"api_key: "sk-ant-..."model: "claude-sonnet-4-20250514"timeout: 300save_response: true

LLM Integration Message Flow

Note: Message types were renamed in a recent refactoring to be provider-agnostic.

MessageTypeSourceHandlerPurpose
LLMProgressMsgInternalServicehandleLLMProgressUpdate progress UI
LLMCompleteMsgInternalServicehandleLLMCompleteStore response, update UI
LLMErrorMsgInternalServicehandleLLMErrorDisplay error, update UI
RescanRequestMsgKey UserReviewhandleRescanRequestTrigger new scan

Service Delegation

The wizard now delegates all LLM operations to the app.ContextService. The NewWizard constructor accepts this service as a dependency.

// Using ContextService for LLM operations in the wizardsvc:=app.NewContextService()
wizard:=wizard.NewWizard(rootPath, scanConfig, templateMgr, svc)

Send Flow

  1. User Action: User presses "Send to LLM" on review screen
  2. Validation: handleSendToLLM() validates:
    • Wizard is on review step
    • Generated content exists
    • Not already sending
  3. Execution: svc.SendToLLMWithProgress() is called:
    • Orchestrates the entire send process
    • Progress callback updates UI during send
    • On success: Saves response (if configured)
    • Returns completion message with response file path

Test Coverage

FunctionTest CoverageTest Cases
handleSendToLLM100%5 cases (step validation, states, errors)
handleRescanRequest100%3 cases (file selection, other steps, all steps)
handleLLMProgress100%1 case
handleLLMComplete100%1 case
handleLLMError100%2 cases

Error Handling

The LLM integration handles various error scenarios through the service layer:

ErrorSourceUser Experience
Invalid providerServiceReturns error, displayed in review
Provider unavailableServiceError shown, send prevented
Not configuredServiceError shown, send prevented
Send timeoutServiceReturns LLMErrorMsg
Save failureServiceReturns error with context

Testing Examples

Service Usage Example

// Using ContextService for LLM operationssvc:=app.NewContextService()
result, err:=svc.SendToLLMWithProgress(ctx, content, app.LLMSendConfig{
Provider: llm.ProviderOpenAI,
APIKey: "your-api-key",
Model: "gpt-4o",
SaveResponse: true,
OutputPath: "./response.md",
}, func(stagestring) {
fmt.Printf("Progress: %s\n", stage)
})

Send Handler Test

funcTestWizardHandleSendToLLM_NotReviewStep(t*testing.T) {
svc:=&mockContextService{}
wizard:=NewWizard("/tmp/test", &scanner.ScanConfig{}, nil, svc)
wizard.step=StepFileSelection// Wrong stepwizard.generatedContent="content"cmd:=wizard.handleSendToLLM()
assert.Nil(t, cmd) // Should return nil for wrong step
}

Cross-References

  • LLM Package: internal/core/llm/
  • Application Service: internal/app/context.go
  • Wizard LLM Tests: internal/ui/wizard_test.go
  • LLM Commands Documentation: "LLM Diagnostic Commands" section

C4 Model Architecture

Context Diagram

</arg_value> </tool_call>

About

Source-available CLI and TUI for generating focused codebase context for AI coding workflows

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages