claude-rpc 0.5.0 → 0.6.1

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 CHANGED
@@ -8,143 +8,119 @@
8
8
  # claude-rpc
9
9
 
10
10
  **Discord Rich Presence for [Claude Code](https://claude.com/claude-code).**
11
- Your model, project, current tool, tokens, and lifetime stats — live in your Discord profile.
11
+ Your live model, project, current tool, tokens, and lifetime stats — in your Discord profile. Driven by the hooks Claude Code already fires. Zero polling between sessions.
12
12
 
13
13
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
14
14
  [![Node 18+](https://img.shields.io/badge/node-%3E%3D18-43853d.svg?logo=node.js&logoColor=white)](https://nodejs.org)
15
15
  [![Claude Code](https://img.shields.io/badge/Claude%20Code-hooks-d97757.svg)](https://claude.com/claude-code)
16
16
  [![Discord RPC](https://img.shields.io/badge/Discord-RPC-5865F2.svg?logo=discord&logoColor=white)](https://discord.com/developers/docs/topics/rpc)
17
+ [![Release](https://img.shields.io/github/v/release/rar-file/claude-rpc?color=4c1)](https://github.com/rar-file/claude-rpc/releases/latest)
17
18
 
18
19
  </div>
19
20
 
20
21
  ---
21
22
 
22
23
  <div align="center">
23
- <img src="docs/demo.gif" width="560" alt="Discord Rich Presence card: Claude Code, working in claude-rpc on Opus 4.7" />
24
+ <img src="docs/demo.gif" width="560" alt="Discord Rich Presence card showing Claude Code working in claude-rpc on Opus 4.7" />
24
25
  </div>
25
26
 
26
- Driven entirely by Claude Code's hook system. Zero polling, zero overhead between sessions.
27
+ A small Node daemon that takes the lifecycle events Claude Code already fires and pipes them into the Discord rich-presence card on your profile. Your friends see what you're building; your future self gets lifetime stats. Built solo, on weekends.
27
28
 
28
- <div align="center">
29
-
30
- <sub>Sample badges (your numbers, your README):</sub>
31
-
32
- <img src="https://img.shields.io/badge/claude%20%C2%B7%207d-12.4h-4c1" alt="hours" />&nbsp;<img src="https://img.shields.io/badge/streak-23%20days-fe7d37" alt="streak" />&nbsp;<img src="https://img.shields.io/badge/claude%20cost%20%C2%B7%2030d-$48.20-3a7" alt="cost" />&nbsp;<img src="https://img.shields.io/badge/lines%20%C2%B7%20all--time-24.1k-08c" alt="lines" />&nbsp;<img src="https://img.shields.io/badge/prompts%20%C2%B7%2030d-1.2k-5865F2" alt="prompts" />
33
-
34
- </div>
35
-
36
- > **What's new in v0.2.0** — Cost estimation, code churn, languages, MCP/built-in split, bash + web + subagent leaderboards, redesigned web dashboard with SSE push, six-tab Electron settings GUI, new `insights` and `badge` subcommands. [Full release notes →](https://github.com/rar-file/claude-rpc/releases/tag/v0.2.0)
37
-
38
- ## Features
39
-
40
- **In Discord**
41
-
42
- | | |
43
- | :--- | :--- |
44
- | 🔴 **Live status** | Model, project, current tool/file, and token counts update as you work |
45
- | 🎞️ **Status art** | Large image swaps between *working*, *thinking*, *idle*, *stale*, *notification* |
46
- | 🔁 **Rotation frames** | Cycle through today's stats, streak, top file, lifetime totals, anything you template |
47
- | 🐙 **Auto GitHub button** | When your cwd is a git repo with a github origin, a *View on GitHub* button appears |
48
-
49
- **Beyond Discord**
50
-
51
- | | |
52
- | :--- | :--- |
53
- | 📊 **All-time aggregates** | Hours, prompts, tokens, streaks, hotspots, **lines changed, languages, cost, bash usage, web domains, subagent runs** — incremental scanner over `~/.claude/projects/*.jsonl` |
54
- | 💰 **Cost estimate** | Per-model spend (Opus/Sonnet/Haiku) using public list prices — editable in `src/pricing.js` |
55
- | 🧠 **Insights** | `claude-rpc insights` generates 3–5 contextual lines: weekly trend, peak weekday, hotspot file, cost pace, streak progress |
56
- | 🖥️ **CLI dashboard** | `claude-rpc status` — heatmap, hour histogram, top tools / files / projects / languages / bash commands / cost |
57
- | 🌐 **Web dashboard** | `claude-rpc serve` — range selector (7d / 30d / 90d / 1y / All), live SSE updates, project drilldown, day-detail modal, achievements, theme toggle |
58
- | 🪪 **README badges** | `claude-rpc badge --metric hours --range 7d --out h.svg` (or live at `/api/badge.svg?metric=…`) |
59
- | ⚙️ **Config GUI** | Electron app with six tabs: Presence (drag-reorder, variable autocomplete, presets), Discord, Assets, Timing, Daemon (start/stop/restart, tail log), Stats |
60
-
61
- ## Screens
62
-
63
- <table>
64
- <tr>
65
- <td align="center" width="50%"><b>Web dashboard</b><br/><sub><code>claude-rpc serve</code></sub><br/><br/><img src="docs/dashboard.png" alt="Web dashboard with range selector, activity chart, heatmap, cost panel, languages stack, and leaderboards" /></td>
66
- <td align="center" width="50%"><b>Settings GUI</b><br/><sub><code>npm run dashboard</code></sub><br/><br/><img src="docs/electron.png" alt="Electron config editor with Presence / Discord / Assets / Timing / Daemon / Stats tabs" /></td>
67
- </tr>
68
- </table>
69
-
70
- ## Install
29
+ ## install
71
30
 
72
- **Windows (no Node required)** — grab the latest portable exe:
31
+ **Windows (no Node required)** — [grab the portable exe from the latest release](https://github.com/rar-file/claude-rpc/releases/latest):
73
32
 
74
33
  ```sh
75
- # From https://github.com/rar-file/claude-rpc/releases/latest
76
- # Download claude-rpc.exe, drop it anywhere on PATH.
77
34
  claude-rpc setup
78
35
  claude-rpc start
79
36
  ```
80
37
 
81
- **Any OS (from source)**Node 18+:
38
+ That's the whole pitch. Open Claude Code in any project the daemon picks it up within a second. Something looks wrong? `claude-rpc doctor`.
39
+
40
+ The Discord *desktop* app must be running. The browser client doesn't expose the local IPC bridge that Rich Presence uses.
41
+
42
+ <details>
43
+ <summary><b>other platforms / from source</b></summary>
82
44
 
83
45
  ```sh
84
46
  git clone https://github.com/rar-file/claude-rpc.git
85
47
  cd claude-rpc
86
48
  npm install
87
- cp config.example.json config.json
88
- npm link # optional, makes `claude-rpc` global
49
+ node ./src/cli.js setup
50
+ node ./src/cli.js start
89
51
  ```
90
52
 
91
- Requires the Discord **desktop** client (RPC IPC is unavailable in the browser client) and Claude Code with hook support.
53
+ Or `npm install -g claude-rpc` for the global bin. Both modes survive `npm update` without losing your `clientId` user config lives under the per-OS config dir, not inside `node_modules`.
54
+ </details>
55
+
56
+ <details>
57
+ <summary><b>use your own Discord app</b></summary>
92
58
 
93
- ## Quick start
59
+ A working public Discord application is bundled into the default config — you don't need to register your own to get started. If you want a different app name on the card, create one in the [Discord Developer Portal](https://discord.com/developers/applications), copy the Application ID, and drop it into your config:
94
60
 
95
61
  ```sh
96
- node ./src/cli.js setup # register hooks into ~/.claude/settings.json
97
- node ./src/cli.js start # launch the daemon (detached)
98
- node ./src/cli.js status # CLI dashboard
99
- node ./src/cli.js serve # web dashboard at http://127.0.0.1:47474
62
+ # Linux
63
+ echo '{ "clientId": "YOUR_ID" }' > ~/.config/claude-rpc/config.json
64
+ # macOS
65
+ echo '{ "clientId": "YOUR_ID" }' > ~/Library/Application\ Support/claude-rpc/config.json
66
+ # Windows (PowerShell)
67
+ '{ "clientId": "YOUR_ID" }' | Set-Content $env:APPDATA\claude-rpc\config.json
100
68
  ```
101
69
 
102
- Open Claude Code in any project. Hooks fire on `SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `Notification`, `Stop`, and `SessionEnd`, and the daemon pushes updated presence to Discord within a second.
70
+ `claude-rpc upgrade-config` if you're carrying forward a v0.3-era file.
71
+ </details>
72
+
73
+ ## what claude-rpc does
103
74
 
104
- If you `npm link` (or install the packaged exe), every command above becomes `claude-rpc <command>`.
75
+ ### on discord
105
76
 
106
- ## Discord app setup
77
+ A card that updates as you work. The large image swaps between five states (working / thinking / idle / stale / notification — those gifs at the top of this README). The two lines of text rotate through frames you template — current file, today's hours, lifetime totals, top hotspot, code churn, cost — and the daemon skips frames whose required template variables are empty. The `SessionEnd` hook clears the card instantly when you close Claude Code; no "is it still running?" timeout.
107
78
 
108
- 1. Open the [Discord Developer Portal](https://discord.com/developers/applications) and create a new application named something like `Claude Code`.
109
- 2. Copy the **Application ID** into `config.json` under `clientId`.
110
- 3. *(Optional)* Under **Rich Presence → Art Assets**, upload images named `claude`, `working`, `thinking`, `idle`, `notification` to match the keys in `statusAssets`.
111
- 4. Or skip uploading and use direct URLs in `statusAssets` (e.g. `"https://example.com/working.gif"`). Modern Discord clients fetch them through their media proxy.
79
+ A *View on GitHub →* button appears automatically when your cwd is a git repo with a github origin. The daemon checks `.git/config` directly — no shell-out, no surprise GH API call.
112
80
 
113
- ## Commands
81
+ ### on your machine
114
82
 
115
- | Command | Description |
116
- | ------------- | -------------------------------------------------------- |
117
- | `setup` | Install hooks into `~/.claude/settings.json` |
118
- | `uninstall` | Remove hooks |
119
- | `start` | Start the daemon (detached) |
120
- | `stop` | Stop the daemon |
121
- | `restart` | Stop then start |
122
- | `status` | Current session + all-time dashboard |
123
- | `today` | Today's stats + 24h histogram |
124
- | `week` | This week's stats + daily breakdown |
125
- | `serve` | Open the local web dashboard (port 47474) |
126
- | `preview` | Show how each rotation frame renders right now |
127
- | `scan` | Incrementally rescan `~/.claude/projects` for aggregates |
128
- | `rescan` | Force re-parse every transcript |
129
- | `insights` | Print 3–5 auto-generated insight lines |
130
- | `badge` | Render a Shields-style SVG (`--metric hours\|streak\|cost\|lines`, `--range 7d\|30d\|all`, `--out file.svg`) |
131
- | `tail` | Tail the daemon log |
132
- | `daemon` | Run the daemon in the foreground (for debugging) |
83
+ Three local surfaces, all reading the same `~/.claude-rpc/aggregate.json`:
84
+
85
+ <table>
86
+ <tr>
87
+ <td align="center" width="50%"><b>web dashboard</b><br/><sub><code>claude-rpc serve</code> · port 47474</sub><br/><br/><img src="docs/dashboard.png" alt="Web dashboard with range selector, activity chart, heatmap, cost panel, languages stack, and leaderboards" /></td>
88
+ <td align="center" width="50%"><b>settings gui</b><br/><sub><code>npm run dashboard</code> · Electron</sub><br/><br/><img src="docs/electron.png" alt="Electron config editor with Presence / Discord / Assets / Timing / Daemon / Stats tabs" /></td>
89
+ </tr>
90
+ </table>
133
91
 
134
- ## Config GUI
92
+ ```text
93
+ claude-rpc status (TUI — heatmap, hour histogram, leaderboards)
94
+ claude-rpc today (today's stats, focused)
95
+ claude-rpc week (weekday breakdown)
96
+ claude-rpc preview (every rotation frame rendered with real data)
97
+ claude-rpc insights (3–5 auto-generated lines: trend, peak, hotspot)
98
+ ```
99
+
100
+ The web dashboard pushes updates via SSE; the TUI refreshes on a 3-second tick.
101
+
102
+ ### beyond your machine
103
+
104
+ Shields-style badges and a poster-style summary card you can paste into a README or a Discord message:
135
105
 
136
106
  ```sh
137
- cd dashboard
138
- npm install
139
- npm start # dev mode
140
- npm run build # → dist/claude-rpc-dashboard.exe (Windows portable)
107
+ claude-rpc badge --metric hours --range 7d --out claude-hours.svg
108
+ claude-rpc badge --metric streak --out claude-streak.svg
109
+ claude-rpc card --range year --out year-on-claude.svg
141
110
  ```
142
111
 
143
- The Electron app reads and writes `config.json` directly. The daemon hot-reloads.
112
+ <div align="center">
113
+ <img src="site/examples/year-on-claude.svg" width="560" alt="Year-on-claude card — hours, prompts, tokens, lines, cost, daily activity strip" />
114
+ </div>
115
+
116
+ Live equivalents when the daemon is up:
144
117
 
145
- ## How it works
118
+ - `http://127.0.0.1:47474/api/badge.svg?metric=hours&range=7d`
119
+ - `http://127.0.0.1:47474/api/card.svg?range=year`
146
120
 
147
- Three cooperating pieces, glued by JSON files on disk.
121
+ Cost numbers come from `src/pricing.js`, seeded with **approximate** public list prices. Your actual Claude Code subscription bill is unrelated.
122
+
123
+ ## three pieces, glued by json files
148
124
 
149
125
  ```
150
126
  Claude Code Discord desktop
@@ -162,139 +138,124 @@ Three cooperating pieces, glued by JSON files on disk.
162
138
  └────────────┘
163
139
  ```
164
140
 
165
- 1. **Hook** (`src/hook.js`) Claude Code spawns it on every lifecycle event. Parses the event JSON from stdin and mutates the shared state file.
166
- 2. **Daemon** (`src/daemon.js`) — Long-running. Connects to Discord's local IPC, watches the state file plus periodic transcript scans, pushes presence frames every few seconds.
167
- 3. **Scanner** (`src/scanner.js`) — Walks `~/.claude/projects/**/*.jsonl` transcripts for all-time aggregates (active time, prompts, tool calls, tokens, streaks, hour-of-day, top files / projects). Cached at `~/.claude-rpc/aggregate.json` for incremental updates.
141
+ No database, no message bus, no background polling when Claude Code isn't running. State on disk you can `cat` and `jq`. The single runtime dependency is `@xhayper/discord-rpc`.
142
+
143
+ 1. **hook** ([`src/hook.js`](src/hook.js))Claude Code spawns it on every lifecycle event. Parses the JSON from stdin and mutates the shared state file. Runs in ~20ms.
144
+ 2. **daemon** ([`src/daemon.js`](src/daemon.js)) — long-running. Connects to Discord's local IPC, watches the state file, pushes presence frames every few seconds. Exponential backoff with jitter on reconnect; `daemon.log` rotates at 5 MB.
145
+ 3. **scanner** ([`src/scanner.js`](src/scanner.js)) — walks `~/.claude/projects/**/*.jsonl` for all-time aggregates (active time, prompts, tools, tokens, streaks, hotspots, lines, languages, cost, bash, web, subagents). Incremental — re-parses only changed files.
168
146
 
169
- Persistent state lives in a few well-known places:
147
+ Persistent state, all human-readable JSON:
170
148
 
171
149
  | Path | What |
172
150
  | ---- | ---- |
173
151
  | `$TMPDIR/claude-rpc/state.json` | Current session, volatile |
174
152
  | `~/.claude-rpc/aggregate.json` | All-time aggregates |
175
153
  | `~/.claude-rpc/scan-cache.json` | Per-transcript scan cache |
154
+ | `~/.claude-rpc/private-list.json` | Runtime privacy toggles |
176
155
  | `~/.claude/settings.json` | Hook registrations (managed by `setup`) |
177
156
 
178
- <details>
179
- <summary><b>Configuration reference</b></summary>
180
-
181
- `config.json` keys, all optional unless noted:
182
-
183
- | Key | Default | Notes |
184
- | ------------------------- | ------- | ------------------------------------------------------------------- |
185
- | `clientId` | — | **Required.** Discord application ID |
186
- | `updateIntervalMs` | `4000` | How often the daemon pushes to Discord |
187
- | `rotationIntervalMs` | `12000` | How fast rotation frames cycle |
188
- | `rescanIntervalSec` | `300` | How often transcripts are re-aggregated |
189
- | `idleThresholdSec` | `60` | No activity for this long → status `idle` |
190
- | `staleSessionMin` | `5` | No activity for this long (minutes) → status `stale`; presence is cleared |
191
- | `notificationWindowSec` | `8` | How long the `notification` status sticks |
192
- | `showElapsed` | `true` | Include the elapsed timer |
193
- | `activityType` | `0` | `0` Playing, `2` Listening, `3` Watching, `5` Competing |
194
- | `statusAssets` | `{}` | Image per status (working / thinking / idle / stale / notification) |
195
- | `presence.largeImageKey` | — | Fallback large image when no `statusAssets` match |
196
- | `presence.largeImageText` | — | Tooltip on hover |
197
- | `presence.smallImageKey` | — | Small badge in the corner of the large image |
198
- | `presence.smallImageText` | — | Tooltip on hover |
199
- | `presence.rotation` | `[]` | Array of frames, each `{ details, state, requires? }` |
200
- | `presence.buttons` | `[]` | Up to 2 `{ label, url }` buttons |
201
- | `statusIcons` | `{}` | Small image key per status (empty string hides it) |
202
-
203
- ### Rotation frames
157
+ User config lives at `%APPDATA%\claude-rpc\config.json` (Windows), `~/Library/Application Support/claude-rpc/config.json` (macOS), or `$XDG_CONFIG_HOME/claude-rpc/config.json` (Linux). It only needs to hold *overrides* — every key has a baked default. `{ "clientId": "..." }` is a complete config file. Defaults live in [`src/default-config.js`](src/default-config.js); the loader deep-merges over them.
158
+
159
+ ## privacy
160
+
161
+ Per-project, runtime, or auto-detected — whichever fits how you work.
204
162
 
205
163
  ```jsonc
206
- {
207
- "presence": {
208
- "rotation": [
209
- { "details": "{statusVerbose} in {project}", "state": "{modelPretty}" },
210
- { "details": "{currentToolPretty} · {currentFilePretty}",
211
- "state": "{tokensFmt} tokens",
212
- "requires": ["currentFile"] },
213
- { "details": "Today · {todayHours}",
214
- "state": "{todayPromptsLabel}",
215
- "requires": ["todayActiveMs"] }
216
- ]
217
- }
218
- }
164
+ // drop at your project root: <project>/.claude-rpc.json
165
+ { "private": true } // shortcut for visibility: "hidden"
166
+ { "visibility": "name-only" } // project name only, no file/tool detail
167
+ { "projectName": "redacted" } // show this name on Discord instead
219
168
  ```
220
169
 
221
- Each frame has:
170
+ Or from the command line, in any project:
222
171
 
223
- - `details` — bold first line (Discord max 128 chars)
224
- - `state` lighter second line (Discord max 128 chars)
225
- - `requires` *(optional)* — a variable name or array of names. The frame is skipped if any required variable is empty / `0`. Lets you have context-dependent frames (e.g. only show the *current tool* frame when there's actually a tool running).
172
+ ```sh
173
+ claude-rpc private # add cwd to ~/.claude-rpc/private-list.json
174
+ claude-rpc public # remove cwd
175
+ claude-rpc privacy # show the resolved visibility for the current dir
176
+ ```
226
177
 
227
- </details>
178
+ Or globally, in `config.json`:
228
179
 
229
- <details>
230
- <summary><b>Template variables</b></summary>
231
-
232
- Both `details` and `state` (and button labels and URLs) support `{name}` substitution.
233
-
234
- | Variable | Sample |
235
- | ----------------------- | ------------------ |
236
- | `{statusVerbose}` | `Working` |
237
- | `{project}` | `claude-rpc` |
238
- | `{modelPretty}` | `Opus 4.7` |
239
- | `{currentToolPretty}` | `Edit` |
240
- | `{currentFilePretty}` | `src/app/page.tsx` |
241
- | `{tokensFmt}` | `2.3k` |
242
- | `{messagesLabel}` | `8 prompts` |
243
- | `{projectSessionLabel}` | `Session #1` |
244
- | `{projectHours}` | `22m` |
245
- | `{todayHours}` | `56m` |
246
- | `{weekHours}` | `3.1h` |
247
- | `{streakLabel}` | `7-day streak` |
248
- | `{daysSinceFirstLabel}` | `Day 31` |
249
- | `{allHours}` | `52h` |
250
- | `{allTokensFmt}` | `2.82B` |
251
- | `{peakHour}` | `22:00` |
252
- | `{topEditedFile}` | `index.html` |
253
- | `{linesAddedFmt}` | `24k` |
254
- | `{todayLinesAddedFmt}` | `320` |
255
- | `{linesNetFmt}` | `+18k` |
256
- | `{topLanguage}` | `TypeScript` |
257
- | `{languagesLabel}` | `TypeScript · Python · Rust` |
258
- | `{topBashCmdLabel}` | `git × 820` |
259
- | `{topDomainLabel}` | `docs.anthropic.com × 28` |
260
- | `{subagentLabel}` | `Explore × 18` |
261
- | `{mcpToolPercentLabel}` | `12% MCP` |
262
- | `{todayCostFmt}` | `$1.23` |
263
- | `{allCostFmt}` | `$89.42` |
264
- | `{weekdayLabel}` | `Thursday` |
265
- | `{startTimeLabel}` | `started 09:14` |
266
-
267
- Run `node ./src/cli.js preview` to see every frame rendered with your real data, including which ones would be hidden by their `requires`.
180
+ ```json
181
+ { "privacy": { "patterns": ["client-*", "secret-stuff"], "mode": "hidden" } }
182
+ ```
268
183
 
269
- </details>
184
+ If [`gh`](https://cli.github.com/) is installed and authenticated, GitHub-private repos auto-hide (`privacy.githubPrivateMode`, default `hidden` — opt out with `privacy.autoDetectGithubPrivate: false`). 5-minute cache, 1.5s timeout, silent skip when `gh` isn't there.
270
185
 
271
- ## Badges
186
+ Aggregates and local dashboards are never affected. Privacy is a one-way valve between local state and Discord.
272
187
 
273
- Generate a Shields-style SVG you can drop into a README:
188
+ ## customizing the card
274
189
 
275
190
  ```sh
276
- claude-rpc badge --metric hours --range 7d --out claude-hours.svg
277
- claude-rpc badge --metric streak --out claude-streak.svg
278
- claude-rpc badge --metric cost --range 30d --out claude-cost.svg
279
- claude-rpc badge --metric lines --range all --out claude-lines.svg
191
+ claude-rpc preview # render every rotation frame with your real data
192
+ claude-rpc vars # dump the full template-variable list as JSON
280
193
  ```
281
194
 
282
- While the daemon's `serve` command is running, the same data is also available live at:
195
+ Frames have a `requires` field; the daemon skips a frame when any of its required vars resolve empty / zero. Write seven frames knowing only the relevant ones render.
283
196
 
284
- ```
285
- http://127.0.0.1:47474/api/badge.svg?metric=hours&range=7d
197
+ ```jsonc
198
+ "idle": {
199
+ "details": "Idle in {project}",
200
+ "state": "{modelPretty} · {todayHours} today",
201
+ "rotation": [
202
+ { "details": "This week · {weekHours}", "state": "{weekPromptsLabel} · {weekTokensFmt} tokens",
203
+ "requires": ["weekActiveMs"] },
204
+ { "details": "Code churn · {linesAddedFmt} added",
205
+ "state": "{linesNetFmt} net · {topLanguage}",
206
+ "requires": ["topLanguage"] }
207
+ ]
208
+ }
286
209
  ```
287
210
 
288
- Cost numbers come from `src/pricing.js`, seeded with **approximate** public list prices for Anthropic models. Edit that file to override your actual Claude Code subscription bill is unrelated.
211
+ The full default config is in [`src/default-config.js`](src/default-config.js) that's the canonical list of every key. ~140 template variables are available; `claude-rpc vars` is the source of truth.
289
212
 
290
- ## Troubleshooting
213
+ ## commands
291
214
 
292
- **Discord doesn't pick up presence.** The Discord *desktop* app must be running. The browser client doesn't expose the local IPC bridge. Verify `clientId` matches your Discord application, and run `claude-rpc tail` to watch the daemon log live.
215
+ | Command | What it does |
216
+ | ---------------- | ------------ |
217
+ | `setup` | Install Claude Code hooks (test-fires one synthetic SessionStart to prove the pipe works) |
218
+ | `uninstall` | Remove Claude Code hooks |
219
+ | `upgrade-config` | Re-run idempotent migrations on `config.json` |
220
+ | `start` / `stop` / `restart` | Lifecycle for the detached daemon |
221
+ | `status` | Interactive TUI — heatmap, hour histogram, leaderboards (`--dump` for plain output) |
222
+ | `today` / `week` | Focused views (today's stats, weekday breakdown) |
223
+ | `serve` | Open the local web dashboard (port 47474) |
224
+ | `preview` | Render every rotation frame against real state |
225
+ | `scan` / `rescan`| Incremental / forced re-parse of `~/.claude/projects` |
226
+ | `backfill <dir>` | Import transcripts from any folder (backup, other machine) |
227
+ | `insights` | Print 3–5 auto-generated lines about your week |
228
+ | `badge` | Shields-style SVG (`--metric` `--range` `--out`) |
229
+ | `card` | Poster-style SVG (`--range year\|month\|week\|all`) |
230
+ | `private` / `public` / `privacy` | Per-cwd visibility toggles + status |
231
+ | `doctor` | Diagnostic checklist with one-line fix hints |
232
+ | `tail` / `logs` | Tail the daemon log |
233
+ | `daemon` | Run the daemon in the foreground (debugging) |
234
+ | `vars` | Dump the full template-var list as JSON |
293
235
 
294
- **Hooks don't fire.** Run `claude-rpc setup` and check the `hooks` section of `~/.claude/settings.json`. Restart Claude Code afterwards so it re-reads the hook config.
236
+ Exit codes: `0` ok · `1` user error · `2` system error · `3` wrong state. `--version` and `--help` work as expected.
237
+
238
+ ## troubleshooting
239
+
240
+ **First step is always `claude-rpc doctor`.** It checks Node version, hook registration, daemon liveness, Discord IPC connection, aggregate freshness, and privacy resolution — with a one-line fix hint per failure.
241
+
242
+ - **Discord doesn't show anything.** Discord *desktop* must be running. The browser client doesn't expose the local IPC bridge. `claude-rpc tail` shows what the daemon is actually doing.
243
+ - **Hooks don't fire.** `claude-rpc setup` re-registers them and now test-fires a synthetic `SessionStart` end-to-end, so a broken hook command surfaces immediately. Restart Claude Code afterwards so it re-reads its hook config.
244
+ - **Config error.** Bad JSON in `config.json` no longer crashes anything — the daemon logs one line and falls back to baked defaults. `claude-rpc tail` shows the parse error verbatim.
245
+ - **Old binary path baked into hooks.** Common after manual exe replacement. `claude-rpc setup` rewrites hook entries to point at the canonical install location.
246
+
247
+ ## development
248
+
249
+ ```sh
250
+ npm test # 134 tests, ~1.7s
251
+ npm run start # run daemon in foreground
252
+ npm run serve # web dashboard against your real data
253
+ npm run dashboard # Electron settings GUI (dev mode)
254
+ npm run build:exe # SEA single-file binary for the current OS
255
+ ```
295
256
 
296
- **Elapsed timer resets on rotation.** Update to the current version. Older builds passed timestamps in seconds; Discord expects milliseconds.
257
+ Tests are `node --test` with zero deps. The CI pipeline ([release.yml](.github/workflows/release.yml)) gates the matrix build and the npm publish behind the test job. Every public export of `src/*.js` is exercised at least once.
297
258
 
298
- ## License
259
+ ## license
299
260
 
300
261
  [MIT](LICENSE) © Archer Simmons
@@ -1,67 +1,4 @@
1
1
  {
2
- "clientId": "1506443909406920948",
3
- "appName": "Claude Code",
4
- "updateIntervalMs": 4000,
5
- "rotationIntervalMs": 12000,
6
- "rescanIntervalSec": 300,
7
- "idleThresholdSec": 60,
8
- "staleSessionMin": 5,
9
- "hideWhenStale": true,
10
- "notificationWindowSec": 8,
11
- "showElapsed": true,
12
- "activityType": 0,
13
- "statusAssets": {
14
- "working": "https://cdn.qualit.ly/clawd-working-building.gif",
15
- "thinking": "https://cdn.qualit.ly/clawd-working-typing.gif",
16
- "idle": "https://cdn.qualit.ly/clawd-sleeping.gif",
17
- "stale": "https://cdn.qualit.ly/clawd-sleeping.gif",
18
- "notification": "https://cdn.qualit.ly/clawd-notification.gif"
19
- },
20
- "presence": {
21
- "largeImageKey": "https://cdn.qualit.ly/clawd-sleeping.gif",
22
- "largeImageText": "{modelPretty} · {allHours} on Claude · {streakLabel}",
23
- "smallImageKey": "{statusIcon}",
24
- "smallImageText": "{statusVerbose}",
25
- "byStatus": {
26
- "working": {
27
- "details": "Working in {project}",
28
- "state": "{currentToolPretty} · {currentFilePretty} · {tokensFmt} tokens",
29
- "largeImageText": "Working on a {fileLang} file"
30
- },
31
- "thinking": {
32
- "details": "Thinking in {project}",
33
- "state": "{modelPretty} · {messagesLabel} · {tokensFmt} tokens",
34
- "largeImageText": "Reasoning with {modelPretty}"
35
- },
36
- "notification": {
37
- "details": "Waiting on you · {project}",
38
- "state": "{modelPretty} · {messagesLabel}",
39
- "largeImageText": "Permission needed"
40
- },
41
- "idle": {
42
- "details": "Idle in {project}",
43
- "state": "{modelPretty} · {todayHours} today",
44
- "largeImageText": "Idle · {modelPretty}",
45
- "rotation": [
46
- { "details": "This week · {weekHours}", "state": "{weekPromptsLabel} · {weekTokensFmt} tokens", "requires": ["weekActiveMs"] },
47
- { "details": "{streakLabel}", "state": "{daysSinceFirstLabel} · {allSessionsLabel}", "requires": ["streakIsMilestone"] },
48
- { "details": "Hotspot · {topEditedFile}", "state": "{topEditedCountLabel} all-time", "requires": ["topEditedCount"] },
49
- { "details": "{allHours} on Claude all-time", "state": "{allSessionsLabel} · {allMessagesFmt} prompts", "requires": ["allSessions"] },
50
- { "details": "Lifetime · {allTokensFmt} tokens", "state": "{allToolsFmt} tool calls · {allFilesFmt} files", "requires": ["allTools"] },
51
- { "details": "Code churn · {linesAddedFmt} added","state": "{linesNetFmt} net · {topLanguage}", "requires": ["topLanguage"] },
52
- { "details": "Cost · {todayCostFmt} today", "state": "{allCostFmt} all-time", "requires": ["allCost"] }
53
- ]
54
- }
55
- },
56
- "buttons": [
57
- { "label": "Claude Code", "url": "https://claude.com/claude-code" }
58
- ]
59
- },
60
- "statusIcons": {
61
- "working": "working",
62
- "thinking": "thinking",
63
- "idle": "idle",
64
- "notification": "",
65
- "stale": ""
66
- }
2
+ "_comment": "Two lines is enough. Defaults for everything else are baked into the binary (see src/default-config.js). Override any key here and it deep-merges over the shipped defaults — no need to copy the full shape.",
3
+ "clientId": "1506443909406920948"
67
4
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-rpc",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Discord Rich Presence for Claude Code — live model, project, tokens, and lifetime stats driven by Claude Code's hook system.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/card.js CHANGED
@@ -14,6 +14,7 @@
14
14
  import { dayKey } from './scanner.js';
15
15
  import { fmtCost } from './pricing.js';
16
16
  import { rangeToDays, rangeLabel, pickWindow } from './badge.js';
17
+ import { VERSION } from './version.js';
17
18
 
18
19
  const W = 880;
19
20
  const H = 540;
@@ -273,7 +274,7 @@ export function renderCard(aggregate, { range = 'year', generatedAt = new Date()
273
274
  font-size="13"
274
275
  fill="${PALETTE.inkMute}">${subtitle}</text>
275
276
  </g>
276
- ${tapeSticker(W - 220, 40, 'claude-rpc · v0.4', { rotate: 3 })}
277
+ ${tapeSticker(W - 220, 40, `claude-rpc · v${VERSION}`, { rotate: 3 })}
277
278
 
278
279
  <!-- ── hero hours card ── -->
279
280
  <g transform="translate(60 130)">