standout 0.5.36 → 0.5.38

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.
Files changed (2) hide show
  1. package/dist/cli.js +266 -55
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -9,10 +9,18 @@ import { tmpdir as tmpdir2 } from "node:os";
9
9
  import { join as join6 } from "node:path";
10
10
  import { createInterface as createInterface3 } from "readline";
11
11
 
12
+ // src/sources.ts
13
+ var SOURCES = ["claude_code", "cowork", "codex", "cursor"];
14
+ var SOURCE_LABELS = {
15
+ claude_code: "Claude Code",
16
+ cowork: "Cowork",
17
+ codex: "Codex",
18
+ cursor: "Cursor"
19
+ };
20
+
12
21
  // src/payload.ts
13
22
  var MAX_BODY_BYTES = 4e6;
14
23
  var MAX_UPLOAD_EXCHANGES = 500;
15
- var TOOLS = ["claude_code", "codex", "cursor"];
16
24
  function byteLength(value) {
17
25
  return Buffer.byteLength(JSON.stringify(value), "utf8");
18
26
  }
@@ -26,7 +34,7 @@ function exchangesOf(aiUsage, tool) {
26
34
  function totalExchanges(payload) {
27
35
  const aiUsage = asRecord(payload.ai_usage);
28
36
  if (!aiUsage) return 0;
29
- return TOOLS.reduce((n, t) => n + exchangesOf(aiUsage, t).length, 0);
37
+ return SOURCES.reduce((n, t) => n + exchangesOf(aiUsage, t).length, 0);
30
38
  }
31
39
  function evenSample(arr, keep) {
32
40
  if (arr.length <= keep) return arr;
@@ -38,7 +46,7 @@ function evenSample(arr, keep) {
38
46
  function capExchangesInPlace(payload, maxTotal) {
39
47
  const aiUsage = asRecord(payload.ai_usage);
40
48
  if (!aiUsage) return;
41
- const present = TOOLS.map((t) => ({
49
+ const present = SOURCES.map((t) => ({
42
50
  t,
43
51
  n: exchangesOf(aiUsage, t).length
44
52
  })).filter((x) => x.n > 0);
@@ -71,7 +79,7 @@ function capPayload(payload) {
71
79
  }
72
80
  const over = () => byteLength(p) > MAX_BODY_BYTES;
73
81
  if (over()) {
74
- for (const t of TOOLS) {
82
+ for (const t of SOURCES) {
75
83
  const tool = asRecord(aiUsage[t]);
76
84
  if (tool && Array.isArray(tool.conversation_samples) && tool.conversation_samples.length) {
77
85
  tool.conversation_samples = [];
@@ -82,7 +90,7 @@ function capPayload(payload) {
82
90
  }
83
91
  }
84
92
  if (over()) {
85
- for (const t of TOOLS) {
93
+ for (const t of SOURCES) {
86
94
  const tool = asRecord(aiUsage[t]);
87
95
  if (tool && Array.isArray(tool.prompt_samples) && tool.prompt_samples.length) {
88
96
  tool.prompt_samples = [];
@@ -110,7 +118,7 @@ function capPayload(payload) {
110
118
  }
111
119
  }
112
120
  while (over() && totalExchanges(p) > 0) {
113
- for (const t of TOOLS) {
121
+ for (const t of SOURCES) {
114
122
  const tool = asRecord(aiUsage[t]);
115
123
  const ex = tool?.exchanges;
116
124
  if (tool && Array.isArray(ex) && ex.length > 0) {
@@ -244,8 +252,15 @@ function boostProficiencyForDisplay(p) {
244
252
  return { ...p, score, craft_bonus: Math.max(0, score - p.base) };
245
253
  }
246
254
 
255
+ // ../../lib/standout/wrapped/sources.ts
256
+ var WRAPPED_SOURCES = [
257
+ "claude_code",
258
+ "cowork",
259
+ "codex",
260
+ "cursor"
261
+ ];
262
+
247
263
  // ../../lib/standout/wrapped/stat-pack.ts
248
- var SOURCES = ["claude_code", "codex", "cursor"];
249
264
  var INFRA_FRAMEWORKS = /* @__PURE__ */ new Set([
250
265
  "github-actions",
251
266
  "terraform",
@@ -296,7 +311,7 @@ function buildStatPack(profile) {
296
311
  const frameworkCounts = /* @__PURE__ */ new Map();
297
312
  const languageCounts = /* @__PURE__ */ new Map();
298
313
  const modelCounts = /* @__PURE__ */ new Map();
299
- for (const src of SOURCES) {
314
+ for (const src of WRAPPED_SOURCES) {
300
315
  const stats = aiUsage[src];
301
316
  if (!stats) continue;
302
317
  const c = stats.concurrency;
@@ -394,7 +409,6 @@ function peakLabel(hour) {
394
409
  }
395
410
 
396
411
  // ../../lib/standout/wrapped/share-stats.ts
397
- var SOURCES2 = ["claude_code", "codex", "cursor"];
398
412
  var WRAPPED_COMMUNITY_BOOST = 1e3;
399
413
  var boostCommunity = (n) => n > 0 ? n + WRAPPED_COMMUNITY_BOOST : 0;
400
414
  var RETAIL_RATES = [
@@ -476,7 +490,7 @@ function computeTokens(aiUsage) {
476
490
  let workTokens = 0;
477
491
  let cacheTokens = 0;
478
492
  let totalCost = 0;
479
- for (const src of SOURCES2) {
493
+ for (const src of WRAPPED_SOURCES) {
480
494
  const stats = aiUsage[src];
481
495
  if (!stats) continue;
482
496
  for (const b of stats.monthly_buckets ?? []) {
@@ -497,7 +511,7 @@ function computeTokens(aiUsage) {
497
511
  function computeHours(aiUsage) {
498
512
  let totalHours = 0;
499
513
  let totalSessions = 0;
500
- for (const src of SOURCES2) {
514
+ for (const src of WRAPPED_SOURCES) {
501
515
  const stats = aiUsage[src];
502
516
  if (!stats) continue;
503
517
  totalHours += num2(stats.total_duration_hours);
@@ -507,7 +521,7 @@ function computeHours(aiUsage) {
507
521
  }
508
522
  function computeHistogram(aiUsage) {
509
523
  const hours = new Array(24).fill(0);
510
- for (const src of SOURCES2) {
524
+ for (const src of WRAPPED_SOURCES) {
511
525
  const stats = aiUsage[src];
512
526
  if (!stats) continue;
513
527
  const allTime = stats.all_time;
@@ -1592,7 +1606,7 @@ async function renderWrappedCardLocally(prefetched, theme = "dark") {
1592
1606
 
1593
1607
  // src/gather.ts
1594
1608
  import { execSync as execSync2 } from "child_process";
1595
- import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
1609
+ import { existsSync as existsSync5, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
1596
1610
  import { join as join5 } from "path";
1597
1611
  import { homedir as homedir5 } from "os";
1598
1612
 
@@ -1601,6 +1615,7 @@ import {
1601
1615
  createReadStream,
1602
1616
  existsSync as existsSync2,
1603
1617
  readdirSync,
1618
+ readFileSync as readFileSync2,
1604
1619
  realpathSync,
1605
1620
  statSync
1606
1621
  } from "fs";
@@ -2362,7 +2377,7 @@ function registerFile(raw, filePath) {
2362
2377
  if (raw.filePaths.size > 5e3) return;
2363
2378
  raw.filePaths.add(filePath);
2364
2379
  }
2365
- function recordSessionMeta(raw, sessionId, ts, cwd, model, version) {
2380
+ function recordSessionMeta(raw, sessionId, ts, cwd, model, version, projectLabel = null) {
2366
2381
  const minute = Math.floor(ts / 6e4);
2367
2382
  const existing = raw.sessions.get(sessionId);
2368
2383
  if (existing) {
@@ -2370,6 +2385,8 @@ function recordSessionMeta(raw, sessionId, ts, cwd, model, version) {
2370
2385
  if (ts > existing.lastTs) existing.lastTs = ts;
2371
2386
  existing.eventTimestamps.add(minute);
2372
2387
  if (!existing.cwd && cwd) existing.cwd = cwd;
2388
+ if (!existing.projectLabel && projectLabel)
2389
+ existing.projectLabel = projectLabel;
2373
2390
  if (model) existing.models.add(model);
2374
2391
  if (!existing.version && version) existing.version = version;
2375
2392
  } else {
@@ -2378,6 +2395,7 @@ function recordSessionMeta(raw, sessionId, ts, cwd, model, version) {
2378
2395
  lastTs: ts,
2379
2396
  eventTimestamps: /* @__PURE__ */ new Set([minute]),
2380
2397
  cwd,
2398
+ projectLabel,
2381
2399
  models: model ? /* @__PURE__ */ new Set([model]) : /* @__PURE__ */ new Set(),
2382
2400
  modelTurnCounts: /* @__PURE__ */ new Map(),
2383
2401
  inputTokens: 0,
@@ -2463,6 +2481,93 @@ function listClaudeCodeFiles() {
2463
2481
  }
2464
2482
  return dedupeByRealpath(files);
2465
2483
  }
2484
+ function coworkSessionsBaseDir() {
2485
+ const home = homedir2();
2486
+ if (process.platform === "darwin") {
2487
+ return join2(
2488
+ home,
2489
+ "Library",
2490
+ "Application Support",
2491
+ "Claude",
2492
+ "local-agent-mode-sessions"
2493
+ );
2494
+ }
2495
+ if (process.platform === "win32") {
2496
+ const appData = process.env.APPDATA;
2497
+ return appData ? join2(appData, "Claude", "local-agent-mode-sessions") : null;
2498
+ }
2499
+ const xdg = process.env.XDG_CONFIG_HOME;
2500
+ return join2(
2501
+ xdg || join2(home, ".config"),
2502
+ "Claude",
2503
+ "local-agent-mode-sessions"
2504
+ );
2505
+ }
2506
+ function listCoworkFiles() {
2507
+ const base = coworkSessionsBaseDir();
2508
+ if (!base || !existsSync2(base)) return [];
2509
+ const found = [];
2510
+ const safeReaddir = (dir) => {
2511
+ try {
2512
+ return readdirSync(dir);
2513
+ } catch {
2514
+ return [];
2515
+ }
2516
+ };
2517
+ const isDir = (p) => {
2518
+ try {
2519
+ return statSync(p).isDirectory();
2520
+ } catch {
2521
+ return false;
2522
+ }
2523
+ };
2524
+ for (const acct of safeReaddir(base)) {
2525
+ const acctDir = join2(base, acct);
2526
+ if (!isDir(acctDir)) continue;
2527
+ for (const org of safeReaddir(acctDir)) {
2528
+ const orgDir = join2(acctDir, org);
2529
+ if (!isDir(orgDir)) continue;
2530
+ for (const entry of safeReaddir(orgDir)) {
2531
+ if (!entry.startsWith("local_")) continue;
2532
+ const sessionDir = join2(orgDir, entry);
2533
+ if (!isDir(sessionDir)) continue;
2534
+ let title = "";
2535
+ try {
2536
+ const meta = JSON.parse(
2537
+ readFileSync2(join2(orgDir, `${entry}.json`), "utf-8")
2538
+ );
2539
+ if (typeof meta.title === "string") title = meta.title.trim();
2540
+ } catch {
2541
+ }
2542
+ const projectsDir = join2(sessionDir, ".claude", "projects");
2543
+ if (!existsSync2(projectsDir)) continue;
2544
+ for (const proj of safeReaddir(projectsDir)) {
2545
+ const projDir = join2(projectsDir, proj);
2546
+ if (!isDir(projDir)) continue;
2547
+ for (const file of safeReaddir(projDir)) {
2548
+ if (!file.endsWith(".jsonl")) continue;
2549
+ const filePath = join2(projDir, file);
2550
+ try {
2551
+ const stat = statSync(filePath);
2552
+ if (!stat.isFile()) continue;
2553
+ found.push({
2554
+ path: filePath,
2555
+ mtime: stat.mtimeMs,
2556
+ title: title || "Cowork session"
2557
+ });
2558
+ } catch {
2559
+ }
2560
+ }
2561
+ }
2562
+ }
2563
+ }
2564
+ }
2565
+ const titleByPath = new Map(found.map((f) => [f.path, f.title]));
2566
+ return dedupeByRealpath(found).map((path2) => ({
2567
+ path: path2,
2568
+ title: titleByPath.get(path2) ?? "Cowork session"
2569
+ }));
2570
+ }
2466
2571
  function listCodexFiles() {
2467
2572
  const base = join2(homedir2(), ".codex", "sessions");
2468
2573
  if (!existsSync2(base)) return [];
@@ -2509,7 +2614,9 @@ function makeTargets(targetInputs) {
2509
2614
  seenUserPromptThisSession: false,
2510
2615
  collectConversation: target.collectConversation ?? false,
2511
2616
  sessionPromptCounts: /* @__PURE__ */ new Map(),
2512
- pendingPrompt: /* @__PURE__ */ new Map()
2617
+ pendingPrompt: /* @__PURE__ */ new Map(),
2618
+ projectLabel: target.projectLabel ?? null,
2619
+ suppressFiles: target.suppressFiles ?? false
2513
2620
  }));
2514
2621
  }
2515
2622
  async function parseClaudeCodeFileTargets(filePath, targetInputs) {
@@ -2553,12 +2660,28 @@ async function parseClaudeCodeFileTargets(filePath, targetInputs) {
2553
2660
  }
2554
2661
  }
2555
2662
  if (shouldCount) {
2556
- recordSessionMeta(target.raw, sessionId, ts, cwd, null, version);
2663
+ recordSessionMeta(
2664
+ target.raw,
2665
+ sessionId,
2666
+ ts,
2667
+ cwd,
2668
+ null,
2669
+ version,
2670
+ target.projectLabel
2671
+ );
2557
2672
  }
2558
2673
  } else if (type === "assistant" && message) {
2559
2674
  const model = typeof message.model === "string" ? message.model : null;
2560
2675
  if (shouldCount) {
2561
- recordSessionMeta(target.raw, sessionId, ts, cwd, model, version);
2676
+ recordSessionMeta(
2677
+ target.raw,
2678
+ sessionId,
2679
+ ts,
2680
+ cwd,
2681
+ model,
2682
+ version,
2683
+ target.projectLabel
2684
+ );
2562
2685
  }
2563
2686
  const usage = message.usage;
2564
2687
  const messageId = typeof message.id === "string" ? message.id : null;
@@ -2593,7 +2716,7 @@ async function parseClaudeCodeFileTargets(filePath, targetInputs) {
2593
2716
  );
2594
2717
  }
2595
2718
  const input = item.input;
2596
- if (input) {
2719
+ if (input && !target.suppressFiles) {
2597
2720
  registerFile(
2598
2721
  target.raw,
2599
2722
  input.file_path
@@ -2917,8 +3040,10 @@ function finalize2(raw) {
2917
3040
  if (s.lastTs > lastTs) lastTs = s.lastTs;
2918
3041
  if (s.cwd) {
2919
3042
  if (PARALLEL_WORKSPACE_RE.test(s.cwd)) parallelCwds.add(s.cwd);
2920
- const key = compactCwd(s.cwd);
2921
- projectCounts.set(key, (projectCounts.get(key) || 0) + 1);
3043
+ }
3044
+ const projectKey = s.projectLabel ?? (s.cwd ? compactCwd(s.cwd) : null);
3045
+ if (projectKey) {
3046
+ projectCounts.set(projectKey, (projectCounts.get(projectKey) || 0) + 1);
2922
3047
  }
2923
3048
  s.models.forEach((m) => {
2924
3049
  if (m && !m.startsWith("<") && !m.includes("synthetic")) modelsSet.add(m);
@@ -3149,14 +3274,47 @@ function withAllTime(recent, allTime, previous = null) {
3149
3274
  if (previous) stats.previous_30d = summarizeUsage(previous);
3150
3275
  return stats;
3151
3276
  }
3277
+ function extractSessionRecords(raw, source) {
3278
+ const out = [];
3279
+ for (const [sessionId, s] of raw.sessions) {
3280
+ if (!sessionId) continue;
3281
+ const d = new Date(s.firstTs);
3282
+ if (isNaN(d.getTime())) continue;
3283
+ const month = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
3284
+ let primary = null;
3285
+ let best = 0;
3286
+ for (const [m, c] of s.modelTurnCounts) {
3287
+ if (c > best && m && !m.startsWith("<") && !m.includes("synthetic")) {
3288
+ best = c;
3289
+ primary = m;
3290
+ }
3291
+ }
3292
+ out.push({
3293
+ source,
3294
+ session_id: sessionId,
3295
+ month,
3296
+ duration_hours: +(estimateActiveDurationMs2(s.eventTimestamps) / 36e5).toFixed(3),
3297
+ input_tokens: s.inputTokens,
3298
+ output_tokens: s.outputTokens,
3299
+ cache_read_tokens: s.cacheReadTokens,
3300
+ cache_write_tokens: s.cacheWriteTokens,
3301
+ primary_model: primary
3302
+ });
3303
+ }
3304
+ return out;
3305
+ }
3152
3306
  async function gatherAiUsage() {
3153
3307
  const claudeFiles = listClaudeCodeFiles();
3154
3308
  const codexFiles = listCodexFiles();
3309
+ const coworkFiles = listCoworkFiles();
3155
3310
  const windowStartMs = Date.now() - USAGE_WINDOW_MS;
3156
3311
  const previousWindowStartMs = windowStartMs - USAGE_WINDOW_MS;
3157
3312
  const claudeRecentRaw = emptyRaw();
3158
3313
  const claudePreviousRaw = emptyRaw();
3159
3314
  const claudeAllRaw = emptyRaw();
3315
+ const coworkRecentRaw = emptyRaw();
3316
+ const coworkPreviousRaw = emptyRaw();
3317
+ const coworkAllRaw = emptyRaw();
3160
3318
  const codexRecentRaw = emptyRaw();
3161
3319
  const codexPreviousRaw = emptyRaw();
3162
3320
  const codexAllRaw = emptyRaw();
@@ -3184,6 +3342,32 @@ async function gatherAiUsage() {
3184
3342
  }
3185
3343
  }
3186
3344
  })(),
3345
+ (async () => {
3346
+ for (const { path: path2, title } of coworkFiles) {
3347
+ try {
3348
+ await parseClaudeCodeFileTargets(path2, [
3349
+ {
3350
+ raw: coworkRecentRaw,
3351
+ opts: { windowStartMs },
3352
+ collectConversation: true,
3353
+ projectLabel: title,
3354
+ suppressFiles: true
3355
+ },
3356
+ {
3357
+ raw: coworkPreviousRaw,
3358
+ opts: {
3359
+ windowStartMs: previousWindowStartMs,
3360
+ windowEndMs: windowStartMs
3361
+ },
3362
+ projectLabel: title,
3363
+ suppressFiles: true
3364
+ },
3365
+ { raw: coworkAllRaw, projectLabel: title, suppressFiles: true }
3366
+ ]);
3367
+ } catch {
3368
+ }
3369
+ }
3370
+ })(),
3187
3371
  (async () => {
3188
3372
  for (const file of codexFiles) {
3189
3373
  try {
@@ -3213,18 +3397,28 @@ async function gatherAiUsage() {
3213
3397
  finalize2(claudeAllRaw),
3214
3398
  finalize2(claudePreviousRaw)
3215
3399
  ),
3400
+ cowork: withAllTime(
3401
+ finalize2(coworkRecentRaw),
3402
+ finalize2(coworkAllRaw),
3403
+ finalize2(coworkPreviousRaw)
3404
+ ),
3216
3405
  codex: withAllTime(
3217
3406
  finalize2(codexRecentRaw),
3218
3407
  finalize2(codexAllRaw),
3219
3408
  finalize2(codexPreviousRaw)
3220
3409
  ),
3221
- cursor
3410
+ cursor,
3411
+ sessions: [
3412
+ ...extractSessionRecords(claudeAllRaw, "claude_code"),
3413
+ ...extractSessionRecords(coworkAllRaw, "cowork"),
3414
+ ...extractSessionRecords(codexAllRaw, "codex")
3415
+ ]
3222
3416
  };
3223
3417
  }
3224
3418
 
3225
3419
  // src/dev-env.ts
3226
3420
  import { execSync } from "child_process";
3227
- import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
3421
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
3228
3422
  import { homedir as homedir3 } from "os";
3229
3423
  import { join as join3 } from "path";
3230
3424
  var INTERESTING_TOOLS = [
@@ -3272,7 +3466,7 @@ function execSafe(cmd, timeout = 5e3) {
3272
3466
  }
3273
3467
  function readFileSafe(path2, maxBytes = 3e3) {
3274
3468
  try {
3275
- const content = readFileSync2(path2, "utf-8");
3469
+ const content = readFileSync3(path2, "utf-8");
3276
3470
  return content.length > maxBytes ? content.slice(0, maxBytes) : content;
3277
3471
  } catch {
3278
3472
  return null;
@@ -3299,7 +3493,7 @@ function collectAgentsMd() {
3299
3493
  function summarizeClaudeSettingsFile(path2, displayPath) {
3300
3494
  if (!existsSync3(path2)) return null;
3301
3495
  try {
3302
- const raw = JSON.parse(readFileSync2(path2, "utf-8"));
3496
+ const raw = JSON.parse(readFileSync3(path2, "utf-8"));
3303
3497
  const hooksObj = raw.hooks;
3304
3498
  const hooks = hooksObj ? Object.keys(hooksObj) : [];
3305
3499
  const mcpServersObj = raw.mcpServers;
@@ -3474,7 +3668,7 @@ import {
3474
3668
  mkdirSync,
3475
3669
  copyFileSync,
3476
3670
  rmSync,
3477
- readFileSync as readFileSync3
3671
+ readFileSync as readFileSync4
3478
3672
  } from "fs";
3479
3673
  import { homedir as homedir4, platform as platform2, tmpdir } from "os";
3480
3674
  import { join as join4 } from "path";
@@ -3558,7 +3752,7 @@ async function waitForDebugPort(userDataDir, timeoutMs) {
3558
3752
  while (Date.now() < deadline) {
3559
3753
  if (existsSync4(portFile)) {
3560
3754
  try {
3561
- const [port] = readFileSync3(portFile, "utf-8").trim().split("\n");
3755
+ const [port] = readFileSync4(portFile, "utf-8").trim().split("\n");
3562
3756
  if (port && /^\d+$/.test(port)) return parseInt(port, 10);
3563
3757
  } catch {
3564
3758
  }
@@ -3826,8 +4020,10 @@ function emptyGatheredProfile(readiness = {
3826
4020
  },
3827
4021
  ai_usage: {
3828
4022
  claude_code: null,
4023
+ cowork: null,
3829
4024
  codex: null,
3830
- cursor: null
4025
+ cursor: null,
4026
+ sessions: []
3831
4027
  },
3832
4028
  dev_environment: {
3833
4029
  agents_md: [],
@@ -3844,7 +4040,9 @@ function emptyGatheredProfile(readiness = {
3844
4040
  };
3845
4041
  }
3846
4042
  function hasAnyUsage(aiUsage) {
3847
- return Boolean(aiUsage.claude_code || aiUsage.codex || aiUsage.cursor);
4043
+ return Boolean(
4044
+ aiUsage.claude_code || aiUsage.cowork || aiUsage.codex || aiUsage.cursor
4045
+ );
3848
4046
  }
3849
4047
  function gatherLog(options, message) {
3850
4048
  if (options.verbose === false) return;
@@ -3865,8 +4063,10 @@ async function gatherUsageStats() {
3865
4063
  return {
3866
4064
  ai_usage: {
3867
4065
  claude_code: null,
4066
+ cowork: null,
3868
4067
  codex: null,
3869
- cursor: null
4068
+ cursor: null,
4069
+ sessions: []
3870
4070
  },
3871
4071
  readiness: {
3872
4072
  usage_stats_status: "error",
@@ -3958,7 +4158,7 @@ function readRepoReadme(repoPath) {
3958
4158
  const p = join5(repoPath, name);
3959
4159
  if (!existsSync5(p)) continue;
3960
4160
  try {
3961
- const text = readFileSync4(p, "utf-8").trim();
4161
+ const text = readFileSync5(p, "utf-8").trim();
3962
4162
  if (text.length > 0) return redactSecrets(text).slice(0, 1200);
3963
4163
  } catch {
3964
4164
  }
@@ -4306,8 +4506,10 @@ async function gather(options = {}) {
4306
4506
  const githubEnrichPromise = safeUsername && !localOnly ? enrichApi("github", safeUsername).catch(() => null) : Promise.resolve(null);
4307
4507
  const aiUsagePromise = options.aiUsage ? Promise.resolve(options.aiUsage) : gatherAiUsage().catch(() => ({
4308
4508
  claude_code: null,
4509
+ cowork: null,
4309
4510
  codex: null,
4310
- cursor: null
4511
+ cursor: null,
4512
+ sessions: []
4311
4513
  }));
4312
4514
  const devEnvPromise = gatherDevEnvironment().catch(
4313
4515
  () => ({
@@ -4528,7 +4730,7 @@ async function gather(options = {}) {
4528
4730
  );
4529
4731
  commitSubjects = subjectsRaw ? subjectsRaw.split("\n").map((s) => s.trim()).filter(Boolean).slice(0, 200).map((s) => redactSecrets(s).slice(0, 160)) : [];
4530
4732
  }
4531
- const claudeMd = existsSync5("CLAUDE.md") ? readFileSync4("CLAUDE.md", "utf-8").slice(0, 3e3) : null;
4733
+ const claudeMd = existsSync5("CLAUDE.md") ? readFileSync5("CLAUDE.md", "utf-8").slice(0, 3e3) : null;
4532
4734
  const claudeDirsRaw = execSafe2(
4533
4735
  `find ${homedir5()} -maxdepth 3 -path "*/.claude" -type d | head -5`
4534
4736
  );
@@ -4542,7 +4744,7 @@ async function gather(options = {}) {
4542
4744
  }
4543
4745
  let cursorrules = null;
4544
4746
  if (existsSync5(".cursorrules")) {
4545
- cursorrules = readFileSync4(".cursorrules", "utf-8").slice(0, 2e3);
4747
+ cursorrules = readFileSync5(".cursorrules", "utf-8").slice(0, 2e3);
4546
4748
  }
4547
4749
  const [aiUsage, devEnvironment, githubEnrichment] = await Promise.all([
4548
4750
  aiUsagePromise,
@@ -4707,7 +4909,7 @@ Your first user message contains a <prefetched_profile> block with a full JSON d
4707
4909
  - \`twitter\`: handle + optional enrichment
4708
4910
  - \`local\`: local git repos, commit patterns, collaborators
4709
4911
  - \`ai_coding\`: AI-assisted commit counts, CLAUDE.md contents, .claude dirs, other AI tool presence, .cursorrules
4710
- - \`ai_usage\`: Claude Code + Codex session parsing \u2014 total sessions, active days, tool-call frequency, projects (cwd), models used, peak hour, weekend ratio, languages inferred from file extensions, frameworks inferred from file paths, sanitized first-line prompt samples
4912
+ - \`ai_usage\`: Claude Code + Cowork + Codex session parsing \u2014 total sessions, active days, tool-call frequency, projects (cwd), models used, peak hour, weekend ratio, languages inferred from file extensions, frameworks inferred from file paths, sanitized first-line prompt samples. \`cowork\` is Claude Cowork (Anthropic's knowledge-work agent) \u2014 treat it as knowledge work, not coding (its projects are session titles, and it has no languages/frameworks).
4711
4913
  - \`dev_environment\`: AGENTS.md contents, Claude hooks/MCPs/commands, cursor rules, VSCode extensions, global npm/brew/pip packages, installed dev CLIs with versions
4712
4914
 
4713
4915
  TREAT PREFETCHED DATA AS AUTHORITATIVE. Do not re-run git log, curl GitHub, scan local files, parse ~/.claude, etc. That work is done. Read the block and proceed.
@@ -4902,6 +5104,7 @@ The JSON should follow this structure:
4902
5104
  },
4903
5105
  "ai_usage": {
4904
5106
  "claude_code": null,
5107
+ "cowork": null,
4905
5108
  "codex": null,
4906
5109
  "cursor": null
4907
5110
  },
@@ -5045,7 +5248,7 @@ ${question}
5045
5248
  });
5046
5249
  });
5047
5250
  }
5048
- var TOOLS2 = [
5251
+ var TOOLS = [
5049
5252
  {
5050
5253
  name: "bash",
5051
5254
  description: "Execute a bash command in a read-only sandbox. Destructive commands (rm, mv, git push, sudo, etc.) are blocked. Use for reading files, git log, curl GET, find, ls, etc.",
@@ -5134,12 +5337,6 @@ async function executeTool(name, input, options = {}) {
5134
5337
  // src/wrapped/aggregate.ts
5135
5338
  var WRAPPED_COMMUNITY_BOOST2 = 1e3;
5136
5339
  var boostCommunity2 = (n) => n > 0 ? n + WRAPPED_COMMUNITY_BOOST2 : 0;
5137
- var SOURCES3 = ["claude_code", "codex", "cursor"];
5138
- var TOOL_LABELS = {
5139
- claude_code: "Claude Code",
5140
- codex: "Codex",
5141
- cursor: "Cursor"
5142
- };
5143
5340
  var RETAIL_RATES2 = [
5144
5341
  {
5145
5342
  match: /claude.*opus/i,
@@ -5304,7 +5501,7 @@ function aggregateView(args) {
5304
5501
  const allTimeMonthly = /* @__PURE__ */ new Map();
5305
5502
  const allTimeHourBuckets = new Array(24).fill(0);
5306
5503
  let allTimeStreak = 0;
5307
- for (const src of SOURCES3) {
5504
+ for (const src of SOURCES) {
5308
5505
  const stats = aiUsage[src];
5309
5506
  if (!stats) continue;
5310
5507
  usageStatsSeen = true;
@@ -5555,7 +5752,7 @@ function computeTokens2(aiUsage) {
5555
5752
  const cursorStats = aiUsage["cursor"];
5556
5753
  const cursorEstimated = !!(cursorStats && (cursorStats.total_sessions ?? 0) >= CURSOR_MIN_SESSIONS);
5557
5754
  let cursorTokens = 0;
5558
- for (const src of SOURCES3) {
5755
+ for (const src of SOURCES) {
5559
5756
  const stats = aiUsage[src];
5560
5757
  if (!stats) continue;
5561
5758
  const buckets = stats.monthly_buckets ?? [];
@@ -5565,7 +5762,7 @@ function computeTokens2(aiUsage) {
5565
5762
  workTokens += work;
5566
5763
  cacheTokens += cache;
5567
5764
  if (src === "cursor") cursorTokens += work + cache;
5568
- byTool[TOOL_LABELS[src] ?? src] = (byTool[TOOL_LABELS[src] ?? src] ?? 0) + work + cache;
5765
+ byTool[SOURCE_LABELS[src] ?? src] = (byTool[SOURCE_LABELS[src] ?? src] ?? 0) + work + cache;
5569
5766
  const rate = rateForModel2(b.primary_model);
5570
5767
  const cacheRead = b.cache_read_tokens ?? (b.cache_write_tokens === void 0 ? b.cache_tokens ?? 0 : 0);
5571
5768
  const cacheWrite = b.cache_write_tokens ?? 0;
@@ -5610,7 +5807,7 @@ function computeTokens2(aiUsage) {
5610
5807
  }
5611
5808
  function computeToolRelationship(aiUsage) {
5612
5809
  const monthMap = /* @__PURE__ */ new Map();
5613
- for (const src of SOURCES3) {
5810
+ for (const src of SOURCES) {
5614
5811
  const stats = aiUsage[src];
5615
5812
  if (!stats) continue;
5616
5813
  for (const b of stats.monthly_buckets ?? []) {
@@ -5630,7 +5827,7 @@ function computeToolRelationship(aiUsage) {
5630
5827
  if (best) {
5631
5828
  return {
5632
5829
  kind: "loyalist",
5633
- tool: TOOL_LABELS[best[0]] ?? best[0],
5830
+ tool: SOURCE_LABELS[best[0]] ?? best[0],
5634
5831
  months_count: 1,
5635
5832
  sessions_count: best[1]
5636
5833
  };
@@ -5665,12 +5862,12 @@ function computeToolRelationship(aiUsage) {
5665
5862
  if (switchIdx > 0) {
5666
5863
  return {
5667
5864
  kind: "switch",
5668
- from_tool: TOOL_LABELS[firstDominant] ?? firstDominant,
5669
- to_tool: TOOL_LABELS[lastDominant] ?? lastDominant,
5865
+ from_tool: SOURCE_LABELS[firstDominant] ?? firstDominant,
5866
+ to_tool: SOURCE_LABELS[lastDominant] ?? lastDominant,
5670
5867
  switch_month: timeline[switchIdx].month,
5671
5868
  timeline: timeline.map((t) => ({
5672
5869
  month: t.month,
5673
- dominant: TOOL_LABELS[t.dominant] ?? t.dominant,
5870
+ dominant: SOURCE_LABELS[t.dominant] ?? t.dominant,
5674
5871
  share: t.share
5675
5872
  }))
5676
5873
  };
@@ -5684,7 +5881,7 @@ function computeToolRelationship(aiUsage) {
5684
5881
  }
5685
5882
  return {
5686
5883
  kind: "loyalist",
5687
- tool: TOOL_LABELS[firstDominant] ?? firstDominant,
5884
+ tool: SOURCE_LABELS[firstDominant] ?? firstDominant,
5688
5885
  months_count: months.length,
5689
5886
  sessions_count: totalSessions
5690
5887
  };
@@ -5697,14 +5894,14 @@ function computeToolRelationship(aiUsage) {
5697
5894
  }
5698
5895
  const grandTotal = [...totalsByTool.values()].reduce((a, b) => a + b, 0);
5699
5896
  const tools = [...totalsByTool.entries()].sort(([, a], [, b]) => b - a).map(([tool, n]) => ({
5700
- tool: TOOL_LABELS[tool] ?? tool,
5897
+ tool: SOURCE_LABELS[tool] ?? tool,
5701
5898
  share: grandTotal > 0 ? n / grandTotal : 0
5702
5899
  }));
5703
5900
  return { kind: "polyglot", tools };
5704
5901
  }
5705
5902
  function computeModels(aiUsage) {
5706
5903
  const allModelCounts = /* @__PURE__ */ new Map();
5707
- for (const src of SOURCES3) {
5904
+ for (const src of SOURCES) {
5708
5905
  const stats = aiUsage[src];
5709
5906
  if (!stats?.model_session_counts) continue;
5710
5907
  for (const [model, n] of Object.entries(stats.model_session_counts)) {
@@ -5774,7 +5971,7 @@ function computeCodeFootprint(aiCoding, totalCommits, line) {
5774
5971
  function computeConcurrency2(aiUsage, line) {
5775
5972
  let best = null;
5776
5973
  let bestSessions = -1;
5777
- for (const src of SOURCES3) {
5974
+ for (const src of SOURCES) {
5778
5975
  const stats = aiUsage[src];
5779
5976
  if (!stats?.concurrency) continue;
5780
5977
  const sessions = stats.total_sessions ?? 0;
@@ -5796,7 +5993,7 @@ function computeConversation(aiUsage, line, themes) {
5796
5993
  let totalPrompts = 0;
5797
5994
  let questionWeighted = 0;
5798
5995
  let charsWeighted = 0;
5799
- for (const src of SOURCES3) {
5996
+ for (const src of SOURCES) {
5800
5997
  const stats = aiUsage[src];
5801
5998
  const c = stats?.conversation;
5802
5999
  if (!c) continue;
@@ -6665,6 +6862,19 @@ async function renderWrappedAll(view) {
6665
6862
  }
6666
6863
 
6667
6864
  // src/wrapped-client.ts
6865
+ function applyLedgerMonthly(profile, ledgerMonthly) {
6866
+ if (!ledgerMonthly) return;
6867
+ const ai = profile?.ai_usage;
6868
+ if (!ai) return;
6869
+ for (const [src, buckets] of Object.entries(ledgerMonthly)) {
6870
+ if (!Array.isArray(buckets) || buckets.length === 0) continue;
6871
+ const stats = ai[src] ?? (ai[src] = {});
6872
+ const at = stats.all_time ?? (stats.all_time = {});
6873
+ at.monthly_buckets = buckets;
6874
+ at.total_duration_hours = +buckets.reduce((s, b) => s + (b.duration_hours ?? 0), 0).toFixed(1);
6875
+ at.total_sessions = buckets.reduce((s, b) => s + (b.sessions ?? 0), 0);
6876
+ }
6877
+ }
6668
6878
  var HEADERS = {
6669
6879
  "Content-Type": "application/json",
6670
6880
  // Vercel BotID flagged Anthropic/JS as bot traffic; same goes for default
@@ -6793,6 +7003,7 @@ async function createWrapped(args) {
6793
7003
  );
6794
7004
  return null;
6795
7005
  }
7006
+ applyLedgerMonthly(args.profile, created.ledger_monthly);
6796
7007
  const view = aggregateView({
6797
7008
  profile: args.profile,
6798
7009
  computed,
@@ -7234,7 +7445,7 @@ async function runAgent(jobId) {
7234
7445
  messages,
7235
7446
  // Cache the entire tools array via cache_control on the last tool definition.
7236
7447
  tools: [
7237
- ...TOOLS2,
7448
+ ...TOOLS,
7238
7449
  {
7239
7450
  type: "web_search_20250305",
7240
7451
  name: "web_search",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "standout",
3
- "version": "0.5.36",
3
+ "version": "0.5.38",
4
4
  "description": "Build your developer profile with AI. One command, zero friction.",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",