Add 'gt dashboard' CLI command (hq-s1bg) (#65)

* Add LastActivity calculation for convoy dashboard (hq-x2xy)

Adds internal/activity package with color-coded activity tracking:
- Green: <2 minutes (active)
- Yellow: 2-5 minutes (stale)
- Red: >5 minutes (stuck)

Features:
- Calculate() function returns Info with formatted age and color class
- Helper methods: IsActive(), IsStale(), IsStuck()
- Handles edge cases: zero time, future time (clock skew)

Tests: 8 test functions with 25 sub-tests covering all thresholds.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add convoy dashboard HTML template with Last Activity (hq-fq1g)

Adds internal/web package with convoy dashboard template:
- convoy.html with Last Activity column and color coding
- Green (<2min), Yellow (2-5min), Red (>5min) activity indicators
- htmx auto-refresh every 30 seconds
- Progress bars for convoy completion
- Status indicators for open/closed convoys
- Empty state when no convoys

Also includes internal/activity package (dependency from hq-x2xy):
- Calculate() returns Info with formatted age and color class
- Helper methods: IsActive(), IsStale(), IsStuck()

Tests: 6 template tests + 8 activity tests, all passing.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add convoy list handler with activity data (hq-3edt)

Adds HTTP handler that wires convoy dashboard template to real data:
- ConvoyHandler: HTTP handler for GET / rendering convoy dashboard
- LiveConvoyFetcher: Fetches convoys from beads with activity data
- ConvoyFetcher interface: Enables mocking for tests

Features:
- Fetches open convoys from town beads
- Calculates progress (completed/total) from tracked issues
- Gets Last Activity from worker agent beads
- Color codes activity: Green (<2min), Yellow (2-5min), Red (>5min)

Includes dependencies (not yet merged):
- internal/activity: Activity calculation (hq-x2xy)
- internal/web/templates: HTML template (hq-fq1g)

Tests: 5 handler tests + 6 template tests + 8 activity tests = 19 total

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add 'gt dashboard' CLI command (hq-s1bg)

Add dashboard command to start the convoy tracking web server.

Usage: gt dashboard [--port=8080] [--open]

Features:
- --port: Configurable HTTP port (default 8080)
- --open: Auto-open browser on start
- Cross-platform browser launch (darwin/linux/windows)
- Graceful workspace detection

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mike Lady
2026-01-03 11:49:42 -08:00
committed by GitHub
parent 7afcea935b
commit 3488933cc2
10 changed files with 1567 additions and 0 deletions

91
internal/cmd/dashboard.go Normal file
View File

@@ -0,0 +1,91 @@
package cmd
import (
"fmt"
"net/http"
"os/exec"
"runtime"
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/web"
"github.com/steveyegge/gastown/internal/workspace"
)
var (
dashboardPort int
dashboardOpen bool
)
var dashboardCmd = &cobra.Command{
Use: "dashboard",
GroupID: GroupDiag,
Short: "Start the convoy tracking web dashboard",
Long: `Start a web server that displays the convoy tracking dashboard.
The dashboard shows real-time convoy status with:
- Convoy list with status indicators
- Progress tracking for each convoy
- Last activity indicator (green/yellow/red)
- Auto-refresh every 30 seconds via htmx
Example:
gt dashboard # Start on default port 8080
gt dashboard --port 3000 # Start on port 3000
gt dashboard --open # Start and open browser`,
RunE: runDashboard,
}
func init() {
dashboardCmd.Flags().IntVar(&dashboardPort, "port", 8080, "HTTP port to listen on")
dashboardCmd.Flags().BoolVar(&dashboardOpen, "open", false, "Open browser automatically")
rootCmd.AddCommand(dashboardCmd)
}
func runDashboard(cmd *cobra.Command, args []string) error {
// Verify we're in a workspace
if _, err := workspace.FindFromCwdOrError(); err != nil {
return fmt.Errorf("not in a Gas Town workspace: %w", err)
}
// Create the live convoy fetcher
fetcher, err := web.NewLiveConvoyFetcher()
if err != nil {
return fmt.Errorf("creating convoy fetcher: %w", err)
}
// Create the handler
handler, err := web.NewConvoyHandler(fetcher)
if err != nil {
return fmt.Errorf("creating convoy handler: %w", err)
}
// Build the URL
url := fmt.Sprintf("http://localhost:%d", dashboardPort)
// Open browser if requested
if dashboardOpen {
go openBrowser(url)
}
// Start the server
fmt.Printf("🚚 Gas Town Dashboard starting at %s\n", url)
fmt.Printf(" Press Ctrl+C to stop\n")
return http.ListenAndServe(fmt.Sprintf(":%d", dashboardPort), handler)
}
// openBrowser opens the specified URL in the default browser.
func openBrowser(url string) {
var cmd *exec.Cmd
switch runtime.GOOS {
case "darwin":
cmd = exec.Command("open", url)
case "linux":
cmd = exec.Command("xdg-open", url)
case "windows":
cmd = exec.Command("cmd", "/c", "start", url)
default:
return
}
_ = cmd.Start()
}