feat: Add charmbracelet TUI for gt feed (gt-u7dxq)

- Add bubbletea and bubbles dependencies
- Create internal/tui/feed package with:
  - model.go: Main bubbletea model with agent tree and event stream
  - view.go: Rendering logic with lipgloss styling
  - keys.go: Vim-style key bindings (j/k, tab, /, q)
  - styles.go: Color palette and component styles
  - events.go: Event source from bd activity
- Update gt feed to use TUI by default (--plain for text mode)
- TUI features: agent tree by role, event stream, keyboard nav

Closes gt-be0as, gt-lexye, gt-1uhmj

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-28 16:24:28 -08:00
parent dcd996495a
commit c7da650f94
8 changed files with 1321 additions and 27 deletions

View File

@@ -7,9 +7,12 @@ import (
"strings"
"syscall"
tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/tmux"
"github.com/steveyegge/gastown/internal/tui/feed"
"github.com/steveyegge/gastown/internal/workspace"
"golang.org/x/term"
)
var (
@@ -21,6 +24,7 @@ var (
feedRig string
feedNoFollow bool
feedWindow bool
feedPlain bool
)
func init() {
@@ -34,6 +38,7 @@ func init() {
feedCmd.Flags().StringVar(&feedType, "type", "", "Filter by event type (create, update, delete, comment)")
feedCmd.Flags().StringVar(&feedRig, "rig", "", "Run from specific rig's beads directory")
feedCmd.Flags().BoolVarP(&feedWindow, "window", "w", false, "Open in dedicated tmux window (creates 'feed' window)")
feedCmd.Flags().BoolVar(&feedPlain, "plain", false, "Use plain text output (bd activity) instead of TUI")
}
var feedCmd = &cobra.Command{
@@ -42,15 +47,16 @@ var feedCmd = &cobra.Command{
Short: "Show real-time activity feed from beads",
Long: `Display a real-time feed of issue and molecule state changes.
This command wraps 'bd activity' to show mutations as they happen,
providing visibility into workflow progress across Gas Town.
By default, launches an interactive TUI dashboard with:
- Agent tree (top): Shows all agents organized by role with latest activity
- Event stream (bottom): Chronological feed you can scroll through
- Vim-style navigation: j/k to scroll, tab to switch panels, q to quit
By default, streams in follow mode. Use --no-follow to show events once.
Use --plain for simple text output (wraps bd activity).
Tmux Integration:
Use --window to open the feed in a dedicated tmux window named 'feed'.
This creates a persistent window you can cycle to with C-b n/p.
If the window already exists, switches to it.
Event symbols:
+ created/bonded - New issue or molecule created
@@ -60,12 +66,10 @@ Event symbols:
⊘ deleted - Issue removed
Examples:
gt feed # Stream all events (default: --follow)
gt feed # Launch TUI dashboard
gt feed --plain # Plain text output (bd activity)
gt feed --window # Open in dedicated tmux window
gt feed -w # Short form of --window
gt feed --no-follow # Show last 100 events and exit
gt feed --since 1h # Events from last hour
gt feed --mol gt-xyz # Filter by issue prefix
gt feed --rig gastown # Use gastown rig's beads`,
RunE: runFeed,
}
@@ -112,7 +116,14 @@ func runFeed(cmd *cobra.Command, args []string) error {
return runFeedInWindow(workDir, bdArgs)
}
// Standard mode: exec bd activity directly
// Use TUI by default if running in a terminal and not --plain
useTUI := !feedPlain && term.IsTerminal(int(os.Stdout.Fd()))
if useTUI {
return runFeedTUI(workDir)
}
// Plain mode: exec bd activity directly
return runFeedDirect(workDir, bdArgs)
}
@@ -167,6 +178,28 @@ func runFeedDirect(workDir string, bdArgs []string) error {
return syscall.Exec(bdPath, fullArgs, os.Environ())
}
// runFeedTUI runs the interactive TUI feed.
func runFeedTUI(workDir string) error {
// Create event source from bd activity
source, err := feed.NewBdActivitySource(workDir)
if err != nil {
return fmt.Errorf("creating event source: %w", err)
}
defer source.Close()
// Create model and connect event source
m := feed.NewModel()
m.SetEventChannel(source.Events())
// Run the TUI
p := tea.NewProgram(m, tea.WithAltScreen())
if _, err := p.Run(); err != nil {
return fmt.Errorf("running TUI: %w", err)
}
return nil
}
// runFeedInWindow opens the feed in a dedicated tmux window.
func runFeedInWindow(workDir string, bdArgs []string) error {
// Check if we're in tmux