Files
claude-code/docs/plans/2026-01-01-claude-ops-dashboard-implementation.md
OpenCode Test e43e052a32 Add design plans for dashboard integration
Add implementation plans for morning report, Claude ops dashboard, and realtime monitoring features.
2026-01-03 10:55:22 -08:00

11 KiB
Raw Permalink Blame History

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 (well 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
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:

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

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
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:

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

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

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 <claudeDir>/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

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
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

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 <section> 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

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

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 arent 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

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)