claude-rpc 1.2.0 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-rpc",
3
- "version": "1.2.0",
3
+ "version": "1.2.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/cli.js CHANGED
@@ -2106,8 +2106,9 @@ async function doWrappedPublish(argv) {
2106
2106
  pair('ships', fmtNum(w.ships)),
2107
2107
  pair('est. cost', fmtCost(w.costUsd), c.yellow),
2108
2108
  pair('projects', w.topProjects.map((p) => p.name).join(' · ') || c.dim + '(none public)' + c.reset, ''),
2109
- pair('languages', w.topLanguages.join(' · ') || '—', ''),
2109
+ pair('languages', w.topLanguages.map((l) => l.name).join(' · ') || '—', ''),
2110
2110
  pair('models', w.topModels.map((m) => `${m.name} ${m.pct}%`).join(' · ') || '—', ''),
2111
+ pair('hotspot', w.hotspot ? `${w.hotspot.name} (${fmtNum(w.hotspot.count)} edits)` : '—', ''),
2111
2112
  ], 72);
2112
2113
  console.log(` ${c.dim}full payload: the fields above plus lines/cache%/peaks — nothing else.${c.reset}`);
2113
2114
  console.log(` ${c.dim}private-listed and pattern-matched project names are already excluded.${c.reset}\n`);
package/src/community.js CHANGED
@@ -26,6 +26,7 @@ import { VERSION } from './version.js';
26
26
  import { profileIsPublishable } from './leaderboard.js';
27
27
  import { projectNameIsPrivate } from './privacy.js';
28
28
  import { cleanProjectName } from './scanner.js';
29
+ import { humanModel } from './format.js';
29
30
 
30
31
  const CURSOR_PATH = join(STATE_DIR, 'community-cursor.json');
31
32
 
@@ -157,25 +158,50 @@ export function buildWrappedPayload(aggregate, config, { instanceId, year, now =
157
158
  .map(([name, activeMs]) => ({ name, activeMs }));
158
159
 
159
160
  // Year-scoped model mix from the per-day byModel buckets (scan cache v7).
160
- const modelTok = {};
161
+ // Share is by SPEND, matching the local wrapped's "your models, by spend"
162
+ // slide; token share is the fallback when everything costed to zero. Names
163
+ // are humanized here so the web story renders "Opus 4.8", same as local.
164
+ const modelAgg = {};
161
165
  for (const [, d] of days) {
162
166
  for (const [m, v] of Object.entries(d.byModel || {})) {
163
- modelTok[m] = (modelTok[m] || 0) + (v.tokens || 0);
167
+ const t = (modelAgg[m] ||= { tokens: 0, cost: 0 });
168
+ t.tokens += v.tokens || 0;
169
+ t.cost += v.cost || 0;
164
170
  }
165
171
  }
166
- const modelTotal = Object.values(modelTok).reduce((a, b) => a + b, 0);
167
- const topModels = Object.entries(modelTok).sort((a, b) => b[1] - a[1]).slice(0, 4)
168
- .map(([name, t]) => ({ name, pct: modelTotal ? Math.round((t / modelTotal) * 100) : 0 }));
172
+ const costTotal = Object.values(modelAgg).reduce((a, b) => a + b.cost, 0);
173
+ const tokTotal = Object.values(modelAgg).reduce((a, b) => a + b.tokens, 0);
174
+ const share = (v) => costTotal > 0 ? v.cost / costTotal : (tokTotal > 0 ? v.tokens / tokTotal : 0);
175
+ const topModels = Object.entries(modelAgg).sort((a, b) => share(b[1]) - share(a[1])).slice(0, 4)
176
+ .map(([name, v]) => ({ name: humanModel(name) || name, pct: Math.round(share(v) * 100) }));
169
177
 
170
178
  // Lifetime dimensions (no yearly slice exists for these).
171
179
  const toolTotal = Object.values(agg.toolBreakdown || {}).reduce((a, b) => a + b, 0);
172
180
  const toolMix = Object.entries(agg.toolBreakdown || {}).sort((a, b) => b[1] - a[1]).slice(0, 3)
173
181
  .map(([name, n]) => ({ name, pct: toolTotal ? Math.round((n / toolTotal) * 100) : 0 }));
174
182
  const topLanguages = Object.entries(agg.languages || {})
175
- .sort((a, b) => (b[1].edits || 0) - (a[1].edits || 0)).slice(0, 5).map(([n]) => n);
183
+ .sort((a, b) => (b[1].edits || 0) - (a[1].edits || 0)).slice(0, 5)
184
+ .map(([name, v]) => ({ name, edits: v.edits || 0 }));
176
185
  const sl = agg.sessionLengths || {};
177
186
  const marathonPct = sl.count ? Math.round((((sl.buckets?.h2to4 || 0) + (sl.buckets?.gt4h || 0)) / sl.count) * 100) : 0;
178
187
 
188
+ // The hotspot file (basename only, never a path) and peak weekday — the two
189
+ // remaining slides of the local story. Both lifetime, like the local page.
190
+ const top = (agg.topEditedFiles || [])[0] || null;
191
+ const hotName = top ? String(top.path || '').split(/[\\/]/).pop() : null;
192
+ const hotspot = top && hotName && !projectNameIsPrivate(hotName, config, cleanProjectName)
193
+ ? { name: hotName, count: top.count || 0, ...(top.daysSinceLastEdit != null ? { daysSinceLastEdit: top.daysSinceLastEdit } : {}) }
194
+ : null;
195
+ let peakWeekday = null;
196
+ {
197
+ const WD = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
198
+ for (const [k, v] of Object.entries(agg.byWeekday || {})) {
199
+ if ((v.activeMs || 0) > 0 && (!peakWeekday || v.activeMs > peakWeekday.activeMs)) {
200
+ peakWeekday = { name: WD[Number(k)] || '', activeMs: v.activeMs };
201
+ }
202
+ }
203
+ }
204
+
179
205
  const compactions = Object.entries(agg.compactionsByDay || {})
180
206
  .filter(([k]) => k.startsWith(`${y}-`)).reduce((acc, [, n]) => acc + n, 0);
181
207
 
@@ -188,6 +214,8 @@ export function buildWrappedPayload(aggregate, config, { instanceId, year, now =
188
214
  tokens,
189
215
  prompts: sum('userMessages'),
190
216
  streakBest: agg.longestStreak || 0,
217
+ streak: agg.streak || 0,
218
+ daysSinceFirst: agg.daysSinceFirst || 0,
191
219
  daysActive: days.filter(([, d]) => (d.activeMs || 0) > 0 || (d.userMessages || 0) > 0).length,
192
220
  linesAdded: sum('linesAdded'),
193
221
  linesRemoved: sum('linesRemoved'),
@@ -204,6 +232,8 @@ export function buildWrappedPayload(aggregate, config, { instanceId, year, now =
204
232
  topLanguages,
205
233
  topModels,
206
234
  toolMix,
235
+ ...(hotspot ? { hotspot } : {}),
236
+ ...(peakWeekday ? { peakWeekday } : {}),
207
237
  },
208
238
  };
209
239
  }
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 = '1.2.0';
14
+ const BAKED = '1.2.1';
15
15
 
16
16
  function readPkgVersion() {
17
17
  try {