standout 0.5.24 → 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 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
- monthly_buckets: {
22
- month: string;
23
- sessions: number;
24
- duration_hours: number;
25
- input_tokens: number;
26
- output_tokens: number;
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
- modelConfig: typeof parsed.latestSelectedModel === "string"
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: 0,
298
- total_output_tokens: 0,
299
- total_cache_read_tokens: 0,
300
- total_cache_write_tokens: 0,
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: 0,
553
+ tool_calls: toolCalls,
474
554
  },
475
- monthly_buckets: [...monthly.entries()]
476
- .sort(([a], [b]) => a.localeCompare(b))
477
- .map(([month, b]) => ({
478
- month,
479
- sessions: b.sessions,
480
- duration_hours: +(b.durationMs / 1000 / 3600).toFixed(1),
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
  }
@@ -469,6 +469,7 @@ export function aggregateView(args) {
469
469
  total_tokens_percentile: rankSummary.total_tokens_percentile ?? null,
470
470
  avg_agents_percentile: rankSummary.avg_agents_percentile ?? null,
471
471
  weekend_pct_percentile: rankSummary.weekend_pct_percentile ?? null,
472
+ ai_assisted_pct_percentile: rankSummary.ai_assisted_pct_percentile ?? null,
472
473
  },
473
474
  proficiency: parseProficiency(computedObj.proficiency),
474
475
  tokens,
@@ -494,13 +495,15 @@ function computeTokens(aiUsage) {
494
495
  const cacheTotal = (b) => b.cache_read_tokens !== undefined || b.cache_write_tokens !== undefined
495
496
  ? (b.cache_read_tokens ?? 0) + (b.cache_write_tokens ?? 0)
496
497
  : (b.cache_tokens ?? 0);
497
- // Cursor exposes no reliable token counts — flag it so the card can say so,
498
- // but only when it was actually used in THIS window (the card is "last 30
499
- // days"). All-time Cursor sessions don't count: claiming "Cursor used too" when
500
- // they haven't opened it in a month is just wrong.
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.
501
503
  const CURSOR_MIN_SESSIONS = 3;
502
504
  const cursorStats = aiUsage["cursor"];
503
- const cursorUntracked = !!(cursorStats && (cursorStats.total_sessions ?? 0) >= CURSOR_MIN_SESSIONS);
505
+ const cursorEstimated = !!(cursorStats && (cursorStats.total_sessions ?? 0) >= CURSOR_MIN_SESSIONS);
506
+ let cursorTokens = 0;
504
507
  for (const src of SOURCES) {
505
508
  const stats = aiUsage[src];
506
509
  if (!stats)
@@ -511,6 +514,8 @@ function computeTokens(aiUsage) {
511
514
  const cache = cacheTotal(b);
512
515
  workTokens += work;
513
516
  cacheTokens += cache;
517
+ if (src === "cursor")
518
+ cursorTokens += work + cache;
514
519
  // The per-tool bars + sparkline show the cache-inclusive total (matches the
515
520
  // rainbow headline); the "generated" callout is the work-only number.
516
521
  byTool[TOOL_LABELS[src] ?? src] =
@@ -569,7 +574,10 @@ function computeTokens(aiUsage) {
569
574
  by_tool: sortedByTool,
570
575
  six_month_buckets: sixMonth,
571
576
  estimated_retail_cost_usd: Math.round(totalCost),
572
- cursor_untracked: cursorUntracked,
577
+ cursor_estimated: cursorEstimated,
578
+ cursor_token_share: cursorEstimated && workTokens + cacheTokens > 0
579
+ ? cursorTokens / (workTokens + cacheTokens)
580
+ : 0,
573
581
  };
574
582
  }
575
583
  function computeToolRelationship(aiUsage) {
@@ -76,6 +76,7 @@ function fixture(s) {
76
76
  total_tokens_percentile: s.intensity,
77
77
  avg_agents_percentile: s.intensity,
78
78
  weekend_pct_percentile: s.consistency,
79
+ ai_assisted_pct_percentile: s.intensity,
79
80
  },
80
81
  proficiency: {
81
82
  score: s.score,
@@ -200,12 +200,11 @@ function card1Open(view) {
200
200
  : perDay >= 1
201
201
  ? "a daily habit at this point."
202
202
  : "just getting warmed up.";
203
- const badge = benchmarkBadge(view, view.rank_summary.hours_percentile, "of devs by session-hours");
203
+ const bench = benchmarkInline(view, view.rank_summary.hours_percentile);
204
204
  const lines = [
205
205
  chalk.dim("YOU MANAGED"),
206
206
  "",
207
- bigNumber(` ${hours} session-hours`),
208
- ...(badge ? ["", badge] : []),
207
+ bigNumber(` ${hours} session-hours`) + (bench ? ` ${bench}` : ""),
209
208
  "",
210
209
  chalk.white(` ${verdict}`),
211
210
  chalk.dim(" across parallel agent sessions, last 30 days"),
@@ -261,7 +260,7 @@ function cardRhythm(view) {
261
260
  : "no clear peak";
262
261
  const weekendPct = Math.round(view.weekend_ratio * 100);
263
262
  const weekdayPct = 100 - weekendPct;
264
- const weekendBadge = benchmarkBadge(view, view.rank_summary.weekend_pct_percentile, "of devs by weekend coding");
263
+ const weekendBench = benchmarkInline(view, view.rank_summary.weekend_pct_percentile);
265
264
  const isNightOwl = critter?.key === "night_owl";
266
265
  const subhead = isNightOwl
267
266
  ? "while the 9-5ers sleep, you ship."
@@ -281,7 +280,7 @@ function cardRhythm(view) {
281
280
  "",
282
281
  ` ${chalk.white("weekdays".padEnd(9))} ${bar(weekdayPct, 100, 20)} ${chalk.dim(weekdayPct + "%")}`,
283
282
  ` ${chalk.white("weekends".padEnd(9))} ${bar(weekendPct, 100, 20)} ${chalk.dim(weekendPct + "%")}`,
284
- ...(weekendBadge ? ["", weekendBadge] : []),
283
+ ...(weekendBench ? ["", ` ${weekendBench}`] : []),
285
284
  "",
286
285
  ];
287
286
  if (view.rhythm_comment) {
@@ -350,13 +349,20 @@ function card7AINative(view) {
350
349
  : pct > 0
351
350
  ? "Claude's in your commit history. no going back."
352
351
  : "no AI-co-authored commits yet.";
352
+ const bench = benchmarkInline(view, view.rank_summary.ai_assisted_pct_percentile);
353
+ const streak = view.streak_days;
353
354
  const lines = [
354
355
  chalk.dim("YOU'RE AI-NATIVE"),
355
356
  "",
356
357
  ` commits since 2024: ${chalk.white(view.total_commits.toLocaleString())}`,
357
358
  ` AI co-authored: ${chalk.white(view.ai_assisted_commits.toLocaleString())}`,
358
359
  "",
359
- ` ${bar(pct, 100, 26)} ${chalk.white(pct + "%")}`,
360
+ ` ${bar(pct, 100, 22)} ${chalk.white(pct + "%")}${bench ? ` ${bench}` : ""}`,
361
+ ...(streak >= 2
362
+ ? [
363
+ ` ${chalk.white(`${streak}-day streak`)}${chalk.dim(", coding without missing a day.")}`,
364
+ ]
365
+ : []),
360
366
  "",
361
367
  chalk.dim(subhead),
362
368
  ].join("\n");
@@ -399,16 +405,20 @@ function topPercent(percentile) {
399
405
  const top = topPercentNumber(percentile);
400
406
  return top === null ? null : `top ${top}%`;
401
407
  }
402
- // Inline "top X%" benchmark for a single metric card, gated on the same 100-user
403
- // cohort that unlocks the rest of the rankings. Returns null (line omitted) for
404
- // early adopters or until the metric has a percentile.
405
- function benchmarkBadge(view, percentile, dimension) {
408
+ // Inline "(top X% of users)" cohort benchmark appended to a metric. Faint, except
409
+ // when the user lands in the top <10% the percentile renders in the rainbow
410
+ // gradient (the rest stays faint). Returns "" (omitted) for early adopters or
411
+ // until the metric has a percentile, so callers can append conditionally.
412
+ function benchmarkInline(view, percentile) {
406
413
  if (view.early_adopter)
407
- return null;
414
+ return "";
408
415
  const top = topPercentNumber(percentile);
409
416
  if (top === null)
410
- return null;
411
- return ` ${chalk.hex("#ff8a00")(`top ${top}%`)} ${chalk.dim(dimension)}`;
417
+ return "";
418
+ const pct = `${top}%`;
419
+ return top < 10
420
+ ? chalk.dim("(top ") + TITLE_GRADIENT(pct) + chalk.dim(" of users)")
421
+ : chalk.dim(`(top ${pct} of users)`);
412
422
  }
413
423
  function cardRankSummary(view) {
414
424
  if (!usageReady(view) || view.total_sessions <= 0)
@@ -443,9 +453,9 @@ function cardRankSummary(view) {
443
453
  .filter((stat) => stat.topNumber !== null)
444
454
  .sort((a, b) => a.topNumber - b.topNumber)[0];
445
455
  const cohort = rank.sample_size > 0
446
- ? `across ${rank.sample_size.toLocaleString()} Standout users`
456
+ ? `across ${rank.sample_size.toLocaleString()} users`
447
457
  : view.cohort_size > 0
448
- ? `among ${view.cohort_size.toLocaleString()} Standout users so far`
458
+ ? `among ${view.cohort_size.toLocaleString()} users so far`
449
459
  : "Standout profile summary";
450
460
  const row = (stat) => {
451
461
  const top = "top" in stat && stat.top
@@ -485,6 +495,7 @@ function cardTokens(view) {
485
495
  if (!usageReady(view) || (t.work_tokens <= 0 && t.cache_tokens <= 0))
486
496
  return "";
487
497
  const totalLine = bigNumber(formatTokens(t.total_30d) + " tokens");
498
+ const tokensBench = benchmarkInline(view, view.rank_summary.total_tokens_percentile);
488
499
  const max = Math.max(...t.by_tool.map((x) => x.tokens), 1);
489
500
  const toolRows = t.by_tool.length > 0
490
501
  ? t.by_tool
@@ -506,6 +517,14 @@ function cardTokens(view) {
506
517
  const costLine = cost > 0
507
518
  ? ` ${chalk.dim("would have cost")} ${TITLE_GRADIENT(`~$${cost.toLocaleString()}`)} ${chalk.dim("at retail API rates")}`
508
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
+ : "";
509
528
  const cachedNote = t.cache_tokens > t.work_tokens
510
529
  ? chalk.dim(" thankfully, most of them were cached :)")
511
530
  : t.cache_tokens > 0
@@ -517,18 +536,15 @@ function cardTokens(view) {
517
536
  const lines = [
518
537
  chalk.dim("YOU BURNED THIS MANY TOKENS"),
519
538
  "",
520
- ` ${totalLine}`,
521
- benchmarkBadge(view, view.rank_summary.total_tokens_percentile, "of devs by tokens") ?? "",
539
+ ` ${totalLine}${tokensBench ? ` ${tokensBench}` : ""}`,
522
540
  "",
523
541
  cachedNote,
524
542
  generatedLine,
525
543
  costLine,
544
+ cursorNote,
526
545
  "",
527
546
  " last 30 days, across:",
528
547
  toolRows,
529
- view.tokens.cursor_untracked
530
- ? chalk.dim(" Cursor used too (its tokens aren't tracked locally)")
531
- : "",
532
548
  "",
533
549
  deltaLine,
534
550
  ]
@@ -754,12 +770,11 @@ function cardConcurrency(view) {
754
770
  const longest = days >= 1.5
755
771
  ? `${days.toFixed(0)} days`
756
772
  : `${Math.round(c.longest_session_hours)}h`;
757
- const badge = benchmarkBadge(view, view.rank_summary.avg_agents_percentile, "of devs by agents juggled");
773
+ const bench = benchmarkInline(view, view.rank_summary.avg_agents_percentile);
758
774
  const lines = [
759
775
  chalk.dim("HOW MANY AGENTS YOU JUGGLE"),
760
776
  "",
761
- ` ${bigNumber(`~${c.open_tab_avg}`)} ${chalk.white("agents open at once")}`,
762
- ...(badge ? ["", badge] : []),
777
+ ` ${bigNumber(`~${c.open_tab_avg}`)} ${chalk.white("agents open at once")}${bench ? ` ${bench}` : ""}`,
763
778
  "",
764
779
  ` ${chalk.white("peak".padEnd(14))} ${chalk.dim(`${c.open_tab_peak} at once`)}`,
765
780
  ` ${chalk.white("longest tab".padEnd(14))} ${chalk.dim(`${longest} open`)}`,
@@ -866,9 +881,9 @@ export function cardProficiency(view) {
866
881
  const scoreTop = topPercentNumber(p.score_percentile ?? null);
867
882
  const sampleSize = view.rank_summary.sample_size;
868
883
  const cohort = scoreTop != null && sampleSize > 0
869
- ? `top ${scoreTop}% of ${sampleSize.toLocaleString()} Standout users`
884
+ ? `top ${scoreTop}% of ${sampleSize.toLocaleString()} users`
870
885
  : view.cohort_size > 0
871
- ? `among ${view.cohort_size.toLocaleString()} Standout users so far`
886
+ ? `among ${view.cohort_size.toLocaleString()} users so far`
872
887
  : "";
873
888
  const metricRow = (label, value) => value === null
874
889
  ? null
@@ -97,6 +97,7 @@ export interface WrappedAggregateView {
97
97
  total_tokens_percentile: number | null;
98
98
  avg_agents_percentile: number | null;
99
99
  weekend_pct_percentile: number | null;
100
+ ai_assisted_pct_percentile: number | null;
100
101
  };
101
102
  proficiency: ProficiencyView | null;
102
103
  tokens: {
@@ -114,7 +115,8 @@ export interface WrappedAggregateView {
114
115
  tokens: number;
115
116
  }[];
116
117
  estimated_retail_cost_usd: number;
117
- cursor_untracked: boolean;
118
+ cursor_estimated: boolean;
119
+ cursor_token_share: number;
118
120
  };
119
121
  tool_relationship: {
120
122
  kind: "switch";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "standout",
3
- "version": "0.5.24",
3
+ "version": "0.5.26",
4
4
  "description": "Build your developer profile with AI. One command, zero friction.",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",