claude-rpc 0.11.1 → 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 +1 -1
- package/src/scanner.js +18 -4
- package/src/server/assets/wrapped.client.js +19 -2
- package/src/server/assets/wrapped.css +37 -15
- package/src/version.js +1 -1
package/package.json
CHANGED
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 =
|
|
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
|
-
|
|
245
|
-
|
|
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) {
|
|
@@ -24,14 +24,15 @@
|
|
|
24
24
|
const perDay = d.prompts / Math.max(1, d.daysSinceFirst);
|
|
25
25
|
const pad2 = (h) => String(h).padStart(2, '0');
|
|
26
26
|
|
|
27
|
-
const
|
|
27
|
+
const year = new Date(d.generatedAt).getFullYear();
|
|
28
|
+
const S = (cls, body, dur, wm) => { _i = 0; out.push({ cls, html: (wm != null && wm !== '' ? `<div class="wm">${esc(wm)}</div>` : '') + body, dur: dur || 5200 }); };
|
|
28
29
|
|
|
29
30
|
// 1. intro
|
|
30
31
|
S('ink', `
|
|
31
32
|
<img class="gif anim" ${A()} src="${GIF}clawd-working-building.gif" alt="" />
|
|
32
33
|
<div class="kicker anim" ${A()}>claude-rpc presents</div>
|
|
33
34
|
<div class="big anim" ${A()}>Your Year<br/>on Claude Code</div>
|
|
34
|
-
<div class="sub anim" ${A()}>${esc(d.daysSinceFirst)} days in the making · tap →</div>`, 4200);
|
|
35
|
+
<div class="sub anim" ${A()}>${esc(d.daysSinceFirst)} days in the making · tap →</div>`, 4200, year);
|
|
35
36
|
|
|
36
37
|
// 2. hours
|
|
37
38
|
S('rust', `
|
|
@@ -151,6 +152,22 @@
|
|
|
151
152
|
const els = [...story.querySelectorAll('.slide')];
|
|
152
153
|
let idx = -1, timer = null, paused = false;
|
|
153
154
|
|
|
155
|
+
// Giant faded background number/word per slide — derived from its headline
|
|
156
|
+
// stat. Slides that already carry a .wm (e.g. the intro's year) are skipped.
|
|
157
|
+
els.forEach((s) => {
|
|
158
|
+
if (s.querySelector('.wm')) return;
|
|
159
|
+
const cnt = s.querySelector('[data-count]');
|
|
160
|
+
const big = s.querySelector('.big, .tapebadge');
|
|
161
|
+
let wm = '';
|
|
162
|
+
if (cnt) {
|
|
163
|
+
const t = parseFloat(cnt.dataset.count) || 0;
|
|
164
|
+
wm = (cnt.dataset.fmt === 'num') ? fmtNum(t) : String(Math.round(t));
|
|
165
|
+
} else if (big) {
|
|
166
|
+
wm = (big.textContent || '').trim().split('\n')[0].split(/\s+/)[0];
|
|
167
|
+
}
|
|
168
|
+
if (wm) { const w = document.createElement('div'); w.className = 'wm'; w.textContent = wm; s.prepend(w); }
|
|
169
|
+
});
|
|
170
|
+
|
|
154
171
|
function runCountups(slide) {
|
|
155
172
|
slide.querySelectorAll('[data-count]').forEach((node) => {
|
|
156
173
|
const target = parseFloat(node.dataset.count) || 0, fmt = node.dataset.fmt || 'int';
|
|
@@ -49,25 +49,47 @@ body {
|
|
|
49
49
|
opacity: 0; visibility: hidden; transition: opacity 0.45s ease;
|
|
50
50
|
}
|
|
51
51
|
.slide.active { opacity: 1; visibility: visible; }
|
|
52
|
-
.slide.ink { background:
|
|
53
|
-
.slide.rust { background:
|
|
54
|
-
.slide.gold { background:
|
|
55
|
-
.slide.grass { background:
|
|
56
|
-
.slide.blurple{ background:
|
|
57
|
-
.slide.plum { background:
|
|
58
|
-
.slide.paper { background:
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
.slide.ink { background: radial-gradient(125% 125% at 50% 0%, #2c241b 0%, #1a1611 55%, #0e0b07 100%); color: var(--paper); }
|
|
53
|
+
.slide.rust { background: linear-gradient(155deg, #d6551e 0%, #b03c14 58%, #8d2f11 100%); color: var(--paper); }
|
|
54
|
+
.slide.gold { background: linear-gradient(155deg, #f7e294 0%, #f0cd54 55%, #e3b133 100%); color: var(--ink); }
|
|
55
|
+
.slide.grass { background: linear-gradient(155deg, #59ab76 0%, #418456 58%, #316843 100%); color: var(--paper); }
|
|
56
|
+
.slide.blurple{ background: linear-gradient(155deg, #717bff 0%, #5158d6 52%, #6b3f86 100%); color: var(--paper); }
|
|
57
|
+
.slide.plum { background: linear-gradient(155deg, #9c5085 0%, #6e3760 55%, #4b2643 100%); color: var(--paper); }
|
|
58
|
+
.slide.paper { background: linear-gradient(155deg, #f9f3e8 0%, #f1e7d6 55%, #e5d9c0 100%); color: var(--ink); }
|
|
59
|
+
|
|
60
|
+
/* content sits above the watermark + glow */
|
|
61
|
+
.slide > * { position: relative; z-index: 1; }
|
|
62
|
+
|
|
63
|
+
/* soft floating glow blob behind everything */
|
|
64
|
+
.slide::before {
|
|
65
|
+
content: ''; position: absolute; z-index: 0; width: 80vmin; height: 80vmin; border-radius: 50%;
|
|
66
|
+
left: 50%; top: 15%; transform: translate(-50%, -50%);
|
|
67
|
+
background: radial-gradient(closest-side, rgba(255,255,255,0.17), transparent 72%);
|
|
68
|
+
pointer-events: none; filter: blur(10px); animation: float 9s ease-in-out infinite;
|
|
69
|
+
}
|
|
70
|
+
.slide.gold::before, .slide.paper::before { background: radial-gradient(closest-side, rgba(194,73,30,0.14), transparent 72%); }
|
|
71
|
+
@keyframes float { 0%, 100% { transform: translate(-50%, -50%) scale(1); } 50% { transform: translate(-45%, -57%) scale(1.15); } }
|
|
72
|
+
|
|
73
|
+
/* giant faded background number / glyph */
|
|
74
|
+
.wm {
|
|
75
|
+
position: absolute; inset: 0; z-index: 0; display: grid; place-items: center; pointer-events: none;
|
|
76
|
+
font-weight: 800; font-size: 46vmin; line-height: 1; letter-spacing: -6px;
|
|
77
|
+
opacity: 0.075; transform: rotate(-9deg); white-space: nowrap;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* subtle dot grid texture */
|
|
81
|
+
.slide::after {
|
|
82
|
+
content: ''; position: absolute; z-index: 0; inset: 0; pointer-events: none; opacity: 0.09;
|
|
61
83
|
background-image: radial-gradient(currentColor 1px, transparent 1.4px);
|
|
62
84
|
background-size: 26px 26px;
|
|
63
85
|
}
|
|
64
86
|
|
|
65
|
-
/* entrance
|
|
66
|
-
.slide .anim { opacity: 0; transform: translateY(
|
|
67
|
-
.slide.active .anim { animation: rise 0.
|
|
68
|
-
@keyframes rise { to { opacity: 1; transform: none; } }
|
|
69
|
-
.slide.active .pop { animation: pop 0.
|
|
70
|
-
@keyframes pop { 0% { opacity: 0; transform: scale(0.
|
|
87
|
+
/* entrance — children with .anim blur + rise in, staggered */
|
|
88
|
+
.slide .anim { opacity: 0; transform: translateY(30px) scale(0.97); filter: blur(7px); }
|
|
89
|
+
.slide.active .anim { animation: rise 0.75s cubic-bezier(.2,.7,.2,1) forwards; animation-delay: calc(var(--i, 0) * 0.13s + 0.12s); }
|
|
90
|
+
@keyframes rise { to { opacity: 1; transform: none; filter: blur(0); } }
|
|
91
|
+
.slide.active .pop { animation: pop 0.65s cubic-bezier(.2,1.4,.4,1) forwards; animation-delay: calc(var(--i, 0) * 0.13s + 0.16s); }
|
|
92
|
+
@keyframes pop { 0% { opacity: 0; transform: scale(0.55); } 100% { opacity: 1; transform: scale(1); } }
|
|
71
93
|
|
|
72
94
|
.kicker { font-family: 'JetBrains Mono', monospace; font-size: clamp(13px, 3.6vw, 17px); letter-spacing: 1px; opacity: 0.85; max-width: 16ch; line-height: 1.5; }
|
|
73
95
|
.huge { font-weight: 800; font-size: clamp(64px, 22vw, 168px); line-height: 0.92; letter-spacing: -4px; }
|