claude-mission-control 1.9.1 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -16
- package/lib/changes.js +121 -0
- package/lib/collector.js +154 -22
- package/lib/config.js +35 -2
- package/lib/demo.js +307 -19
- package/lib/detail.js +1 -40
- package/lib/diff.js +152 -0
- package/lib/inventory.js +175 -0
- package/lib/mdfiles.js +78 -0
- package/lib/notify.js +27 -1
- package/lib/opener.js +23 -1
- package/lib/paths.js +9 -1
- package/lib/pricing.js +156 -17
- package/lib/search.js +30 -1
- package/lib/teams.js +101 -0
- package/lib/toolset.js +419 -0
- package/lib/transcripts.js +291 -11
- package/menubar/claude-dash.15s.sh +7 -3
- package/package.json +1 -1
- package/public/index.html +617 -22
- package/server.js +112 -11
package/README.md
CHANGED
|
@@ -1,12 +1,52 @@
|
|
|
1
1
|
# Claude Dashboard
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
One web page that shows you everything Claude Code is doing on your machine.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Claude Code runs in a terminal. Once you have two or three sessions going, you lose track. Which one is waiting for an answer? What did that session change while you were away? How much has this week cost? Claude Dashboard answers those questions on one screen, updated live, from the files Claude Code already writes to `~/.claude`.
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**What you get**
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- A departures board of every running session. When Claude stops to ask you something, the row turns amber and shows the question, so you go straight to the right terminal.
|
|
10
|
+
- What each running session costs so far, how fast it is spending, and how full its context window is.
|
|
11
|
+
- A diff of every file Claude edited or created in a session, taken from the backups Claude Code keeps. Read what changed before you commit it.
|
|
12
|
+
- The full transcript of any session, past or present, and a search across every prompt you have ever sent.
|
|
13
|
+
- Cost per session, project, model, and week, estimated from the tokens in your transcripts, with a breakdown by model and cache use.
|
|
14
|
+
- Per project, the plugins, agents, skills, and MCP servers Claude Code would actually use there, and the exact setting to switch each one on or off.
|
|
15
|
+
- A macOS notification the moment a session needs you, and a reminder if you leave it waiting.
|
|
16
|
+
|
|
17
|
+
**Why you would want it**
|
|
18
|
+
|
|
19
|
+
- You stop checking terminals. The board and the notifications tell you when to look.
|
|
20
|
+
- You stop losing sessions. Every session has a resume button that reopens it in the right folder.
|
|
21
|
+
- You see where the time and the money go, per project.
|
|
22
|
+
- It is read-only. It never writes to Claude's files. Nothing leaves your Mac except one optional call to Anthropic's usage API for your quota meters, using the sign-in Claude Code already has.
|
|
23
|
+
- Nothing to configure. No API keys. Plain Node.js, zero npm dependencies, bound to `127.0.0.1` only.
|
|
24
|
+
|
|
25
|
+
**Who it is for:** anyone who runs Claude Code in more than one terminal.
|
|
26
|
+
|
|
27
|
+
**Requirements:** macOS or Linux (Windows experimental, see below), Node.js ≥ 18, Claude Code.
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
*Every screenshot here is demo data (`CLAUDE_DASH_DEMO=1`). The projects are invented.*
|
|
32
|
+
|
|
33
|
+
**New in 1.11.0**
|
|
34
|
+
|
|
35
|
+
- Live cost and a `$/h` burn rate on every running session, and a cost breakdown by model, cache use, and subagents in each transcript.
|
|
36
|
+
- A context meter on every running session, amber from 70% full and red from 90%, plus tool-call and failure counts in Mission Control.
|
|
37
|
+
- A Toolset view in each project's slide-over: which plugins, agents, skills, and MCP servers apply there, which settings file decided each one, and the snippet to flip it.
|
|
38
|
+
- Current list prices for every Claude model, your organization's contracted rates when Claude Code's `modelPricing` is set, and an amber `?` on any model the table does not price.
|
|
39
|
+
- The ten most expensive sessions in stats, and your cache hit rate in the weekly report.
|
|
40
|
+
- Cost labels that match your plan: *estimated cost* on API billing, *list-price value* on a subscription.
|
|
41
|
+
|
|
42
|
+
**New in 1.10.0**
|
|
43
|
+
|
|
44
|
+
- The board says what a waiting session needs: the question, the command awaiting permission, or Claude's last line.
|
|
45
|
+
- `Δ n` on every session row, and a changes panel with a diff of every file the session touched.
|
|
46
|
+
- Notes on any session, searchable.
|
|
47
|
+
- A Plugins & MCP inventory: what is installed, enabled, stale, disabled, or needing auth.
|
|
48
|
+
- Agent teams and subagents shown live in Mission Control.
|
|
49
|
+
- An idle reminder when a session has waited on you for an hour.
|
|
10
50
|
|
|
11
51
|
## Try it in one command
|
|
12
52
|
|
|
@@ -66,43 +106,68 @@ You should see a dark board listing your projects. If a Claude Code session is r
|
|
|
66
106
|
**Using it day to day** — three things cover most of it:
|
|
67
107
|
|
|
68
108
|
- **Glance at the top row.** That's what's running right now. Amber **needs you** means go back to that terminal — Claude asked a question.
|
|
69
|
-
- **Click any session title** to read the whole conversation. If it's still running, new messages stream in live.
|
|
109
|
+
- **Click any session title** to read the whole conversation. If it's still running, new messages stream in live. The `≈$ ▾` button in its header shows what it cost, by model.
|
|
70
110
|
- **Click `open ⬈`** next to an old session to pick it up again — it opens a terminal in the right folder with the conversation restored.
|
|
71
111
|
|
|
72
112
|
If something looks off, the log is at `~/Library/Logs/claude-dashboard.log`. To remove everything, run `./uninstall.sh` from the same folder.
|
|
73
113
|
|
|
74
114
|
## What you're looking at
|
|
75
115
|
|
|
76
|
-
- **Departures** — every live `claude` CLI session as a row on the board: start time, project, current task, model, elapsed time, and a split-flap status cell. The cell flips to an amber **needs you** when a session is waiting on your input — readable from across the room.
|
|
116
|
+
- **Departures** — every live `claude` CLI session as a row on the board: start time, project, current task, model, cost so far with a `$/h` burn rate, a context-window meter, elapsed time, and a split-flap status cell. The cell flips to an amber **needs you** when a session is waiting on your input — readable from across the room. A second line under the title says what it needs: `?` for a question Claude asked (with its options), `🔒` for a tool call awaiting permission (with the command or file), or the last question in Claude's reply, or failing that the first line of its final paragraph.
|
|
117
|
+
- **Mission Control** — the `▦` button in the header (or `⌘K` → Mission Control) tiles every live session side by side, each pane following its own transcript. Each pane head shows the model, cost and burn rate, the context meter, and how many tool calls the session has made (failures in amber). One screen for "what is everything doing right now".
|
|
118
|
+
- **Agents** — a session running subagents or an agent team shows them under its Mission Control pane: name, type, model, and a pulsing `●` while an agent has written in the last two minutes, `○` once it has gone quiet (idle teammates stay alive, so quiet is not finished), with the team name when there is one. Finished sessions list them at the top of the transcript.
|
|
119
|
+
- **Pinned** — `☆ pin` on any transcript sticks that session to a strip under the departures board, so the thing you care about stays in view whether it's running or not.
|
|
77
120
|
- **Digest** — what happened across every project, grouped by day. Each entry shows the session's recap (Claude's own "away summary" where one exists — click to expand), how many tasks it completed, and an open button. Switch the window between day / 3 days / week; collapses out of the way and remembers your choice.
|
|
78
121
|
- **Project cards** — sorted by last activity. Branch chip, `●n` uncommitted changes, `↑n` unpushed commits. The sparkline is prompts per day for the last two weeks. Each session row has an `open ⬈` button that resumes the session in your terminal (new window, right directory) or imports it into the Claude desktop app when it's installed — the **Open in** selector in the header names whichever terminal you've configured (auto-detected on first run). The `⧉` button copies the `claude --resume` command instead.
|
|
79
|
-
- **Header meters** — your plan and rate-limit tier (Free/Pro/Max/Enterprise/API, detected from Claude Code's local account cache), live session and weekly usage from Anthropic's usage API via your existing Claude Code sign-in, plus extra-usage spend. First run may show one macOS Keychain prompt — click Always Allow.
|
|
80
|
-
|
|
122
|
+
- **Header meters** — your plan and rate-limit tier (Free/Pro/Max/Enterprise/API, detected from Claude Code's local account cache), live session and weekly usage from Anthropic's usage API via your existing Claude Code sign-in, plus extra-usage spend. Set a **weekly budget** in settings and a third meter tracks estimated cost (list-price value on a subscription) against it, with one alert each at 75 / 90 / 100%. First run may show one macOS Keychain prompt — click Always Allow.
|
|
123
|
+
|
|
124
|
+

|
|
125
|
+
|
|
126
|
+

|
|
81
127
|
|
|
82
128
|
- **Transcripts** — click any session title (cards, digest, search results, project drawer) to read the conversation: your prompts, Claude's replies rendered as markdown, tool calls as compact one-liners, and away-summaries highlighted. Long sessions show the newest ~1200 events. **Running sessions follow live** — a `● live` badge appears, new turns stream in every few seconds, and the view sticks to the bottom unless you've scrolled up to read.
|
|
83
|
-
- **Search** — the header box searches every prompt you've ever sent plus all session titles (Enter to run, 2+ characters). Narrow with `project:name` or `since:7d` / `since:2026-08-01`. Clicking a prompt result opens the transcript scrolled to the matching turn.
|
|
129
|
+
- **Search** — the header box searches every prompt you've ever sent plus all session titles (Enter to run, 2+ characters). Narrow with `project:name` or `since:7d` / `since:2026-08-01`. Clicking a prompt result opens the transcript scrolled to the matching turn. If the answer isn't in a prompt, **search inside full transcripts** runs the same query across the conversation bodies — slower, so it's a second click rather than the default.
|
|
84
130
|
- **Command palette** — `⌘K` from anywhere: fuzzy-jump to any project or session, watch a live session, start a new one, open settings or stats. Arrow keys + Enter.
|
|
85
|
-
- **Stats** — click the weekly bar chart in the header: a 26-week activity heatmap, your busiest hours, a weekly-rhythm grid (prompts by day of week and hour), estimated
|
|
131
|
+
- **Stats** — click the weekly bar chart in the header: a 26-week activity heatmap, your busiest hours, a weekly-rhythm grid (prompts by day of week and hour), estimated cost per day for the last 90 days (list-price value on a subscription), and an all-time per-model token/cost breakdown. The daily cost history is computed from your existing transcripts, so it's full from the first run.
|
|
132
|
+
- **Your week with Claude** — a 7-day report (`⌘K`, or the button at the top of stats): sessions, prompts, estimated cost (list-price value on a subscription) against your budget, busiest day and hour, models used, how long you typically take to answer a question, and your cache hit rate. Exports as markdown.
|
|
133
|
+
- **Day timeline** — `⌘K` → Day timeline: the last 24 hours as one lane per project, with amber ticks where a session needed you.
|
|
134
|
+
- **Catch-up** — the `🔔` in the header keeps every notification the server has fired since it started, with an unread count. Useful after a morning away from the desk.
|
|
86
135
|
- **Export** — any transcript downloads as clean markdown via the `export ⇩` button.
|
|
136
|
+
- **Changes** — a `Δ n` chip on any session row counts the files Claude edited. Click it (or `changes` in the transcript header) for the list with `+added −removed` per file and a unified diff of what the session changed, from the pre-edit backup Claude Code keeps to the file as it is on disk now. Files with several checkpoints also offer each step (`v1 → v2`). Read-only: nothing here reverts anything.
|
|
137
|
+
|
|
138
|
+

|
|
139
|
+
|
|
140
|
+
- **Notes** — `note` in the transcript header attaches a free-text note to a session (⌘⏎ saves, empty removes). Rows with a note show `≡`, hover to read it, and notes are searchable from the header box. Stored in `config.json` under `sessionNotes`.
|
|
87
141
|
- **New session** — the `⊕` button on a project card opens a fresh terminal window in that project running `claude`.
|
|
88
142
|
- **Cost trend** — the small bar chart in the header is estimated cost per week for the last 8 weeks (hover for numbers). Costs include subagent tokens.
|
|
89
143
|
- **Models everywhere** — every session shows which model ran it (live cards, digest, session lists), and the stats view breaks down usage per model and per project.
|
|
90
144
|
- **Claude.ai chats** — import the official export from claude.ai (Settings → Privacy → Export data, then feed `conversations.json` to ⚙ settings here) and your chats become browsable (`⌘K` → Claude.ai chats) and full-text searchable next to your coding sessions. Stored slimmed in your local config dir, gitignored, never uploaded anywhere.
|
|
91
145
|
|
|
92
146
|

|
|
93
|
-
|
|
147
|
+
|
|
148
|
+
- **Project details** — click any project's name for a slide-over with its full session list, recent git commits, rendered CLAUDE.md, per-project memory files, skills/agents/commands from `.claude/`, and settings (permissions, MCP servers, allowed tools), plus the resolved **Toolset** below. Read-only; also a quick audit of which projects are missing instructions or memory. Esc closes.
|
|
149
|
+
- **Toolset** — inside the project slide-over: what Claude Code would actually use in that project. Every installed plugin, every agent and skill from the project, your user dir, and enabled plugins, and every MCP server that applies, each marked on or off with where it came from and which settings scope decided it, resolved with Claude Code's own precedence (managed › local › project › user, deny beats allow, project agents shadow user agents shadow plugin agents). An amber list at the top flags things worth a look: a plugin enabled but not installed or gone stale, a deny rule naming an agent that no longer exists, a server that needs auth, a settings file that doesn't parse. The `⧉` on a row copies the exact snippet to flip it, aimed at `.claude/settings.local.json`; MCP toggles live in Claude's own registry, so that recipe tells you to run `/mcp` in a session instead (servers from a project's `.mcp.json` also honour the settings keys `enabledMcpjsonServers`, `disabledMcpjsonServers` and `enableAllProjectMcpServers`, and their recipes use those). Buttons at the foot reveal the project's settings files in Finder. Read-only — the dashboard never edits Claude configuration.
|
|
150
|
+
- **Plugins & MCP** — `⌘K` → Plugins & MCP (or the button in settings) lists every installed plugin with its marketplace, version, on/off state and a `stale` flag when its marketplace was refreshed after the plugin was last updated, plus every MCP server with its scope, the projects that use it, an `off` chip for servers you have disabled in a project, and an amber `needs auth` when Claude Code says so. Read-only; server secrets never reach the browser.
|
|
151
|
+
|
|
152
|
+

|
|
153
|
+
|
|
94
154
|
- **Unpushed work strip** — an amber band listing every repo with uncommitted changes (`●n`) or unpushed commits (`↑n`), sorted by recent activity. Disappears when everything's clean.
|
|
95
155
|
- **Dormant** — projects with no activity for 60+ days, tucked away at the bottom.
|
|
96
156
|
- Worktree sessions (`.claude/worktrees/…`) are folded into their parent project and badged `⎇`.
|
|
97
|
-
- **Notifications** — the moment any session flips to "waiting for input", you get a macOS notification (with sound) naming the project. Fires once per wait, never on server restart. Mute a single noisy project from its slide-over (the bell button at the top), or disable everything with `CLAUDE_DASH_NOTIFY=0` in the plist. Notifications arrive via Script Editor/osascript — if you don't see them, allow it under System Settings → Notifications.
|
|
98
|
-
- **Cost estimates** —
|
|
157
|
+
- **Notifications** — the moment any session flips to "waiting for input", you get a macOS notification (with sound) naming the project and what it needs — the question, the command awaiting permission, or Claude's last line. Fires once per wait, never on server restart. Mute a single noisy project from its slide-over (the bell button at the top), or disable everything with `CLAUDE_DASH_NOTIFY=0` in the plist. Notifications arrive via Script Editor/osascript — if you don't see them, allow it under System Settings → Notifications.
|
|
158
|
+
- **Cost estimates** — every session, project, and live row carries a `≈$` figure computed from the token counts in its transcript. On an API-key login it is labelled *estimated cost*; on a subscription it is *list-price value*, a relative weight rather than a bill (hover a cost chip for the wording). Rates are Anthropic list prices bundled with the dashboard (cached 2026-10-06). If your organization deploys Claude Code's `modelPricing` managed setting, the dashboard reads it and reports at your contracted rates; you can also put the same `modelPricing` block in `config.json` (⚙ settings → *copy skeleton*). A model the table doesn't know is priced at the Opus tier and marked with an amber `?` so a stale table is never silent. Subagent tokens are included.
|
|
159
|
+
- **Live cost** — running sessions show their cost so far on the departures board and in Mission Control, with a `$/h` burn rate once the session is five minutes old.
|
|
160
|
+
- **Session cost breakdown** — the `≈$ ▾` button in a transcript's header opens a table by model (input, output, cache read, cache write), the subagent share, and the session's cache hit rate.
|
|
161
|
+
- **Context and tools** — each live session shows how full its context window was on the last turn (`412k / 1M`, amber from 70%, red from 90%), so you can see a compaction or a fresh session coming. Mission Control panes add the session's tool-call count with failures in amber, and the transcript's cost panel shows the same count in its footer. Read from the same incremental transcript scan as everything else; subagents are not included.
|
|
162
|
+
- **Most expensive sessions** — in the stats view: the ten costliest sessions all time, click to open.
|
|
99
163
|
- **Stuck flag** — a session that's "busy" but has written nothing to its transcript for 10+ minutes gets an amber `quiet Nm` cell; at 20 minutes you get one notification. It's a hint, not a verdict — a session waiting on slow background work can look the same.
|
|
164
|
+
- **Idle reminder** — a session left on **needs you** for an hour (configurable in settings, `0` to turn off) gets one more notification and a `still waiting` entry in the catch-up bell, and its elapsed cell turns amber with the wait time. Nothing repeats until you answer it.
|
|
100
165
|
|
|
101
166
|

|
|
102
167
|
|
|
103
168
|
## Menu bar companion
|
|
104
169
|
|
|
105
|
-
A SwiftBar plugin lives in `menubar/claude-dash.15s.sh`. The menu bar shows `❯ N` while sessions run, `❯ N⚠` in amber when one is waiting on you, and `❯ N?` when a busy session has gone quiet. The dropdown lists live sessions, repos with unpushed work, the 7-day estimate, and an "Open dashboard" link.
|
|
170
|
+
A SwiftBar plugin lives in `menubar/claude-dash.15s.sh`. The menu bar shows `❯ N` while sessions run, `❯ N⚠` in amber when one is waiting on you, and `❯ N?` when a busy session has gone quiet, and `❯ N⚠⏳` when one of them has waited past the idle reminder. The dropdown lists live sessions, repos with unpushed work, the 7-day estimate, and an "Open dashboard" link.
|
|
106
171
|
|
|
107
172
|
Setup: `brew install --cask swiftbar`, then point SwiftBar's plugin folder at this repo's `menubar/` directory. The plugin refreshes every 15 seconds (rename the file to change the interval).
|
|
108
173
|
|
|
@@ -112,9 +177,15 @@ The ⚙ gear in the header opens settings — no JSON editing required:
|
|
|
112
177
|
|
|
113
178
|
- **Notifications** on/off (writes `config.json`); per-project mute lives on each project's slide-over
|
|
114
179
|
- **Terminal** for open/new-session buttons: Ghostty, iTerm2, or Terminal.app, auto-detected (`config.json`)
|
|
115
|
-
- **
|
|
180
|
+
- **Claude desktop app**: lets `open ⬈` hand a session to the Claude app instead of a terminal
|
|
181
|
+
- **Usage meters** on/off — this is the switch for the one automatic network call
|
|
116
182
|
- **Theme**: Departures board (follows system light/dark), Phosphor, Amber CRT, Midnight, or Newsprint (`config.json`)
|
|
183
|
+
- **Weekly budget** in dollars — drives the header meter and the 75 / 90 / 100% alerts; `0` turns it off
|
|
184
|
+
- **Idle reminder** in minutes — one extra notification when a session has waited that long; `0` turns it off
|
|
185
|
+
- **Rename any project** (writes `names.json`) or **hide it** and its whole subtree (writes `ignore.json`), with an unhide list below
|
|
186
|
+
- **Claude.ai chats**: pick the `conversations.json` from your claude.ai export to make those chats searchable here
|
|
117
187
|
- **Updates**: "check for updates" asks GitHub only when you click; when a new release is out, **update now** pulls it in place (git or npm installs) and service installs restart themselves on the new version
|
|
188
|
+
- **Pricing** shows which rate table is active (bundled, `config.json`, or your organization's managed `modelPricing`) and lists any model the table doesn't price; *copy skeleton* puts a `modelPricing` block on the clipboard to paste into `config.json`
|
|
118
189
|
|
|
119
190
|
Everything saves instantly; the underlying files stay hand-editable. Keyboard: `⌘K` for the palette, `/` for search, `Esc` closes anything.
|
|
120
191
|
|
|
@@ -133,6 +204,7 @@ Edit `names.json` to control how projects are titled:
|
|
|
133
204
|
Unlisted projects fall back to a cleaned-up folder name. Changes are picked up automatically — no restart needed.
|
|
134
205
|
|
|
135
206
|
Sessions can be renamed too: click the ✎ next to any session title (live board, project cards, pinned strip, or a project's slide-over), type a name, and press Enter. Escape cancels; an empty name goes back to the automatic title. Custom names live in `config.json` under `sessionNames`, keyed by session id, and win over the AI-generated or first-prompt title everywhere.
|
|
207
|
+
Notes work the same way: `sessionNotes` in `config.json`, keyed by session id, editable by hand.
|
|
136
208
|
|
|
137
209
|
## Hiding projects
|
|
138
210
|
|
|
@@ -147,10 +219,26 @@ Edit `ignore.json` — an array of absolute path prefixes. A project is hidden i
|
|
|
147
219
|
| `CLAUDE_DASH_NOTIFY` | unset | `0` = disable macOS notifications |
|
|
148
220
|
| `CLAUDE_DASH_HOST` | `127.0.0.1` | Bind address — see below before changing |
|
|
149
221
|
| `CLAUDE_DASH_DEMO` | unset | `1` = serve believable fake data (screenshots, trying it without Claude history) |
|
|
222
|
+
| `CLAUDE_DASH_OPEN` | unset | `1` = open the browser on startup (what `npx` sets for you) |
|
|
150
223
|
| `CLAUDE_DASH_CONFIG_DIR` | repo dir | Where config.json/names.json/ignore.json live (auto-falls back to `~/.config/claude-dashboard`) |
|
|
151
224
|
|
|
152
225
|
The server binds to `127.0.0.1` only by default.
|
|
153
226
|
|
|
227
|
+
**Your own rates.** To price usage at rates other than Anthropic's list prices, add a `modelPricing` block to `config.json`. It uses the same shape as Claude Code's managed setting: a `multiplier` that scales every cost, and per-model `overrides` in USD per million tokens. ⚙ settings → **Pricing** → *copy skeleton* puts a starting block on your clipboard:
|
|
228
|
+
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"modelPricing": {
|
|
232
|
+
"multiplier": 1,
|
|
233
|
+
"overrides": {
|
|
234
|
+
"claude-opus-5-5": { "input": 4, "output": 20, "cacheRead": 0.2, "cacheWrite": 5 }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
If your organization deploys `modelPricing` in Claude Code's managed settings, the dashboard reads that instead, and ⚙ settings says which source is active.
|
|
241
|
+
|
|
154
242
|
## Windows (experimental — testers wanted)
|
|
155
243
|
|
|
156
244
|
The core is plain cross-platform Node, and Windows support is wired in: paths handle drive letters, notifications use native toasts, and sessions open in Windows Terminal, PowerShell, or cmd (auto-detected). Install from PowerShell in the repo folder:
|
|
@@ -165,7 +253,7 @@ That registers a logon Scheduled Task ("ClaudeDashboard") running the server hid
|
|
|
165
253
|
|
|
166
254
|
## Access from your phone
|
|
167
255
|
|
|
168
|
-
The recommended path is [Tailscale](https://tailscale.com): install it on the Mac and your phone, then run `tailscale serve --bg 4517`. That publishes the dashboard over HTTPS inside your private tailnet while the server itself stays loopback-only — nothing is exposed to the internet or your LAN. Alternatively set `CLAUDE_DASH_HOST=0.0.0.0` in the plist to bind to all interfaces, but understand what that means: anyone on the same network can view the dashboard **and use the open/new-session endpoints, which launch terminal commands on this Mac
|
|
256
|
+
The recommended path is [Tailscale](https://tailscale.com): install it on the Mac and your phone, then run `tailscale serve --bg 4517`. That publishes the dashboard over HTTPS inside your private tailnet while the server itself stays loopback-only — nothing is exposed to the internet or your LAN. Alternatively set `CLAUDE_DASH_HOST=0.0.0.0` in the plist to bind to all interfaces, but understand what that means: anyone on the same network can view the dashboard **and use the open/new-session endpoints, which launch terminal commands on this Mac**, plus the reveal-file endpoint, which opens Finder on a project's settings files. Don't do that on a network you don't fully control.
|
|
169
257
|
|
|
170
258
|
## Maintenance
|
|
171
259
|
|
|
@@ -173,6 +261,14 @@ The recommended path is [Tailscale](https://tailscale.com): install it on the Ma
|
|
|
173
261
|
- Restart after pulling changes: `launchctl kickstart -k gui/$(id -u)/com.claude-dashboard`
|
|
174
262
|
- Tests: `node --test test/pure-logic.test.js`
|
|
175
263
|
|
|
264
|
+
## Demo mode
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
CLAUDE_DASH_DEMO=1 node server.js
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Every `/api/*` route serves invented data from `lib/demo.js` instead of reading `~/.claude` — two live sessions, a handful of projects, a full transcript. Nothing touches your own history, so it's the safe way to try the UI, record a walkthrough, or take screenshots. Every image in this README comes from it. Edit `lib/demo.js` if you want different fixtures.
|
|
271
|
+
|
|
176
272
|
## Data sources
|
|
177
273
|
|
|
178
274
|
| What | Where |
|
|
@@ -183,6 +279,11 @@ The recommended path is [Tailscale](https://tailscale.com): install it on the Ma
|
|
|
183
279
|
| Activity | `~/.claude/history.jsonl` |
|
|
184
280
|
| Session todos | `~/.claude/tasks/<sessionId>/` |
|
|
185
281
|
| Usage meters | Anthropic usage API via Claude Code's Keychain sign-in (fallback: `~/.claude/.statusline-usage-cache`) |
|
|
282
|
+
| Cost, context, tool counts | Token usage, tool calls, and failed tool results in the same transcripts, read incrementally (only bytes added since the last scan) |
|
|
283
|
+
| Pricing overrides | `modelPricing` in Claude Code's managed settings (`/Library/Application Support/ClaudeCode/managed-settings.json` on macOS), then `config.json` |
|
|
284
|
+
| File changes | `~/.claude/file-history/<sessionId>/` (Claude Code's pre-edit backups) |
|
|
285
|
+
| Plugins & MCP | `~/.claude/plugins/installed_plugins.json`, `known_marketplaces.json`, `~/.claude.json`, each project's `.mcp.json` |
|
|
286
|
+
| Toolset | `~/.claude/settings.json`, each project's `.claude/settings.json` and `.claude/settings.local.json`, managed settings, `~/.claude/agents` and `~/.claude/skills`, the project's `.claude/agents` and `.claude/skills`, and each enabled plugin's `agents/` and `skills/` |
|
|
186
287
|
|
|
187
288
|
## Credits
|
|
188
289
|
|
package/lib/changes.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Files Claude edited in a session, from the file-history records in the
|
|
3
|
+
// transcript, plus read-only access to the backups Claude Code keeps under
|
|
4
|
+
// ~/.claude/file-history/<sessionId>/. Pure helpers first; fs code below.
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fsp = require('fs/promises');
|
|
7
|
+
const { CLAUDE_DIR } = require('./paths');
|
|
8
|
+
const { diffLines } = require('./diff');
|
|
9
|
+
|
|
10
|
+
const FILE_HISTORY_DIR = path.join(CLAUDE_DIR, 'file-history');
|
|
11
|
+
const BACKUP_NAME = /^[0-9a-f]{8,32}@v\d+$/;
|
|
12
|
+
|
|
13
|
+
// changes: { [realPath]: { versions: [{ v, backup, at }], first, last } }
|
|
14
|
+
// `backup` is null only for v1 of a file Claude created (nothing existed
|
|
15
|
+
// before it, so there is no pre-edit backup to point at). A null name at any
|
|
16
|
+
// other version means the file already has a real earlier backup; ignore it.
|
|
17
|
+
function recordChange(changes, realPath, backup) {
|
|
18
|
+
if (!realPath || !backup) return changes;
|
|
19
|
+
const v = Number(backup.version) || 0;
|
|
20
|
+
if (!backup.backupFileName && v !== 1) return changes;
|
|
21
|
+
const at = backup.backupTime ? Date.parse(backup.backupTime) : NaN;
|
|
22
|
+
const e = changes[realPath] || (changes[realPath] = { versions: [], first: null, last: null });
|
|
23
|
+
if (!e.versions.some((x) => x.v === v)) {
|
|
24
|
+
e.versions.push({ v, backup: backup.backupFileName ? String(backup.backupFileName) : null, at: Number.isNaN(at) ? null : at });
|
|
25
|
+
e.versions.sort((x, y) => x.v - y.v);
|
|
26
|
+
}
|
|
27
|
+
if (!Number.isNaN(at)) {
|
|
28
|
+
if (e.first === null || at < e.first) e.first = at;
|
|
29
|
+
if (e.last === null || at > e.last) e.last = at;
|
|
30
|
+
}
|
|
31
|
+
return changes;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function mergeChanges(target, src) {
|
|
35
|
+
for (const [p, e] of Object.entries(src || {})) {
|
|
36
|
+
for (const ver of e.versions) {
|
|
37
|
+
recordChange(target, p, { backupFileName: ver.backup, version: ver.v, backupTime: ver.at ? new Date(ver.at).toISOString() : null });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return target;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Real path for a file-history entry: the key/trackingPath can be an odd
|
|
44
|
+
// encoded form, but realParentDir is always the true directory.
|
|
45
|
+
function realPathOf(key, backup) {
|
|
46
|
+
if (!backup || !backup.realParentDir || !key) return null;
|
|
47
|
+
return path.join(backup.realParentDir, path.basename(String(key)));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const SESSION_ID = /^[0-9a-f-]{8,64}$/i;
|
|
51
|
+
|
|
52
|
+
function changeFiles(changes) {
|
|
53
|
+
return Object.entries(changes || {})
|
|
54
|
+
.sort((a, b) => (b[1].last || 0) - (a[1].last || 0) || a[0].localeCompare(b[0]))
|
|
55
|
+
.map(([p, e], n) => ({ n, path: p, versions: e.versions, first: e.first, last: e.last }));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function relPath(abs, root) {
|
|
59
|
+
const r = String(root || '').replace(/\/+$/, '');
|
|
60
|
+
return r && abs.startsWith(r + '/') ? abs.slice(r.length + 1) : abs;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function backupPath(sessionId, backupName) {
|
|
64
|
+
if (!SESSION_ID.test(String(sessionId)) || !BACKUP_NAME.test(String(backupName))) return null;
|
|
65
|
+
return path.join(FILE_HISTORY_DIR, sessionId, backupName);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function defaultRead(abs) {
|
|
69
|
+
try {
|
|
70
|
+
return await fsp.readFile(abs, 'utf8');
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// A null backup (v1 of a created file) has no file to read: the file didn't
|
|
77
|
+
// exist before, so the "original" is the empty string, not a missing read.
|
|
78
|
+
async function readVersion(sessionId, file, v, readFile = defaultRead) {
|
|
79
|
+
const ver = file.versions.find((x) => x.v === v);
|
|
80
|
+
if (!ver) return null;
|
|
81
|
+
if (ver.backup === null) return '';
|
|
82
|
+
const p = backupPath(sessionId, ver.backup);
|
|
83
|
+
return p ? readFile(p) : null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function sessionChangeList({ sessionId, changes, root, readFile = defaultRead }) {
|
|
87
|
+
const files = [];
|
|
88
|
+
for (const f of changeFiles(changes)) {
|
|
89
|
+
const original = await readVersion(sessionId, f, f.versions[0].v, readFile);
|
|
90
|
+
const disk = await readFile(f.path);
|
|
91
|
+
const d = original === null ? { added: 0, removed: 0, tooLarge: false } : diffLines(original, disk || '');
|
|
92
|
+
files.push({
|
|
93
|
+
n: f.n, path: f.path, rel: relPath(f.path, root),
|
|
94
|
+
versions: f.versions.map((x) => x.v), first: f.first, last: f.last,
|
|
95
|
+
exists: disk !== null, missingBackup: original === null,
|
|
96
|
+
added: d.added, removed: d.removed, tooLarge: !!d.tooLarge, truncated: !!d.truncated,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return { files };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Normalise the `to` side of a diff request: anything that is not a usable
|
|
103
|
+
// version number means "the file on disk".
|
|
104
|
+
function diffTarget(to) {
|
|
105
|
+
if (to === undefined || to === null || to === 'disk') return 'disk';
|
|
106
|
+
const n = Number(to);
|
|
107
|
+
return Number.isInteger(n) && n > 0 ? n : 'disk';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function sessionFileDiff({ sessionId, changes, n, from, to, root, readFile = defaultRead }) {
|
|
111
|
+
const f = changeFiles(changes)[n];
|
|
112
|
+
if (!f) return null;
|
|
113
|
+
const fromV = Number.isInteger(from) ? from : f.versions[0].v;
|
|
114
|
+
const toV = diffTarget(to);
|
|
115
|
+
const a = await readVersion(sessionId, f, fromV, readFile);
|
|
116
|
+
const b = toV === 'disk' ? (await readFile(f.path)) || '' : await readVersion(sessionId, f, toV, readFile);
|
|
117
|
+
if (a === null || b === null) return { path: f.path, rel: relPath(f.path, root), from: fromV, to: toV, missingBackup: true, hunks: [], added: 0, removed: 0, truncated: false, tooLarge: false };
|
|
118
|
+
return { path: f.path, rel: relPath(f.path, root), from: fromV, to: toV, ...diffLines(a, b) };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = { recordChange, mergeChanges, realPathOf, changeFiles, relPath, backupPath, diffTarget, sessionChangeList, sessionFileDiff };
|