standout 0.5.36 → 0.5.37

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 +211 -51
  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);
@@ -3152,11 +3277,15 @@ function withAllTime(recent, allTime, previous = null) {
3152
3277
  async function gatherAiUsage() {
3153
3278
  const claudeFiles = listClaudeCodeFiles();
3154
3279
  const codexFiles = listCodexFiles();
3280
+ const coworkFiles = listCoworkFiles();
3155
3281
  const windowStartMs = Date.now() - USAGE_WINDOW_MS;
3156
3282
  const previousWindowStartMs = windowStartMs - USAGE_WINDOW_MS;
3157
3283
  const claudeRecentRaw = emptyRaw();
3158
3284
  const claudePreviousRaw = emptyRaw();
3159
3285
  const claudeAllRaw = emptyRaw();
3286
+ const coworkRecentRaw = emptyRaw();
3287
+ const coworkPreviousRaw = emptyRaw();
3288
+ const coworkAllRaw = emptyRaw();
3160
3289
  const codexRecentRaw = emptyRaw();
3161
3290
  const codexPreviousRaw = emptyRaw();
3162
3291
  const codexAllRaw = emptyRaw();
@@ -3184,6 +3313,32 @@ async function gatherAiUsage() {
3184
3313
  }
3185
3314
  }
3186
3315
  })(),
