claude-rpc 0.11.2 → 0.12.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-rpc",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
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/scanner.js CHANGED
@@ -7,7 +7,7 @@ import { costFor, pricingKeyFor } from './pricing.js';
7
7
 
8
8
  // Bumping this forces a full re-parse on next scan. Increment whenever the
9
9
  // per-transcript summary schema changes in a way old caches can't satisfy.
10
- const CACHE_VERSION = 3;
10
+ const CACHE_VERSION = 4;
11
11
 
12
12
  // Cap counted gap between consecutive timestamps. Anything larger is treated
13
13
  // as the user walking away — we count only what's plausibly active time.
@@ -188,6 +188,13 @@ export function parseTranscript(filePath) {
188
188
  byModel: {}, // pricing key → { turns, tokens, cost } (model split)
189
189
  };
190
190
  const fileSet = new Set();
191
+ // Claude Code splits one assistant message (a single message.id) across
192
+ // several JSONL lines — one per content block (thinking / text / tool_use)
193
+ // — and repeats the SAME `usage` object on every line. Token/cost/turn
194
+ // counting must happen once per message.id, or a 3-block turn is counted 3×
195
+ // (this was the source of wildly inflated token + cost totals). Content
196
+ // blocks themselves are distinct per line, so those stay counted per line.
197
+ const usageCountedIds = new Set();
191
198
  // Records in their original order, retaining timestamps for per-day bucketing.
192
199
  const records = [];
193
200
 
@@ -212,10 +219,15 @@ export function parseTranscript(filePath) {
212
219
  if (r.type === 'assistant') {
213
220
  const turnModel = r.message?.model || summary.model;
214
221
  const u = r.message?.usage;
222
+ // Count usage/cost/turn only the first time we see this message.id (see
223
+ // usageCountedIds note above). No id (rare/legacy) → count it.
224
+ const msgId = r.message?.id;
225
+ const firstSeen = !msgId || !usageCountedIds.has(msgId);
226
+ if (msgId) usageCountedIds.add(msgId);
215
227
  // Per-model split bucket, keyed by pricing key so cost/tokens/turns align.
216
228
  const mkey = turnModel ? pricingKeyFor(turnModel) : null;
217
229
  const mb = mkey ? (summary.byModel[mkey] ||= { turns: 0, tokens: 0, cost: 0 }) : null;
218
- if (u) {
230
+ if (u && firstSeen) {
219
231
  summary.inputTokens += u.input_tokens || 0;
220
232
  summary.outputTokens += u.output_tokens || 0;
221
233
  summary.cacheReadTokens += u.cache_read_input_tokens || 0;
@@ -241,8 +253,10 @@ export function parseTranscript(filePath) {
241
253
  }
242
254
  if (turnModel) {
243
255
  if (!summary.model) summary.model = turnModel;
244
- summary.modelsUsed[turnModel] = (summary.modelsUsed[turnModel] || 0) + 1;
245
- if (mb) mb.turns += 1;
256
+ if (firstSeen) {
257
+ summary.modelsUsed[turnModel] = (summary.modelsUsed[turnModel] || 0) + 1;
258
+ if (mb) mb.turns += 1;
259
+ }
246
260
  }
247
261
  const blocks = r.message?.content || [];
248
262
  for (const b of blocks) {
package/src/version.js CHANGED
@@ -11,7 +11,7 @@ import { readFileSync } from 'node:fs';
11
11
  import { join } from 'node:path';
12
12
  import { ROOT } from './paths.js';
13
13
 
14
- const BAKED = '0.11.2';
14
+ const BAKED = '0.12.0';
15
15
 
16
16
  function readPkgVersion() {
17
17
  try {