standout 0.5.25 → 0.5.26
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/dist/cursor.d.ts +21 -18
- package/dist/cursor.js +95 -20
- package/dist/wrapped/aggregate.js +13 -6
- package/dist/wrapped/render.js +13 -7
- package/dist/wrapped/types.d.ts +2 -1
- package/package.json +1 -1
package/dist/cursor.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface CursorStats {
|
|
|
11
11
|
total: number;
|
|
12
12
|
};
|
|
13
13
|
models: string[];
|
|
14
|
+
model_session_counts: Record<string, number>;
|
|
15
|
+
tool_call_counts: Record<string, number>;
|
|
14
16
|
hour_buckets: number[];
|
|
15
17
|
weekend_ratio: number;
|
|
16
18
|
languages: Record<string, number>;
|
|
@@ -18,15 +20,12 @@ export interface CursorStats {
|
|
|
18
20
|
prompt_frequency: PromptFrequency[];
|
|
19
21
|
exchanges: ConversationExchange[];
|
|
20
22
|
interaction: InteractionSignals;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
cache_tokens: number;
|
|
28
|
-
primary_model: string | null;
|
|
29
|
-
}[];
|
|
23
|
+
total_input_tokens: number;
|
|
24
|
+
total_output_tokens: number;
|
|
25
|
+
total_cache_read_tokens: number;
|
|
26
|
+
total_cache_write_tokens: number;
|
|
27
|
+
tokens_estimated: true;
|
|
28
|
+
monthly_buckets: CursorMonthlyBucket[];
|
|
30
29
|
all_time?: {
|
|
31
30
|
total_sessions: number;
|
|
32
31
|
active_days: number;
|
|
@@ -37,18 +36,22 @@ export interface CursorStats {
|
|
|
37
36
|
total_output_tokens: number;
|
|
38
37
|
total_cache_read_tokens: number;
|
|
39
38
|
total_cache_write_tokens: number;
|
|
40
|
-
monthly_buckets:
|
|
41
|
-
month: string;
|
|
42
|
-
sessions: number;
|
|
43
|
-
duration_hours: number;
|
|
44
|
-
input_tokens: number;
|
|
45
|
-
output_tokens: number;
|
|
46
|
-
cache_tokens: number;
|
|
47
|
-
primary_model: string | null;
|
|
48
|
-
}[];
|
|
39
|
+
monthly_buckets: CursorMonthlyBucket[];
|
|
49
40
|
};
|
|
50
41
|
}
|
|
42
|
+
interface CursorMonthlyBucket {
|
|
43
|
+
month: string;
|
|
44
|
+
sessions: number;
|
|
45
|
+
duration_hours: number;
|
|
46
|
+
input_tokens: number;
|
|
47
|
+
output_tokens: number;
|
|
48
|
+
cache_read_tokens: number;
|
|
49
|
+
cache_write_tokens: number;
|
|
50
|
+
cache_tokens: number;
|
|
51
|
+
primary_model: string | null;
|
|
52
|
+
}
|
|
51
53
|
export declare function gatherCursor(opts?: {
|
|
52
54
|
dbPath?: string;
|
|
53
55
|
windowStartMs?: number;
|
|
54
56
|
}): Promise<CursorStats | null>;
|
|
57
|
+
export {};
|
package/dist/cursor.js
CHANGED
|
@@ -16,6 +16,27 @@ const CORRECTION_RE = /\b(no|nope|actually|wrong|incorrect|instead|revert|undo|d
|
|
|
16
16
|
// Mirrors ai-usage.ts — cannot import (would create a module cycle).
|
|
17
17
|
const TRIVIAL_ASK_RE = /^(y|n|yes|yep|yeah|ya|ok|okay|k|sure|go|go ahead|do it|continue|please continue|next|thanks|thank you|thx|ty|nice|cool|perfect|great|good|done|ship it|lgtm|👍|\.|\?)\b[\s.!]*$/i;
|
|
18
18
|
const SHELL_COMMAND_RE = /^(npx|npm|pnpm|yarn|bun|git|cd|ls|cat|node|python|pip|brew|sudo|docker|curl|echo|mkdir|rm|mv|cp)\b/i;
|
|
19
|
+
// Cursor persists no usable token/cost data locally — bubble `tokenCount` is
|
|
20
|
+
// zeroed and composer `usageData` is empty. We estimate both from active
|
|
21
|
+
// duration using the economics of our Claude Code cohort (Cursor runs the same
|
|
22
|
+
// Claude models). The per-hour token split is calibrated so that, priced at the
|
|
23
|
+
// generic retail fallback rate, it totals ~$20/active-hour — the cohort's
|
|
24
|
+
// blended retail $/hour. Surfaced with an "estimated" disclaimer on the cards.
|
|
25
|
+
const CURSOR_EST_TOKENS_PER_HOUR = {
|
|
26
|
+
input: 38_000,
|
|
27
|
+
output: 110_000,
|
|
28
|
+
cacheRead: 24_750_000,
|
|
29
|
+
cacheWrite: 750_000,
|
|
30
|
+
};
|
|
31
|
+
function estimateCursorTokens(hours) {
|
|
32
|
+
const h = hours > 0 ? hours : 0;
|
|
33
|
+
return {
|
|
34
|
+
input: Math.round(CURSOR_EST_TOKENS_PER_HOUR.input * h),
|
|
35
|
+
output: Math.round(CURSOR_EST_TOKENS_PER_HOUR.output * h),
|
|
36
|
+
cacheRead: Math.round(CURSOR_EST_TOKENS_PER_HOUR.cacheRead * h),
|
|
37
|
+
cacheWrite: Math.round(CURSOR_EST_TOKENS_PER_HOUR.cacheWrite * h),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
19
40
|
function isLowSignalAsk(sample) {
|
|
20
41
|
const t = sample.trim();
|
|
21
42
|
return t.length < 12 || TRIVIAL_ASK_RE.test(t) || SHELL_COMMAND_RE.test(t);
|
|
@@ -159,13 +180,20 @@ async function readState(dbPath, Driver) {
|
|
|
159
180
|
continue;
|
|
160
181
|
const composerId = parsed.composerId ??
|
|
161
182
|
row.key.slice("composerData:".length);
|
|
183
|
+
const modelConfig = parsed.modelConfig && typeof parsed.modelConfig === "object"
|
|
184
|
+
? parsed.modelConfig
|
|
185
|
+
: null;
|
|
186
|
+
const modelName = (modelConfig && typeof modelConfig.modelName === "string"
|
|
187
|
+
? modelConfig.modelName
|
|
188
|
+
: null) ??
|
|
189
|
+
(typeof parsed.latestSelectedModel === "string"
|
|
190
|
+
? parsed.latestSelectedModel
|
|
191
|
+
: null);
|
|
162
192
|
composers.set(composerId, {
|
|
163
193
|
composerId,
|
|
164
194
|
createdAt: numericTs(parsed.createdAt),
|
|
165
195
|
lastUpdatedAt: numericTs(parsed.lastUpdatedAt),
|
|
166
|
-
|
|
167
|
-
? parsed.latestSelectedModel
|
|
168
|
-
: null,
|
|
196
|
+
modelName: modelName && modelName !== "default" ? modelName : null,
|
|
169
197
|
});
|
|
170
198
|
}
|
|
171
199
|
if (rows.length < PAGE)
|
|
@@ -196,12 +224,22 @@ async function readState(dbPath, Driver) {
|
|
|
196
224
|
const text = typeof parsed.text === "string" ? parsed.text : null;
|
|
197
225
|
const createdAt = numericTs(parsed.createdAt);
|
|
198
226
|
const filePaths = extractFilePaths(parsed);
|
|
227
|
+
const tool = parsed.toolFormerData && typeof parsed.toolFormerData === "object"
|
|
228
|
+
? parsed.toolFormerData
|
|
229
|
+
: null;
|
|
230
|
+
const toolName = tool &&
|
|
231
|
+
(typeof tool.name === "string"
|
|
232
|
+
? tool.name
|
|
233
|
+
: typeof tool.tool === "string"
|
|
234
|
+
? tool.tool
|
|
235
|
+
: null);
|
|
199
236
|
bubbles.set(parts[2], {
|
|
200
237
|
conversationId,
|
|
201
238
|
type,
|
|
202
239
|
text,
|
|
203
240
|
createdAt,
|
|
204
241
|
filePaths,
|
|
242
|
+
toolName: toolName || null,
|
|
205
243
|
});
|
|
206
244
|
}
|
|
207
245
|
if (rows.length < PAGE)
|
|
@@ -294,10 +332,10 @@ export async function gatherCursor(opts = {}) {
|
|
|
294
332
|
first_session: allTime.first_session,
|
|
295
333
|
last_session: allTime.last_session,
|
|
296
334
|
total_duration_hours: allTime.total_duration_hours,
|
|
297
|
-
total_input_tokens:
|
|
298
|
-
total_output_tokens:
|
|
299
|
-
total_cache_read_tokens:
|
|
300
|
-
total_cache_write_tokens:
|
|
335
|
+
total_input_tokens: allTime.total_input_tokens,
|
|
336
|
+
total_output_tokens: allTime.total_output_tokens,
|
|
337
|
+
total_cache_read_tokens: allTime.total_cache_read_tokens,
|
|
338
|
+
total_cache_write_tokens: allTime.total_cache_write_tokens,
|
|
301
339
|
monthly_buckets: allTime.monthly_buckets,
|
|
302
340
|
};
|
|
303
341
|
return stats.total_sessions > 0 || allTime.total_sessions > 0 ? stats : null;
|
|
@@ -335,6 +373,9 @@ function finalize(raw, opts = {}) {
|
|
|
335
373
|
let userTurns = 0;
|
|
336
374
|
let correctionTurns = 0;
|
|
337
375
|
let countedSessions = 0;
|
|
376
|
+
let toolCalls = 0;
|
|
377
|
+
const modelSessionCounts = {};
|
|
378
|
+
const toolCallCounts = {};
|
|
338
379
|
const monthly = new Map();
|
|
339
380
|
for (const [composerId, composer] of raw.composers.entries()) {
|
|
340
381
|
const entry = sessionBubbles.get(composerId);
|
|
@@ -366,6 +407,10 @@ function finalize(raw, opts = {}) {
|
|
|
366
407
|
month.sessions += 1;
|
|
367
408
|
month.durationMs += durationMs;
|
|
368
409
|
monthly.set(monthKey, month);
|
|
410
|
+
if (composer.modelName) {
|
|
411
|
+
modelSessionCounts[composer.modelName] =
|
|
412
|
+
(modelSessionCounts[composer.modelName] ?? 0) + 1;
|
|
413
|
+
}
|
|
369
414
|
for (const ts of windowTs) {
|
|
370
415
|
const d = new Date(ts);
|
|
371
416
|
if (Number.isNaN(d.getTime()))
|
|
@@ -385,6 +430,11 @@ function finalize(raw, opts = {}) {
|
|
|
385
430
|
if (allFiles.size < MAX_FILE_PATHS)
|
|
386
431
|
allFiles.add(fp);
|
|
387
432
|
}
|
|
433
|
+
if (bubble.toolName) {
|
|
434
|
+
toolCalls += 1;
|
|
435
|
+
toolCallCounts[bubble.toolName] =
|
|
436
|
+
(toolCallCounts[bubble.toolName] ?? 0) + 1;
|
|
437
|
+
}
|
|
388
438
|
}
|
|
389
439
|
const windowSamples = windowBubbles
|
|
390
440
|
.filter((bubble) => bubble.type === 1 && bubble.text)
|
|
@@ -443,6 +493,34 @@ function finalize(raw, opts = {}) {
|
|
|
443
493
|
languages[lang] = (languages[lang] || 0) + 1;
|
|
444
494
|
}
|
|
445
495
|
}
|
|
496
|
+
const monthlyBuckets = [...monthly.entries()]
|
|
497
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
498
|
+
.map(([month, b]) => {
|
|
499
|
+
const hours = +(b.durationMs / 1000 / 3600).toFixed(1);
|
|
500
|
+
const est = estimateCursorTokens(hours);
|
|
501
|
+
return {
|
|
502
|
+
month,
|
|
503
|
+
sessions: b.sessions,
|
|
504
|
+
duration_hours: hours,
|
|
505
|
+
input_tokens: est.input,
|
|
506
|
+
output_tokens: est.output,
|
|
507
|
+
cache_read_tokens: est.cacheRead,
|
|
508
|
+
cache_write_tokens: est.cacheWrite,
|
|
509
|
+
cache_tokens: est.cacheRead + est.cacheWrite,
|
|
510
|
+
primary_model: null,
|
|
511
|
+
};
|
|
512
|
+
});
|
|
513
|
+
// Totals are the exact sum of the (rounded-per-month) buckets so the top-level
|
|
514
|
+
// figures metrics.ts reads match what the card sums from monthly_buckets.
|
|
515
|
+
const totals = monthlyBuckets.reduce((acc, b) => ({
|
|
516
|
+
input: acc.input + b.input_tokens,
|
|
517
|
+
output: acc.output + b.output_tokens,
|
|
518
|
+
cacheRead: acc.cacheRead + b.cache_read_tokens,
|
|
519
|
+
cacheWrite: acc.cacheWrite + b.cache_write_tokens,
|
|
520
|
+
}), { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 });
|
|
521
|
+
const models = Object.entries(modelSessionCounts)
|
|
522
|
+
.sort((a, b) => b[1] - a[1])
|
|
523
|
+
.map(([model]) => model);
|
|
446
524
|
return {
|
|
447
525
|
total_sessions: countedSessions,
|
|
448
526
|
active_days: activeDays.size,
|
|
@@ -458,7 +536,9 @@ function finalize(raw, opts = {}) {
|
|
|
458
536
|
assistant: assistantTotal,
|
|
459
537
|
total: userTotal + assistantTotal,
|
|
460
538
|
},
|
|
461
|
-
models
|
|
539
|
+
models,
|
|
540
|
+
model_session_counts: modelSessionCounts,
|
|
541
|
+
tool_call_counts: toolCallCounts,
|
|
462
542
|
hour_buckets: hourBuckets,
|
|
463
543
|
weekend_ratio: totalEvents > 0 ? +(weekendEvents / totalEvents).toFixed(2) : 0,
|
|
464
544
|
languages,
|
|
@@ -470,18 +550,13 @@ function finalize(raw, opts = {}) {
|
|
|
470
550
|
interaction: {
|
|
471
551
|
user_turns: userTurns,
|
|
472
552
|
correction_turns: correctionTurns,
|
|
473
|
-
tool_calls:
|
|
553
|
+
tool_calls: toolCalls,
|
|
474
554
|
},
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
input_tokens: 0,
|
|
482
|
-
output_tokens: 0,
|
|
483
|
-
cache_tokens: 0,
|
|
484
|
-
primary_model: null,
|
|
485
|
-
})),
|
|
555
|
+
total_input_tokens: totals.input,
|
|
556
|
+
total_output_tokens: totals.output,
|
|
557
|
+
total_cache_read_tokens: totals.cacheRead,
|
|
558
|
+
total_cache_write_tokens: totals.cacheWrite,
|
|
559
|
+
tokens_estimated: true,
|
|
560
|
+
monthly_buckets: monthlyBuckets,
|
|
486
561
|
};
|
|
487
562
|
}
|
|
@@ -495,13 +495,15 @@ function computeTokens(aiUsage) {
|
|
|
495
495
|
const cacheTotal = (b) => b.cache_read_tokens !== undefined || b.cache_write_tokens !== undefined
|
|
496
496
|
? (b.cache_read_tokens ?? 0) + (b.cache_write_tokens ?? 0)
|
|
497
497
|
: (b.cache_tokens ?? 0);
|
|
498
|
-
// Cursor exposes no
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
//
|
|
498
|
+
// Cursor exposes no local token counts — its tokens/cost are estimated from
|
|
499
|
+
// active time (see cursor.ts). Flag it so the card discloses the estimate, but
|
|
500
|
+
// only when it was actually used in THIS window (the card is "last 30 days").
|
|
501
|
+
// All-time Cursor sessions don't count: claiming "Cursor too" when they haven't
|
|
502
|
+
// opened it in a month is just wrong.
|
|
502
503
|
const CURSOR_MIN_SESSIONS = 3;
|
|
503
504
|
const cursorStats = aiUsage["cursor"];
|
|
504
|
-
const
|
|
505
|
+
const cursorEstimated = !!(cursorStats && (cursorStats.total_sessions ?? 0) >= CURSOR_MIN_SESSIONS);
|
|
506
|
+
let cursorTokens = 0;
|
|
505
507
|
for (const src of SOURCES) {
|
|
506
508
|
const stats = aiUsage[src];
|
|
507
509
|
if (!stats)
|
|
@@ -512,6 +514,8 @@ function computeTokens(aiUsage) {
|
|
|
512
514
|
const cache = cacheTotal(b);
|
|
513
515
|
workTokens += work;
|
|
514
516
|
cacheTokens += cache;
|
|
517
|
+
if (src === "cursor")
|
|
518
|
+
cursorTokens += work + cache;
|
|
515
519
|
// The per-tool bars + sparkline show the cache-inclusive total (matches the
|
|
516
520
|
// rainbow headline); the "generated" callout is the work-only number.
|
|
517
521
|
byTool[TOOL_LABELS[src] ?? src] =
|
|
@@ -570,7 +574,10 @@ function computeTokens(aiUsage) {
|
|
|
570
574
|
by_tool: sortedByTool,
|
|
571
575
|
six_month_buckets: sixMonth,
|
|
572
576
|
estimated_retail_cost_usd: Math.round(totalCost),
|
|
573
|
-
|
|
577
|
+
cursor_estimated: cursorEstimated,
|
|
578
|
+
cursor_token_share: cursorEstimated && workTokens + cacheTokens > 0
|
|
579
|
+
? cursorTokens / (workTokens + cacheTokens)
|
|
580
|
+
: 0,
|
|
574
581
|
};
|
|
575
582
|
}
|
|
576
583
|
function computeToolRelationship(aiUsage) {
|
package/dist/wrapped/render.js
CHANGED
|
@@ -453,9 +453,9 @@ function cardRankSummary(view) {
|
|
|
453
453
|
.filter((stat) => stat.topNumber !== null)
|
|
454
454
|
.sort((a, b) => a.topNumber - b.topNumber)[0];
|
|
455
455
|
const cohort = rank.sample_size > 0
|
|
456
|
-
? `across ${rank.sample_size.toLocaleString()}
|
|
456
|
+
? `across ${rank.sample_size.toLocaleString()} users`
|
|
457
457
|
: view.cohort_size > 0
|
|
458
|
-
? `among ${view.cohort_size.toLocaleString()}
|
|
458
|
+
? `among ${view.cohort_size.toLocaleString()} users so far`
|
|
459
459
|
: "Standout profile summary";
|
|
460
460
|
const row = (stat) => {
|
|
461
461
|
const top = "top" in stat && stat.top
|
|
@@ -517,6 +517,14 @@ function cardTokens(view) {
|
|
|
517
517
|
const costLine = cost > 0
|
|
518
518
|
? ` ${chalk.dim("would have cost")} ${TITLE_GRADIENT(`~$${cost.toLocaleString()}`)} ${chalk.dim("at retail API rates")}`
|
|
519
519
|
: "";
|
|
520
|
+
// Cursor stores no token counts locally, so its tokens + cost are estimated
|
|
521
|
+
// from active time. Disclose it — wording scales with how much of this card is
|
|
522
|
+
// Cursor (cursor-only users: the whole number is an estimate).
|
|
523
|
+
const cursorNote = t.cursor_estimated
|
|
524
|
+
? t.cursor_token_share >= 0.9
|
|
525
|
+
? chalk.dim(" ≈ estimated from time spent in Cursor (no local token data)")
|
|
526
|
+
: chalk.dim(" includes Cursor (tokens + cost estimated from active time)")
|
|
527
|
+
: "";
|
|
520
528
|
const cachedNote = t.cache_tokens > t.work_tokens
|
|
521
529
|
? chalk.dim(" thankfully, most of them were cached :)")
|
|
522
530
|
: t.cache_tokens > 0
|
|
@@ -533,12 +541,10 @@ function cardTokens(view) {
|
|
|
533
541
|
cachedNote,
|
|
534
542
|
generatedLine,
|
|
535
543
|
costLine,
|
|
544
|
+
cursorNote,
|
|
536
545
|
"",
|
|
537
546
|
" last 30 days, across:",
|
|
538
547
|
toolRows,
|
|
539
|
-
view.tokens.cursor_untracked
|
|
540
|
-
? chalk.dim(" Cursor used too (its tokens aren't tracked locally)")
|
|
541
|
-
: "",
|
|
542
548
|
"",
|
|
543
549
|
deltaLine,
|
|
544
550
|
]
|
|
@@ -875,9 +881,9 @@ export function cardProficiency(view) {
|
|
|
875
881
|
const scoreTop = topPercentNumber(p.score_percentile ?? null);
|
|
876
882
|
const sampleSize = view.rank_summary.sample_size;
|
|
877
883
|
const cohort = scoreTop != null && sampleSize > 0
|
|
878
|
-
? `top ${scoreTop}% of ${sampleSize.toLocaleString()}
|
|
884
|
+
? `top ${scoreTop}% of ${sampleSize.toLocaleString()} users`
|
|
879
885
|
: view.cohort_size > 0
|
|
880
|
-
? `among ${view.cohort_size.toLocaleString()}
|
|
886
|
+
? `among ${view.cohort_size.toLocaleString()} users so far`
|
|
881
887
|
: "";
|
|
882
888
|
const metricRow = (label, value) => value === null
|
|
883
889
|
? null
|
package/dist/wrapped/types.d.ts
CHANGED
|
@@ -115,7 +115,8 @@ export interface WrappedAggregateView {
|
|
|
115
115
|
tokens: number;
|
|
116
116
|
}[];
|
|
117
117
|
estimated_retail_cost_usd: number;
|
|
118
|
-
|
|
118
|
+
cursor_estimated: boolean;
|
|
119
|
+
cursor_token_share: number;
|
|
119
120
|
};
|
|
120
121
|
tool_relationship: {
|
|
121
122
|
kind: "switch";
|