# Claude Ops Dashboard Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Extend the existing Go web dashboard to monitor Claude Code agent activity, context usage, skills/commands usage signals, and cost-related token stats from your local `~/.claude/` directory. **Architecture:** Keep the current lightweight Go HTTP server + static HTML/JS frontend. Add a new “Claude Ops” section with API endpoints that parse local Claude Code state files (primarily `~/.claude/stats-cache.json`, `~/.claude/history.jsonl`, `~/.claude/state/component-registry.json`, and directory listings under `~/.claude/agents`, `~/.claude/skills`, `~/.claude/commands`). Frontend gains new navigation tabs + tables/charts using vanilla JS. **Tech Stack:** Go 1.21+, chi router, vanilla HTML/CSS/JS (no React), file-based data sources under `~/.claude/`. --- ## Scope (YAGNI) In this first iteration, focus on read-only analytics: - Daily activity + token usage (from `~/.claude/stats-cache.json`) - Recent sessions list (from `~/.claude/history.jsonl` without parsing full message bodies) - Installed agents/skills/commands inventory (from `~/.claude/agents/`, `~/.claude/skills/`, `~/.claude/commands/`) - “Debug” view: show which data files are missing/unreadable and last-modified timestamps Explicitly out of scope: - Real-time websocket streaming - Multi-user auth - Editing/triggering slash commands - Deep semantic parsing of every history event (we’ll add iteratively) --- ## Task 1: Add Claude directory config to server **Files:** - Modify: `~/.claude/dashboard/cmd/server/main.go` - Modify: `~/.claude/dashboard/README.md` **Step 1: Write the failing test** Create a minimal unit test that ensures server config defaults to `~/.claude` when not specified. - Create: `~/.claude/dashboard/cmd/server/config_test.go` ```go package main import ( "os" "path/filepath" "testing" ) func TestDefaultClaudeDir(t *testing.T) { home, err := os.UserHomeDir() if err != nil { t.Fatalf("UserHomeDir: %v", err) } want := filepath.Join(home, ".claude") got := defaultClaudeDir() if got != want { t.Fatalf("defaultClaudeDir() = %q, want %q", got, want) } } ``` **Step 2: Run test to verify it fails** Run: `go test ./...` Expected: FAIL with `undefined: defaultClaudeDir` **Step 3: Write minimal implementation** - Modify: `~/.claude/dashboard/cmd/server/main.go` Add helper: ```go func defaultClaudeDir() string { home, err := os.UserHomeDir() if err != nil { return "/home/will/.claude" // fallback; best-effort } return filepath.Join(home, ".claude") } ``` Add CLI flag: - `--claude` (default `~/.claude`) **Step 4: Run test to verify it passes** Run: `go test ./...` Expected: PASS **Step 5: Commit** ```bash git add cmd/server/main.go cmd/server/config_test.go README.md git commit -m "feat: add default claude dir config" ``` --- ## Task 2: Add Claude models for API responses **Files:** - Create: `~/.claude/dashboard/internal/claude/models.go` **Step 1: Write the failing test** - Create: `~/.claude/dashboard/internal/claude/models_test.go` ```go package claude import "testing" func TestModelTypesCompile(t *testing.T) { _ = StatsCache{} _ = DailyActivity{} _ = ModelUsage{} } ``` **Step 2: Run test to verify it fails** Run: `go test ./...` Expected: FAIL with `undefined: StatsCache` **Step 3: Write minimal implementation** - Create: `~/.claude/dashboard/internal/claude/models.go` Implement structs matching the subset we use: ```go package claude type DailyActivity struct { Date string `json:"date"` MessageCount int `json:"messageCount"` SessionCount int `json:"sessionCount"` ToolCallCount int `json:"toolCallCount"` } type DailyModelTokens struct { Date string `json:"date"` TokensByModel map[string]int `json:"tokensByModel"` } type ModelUsage struct { InputTokens int `json:"inputTokens"` OutputTokens int `json:"outputTokens"` CacheReadInputTokens int `json:"cacheReadInputTokens"` CacheCreationInputTokens int `json:"cacheCreationInputTokens"` WebSearchRequests int `json:"webSearchRequests"` CostUSD float64 `json:"costUSD"` ContextWindow int `json:"contextWindow"` } type StatsCache struct { Version int `json:"version"` LastComputedDate string `json:"lastComputedDate"` DailyActivity []DailyActivity `json:"dailyActivity"` DailyModelTokens []DailyModelTokens `json:"dailyModelTokens"` ModelUsage map[string]ModelUsage `json:"modelUsage"` TotalSessions int `json:"totalSessions"` TotalMessages int `json:"totalMessages"` } ``` **Step 4: Run test to verify it passes** Run: `go test ./...` Expected: PASS **Step 5: Commit** ```bash git add internal/claude/models.go internal/claude/models_test.go git commit -m "feat: add claude stats response models" ``` --- ## Task 3: Implement Claude file loader **Files:** - Create: `~/.claude/dashboard/internal/claude/loader.go` - Test: `~/.claude/dashboard/internal/claude/loader_test.go` **Step 1: Write the failing test** ```go package claude import ( "os" "path/filepath" "testing" ) func TestLoadStatsCache(t *testing.T) { dir := t.TempDir() p := filepath.Join(dir, "stats-cache.json") err := os.WriteFile(p, []byte(`{"version":1,"lastComputedDate":"2025-12-31","totalSessions":1,"totalMessages":2}`), 0644) if err != nil { t.Fatalf("WriteFile: %v", err) } loader := NewLoader(dir) stats, err := loader.LoadStatsCache() if err != nil { t.Fatalf("LoadStatsCache: %v", err) } if stats.TotalSessions != 1 { t.Fatalf("TotalSessions=%d", stats.TotalSessions) } } ``` **Step 2: Run test to verify it fails** Run: `go test ./...` Expected: FAIL with `undefined: NewLoader` **Step 3: Write minimal implementation** Implement `Loader` with: - `NewLoader(claudeDir string)` - `LoadStatsCache() (*StatsCache, error)` reads `/stats-cache.json` - `ListDir(name string) ([]DirEntry, error)` for `agents/`, `skills/`, `commands/` - `FileInfo(path string) (FileMeta, error)` for debug view **Step 4: Run test to verify it passes** Run: `go test ./...` Expected: PASS **Step 5: Commit** ```bash git add internal/claude/loader.go internal/claude/loader_test.go git commit -m "feat: load claude stats-cache.json" ``` --- ## Task 4: Add new Claude Ops API routes **Files:** - Modify: `~/.claude/dashboard/cmd/server/main.go` - Create: `~/.claude/dashboard/internal/api/claude_handlers.go` - Modify: `~/.claude/dashboard/internal/api/handlers.go` (only if you want shared helpers) **Step 1: Write the failing test** - Create: `~/.claude/dashboard/internal/api/claude_handlers_test.go` ```go package api import ( "net/http" "net/http/httptest" "testing" "github.com/go-chi/chi/v5" "github.com/will/k8s-agent-dashboard/internal/claude" ) type fakeLoader struct{} func (f fakeLoader) LoadStatsCache() (*claude.StatsCache, error) { return &claude.StatsCache{TotalSessions: 3}, nil } func TestGetClaudeStats(t *testing.T) { r := chi.NewRouter() r.Get("/api/claude/stats", GetClaudeStats(fakeLoader{})) req := httptest.NewRequest(http.MethodGet, "/api/claude/stats", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != 200 { t.Fatalf("status=%d body=%s", w.Code, w.Body.String()) } } ``` **Step 2: Run test to verify it fails** Run: `go test ./...` Expected: FAIL with `undefined: GetClaudeStats` **Step 3: Write minimal implementation** Create endpoints: - `GET /api/claude/health` → returns `{status:"ok", claudeDir:"..."}` and file presence checks - `GET /api/claude/stats` → returns parsed `StatsCache` - `GET /api/claude/inventory` → lists agents/skills/commands entries - `GET /api/claude/debug/files` → returns file metas for key files and last-modified Wire in `cmd/server/main.go`: - Build loader from `--claude` flag - Register routes under `/api/claude` **Step 4: Run test to verify it passes** Run: `go test ./...` Expected: PASS **Step 5: Commit** ```bash git add cmd/server/main.go internal/api/claude_handlers.go internal/api/claude_handlers_test.go git commit -m "feat: add claude ops api endpoints" ``` --- ## Task 5: Add new UI navigation tabs **Files:** - Modify: `~/.claude/dashboard/cmd/server/web/index.html` - Modify: `~/.claude/dashboard/cmd/server/web/static/css/style.css` **Step 1: Make a minimal UI change (no tests)** Add nav buttons: - Overview - Usage - Inventory - Debug Add new `
` elements mirroring existing “views” pattern. **Step 2: Manual verification** Run: `go run ./cmd/server --port 8080 --data /tmp/k8s --claude ~/.claude` Expected: New tabs switch views (even if empty). **Step 3: Commit** ```bash git add cmd/server/web/index.html cmd/server/web/static/css/style.css git commit -m "feat: add claude ops dashboard views" ``` --- ## Task 6: Implement frontend data fetching + rendering **Files:** - Modify: `~/.claude/dashboard/cmd/server/web/static/js/app.js` **Step 1: Add API calls** Add functions: - `loadClaudeStats()` → `GET /api/claude/stats` - `loadClaudeInventory()` → `GET /api/claude/inventory` - `loadClaudeDebugFiles()` → `GET /api/claude/debug/files` Integrate into `loadAllData()`. **Step 2: Add render functions** - Overview: show totals + lastComputedDate - Usage: simple table for `dailyActivity` (date, messages, sessions, tool calls) - Inventory: 3 columns lists: agents, skills, commands - Debug: table of key files with status/missing + mtime **Step 3: Manual verification** Run: `go run ./cmd/server --port 8080 --data /tmp/k8s --claude ~/.claude` Expected: Data populates from your local `~/.claude/stats-cache.json`. **Step 4: Commit** ```bash git add cmd/server/web/static/js/app.js git commit -m "feat: render claude usage and inventory data" ``` --- ## Task 7: Add “cost optimization” signals (derived) **Files:** - Modify: `~/.claude/dashboard/internal/api/claude_handlers.go` - Modify: `~/.claude/dashboard/internal/claude/models.go` **Step 1: Write the failing test** Add a test that expects derived fields: - cache hit ratio estimate: `cacheReadInputTokens / (inputTokens + cacheReadInputTokens + cacheCreationInputTokens)` (best-effort) - top model by output tokens **Step 2: Run test to verify it fails** Run: `go test ./...` Expected: FAIL because fields aren’t present **Step 3: Implement derived summary endpoint** - `GET /api/claude/summary` returns: - totals - per-model tokens - derived cost signals **Step 4: Run tests** Run: `go test ./...` Expected: PASS **Step 5: Commit** ```bash git add internal/api/claude_handlers.go internal/claude/models.go internal/api/claude_handlers_test.go git commit -m "feat: add claude summary and cache efficiency signals" ``` --- ## Task 8: End-to-end check **Files:** - None **Step 1: Run tests** Run: `go test ./...` Expected: PASS **Step 2: Run server locally** Run: `go run ./cmd/server --port 8080 --data /tmp/k8s --claude ~/.claude` Expected: Browser shows Claude Ops tabs with live data. --- ## Notes / Follow-ups (later iterations) - Parse `~/.claude/history.jsonl` into “sessions” and show recent slash commands usage (requires schema discovery) - Add “top tools called” chart (requires richer history parsing) - Add alert thresholds (e.g., token spikes day-over-day)