3316
+ (async () => {
3317
+ for (const { path: path2, title } of coworkFiles) {
3318
+ try {
3319
+ await parseClaudeCodeFileTargets(path2, [
3320
+ {
3321
+ raw: coworkRecentRaw,
3322
+ opts: { windowStartMs },
3323
+ collectConversation: true,
3324
+ projectLabel: title,
3325
+ suppressFiles: true
3326
+ },
3327
+ {
3328
+ raw: coworkPreviousRaw,
3329
+ opts: {
3330
+ windowStartMs: previousWindowStartMs,
3331
+ windowEndMs: windowStartMs
3332
+ },
3333
+ projectLabel: title,
3334
+ suppressFiles: true
3335
+ },
3336
+ { raw: coworkAllRaw, projectLabel: title, suppressFiles: true }
3337
+ ]);
3338
+ } catch {
3339
+ }
3340
+ }
3341
+ })(),
3187
3342
  (async () => {
3188
3343
  for (const file of codexFiles) {
3189
3344
  try {
@@ -3213,6 +3368,11 @@ async function gatherAiUsage() {
3213
3368
  finalize2(claudeAllRaw),
3214
3369
  finalize2(claudePreviousRaw)
3215
3370
  ),
3371
+ cowork: withAllTime(
3372
+ finalize2(coworkRecentRaw),
3373
+ finalize2(coworkAllRaw),
3374
+ finalize2(coworkPreviousRaw)
3375
+ ),
3216
3376
  codex: withAllTime(
3217
3377
  finalize2(codexRecentRaw),
3218
3378
  finalize2(codexAllRaw),
@@ -3224,7 +3384,7 @@ async function gatherAiUsage() {
3224
3384
 
3225
3385
  // src/dev-env.ts
3226
3386
  import { execSync } from "child_process";
3227
- import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
3387
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
3228
3388
  import { homedir as homedir3 } from "os";
3229
3389
  import { join as join3 } from "path";
3230
3390
  var INTERESTING_TOOLS = [
@@ -3272,7 +3432,7 @@ function execSafe(cmd, timeout = 5e3) {
3272
3432
  }
3273
3433
  function readFileSafe(path2, maxBytes = 3e3) {
3274
3434
  try {
3275
- const content = readFileSync2(path2, "utf-8");
3435
+ const content = readFileSync3(path2, "utf-8");
3276
3436
  return content.length > maxBytes ? content.slice(0, maxBytes) : content;
3277
3437
  } catch {
3278
3438
  return null;
@@ -3299,7 +3459,7 @@ function collectAgentsMd() {
3299
3459
  function summarizeClaudeSettingsFile(path2, displayPath) {
3300
3460
  if (!existsSync3(path2)) return null;
3301
3461
  try {
3302
- const raw = JSON.parse(readFileSync2(path2, "utf-8"));
3462
+ const raw = JSON.parse(readFileSync3(path2, "utf-8"));
3303
3463
  const hooksObj = raw.hooks;
3304
3464
  const hooks = hooksObj ? Object.keys(hooksObj) : [];
3305
3465
  const mcpServersObj = raw.mcpServers;
@@ -3474,7 +3634,7 @@ import {
3474
3634
  mkdirSync,
3475
3635
  copyFileSync,
3476
3636
  rmSync,
3477
- readFileSync as readFileSync3
3637
+ readFileSync as readFileSync4
3478
3638
  } from "fs";
3479
3639
  import { homedir as homedir4, platform as platform2, tmpdir } from "os";
3480
3640
  import { join as join4 } from "path";
@@ -3558,7 +3718,7 @@ async function waitForDebugPort(userDataDir, timeoutMs) {
3558
3718
  while (Date.now() < deadline) {
3559
3719
  if (existsSync4(portFile)) {
3560
3720
  try {
3561
- const [port] = readFileSync3(portFile, "utf-8").trim().split("\n");
3721
+ const [port] = readFileSync4(portFile, "utf-8").trim().split("\n");
3562
3722
  if (port && /^\d+$/.test(port)) return parseInt(port, 10);
3563
3723
  } catch {
3564
3724
  }
@@ -3826,6 +3986,7 @@ function emptyGatheredProfile(readiness = {
3826
3986
  },
3827
3987
  ai_usage: {
3828
3988
  claude_code: null,
3989
+ cowork: null,
3829
3990
  codex: null,
3830
3991
  cursor: null
3831
3992
  },
@@ -3844,7 +4005,9 @@ function emptyGatheredProfile(readiness = {
3844
4005
  };
3845
4006
  }
3846
4007
  function hasAnyUsage(aiUsage) {
3847
- return Boolean(aiUsage.claude_code || aiUsage.codex || aiUsage.cursor);
4008
+ return Boolean(
4009
+ aiUsage.claude_code || aiUsage.cowork || aiUsage.codex || aiUsage.cursor
4010
+ );
3848
4011
  }
3849
4012
  function gatherLog(options, message) {
3850
4013
  if (options.verbose === false) return;
@@ -3865,6 +4028,7 @@ async function gatherUsageStats() {
3865
4028
  return {
3866
4029
  ai_usage: {
3867
4030
  claude_code: null,
4031
+ cowork: null,
3868
4032
  codex: null,
3869
4033
  cursor: null
3870
4034
  },
@@ -3958,7 +4122,7 @@ function readRepoReadme(repoPath) {
3958
4122
  const p = join5(repoPath, name);
3959
4123
  if (!existsSync5(p)) continue;
3960
4124
  try {
3961
- const text = readFileSync4(p, "utf-8").trim();
4125
+ const text = readFileSync5(p, "utf-8").trim();
3962
4126
  if (text.length > 0) return redactSecrets(text).slice(0, 1200);
3963
4127
  } catch {
3964
4128
  }
@@ -4306,6 +4470,7 @@ async function gather(options = {}) {
4306
4470
  const githubEnrichPromise = safeUsername && !localOnly ? enrichApi("github", safeUsername).catch(() => null) : Promise.resolve(null);
4307
4471
  const aiUsagePromise = options.aiUsage ? Promise.resolve(options.aiUsage) : gatherAiUsage().catch(() => ({
4308
4472
  claude_code: null,
4473
+ cowork: null,
4309
4474
  codex: null,
4310
4475
  cursor: null
4311
4476
  }));
@@ -4528,7 +4693,7 @@ async function gather(options = {}) {
4528
4693
  );
4529
4694
  commitSubjects = subjectsRaw ? subjectsRaw.split("\n").map((s) => s.trim()).filter(Boolean).slice(0, 200).map((s) => redactSecrets(s).slice(0, 160)) : [];
4530
4695
  }
4531
- const claudeMd = existsSync5("CLAUDE.md") ? readFileSync4("CLAUDE.md", "utf-8").slice(0, 3e3) : null;
4696
+ const claudeMd = existsSync5("CLAUDE.md") ? readFileSync5("CLAUDE.md", "utf-8").slice(0, 3e3) : null;
4532
4697
  const claudeDirsRaw = execSafe2(
4533
4698
  `find ${homedir5()} -maxdepth 3 -path "*/.claude" -type d | head -5`
4534
4699
  );
@@ -4542,7 +4707,7 @@ async function gather(options = {}) {
4542
4707
  }
4543
4708
  let cursorrules = null;
4544
4709
  if (existsSync5(".cursorrules")) {
4545
- cursorrules = readFileSync4(".cursorrules", "utf-8").slice(0, 2e3);
4710
+ cursorrules = readFileSync5(".cursorrules", "utf-8").slice(0, 2e3);
4546
4711
  }
4547
4712
  const [aiUsage, devEnvironment, githubEnrichment] = await Promise.all([
4548
4713
  aiUsagePromise,
@@ -4707,7 +4872,7 @@ Your first user message contains a <prefetched_profile> block with a full JSON d
4707
4872
  - \`twitter\`: handle + optional enrichment
4708
4873
  - \`local\`: local git repos, commit patterns, collaborators
4709
4874
  - \`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
4875
+ - \`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
4876
  - \`dev_environment\`: AGENTS.md contents, Claude hooks/MCPs/commands, cursor rules, VSCode extensions, global npm/brew/pip packages, installed dev CLIs with versions
4712
4877
 
4713
4878
  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 +5067,7 @@ The JSON should follow this structure:
4902
5067
  },
4903
5068
  "ai_usage": {
4904
5069
  "claude_code": null,
5070
+ "cowork": null,
4905
5071
  "codex": null,
4906
5072
  "cursor": null
4907
5073
  },
@@ -5045,7 +5211,7 @@ ${question}
5045
5211
  });
5046
5212
  });
5047
5213
  }
5048
- var TOOLS2 = [
5214
+ var TOOLS = [
5049
5215
  {
5050
5216
  name: "bash",
5051
5217
  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 +5300,6 @@ async function executeTool(name, input, options = {}) {
5134
5300
  // src/wrapped/aggregate.ts
5135
5301
  var WRAPPED_COMMUNITY_BOOST2 = 1e3;
5136
5302
  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
5303
  var RETAIL_RATES2 = [
5144
5304
  {
5145
5305
  match: /claude.*opus/i,
@@ -5304,7 +5464,7 @@ function aggregateView(args) {
5304
5464
  const allTimeMonthly = /* @__PURE__ */ new Map();
5305
5465
  const allTimeHourBuckets = new Array(24).fill(0);
5306
5466
  let allTimeStreak = 0;
5307
- for (const src of SOURCES3) {
5467
+ for (const src of SOURCES) {
5308
5468
  const stats = aiUsage[src];
5309
5469
  if (!stats) continue;
5310
5470
  usageStatsSeen = true;
@@ -5555,7 +5715,7 @@ function computeTokens2(aiUsage) {
5555
5715
  const cursorStats = aiUsage["cursor"];
5556
5716
  const cursorEstimated = !!(cursorStats && (cursorStats.total_sessions ?? 0) >= CURSOR_MIN_SESSIONS);
5557
5717
  let cursorTokens = 0;
5558
- for (const src of SOURCES3) {
5718
+ for (const src of SOURCES) {
5559
5719
  const stats = aiUsage[src];
5560
5720
  if (!stats) continue;
5561
5721
  const buckets = stats.monthly_buckets ?? [];
@@ -5565,7 +5725,7 @@ function computeTokens2(aiUsage) {
5565
5725
  workTokens += work;
5566
5726
  cacheTokens += cache;
5567
5727
  if (src === "cursor") cursorTokens += work + cache;
5568
- byTool[TOOL_LABELS[src] ?? src] = (byTool[TOOL_LABELS[src] ?? src] ?? 0) + work + cache;
5728
+ byTool[SOURCE_LABELS[src] ?? src] = (byTool[SOURCE_LABELS[src] ?? src] ?? 0) + work + cache;
5569
5729
  const rate = rateForModel2(b.primary_model);
5570
5730
  const cacheRead = b.cache_read_tokens ?? (b.cache_write_tokens === void 0 ? b.cache_tokens ?? 0 : 0);
5571
5731
  const cacheWrite = b.cache_write_tokens ?? 0;
@@ -5610,7 +5770,7 @@ function computeTokens2(aiUsage) {
5610
5770
  }
5611
5771
  function computeToolRelationship(aiUsage) {
5612
5772
  const monthMap = /* @__PURE__ */ new Map();
5613
- for (const src of SOURCES3) {
5773
+ for (const src of SOURCES) {
5614
5774
  const stats = aiUsage[src];
5615
5775
  if (!stats) continue;
5616
5776
  for (const b of stats.monthly_buckets ?? []) {
@@ -5630,7 +5790,7 @@ function computeToolRelationship(aiUsage) {
5630
5790
  if (best) {
5631
5791
  return {
5632
5792
  kind: "loyalist",
5633
- tool: TOOL_LABELS[best[0]] ?? best[0],
5793
+ tool: SOURCE_LABELS[best[0]] ?? best[0],
5634
5794
  months_count: 1,
5635
5795
  sessions_count: best[1]
5636
5796
  };
@@ -5665,12 +5825,12 @@ function computeToolRelationship(aiUsage) {
5665
5825
  if (switchIdx > 0) {
5666
5826
  return {
5667
5827
  kind: "switch",
5668
- from_tool: TOOL_LABELS[firstDominant] ?? firstDominant,
5669
- to_tool: TOOL_LABELS[lastDominant] ?? lastDominant,
5828
+ from_tool: SOURCE_LABELS[firstDominant] ?? firstDominant,
5829
+ to_tool: SOURCE_LABELS[lastDominant] ?? lastDominant,
5670
5830
  switch_month: timeline[switchIdx].month,
5671
5831
  timeline: timeline.map((t) => ({
5672
5832
  month: t.month,
5673
- dominant: TOOL_LABELS[t.dominant] ?? t.dominant,
5833
+ dominant: SOURCE_LABELS[t.dominant] ?? t.dominant,
5674
5834
  share: t.share
5675
5835
  }))
5676
5836
  };
@@ -5684,7 +5844,7 @@ function computeToolRelationship(aiUsage) {
5684
5844
  }
5685
5845
  return {
5686
5846
  kind: "loyalist",
5687
- tool: TOOL_LABELS[firstDominant] ?? firstDominant,
5847
+ tool: SOURCE_LABELS[firstDominant] ?? firstDominant,
5688
5848
  months_count: months.length,
5689
5849
  sessions_count: totalSessions
5690
5850
  };
@@ -5697,14 +5857,14 @@ function computeToolRelationship(aiUsage) {
5697
5857
  }
5698
5858
  const grandTotal = [...totalsByTool.values()].reduce((a, b) => a + b, 0);
5699
5859
  const tools = [...totalsByTool.entries()].sort(([, a], [, b]) => b - a).map(([tool, n]) => ({
5700
- tool: TOOL_LABELS[tool] ?? tool,
5860
+ tool: SOURCE_LABELS[tool] ?? tool,
5701
5861
  share: grandTotal > 0 ? n / grandTotal : 0
5702
5862
  }));
5703
5863
  return { kind: "polyglot", tools };
5704
5864
  }
5705
5865
  function computeModels(aiUsage) {
5706
5866
  const allModelCounts = /* @__PURE__ */ new Map();
5707
- for (const src of SOURCES3) {
5867
+ for (const src of SOURCES) {
5708
5868
  const stats = aiUsage[src];
5709
5869
  if (!stats?.model_session_counts) continue;
5710
5870
  for (const [model, n] of Object.entries(stats.model_session_counts)) {
@@ -5774,7 +5934,7 @@ function computeCodeFootprint(aiCoding, totalCommits, line) {
5774
5934
  function computeConcurrency2(aiUsage, line) {
5775
5935
  let best = null;
5776
5936
  let bestSessions = -1;
5777
- for (const src of SOURCES3) {
5937
+ for (const src of SOURCES) {
5778
5938
  const stats = aiUsage[src];
5779
5939
  if (!stats?.concurrency) continue;
5780
5940
  const sessions = stats.total_sessions ?? 0;
@@ -5796,7 +5956,7 @@ function computeConversation(aiUsage, line, themes) {
5796
5956
  let totalPrompts = 0;
5797
5957
  let questionWeighted = 0;
5798
5958
  let charsWeighted = 0;
5799
- for (const src of SOURCES3) {
5959
+ for (const src of SOURCES) {
5800
5960
  const stats = aiUsage[src];
5801
5961
  const c = stats?.conversation;
5802
5962
  if (!c) continue;
@@ -7234,7 +7394,7 @@ async function runAgent(jobId) {
7234
7394
  messages,
7235
7395
  // Cache the entire tools array via cache_control on the last tool definition.
7236
7396
  tools: [
7237
- ...TOOLS2,
7397
+ ...TOOLS,
7238
7398
  {
7239
7399
  type: "web_search_20250305",
7240
7400
  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.37",
4
4
  "description": "Build your developer profile with AI. One command, zero friction.",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",