This document captures the key decisions made when building gh-agent-viz.
gh Extension and Not a Copilot CLI PluginThe GitHub Copilot CLI (github/copilot-cli) has a plugin system that can be accessed via /plugin install. Plugins can provide several capabilities:
.agent.md filesSKILL.md files.mcp.json configurationpreToolUse, agentStop)The official plugin repository is github/copilot-plugins, with examples like:
workiq - Combines MCP server and skill functionalityspark - Provides skills onlyPlugins operate within the agent conversation model. They give the agent new knowledge and tools, but provide zero control over the terminal UI. There is no plugin API for:
A plugin could answer “what are my agent sessions?” conversationally, but it cannot render an interactive dashboard.
For interactive visualization, a gh CLI extension is the correct approach because:
gh-dashgh auth systemgh extension installDecision: Use Go as the primary language.
Rationale:
gh CLI ecosystemgh-dash (10k+ stars)Decision: Use the Charmbracelet stack for the TUI.
Rationale:
gh-dash, proven to work well for gh extensionsDecision: Use Cobra for command-line argument parsing.
Rationale:
gh itself and gh-dashDecision: Fetch data primarily via direct HTTP calls to the Copilot API (api.githubcopilot.com), falling back to gh agent-task CLI commands if CAPI auth fails.
Primary — Copilot API (CAPI):
api.githubcopilot.com using the user’s gh auth OAuth token (Bearer gho_ prefix)Copilot-Integration-Id: copilot-4-cli, X-GitHub-Api-Version: 2026-01-09internal/data/capi/ (client.go, types.go, sessions.go)Fallback — CLI:
gh agent-task list - Lists recent agent sessions with status, repo, and timestampsgh agent-task view <id> --log - Shows event log for a sessiongh agent-task view <id> --log --follow - Streams live logsRationale:
go-gh library (picks up user’s gh auth token)gh extension installDecision: Distribute as a gh extension.
Installation:
gh extension install maxbeizer/gh-agent-viz
Usage:
gh agent-viz
gh agent-viz --repo owner/repo
Rationale:
gh usersgh extension upgradegh-dashDecision: Model architecture on dlvhdr/gh-dash.
Rationale:
gh extensions (10k+ stars)| Package | Purpose |
|---|---|
github.com/charmbracelet/bubbletea |
TUI framework (Elm architecture) |
github.com/charmbracelet/lipgloss |
Terminal styling and layout |
github.com/charmbracelet/bubbles |
Pre-built UI components |
github.com/spf13/cobra |
CLI argument parsing |
github.com/cli/go-gh/v2 |
GitHub auth context and API client |
gopkg.in/yaml.v3 |
Configuration file parsing |
Why not: No API for custom UI rendering. Plugins can only provide conversational responses within the agent’s chat interface.
When it makes sense: If we wanted to add a conversational interface to query agent sessions (“show me my last 5 agent runs”), a plugin could complement the TUI.
gh EcosystemWhy not: Would require separate authentication setup and wouldn’t integrate with the gh CLI workflow.
When it makes sense: If we needed to support users who don’t use gh CLI at all.
Why not (standalone): The Copilot API is now the primary data source (see above), but a CLI fallback is retained for auth-edge cases.
When it makes sense: Future enhancement when GitHub provides a proper API endpoint.
The primary data path now uses the Copilot API (api.githubcopilot.com) directly. If GitHub introduces a separate dedicated REST API endpoint for agent sessions, we should evaluate migrating to it for broader compatibility.
The --follow flag on gh agent-task view <id> --log enables live log streaming. This could be implemented using:
We could create a plugin that provides:
gh agent-task commandsFuture config file options could include:
gh-agent-viz/
├── gh-agent-viz.go # Entry point
├── cmd/
│ └── root.go # Cobra root command
├── internal/
│ ├── data/
│ │ ├── agentapi.go # Data fetching layer (orchestrates CAPI + CLI fallback)
│ │ └── capi/ # Direct Copilot API client
│ ├── config/
│ │ └── config.go # Configuration parsing
│ └── tui/
│ ├── ui.go # Main Bubble Tea model
│ ├── context.go # Shared program context
│ ├── theme.go # Lip Gloss styles
│ ├── keys.go # Key bindings
│ └── components/
│ ├── header/
│ ├── footer/
│ ├── tasklist/
│ ├── taskdetail/
│ └── logview/
├── docs/
│ └── DECISIONS.md # This file
├── .goreleaser.yaml # Cross-platform builds
├── .gitignore # Go ignores
└── README.md # Install and usage docs
// Primary: direct CAPI call (internal/data/capi/)
client := capi.NewClient(token)
sessions, err := client.ListSessions(ctx)
// Fallback: shell out to gh agent-task
cmd := exec.Command("gh", "agent-task", "list", "--json")
output, err := cmd.Output()
type Model struct {
// State fields
}
func (m Model) Init() tea.Cmd { /* ... */ }
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { /* ... */ }
func (m Model) View() string { /* ... */ }
Each UI component lives in its own package with:
Define all key bindings in one place for:
Lip Gloss styles defined in a theme package: