Operator dashboard

AI Ops Dashboard

Where it all comes together

βš™ Tabs

Show or hide any dashboard tab.

How: Toggle tabs on/off and pick the default tab. Hidden tabs stay installed and can be turned back on anytime β€” nothing is deleted.

Change the dashboard's color theme.

How: Pick any of the 8 themes β€” your choice is saved in this browser only (it doesn't change the shared default).

Data updated 10/19/2018, 6:46 PM PDT
Rendered 6/20/2026, 12:09 AM PDT Β· auto-refresh 120s
← AI Operations Dashboard docsΒ·AGENTS.md

AI Operations Dashboard Β· For AI Agents

This file is the entry point for any AI session working in this repository. Read it first; it tells you what AI Operations Dashboard is, where the moving parts live, what tasks you can safely take on, and what's off-limits. When in doubt, defer to the human operator.

What this project is

AI Operations Dashboard is a single-page operator dashboard for solo founders and small teams running AI-heavy projects. One Next.js 16 (App Router, React 19, Tailwind v4) page renders every signal that matters β€” pending work, operator log, AI spend, scheduled jobs, postmortems, the strategic roadmap, and recent git activity β€” by reading hand-edited markdown and JSON files from the repo. There is no database and no API: every panel is a server component that loads its data at render time from files in data/, docs/, plans/, and knowledge/. The project is meant to be forked, themed via ai-ops-dashboard.config.ts, and operated by humans with AI agents acting as scoped assistants.

Where to find things

  • Panels β€” components/panels/*-panel.tsx. Each panel exports a PanelDefinition with id, label, an async loader(ctx) that reads files, an optional sources[] array declaring its inputs, and a default-export server component that renders the loaded data. Current panels: overview, plan, sprints, activity, pending, log, postmortems, usage, scheduled, repos, docs, skills.
  • Data β€” data/*.json (and data/*.jsonl for instrumented logs). Hand-edited for source-of-truth files (pending.json, scheduled-jobs.json), script-regenerated for derived files (recent-commits.json, usage.json).
  • Narrative content β€” docs/operator-log.md, plans/roadmap.md, plans/execution-status.md, knowledge/postmortems/*.md. Markdown is the source of truth; the dashboard parses it on render.
  • Config β€” ai-ops-dashboard.config.ts is the single file you edit to brand the dashboard, pick a theme, add/remove panels, and toggle AI features. It re-reads on every render in dev.
  • Schemas β€” help/data-schemas.md documents the JSON / markdown shape every loader expects. Treat it as authoritative; if a loader and the schema disagree, the schema is wrong β€” fix it.
  • Scripts β€” scripts/*.mjs. Data-build scripts (build-data.mjs, freeze-commits.mjs, aggregate-usage.mjs) and dev helpers (watch-data.mjs, new-panel.mjs).
  • Knowledge graph β€” graphify-out/ holds a committed, queryable map of the whole repo (graph.json + GRAPH_REPORT.md). Query it before grepping β€” see *Reading project state* below.

Common tasks

If a recipe exists for what you're about to do, follow it verbatim β€” recipes encode the exact step order, validators, and pitfalls. Recipes live in .ai-ops-dashboard/recipes/<task>.md:

  • .ai-ops-dashboard/recipes/add-panel.md β€” scaffold a new panel, register it in ai-ops-dashboard.config.ts, and wire its data source.
  • .ai-ops-dashboard/recipes/wire-data-source.md β€” connect a new JSON/markdown file to an existing panel via the lib/data-sources helpers.
  • .ai-ops-dashboard/recipes/change-theme.md β€” switch theme presets or override CSS custom properties without breaking other panels.
  • .ai-ops-dashboard/recipes/refresh-data.md β€” manually run the data-build chain (freeze commits, aggregate usage, refresh manifest) when something looks stale.
  • .ai-ops-dashboard/recipes/diagnose-panel.md β€” triage a panel that's rendering empty, crashing, or stale; the standard "is the loader returning data?" runbook.
  • .ai-ops-dashboard/recipes/add-postmortem.md β€” file a new incident under knowledge/postmortems/, including the required ## Prevention section and the matching pending.json follow-up.
  • .ai-ops-dashboard/recipes/connect-repo.md β€” wire a separate repo into this dashboard via the ai-ops-dashboard CLI so its commits, pending items, and operator-log entries flow into the panels here.

Cross-repo coordination

In v0.3, a single AI Operations Dashboard can receive writes from multiple external repos via the ai-ops-dashboard CLI. Coordinate accordingly:

  • Listing connected repos β€” read data/connected-repos.json. Each entry has the external repo's path, its display name, and the timestamp of its last sync. This file is the authoritative answer to "which repos feed this dashboard?"
  • Knowing which repo a log entry came from β€” parse the entry footer. The CLI appends a from-repo: <name> tag on the last line of every operator-log entry and every pending item it writes. If the tag is missing, the entry was hand-written directly in the dashboard repo.
  • Adding a new connected repo β€” run npx ai-ops-dashboard init from inside the external repo (it will prompt for this dashboard's path), or re-run the /setup/connect flow from the dashboard's setup UI. Both paths end with a new entry in data/connected-repos.json and a .ai-ops-dashboard-client/ directory in the external repo.

v0.3 connected repos

As of v0.3, this dashboard may receive writes from external repos via the ai-ops-dashboard CLI. You are not always the only source of changes β€” a commit you didn't make may appear in the activity panel because another repo's snapshot got merged in. Know the shape:

  • `data/connected-repos.json` is the registry. It tracks every external repo wired into this dashboard: absolute path on disk, display name, connection date, last-sync timestamp, and the CLI version that wrote it. Reading this file tells you which external paths exist and where they live, so you can navigate to them when the user mentions "my X repo" or "the Y project" without asking.
  • The `/repos` panel renders this registry on the dashboard β€” one card per connected repo with sync status and a deep link to the repo's last commit. If a repo stops syncing, its card goes stale and the panel surfaces the gap.
  • The activity panel merges commit history across the fleet. Each connected repo freezes its own data/recent-commits.json, which the dashboard reads at <repo>/.ai-ops/data/recent-commits.json (with a back-compat fallback to <repo>/data/recent-commits.json); the dashboard's own commits come from its root data/recent-commits.json. The activity loader unions them, sorts by timestamp, and tags each row with its source repo.
  • Writes targeting an external repo should go through the CLI, not direct edits to dashboard files. The CLI knows the right destination file, the right format, and adds the repo-attribution footer automatically. Direct edits to data/pending.json or docs/operator-log.md from inside the dashboard repo are still fine for dashboard-scoped work β€” they just won't carry a from-repo tag.

Per-repo data layout (.ai-ops/) β€” the local-write model

As of v0.7, each connected repo owns its operator state locally in a committed .ai-ops/ directory at the repo root, instead of writing into this dashboard's central data/ / docs/ / knowledge/. The dashboard aggregates across the fleet at render time.

<connected-repo>/.ai-ops/data/pending.json
<connected-repo>/.ai-ops/docs/operator-log.md
<connected-repo>/.ai-ops/knowledge/postmortems/YYYY-MM-DD-<slug>.md
  • The constant is REPO_DATA_DIR = ".ai-ops" (lib/repos/fleet.ts) β€” distinct from the client-kit dir .ai-ops-dashboard-client/.
  • Each repo is a RepoDescriptor with a dataRoot: for the self repo dataRoot === projectRoot (the dashboard keeps data/, docs/, knowledge/ exactly where they are β€” zero migration); for a connected repo dataRoot === path.join(repo.path, REPO_DATA_DIR).
  • The fleet-aware panels (pending, operator log, postmortems) read across every member via loadAcrossFleet. With ?repo=<id> the header filter scopes to one repo; absent/unknown β†’ the sectioned "All repos" view.
  • .ai-ops/ is committed in each connected repo (it's that repo's source of truth). .ai-ops-dashboard-client/ (write token, config) stays git-ignored.
  • npm run check:data validates each connected repo's .ai-ops/ layout: absence is a warning (linked-but-uninitialised), malformed content is an error.

Conventions

  • Filenames are kebab-case and self-explanatory. Panels: <id>-panel.tsx. Scripts: <verb>-<noun>.mjs. Postmortems: YYYY-MM-DD-<slug>.md. Do not strip topical prefixes during cleanup.
  • Panels are server components. Loaders never throw β€” they return an empty-but-typed value and the panel renders an empty state. Crashing in a loader takes down the page.
  • All ISO timestamps use UTC with an explicit Z suffix.
  • The operator log is append-only and newest-on-top. Every entry has **Why:**, **What shipped:**, **Tags:**, **Next:**.
  • Pending items sort by priority (high | medium | low) then dateAdded descending. When you close one, append a matching operator-log entry in the same commit.
  • AI agents acting under the ai-ops-dashboard-operator SKILL may only perform actions listed in ai-ops-dashboard.config.ts β†’ ai.allowedTasks. Anything else requires explicit user confirmation.

Validators that gate every commit

The build runs npm run data:build before next build, so every Vercel deploy ships through the same chain a local npm run build runs. The data-build orchestrator (scripts/build-data.mjs) runs freeze-commits.mjs, aggregate-usage.mjs, and refresh-manifest.mjs in order. If you add a new validator (frontmatter shape, schema check, cross-link integrity, etc.), wire it into build-data.mjs as a non-optional step so it can't be skipped. Anything that can affect the rendered user experience and isn't already validated needs a validator stage β€” see .ai-ops-dashboard/CONVENTIONS.md for the "why didn't the audit catch this?" rule.

Where NOT to make changes

  • Don't hand-edit `data/recent-commits.json` or `data/usage.json` β€” they're regenerated by scripts. Edits will be overwritten on the next npm run data:build. Edit the source (git history or usage-log.jsonl) instead.
  • Don't hand-edit `data/savings.json` or `data/savings-history.json` β€” they're generated by scripts/build-savings.mjs (the Token Savings collector), which reads the global claude-mem DB (sqlite3 CLI) + Graphify (graphify benchmark + graphify-out/cost.json). It's fail-safe (errors β†’ errors[], exit 0) and degrades to an empty state when the DB/CLI are absent β€” so it can't run on CI/Vercel; freshness comes from the committed snapshot plus a locally-run collector. Refresh via npm run data:collect-savings, the data:build chain (it's an optional step), or the auto-installed SessionStart hook (.claude/settings.json, added when the Token Savings tab is enabled). The snapshot is machine-global and lists project names by design (private repo); never writes an absolute dbPath (only cloudMem.dbFound); trim projects via scope.exclude in .ai-ops-dashboard/savings.config.json. Knobs (rate, recall, assumed queries/day) live in that config.
  • Don't edit `.ai-ops-dashboard/manifest.json` by hand β€” regenerate it via npm run manifest:refresh. Hand-edits will drift from repo reality and silently lie to other agents.
  • Don't add a panel without registering it in `ai-ops-dashboard.config.ts` β€” orphan panel files render nothing and confuse the manifest.
  • Don't bypass the loader pattern. Server components must read from lib/data-sources helpers, not direct fs calls, so the readJson / readMarkdown / readGitLog fallbacks (Vercel shallow-clone safety, missing-file defaults) stay consistent.
  • Don't change panel `id` values once published. Other agents and the manifest key off them. If you must rename, do it in one commit that updates the panel, the config, the manifest, and any links.

Reading project state

Machine-readable state lives at .ai-ops-dashboard/manifest.json. It enumerates every panel (with its declared sources), every data file (with schema + purpose), every command, and every convention. Read it before making structural changes β€” it's the source of truth for what the project currently looks like to tools, agents, and humans. Regenerate it via npm run manifest:refresh whenever you add or rename a panel, a data file, or a script.

Knowledge graph β€” query first, keep fresh

For "how does X work / what calls Y / where does Z live / what connects A to B" questions, the committed knowledge graph in `graphify-out/` is the cheapest way to answer β€” a graph query costs ~54Γ— fewer tokens than grepping and reading raw files. Use it before fanning out filesystem searches:

graphify query "how does the spend banner read usage data"   # BFS context
graphify path "spend-banner.tsx" "UsageData"                 # shortest path between two nodes
graphify explain "PanelDefinition"                            # a node + its neighbors

…or read graphify-out/GRAPH_REPORT.md for the god nodes and the community map. The graph is checked in (only graphify-out/graph.json, graphify-out/GRAPH_REPORT.md, graphify-out/manifest.json β€” graphify's own manifest, not the dashboard's .ai-ops-dashboard/manifest.json; see graphify-out/.gitignore) so every Claude Code session shares one current copy. A global SessionStart hook flags it as STALE when repo content outruns it; when that fires, or after a substantive change, refresh with the `/graphify --update` skill flow (cache-backed, cheap β€” prefer it over the bare graphify update CLI) and commit the regenerated graph.json / GRAPH_REPORT.md / manifest.json alongside your change. Never hand-edit graphify-out/. The full rule lives in `CLAUDE.md`.

v0.4 β€” custom tabs + Skills

v0.4 turns AI Operations Dashboard into a fully extensible dashboard surface. Two big additions: custom panels (archetype-driven, scaffolded by the CLI, dispatch-routed by a generic verb handler) and a Skills inventory panel (read-only filesystem scan of the operator's Claude Code installations β€” Claude-only).

Custom tabs β€” the 5 archetypes

A custom panel is any panel the operator (or an AI agent) scaffolds outside the built-ins. Every custom panel is generated from one of five archetypes so the CLI can route writes generically without bespoke per-panel handlers:

| Archetype | Shape | Typical use | |---|---|---| | list | Flat array of records, sorted by priority then dateAdded | Backlogs, watchlists, follow-ups, reading queues | | timeline | Append-only journal, newest on top, required at ISO timestamp | Decision logs, narrative changelogs, retros | | metrics | Daily key/value rollup with optional thresholds | Health scores, single-number KPIs, gauge dashboards | | folder | Walk a markdown directory + parse frontmatter | Postmortem-style folders, runbooks, playbooks | | rest | HTTP fetch + cache (with TTL) against an external JSON endpoint | Live metrics, third-party status, server-rendered scoreboards |

Each archetype ships with: a loader, a default Component, a JSON schema, a CLI verb set (e.g. list exposes add | close | edit | remove; timeline exposes append; metrics exposes record; folder exposes new; rest exposes refresh). The handlers live under lib/panels/archetypes/<name>/.

Where custom panels register

When a custom panel is created, three things happen in lockstep:

  1. Component file lands at components/panels/<slug>-panel.tsx. It re-exports the archetype's loader + Component with the operator's slug, label, and source path injected.
  2. Config entry is appended to ai-ops-dashboard.config.ts's panels array via codemod.
  3. Manifest entry is appended to .ai-ops-dashboard/manifest.json under a new top-level key β€” customPanels[]. Each entry has the shape:

``json { "id": "<slug>", "label": "<human label>", "archetype": "list" | "timeline" | "metrics" | "folder" | "rest", "dataPath": "data/<slug>.json", "fields": [{ "name": "...", "type": "string", "required": true }, ...], "aiAllowedTasks": ["add", "edit", "close"], "generatedAt": "2026-06-02T...Z" } ``

aiAllowedTasks is the per-panel allow-list (defaults to the full verb set for that archetype). The human can hand-edit this to revoke specific writes; the CLI dispatcher and the operator SKILL both honour it.

The manifest's customPanels[] is the source of truth for what custom panels exist. Never hand-edit components/panels/<slug>-panel.tsx without also updating ai-ops-dashboard.config.ts and re-running npm run manifest:refresh β€” drift between the three causes silent renders-but-doesn't-write bugs.

The CLI surface β€” npx ai-ops-dashboard panel add + generic dispatcher

# Add a new custom panel (interactive)
npx ai-ops-dashboard panel add --slug followups --label "Followups" --archetype list

# Generic dispatcher β€” `npx ai-ops-dashboard <slug> <verb>` routes by archetype
npx ai-ops-dashboard followups add --title "Email about pricing" --priority medium
npx ai-ops-dashboard followups close <id>

# Timeline archetype example
npx ai-ops-dashboard decisions append --note "Picked Postgres over SQLite for the v0.5 cache"

# Metrics archetype example
npx ai-ops-dashboard health record --date 2026-06-02 --score 87 --note "INP regressed 5ms"

# Folder archetype example
npx ai-ops-dashboard runbooks new --slug deploy-rollback

# REST archetype example
npx ai-ops-dashboard vercel-status refresh

The dispatcher looks the slug up in customPanels[], finds the archetype, validates the verb against aiAllowedTasks, then delegates to lib/panels/archetypes/<archetype>/handlers/<verb>.mjs. If the slug isn't a custom panel, the dispatcher falls back to the existing top-level subcommands (log, pending, postmortem, etc.).

When using this from an AI agent: always check customPanels[<slug>].aiAllowedTasks first. If the verb isn't listed, surface a confirmation prompt to the human before invoking. The human owns the allow-list; AI never silently expands its own permissions.

The Skills inventory panel β€” Claude Code only

The Skills panel is opt-in (gated by config.ai.includeSkillsInventory: true). When enabled, it scans the operator's local Claude Code installation surfaces and renders an inventory:

  • `~/.claude/skills/` β€” user-installed skills (SKILL.md frontmatter parsed for name + description)
  • `~/.claude/agents/` β€” sub-agent definitions
  • `~/.claude/plugins/` β€” installed plugins (each contributes its own skills + MCP servers)
  • MCP servers β€” parsed from ~/.claude/settings.json + plugin mcp.json manifests, deduped by server name

Read-only by default. The panel never writes to ~/.claude/* β€” it surfaces what's installed and lets the operator review.

Review mode. A "Review" toggle on the panel lets the operator multi-select skills/agents/MCP servers and export the selection:

  • JSON export β€” ~/Downloads/ai-ops-dashboard-skills-inventory-<timestamp>.json (full structured dump)
  • Markdown manifest β€” ~/Downloads/ai-ops-dashboard-skills-inventory-<timestamp>.md (human-readable summary for sharing or paste-into-Notion)

The Markdown export uses the same shape as the inventory recipe (.ai-ops-dashboard/recipes/skills-inventory-review.md) so an operator can hand it to another Claude Code session to mirror their setup on a different machine.

Why Claude-only. The inventory paths and frontmatter conventions are specific to Claude Code. Cursor / Aider / other IDEs have different layouts; rendering a Claude inventory in a non-Claude session would be misleading. The panel is gated on config.ai.includeSkillsInventory AND a runtime check for ~/.claude/ existence β€” if either is false, the panel silently drops out of the tab strip.

Hard rule for agents. Never *remove* skills, agents, MCP servers, or plugins from disk based on the Review mode output. The export is a manifest; deletions require the operator running rm -rf themselves. If the operator says "remove these," surface the relevant paths and the exact rm commands as a confirmation block, then stop. Do not execute the deletions.

Custom panels

<!-- ai-ops-dashboard:custom-panel:project-a -->

Project A (project-a)

  • Archetype: list
  • Data path: data/project-a.json
  • Component: components/panels/project-a-panel.tsx
  • AI permissions: update, delete

Test Project

CLI commands:

  • npx ai-ops-dashboard project-a update ...
  • npx ai-ops-dashboard project-a delete ...

<!-- /ai-ops-dashboard:custom-panel:project-a -->

AI Operations Dashboard Β· For AI Agents