standout 0.5.35 → 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 +217 -188
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -2,17 +2,25 @@
2
2
  var __toBinaryNode = Uint8Array.fromBase64 || ((base64) => new Uint8Array(Buffer.from(base64, "base64")));
3
3
 
4
4
  // src/cli.ts
5
- import Anthropic2 from "@anthropic-ai/sdk";
5
+ import Anthropic from "@anthropic-ai/sdk";
6
6
  import chalk2 from "chalk";
7
7
  import { writeFileSync } from "node:fs";
8
8
  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,139 +252,15 @@ function boostProficiencyForDisplay(p) {
244
252
  return { ...p, score, craft_bonus: Math.max(0, score - p.base) };
245
253
  }
246
254
 
247
- // ../../lib/llm/useCase.ts
248
- import Anthropic from "@anthropic-ai/sdk";
249
- import { SpanStatusCode as SpanStatusCode2 } from "@opentelemetry/api";
250
- import { startObservation } from "@langfuse/tracing";
251
- import OpenAI from "openai";
252
-
253
- // ../../lib/llm/langfuse.ts
254
- import { SpanStatusCode } from "@opentelemetry/api";
255
- import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
256
- import { LangfuseSpanProcessor } from "@langfuse/otel";
257
- import { setLangfuseTracerProvider } from "@langfuse/tracing";
258
- import { CallbackHandler } from "@langfuse/langchain";
259
-
260
- // ../../lib/llm/useCase.ts
261
- var DEFAULT_RETRY_STRATEGY = {
262
- maxRetries: 5,
263
- initialDelaySeconds: 1,
264
- maxDelaySeconds: 16,
265
- retryAfterFloorMs: 500
266
- };
267
- function sleep(ms) {
268
- return new Promise((resolve) => setTimeout(resolve, ms));
269
- }
270
- var RetryingAnthropic = class extends Anthropic {
271
- constructor(opts = {}) {
272
- const strategy = {
273
- ...DEFAULT_RETRY_STRATEGY,
274
- ...opts.retryStrategy ?? {}
275
- };
276
- const { retryStrategy: _strategy, ...clientOpts } = opts;
277
- super({
278
- ...clientOpts,
279
- maxRetries: clientOpts.maxRetries ?? strategy.maxRetries
280
- });
281
- this.retryStrategy = strategy;
282
- }
283
- };
284
- RetryingAnthropic.prototype.retryRequest = async function(options, retriesRemaining, requestLogID, responseHeaders) {
285
- const strategy = this.retryStrategy;
286
- let timeoutMillis;
287
- const retryAfterMillisHeader = responseHeaders?.get("retry-after-ms");
288
- if (retryAfterMillisHeader) {
289
- const ms = parseFloat(retryAfterMillisHeader);
290
- if (!Number.isNaN(ms)) timeoutMillis = ms;
291
- }
292
- const retryAfterHeader = responseHeaders?.get("retry-after");
293
- if (retryAfterHeader && timeoutMillis === void 0) {
294
- const seconds = parseFloat(retryAfterHeader);
295
- if (!Number.isNaN(seconds)) {
296
- timeoutMillis = seconds * 1e3;
297
- } else {
298
- timeoutMillis = Date.parse(retryAfterHeader) - Date.now();
299
- }
300
- }
301
- if (timeoutMillis === void 0 || Number.isNaN(timeoutMillis)) {
302
- const numRetries = strategy.maxRetries - retriesRemaining;
303
- const sleepSeconds = Math.min(
304
- strategy.initialDelaySeconds * Math.pow(2, numRetries),
305
- strategy.maxDelaySeconds
306
- );
307
- const jitter = 1 - Math.random() * 0.25;
308
- timeoutMillis = sleepSeconds * jitter * 1e3;
309
- } else {
310
- timeoutMillis = Math.max(timeoutMillis, strategy.retryAfterFloorMs);
311
- }
312
- await sleep(timeoutMillis);
313
- const makeRequest = this.makeRequest;
314
- return makeRequest.call(this, options, retriesRemaining - 1, requestLogID);
315
- };
316
- var RetryingOpenAI = class extends OpenAI {
317
- constructor(opts = {}) {
318
- const strategy = {
319
- ...DEFAULT_RETRY_STRATEGY,
320
- ...opts.retryStrategy ?? {}
321
- };
322
- const { retryStrategy: _strategy, ...clientOpts } = opts;
323
- super({
324
- ...clientOpts,
325
- maxRetries: clientOpts.maxRetries ?? strategy.maxRetries
326
- });
327
- this.retryStrategy = strategy;
328
- }
329
- };
330
- RetryingOpenAI.prototype.retryRequest = async function(options, retriesRemaining, responseHeaders) {
331
- const strategy = this.retryStrategy;
332
- let timeoutMillis;
333
- const retryAfterMillisHeader = responseHeaders?.get("retry-after-ms");
334
- if (retryAfterMillisHeader) {
335
- const ms = parseFloat(retryAfterMillisHeader);
336
- if (!Number.isNaN(ms)) timeoutMillis = ms;
337
- }
338
- const retryAfterHeader = responseHeaders?.get("retry-after");
339
- if (retryAfterHeader && timeoutMillis === void 0) {
340
- const seconds = parseFloat(retryAfterHeader);
341
- if (!Number.isNaN(seconds)) {
342
- timeoutMillis = seconds * 1e3;
343
- } else {
344
- timeoutMillis = Date.parse(retryAfterHeader) - Date.now();
345
- }
346
- }
347
- if (timeoutMillis === void 0 || Number.isNaN(timeoutMillis)) {
348
- const numRetries = strategy.maxRetries - retriesRemaining;
349
- const sleepSeconds = Math.min(
350
- strategy.initialDelaySeconds * Math.pow(2, numRetries),
351
- strategy.maxDelaySeconds
352
- );
353
- const jitter = 1 - Math.random() * 0.25;
354
- timeoutMillis = sleepSeconds * jitter * 1e3;
355
- } else {
356
- timeoutMillis = Math.max(timeoutMillis, strategy.retryAfterFloorMs);
357
- }
358
- await sleep(timeoutMillis);
359
- const makeRequest = this.makeRequest;
360
- return makeRequest.call(this, options, retriesRemaining - 1);
361
- };
362
-
363
- // ../../lib/llm/disableLangsmith.ts
364
- process.env.LANGSMITH_TRACING = "false";
365
- process.env.LANGCHAIN_TRACING_V2 = "false";
366
- process.env.LANGCHAIN_TRACING = "false";
367
- delete process.env.LANGSMITH_API_KEY;
368
- delete process.env.LANGCHAIN_API_KEY;
369
- delete process.env.LANGSMITH_ENDPOINT;
370
- delete process.env.LANGCHAIN_ENDPOINT;
371
- delete process.env.LANGSMITH_PROJECT;
372
- delete process.env.LANGCHAIN_PROJECT;
373
-
374
- // ../../lib/llm/models.ts
375
- import { ChatAnthropic } from "@langchain/anthropic";
376
- import { ChatOpenAI } from "@langchain/openai";
255
+ // ../../lib/standout/wrapped/sources.ts
256
+ var WRAPPED_SOURCES = [
257
+ "claude_code",
258
+ "cowork",
259
+ "codex",
260
+ "cursor"
261
+ ];
377
262
 
378
- // ../../lib/standout/wrapped/narrative.ts
379
- var SOURCES = ["claude_code", "codex", "cursor"];
263
+ // ../../lib/standout/wrapped/stat-pack.ts
380
264
  var INFRA_FRAMEWORKS = /* @__PURE__ */ new Set([
381
265
  "github-actions",
382
266
  "terraform",
@@ -427,7 +311,7 @@ function buildStatPack(profile) {
427
311
  const frameworkCounts = /* @__PURE__ */ new Map();
428
312
  const languageCounts = /* @__PURE__ */ new Map();
429
313
  const modelCounts = /* @__PURE__ */ new Map();
430
- for (const src of SOURCES) {
314
+ for (const src of WRAPPED_SOURCES) {
431
315
  const stats = aiUsage[src];
432
316
  if (!stats) continue;
433
317
  const c = stats.concurrency;
@@ -525,7 +409,6 @@ function peakLabel(hour) {
525
409
  }
526
410
 
527
411
  // ../../lib/standout/wrapped/share-stats.ts
528
- var SOURCES2 = ["claude_code", "codex", "cursor"];
529
412
  var WRAPPED_COMMUNITY_BOOST = 1e3;
530
413
  var boostCommunity = (n) => n > 0 ? n + WRAPPED_COMMUNITY_BOOST : 0;
531
414
  var RETAIL_RATES = [
@@ -607,7 +490,7 @@ function computeTokens(aiUsage) {
607
490
  let workTokens = 0;
608
491
  let cacheTokens = 0;
609
492
  let totalCost = 0;
610
- for (const src of SOURCES2) {
493
+ for (const src of WRAPPED_SOURCES) {
611
494
  const stats = aiUsage[src];
612
495
  if (!stats) continue;
613
496
  for (const b of stats.monthly_buckets ?? []) {
@@ -628,7 +511,7 @@ function computeTokens(aiUsage) {
628
511
  function computeHours(aiUsage) {
629
512
  let totalHours = 0;
630
513
  let totalSessions = 0;
631
- for (const src of SOURCES2) {
514
+ for (const src of WRAPPED_SOURCES) {
632
515
  const stats = aiUsage[src];
633
516
  if (!stats) continue;
634
517
  totalHours += num2(stats.total_duration_hours);
@@ -638,7 +521,7 @@ function computeHours(aiUsage) {
638
521
  }
639
522
  function computeHistogram(aiUsage) {
640
523
  const hours = new Array(24).fill(0);
641
- for (const src of SOURCES2) {
524
+ for (const src of WRAPPED_SOURCES) {
642
525
  const stats = aiUsage[src];
643
526
  if (!stats) continue;
644
527
  const allTime = stats.all_time;
@@ -1723,7 +1606,7 @@ async function renderWrappedCardLocally(prefetched, theme = "dark") {
1723
1606
 
1724
1607
  // src/gather.ts
1725
1608
  import { execSync as execSync2 } from "child_process";
1726
- 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";
1727
1610
  import { join as join5 } from "path";
1728
1611
  import { homedir as homedir5 } from "os";
1729
1612
 
@@ -1732,6 +1615,7 @@ import {
1732
1615
  createReadStream,
1733
1616
  existsSync as existsSync2,
1734
1617
  readdirSync,
1618
+ readFileSync as readFileSync2,
1735
1619
  realpathSync,
1736
1620
  statSync
1737
1621
  } from "fs";
@@ -2493,7 +2377,7 @@ function registerFile(raw, filePath) {
2493
2377
  if (raw.filePaths.size > 5e3) return;
2494
2378
  raw.filePaths.add(filePath);
2495
2379
  }
2496
- function recordSessionMeta(raw, sessionId, ts, cwd, model, version) {
2380
+ function recordSessionMeta(raw, sessionId, ts, cwd, model, version, projectLabel = null) {
2497
2381
  const minute = Math.floor(ts / 6e4);
2498
2382
  const existing = raw.sessions.get(sessionId);
2499
2383
  if (existing) {
@@ -2501,6 +2385,8 @@ function recordSessionMeta(raw, sessionId, ts, cwd, model, version) {
2501
2385
  if (ts > existing.lastTs) existing.lastTs = ts;
2502
2386
  existing.eventTimestamps.add(minute);
2503
2387
  if (!existing.cwd && cwd) existing.cwd = cwd;
2388
+ if (!existing.projectLabel && projectLabel)
2389
+ existing.projectLabel = projectLabel;
2504
2390
  if (model) existing.models.add(model);
2505
2391
  if (!existing.version && version) existing.version = version;
2506
2392
  } else {
@@ -2509,6 +2395,7 @@ function recordSessionMeta(raw, sessionId, ts, cwd, model, version) {
2509
2395
  lastTs: ts,
2510
2396
  eventTimestamps: /* @__PURE__ */ new Set([minute]),
2511
2397
  cwd,
2398
+ projectLabel,
2512
2399
  models: model ? /* @__PURE__ */ new Set([model]) : /* @__PURE__ */ new Set(),
2513
2400
  modelTurnCounts: /* @__PURE__ */ new Map(),
2514
2401
  inputTokens: 0,
@@ -2594,6 +2481,93 @@ function listClaudeCodeFiles() {
2594
2481
  }
2595
2482
  return dedupeByRealpath(files);
2596
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
+ }
2597
2571
  function listCodexFiles() {
2598
2572
  const base = join2(homedir2(), ".codex", "sessions");
2599
2573
  if (!existsSync2(base)) return [];
@@ -2640,7 +2614,9 @@ function makeTargets(targetInputs) {
2640
2614
  seenUserPromptThisSession: false,
2641
2615
  collectConversation: target.collectConversation ?? false,
2642
2616
  sessionPromptCounts: /* @__PURE__ */ new Map(),
2643
- pendingPrompt: /* @__PURE__ */ new Map()
2617
+ pendingPrompt: /* @__PURE__ */ new Map(),
2618
+ projectLabel: target.projectLabel ?? null,
2619
+ suppressFiles: target.suppressFiles ?? false
2644
2620
  }));
2645
2621
  }
2646
2622
  async function parseClaudeCodeFileTargets(filePath, targetInputs) {
@@ -2684,12 +2660,28 @@ async function parseClaudeCodeFileTargets(filePath, targetInputs) {
2684
2660
  }
2685
2661
  }
2686
2662
  if (shouldCount) {
2687
- 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
+ );
2688
2672
  }
2689
2673
  } else if (type === "assistant" && message) {
2690
2674
  const model = typeof message.model === "string" ? message.model : null;
2691
2675
  if (shouldCount) {
2692
- 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
+ );
2693
2685
  }
2694
2686
  const usage = message.usage;
2695
2687
  const messageId = typeof message.id === "string" ? message.id : null;
@@ -2724,7 +2716,7 @@ async function parseClaudeCodeFileTargets(filePath, targetInputs) {
2724
2716
  );
2725
2717
  }
2726
2718
  const input = item.input;
2727
- if (input) {
2719
+ if (input && !target.suppressFiles) {
2728
2720
  registerFile(
2729
2721
  target.raw,
2730
2722
  input.file_path
@@ -3048,8 +3040,10 @@ function finalize2(raw) {
3048
3040
  if (s.lastTs > lastTs) lastTs = s.lastTs;
3049
3041
  if (s.cwd) {
3050
3042
  if (PARALLEL_WORKSPACE_RE.test(s.cwd)) parallelCwds.add(s.cwd);
3051
- const key = compactCwd(s.cwd);
3052
- 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);
3053
3047
  }
3054
3048
  s.models.forEach((m) => {
3055
3049
  if (m && !m.startsWith("<") && !m.includes("synthetic")) modelsSet.add(m);
@@ -3283,11 +3277,15 @@ function withAllTime(recent, allTime, previous = null) {
3283
3277
  async function gatherAiUsage() {
3284
3278
  const claudeFiles = listClaudeCodeFiles();
3285
3279
  const codexFiles = listCodexFiles();
3280
+ const coworkFiles = listCoworkFiles();
3286
3281
  const windowStartMs = Date.now() - USAGE_WINDOW_MS;
3287
3282
  const previousWindowStartMs = windowStartMs - USAGE_WINDOW_MS;
3288
3283
  const claudeRecentRaw = emptyRaw();
3289
3284
  const claudePreviousRaw = emptyRaw();
3290
3285
  const claudeAllRaw = emptyRaw();
3286
+ const coworkRecentRaw = emptyRaw();
3287
+ const coworkPreviousRaw = emptyRaw();
3288
+ const coworkAllRaw = emptyRaw();
3291
3289
  const codexRecentRaw = emptyRaw();
3292
3290
  const codexPreviousRaw = emptyRaw();
3293
3291
  const codexAllRaw = emptyRaw();
@@ -3315,6 +3313,32 @@ async function gatherAiUsage() {
3315
3313
  }
3316
3314
  }
3317
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
+ })(),
3318
3342
  (async () => {
3319
3343
  for (const file of codexFiles) {
3320
3344
  try {
@@ -3344,6 +3368,11 @@ async function gatherAiUsage() {
3344
3368
  finalize2(claudeAllRaw),
3345
3369
  finalize2(claudePreviousRaw)
3346
3370
  ),
3371
+ cowork: withAllTime(
3372
+ finalize2(coworkRecentRaw),
3373
+ finalize2(coworkAllRaw),
3374
+ finalize2(coworkPreviousRaw)
3375
+ ),
3347
3376
  codex: withAllTime(
3348
3377
  finalize2(codexRecentRaw),
3349
3378
  finalize2(codexAllRaw),
@@ -3355,7 +3384,7 @@ async function gatherAiUsage() {
3355
3384
 
3356
3385
  // src/dev-env.ts
3357
3386
  import { execSync } from "child_process";
3358
- 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";
3359
3388
  import { homedir as homedir3 } from "os";
3360
3389
  import { join as join3 } from "path";
3361
3390
  var INTERESTING_TOOLS = [
@@ -3403,7 +3432,7 @@ function execSafe(cmd, timeout = 5e3) {
3403
3432
  }
3404
3433
  function readFileSafe(path2, maxBytes = 3e3) {
3405
3434
  try {
3406
- const content = readFileSync2(path2, "utf-8");
3435
+ const content = readFileSync3(path2, "utf-8");
3407
3436
  return content.length > maxBytes ? content.slice(0, maxBytes) : content;
3408
3437
  } catch {
3409
3438
  return null;
@@ -3430,7 +3459,7 @@ function collectAgentsMd() {
3430
3459
  function summarizeClaudeSettingsFile(path2, displayPath) {
3431
3460
  if (!existsSync3(path2)) return null;
3432
3461
  try {
3433
- const raw = JSON.parse(readFileSync2(path2, "utf-8"));
3462
+ const raw = JSON.parse(readFileSync3(path2, "utf-8"));
3434
3463
  const hooksObj = raw.hooks;
3435
3464
  const hooks = hooksObj ? Object.keys(hooksObj) : [];
3436
3465
  const mcpServersObj = raw.mcpServers;
@@ -3605,7 +3634,7 @@ import {
3605
3634
  mkdirSync,
3606
3635
  copyFileSync,
3607
3636
  rmSync,
3608
- readFileSync as readFileSync3
3637
+ readFileSync as readFileSync4
3609
3638
  } from "fs";
3610
3639
  import { homedir as homedir4, platform as platform2, tmpdir } from "os";
3611
3640
  import { join as join4 } from "path";
@@ -3689,7 +3718,7 @@ async function waitForDebugPort(userDataDir, timeoutMs) {
3689
3718
  while (Date.now() < deadline) {
3690
3719
  if (existsSync4(portFile)) {
3691
3720
  try {
3692
- const [port] = readFileSync3(portFile, "utf-8").trim().split("\n");
3721
+ const [port] = readFileSync4(portFile, "utf-8").trim().split("\n");
3693
3722
  if (port && /^\d+$/.test(port)) return parseInt(port, 10);
3694
3723
  } catch {
3695
3724
  }
@@ -3957,6 +3986,7 @@ function emptyGatheredProfile(readiness = {
3957
3986
  },
3958
3987
  ai_usage: {
3959
3988
  claude_code: null,
3989
+ cowork: null,
3960
3990
  codex: null,
3961
3991
  cursor: null
3962
3992
  },
@@ -3975,7 +4005,9 @@ function emptyGatheredProfile(readiness = {
3975
4005
  };
3976
4006
  }
3977
4007
  function hasAnyUsage(aiUsage) {
3978
- return Boolean(aiUsage.claude_code || aiUsage.codex || aiUsage.cursor);
4008
+ return Boolean(
4009
+ aiUsage.claude_code || aiUsage.cowork || aiUsage.codex || aiUsage.cursor
4010
+ );
3979
4011
  }
3980
4012
  function gatherLog(options, message) {
3981
4013
  if (options.verbose === false) return;
@@ -3996,6 +4028,7 @@ async function gatherUsageStats() {
3996
4028
  return {
3997
4029
  ai_usage: {
3998
4030
  claude_code: null,
4031
+ cowork: null,
3999
4032
  codex: null,
4000
4033
  cursor: null
4001
4034
  },
@@ -4089,7 +4122,7 @@ function readRepoReadme(repoPath) {
4089
4122
  const p = join5(repoPath, name);
4090
4123
  if (!existsSync5(p)) continue;
4091
4124
  try {
4092
- const text = readFileSync4(p, "utf-8").trim();
4125
+ const text = readFileSync5(p, "utf-8").trim();
4093
4126
  if (text.length > 0) return redactSecrets(text).slice(0, 1200);
4094
4127
  } catch {
4095
4128
  }
@@ -4437,6 +4470,7 @@ async function gather(options = {}) {
4437
4470
  const githubEnrichPromise = safeUsername && !localOnly ? enrichApi("github", safeUsername).catch(() => null) : Promise.resolve(null);
4438
4471
  const aiUsagePromise = options.aiUsage ? Promise.resolve(options.aiUsage) : gatherAiUsage().catch(() => ({
4439
4472
  claude_code: null,
4473
+ cowork: null,
4440
4474
  codex: null,
4441
4475
  cursor: null
4442
4476
  }));
@@ -4659,7 +4693,7 @@ async function gather(options = {}) {
4659
4693
  );
4660
4694
  commitSubjects = subjectsRaw ? subjectsRaw.split("\n").map((s) => s.trim()).filter(Boolean).slice(0, 200).map((s) => redactSecrets(s).slice(0, 160)) : [];
4661
4695
  }
4662
- 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;
4663
4697
  const claudeDirsRaw = execSafe2(
4664
4698
  `find ${homedir5()} -maxdepth 3 -path "*/.claude" -type d | head -5`
4665
4699
  );
@@ -4673,7 +4707,7 @@ async function gather(options = {}) {
4673
4707
  }
4674
4708
  let cursorrules = null;
4675
4709
  if (existsSync5(".cursorrules")) {
4676
- cursorrules = readFileSync4(".cursorrules", "utf-8").slice(0, 2e3);
4710
+ cursorrules = readFileSync5(".cursorrules", "utf-8").slice(0, 2e3);
4677
4711
  }
4678
4712
  const [aiUsage, devEnvironment, githubEnrichment] = await Promise.all([
4679
4713
  aiUsagePromise,
@@ -4838,7 +4872,7 @@ Your first user message contains a <prefetched_profile> block with a full JSON d
4838
4872
  - \`twitter\`: handle + optional enrichment
4839
4873
  - \`local\`: local git repos, commit patterns, collaborators
4840
4874
  - \`ai_coding\`: AI-assisted commit counts, CLAUDE.md contents, .claude dirs, other AI tool presence, .cursorrules
4841
- - \`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).
4842
4876
  - \`dev_environment\`: AGENTS.md contents, Claude hooks/MCPs/commands, cursor rules, VSCode extensions, global npm/brew/pip packages, installed dev CLIs with versions
4843
4877
 
4844
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.
@@ -5033,6 +5067,7 @@ The JSON should follow this structure:
5033
5067
  },
5034
5068
  "ai_usage": {
5035
5069
  "claude_code": null,
5070
+ "cowork": null,
5036
5071
  "codex": null,
5037
5072
  "cursor": null
5038
5073
  },
@@ -5176,7 +5211,7 @@ ${question}
5176
5211
  });
5177
5212
  });
5178
5213
  }
5179
- var TOOLS2 = [
5214
+ var TOOLS = [
5180
5215
  {
5181
5216
  name: "bash",
5182
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.",
@@ -5265,12 +5300,6 @@ async function executeTool(name, input, options = {}) {
5265
5300
  // src/wrapped/aggregate.ts
5266
5301
  var WRAPPED_COMMUNITY_BOOST2 = 1e3;
5267
5302
  var boostCommunity2 = (n) => n > 0 ? n + WRAPPED_COMMUNITY_BOOST2 : 0;
5268
- var SOURCES3 = ["claude_code", "codex", "cursor"];
5269
- var TOOL_LABELS = {
5270
- claude_code: "Claude Code",
5271
- codex: "Codex",
5272
- cursor: "Cursor"
5273
- };
5274
5303
  var RETAIL_RATES2 = [
5275
5304
  {
5276
5305
  match: /claude.*opus/i,
@@ -5435,7 +5464,7 @@ function aggregateView(args) {
5435
5464
  const allTimeMonthly = /* @__PURE__ */ new Map();
5436
5465
  const allTimeHourBuckets = new Array(24).fill(0);
5437
5466
  let allTimeStreak = 0;
5438
- for (const src of SOURCES3) {
5467
+ for (const src of SOURCES) {
5439
5468
  const stats = aiUsage[src];
5440
5469
  if (!stats) continue;
5441
5470
  usageStatsSeen = true;
@@ -5686,7 +5715,7 @@ function computeTokens2(aiUsage) {
5686
5715
  const cursorStats = aiUsage["cursor"];
5687
5716
  const cursorEstimated = !!(cursorStats && (cursorStats.total_sessions ?? 0) >= CURSOR_MIN_SESSIONS);
5688
5717
  let cursorTokens = 0;
5689
- for (const src of SOURCES3) {
5718
+ for (const src of SOURCES) {
5690
5719
  const stats = aiUsage[src];
5691
5720
  if (!stats) continue;
5692
5721
  const buckets = stats.monthly_buckets ?? [];
@@ -5696,7 +5725,7 @@ function computeTokens2(aiUsage) {
5696
5725
  workTokens += work;
5697
5726
  cacheTokens += cache;
5698
5727
  if (src === "cursor") cursorTokens += work + cache;
5699
- 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;
5700
5729
  const rate = rateForModel2(b.primary_model);
5701
5730
  const cacheRead = b.cache_read_tokens ?? (b.cache_write_tokens === void 0 ? b.cache_tokens ?? 0 : 0);
5702
5731
  const cacheWrite = b.cache_write_tokens ?? 0;
@@ -5741,7 +5770,7 @@ function computeTokens2(aiUsage) {
5741
5770
  }
5742
5771
  function computeToolRelationship(aiUsage) {
5743
5772
  const monthMap = /* @__PURE__ */ new Map();
5744
- for (const src of SOURCES3) {
5773
+ for (const src of SOURCES) {
5745
5774
  const stats = aiUsage[src];
5746
5775
  if (!stats) continue;
5747
5776
  for (const b of stats.monthly_buckets ?? []) {
@@ -5761,7 +5790,7 @@ function computeToolRelationship(aiUsage) {
5761
5790
  if (best) {
5762
5791
  return {
5763
5792
  kind: "loyalist",
5764
- tool: TOOL_LABELS[best[0]] ?? best[0],
5793
+ tool: SOURCE_LABELS[best[0]] ?? best[0],
5765
5794
  months_count: 1,
5766
5795
  sessions_count: best[1]
5767
5796
  };
@@ -5796,12 +5825,12 @@ function computeToolRelationship(aiUsage) {
5796
5825
  if (switchIdx > 0) {
5797
5826
  return {
5798
5827
  kind: "switch",
5799
- from_tool: TOOL_LABELS[firstDominant] ?? firstDominant,
5800
- to_tool: TOOL_LABELS[lastDominant] ?? lastDominant,
5828
+ from_tool: SOURCE_LABELS[firstDominant] ?? firstDominant,
5829
+ to_tool: SOURCE_LABELS[lastDominant] ?? lastDominant,
5801
5830
  switch_month: timeline[switchIdx].month,
5802
5831
  timeline: timeline.map((t) => ({
5803
5832
  month: t.month,
5804
- dominant: TOOL_LABELS[t.dominant] ?? t.dominant,
5833
+ dominant: SOURCE_LABELS[t.dominant] ?? t.dominant,
5805
5834
  share: t.share
5806
5835
  }))
5807
5836
  };
@@ -5815,7 +5844,7 @@ function computeToolRelationship(aiUsage) {
5815
5844
  }
5816
5845
  return {
5817
5846
  kind: "loyalist",
5818
- tool: TOOL_LABELS[firstDominant] ?? firstDominant,
5847
+ tool: SOURCE_LABELS[firstDominant] ?? firstDominant,
5819
5848
  months_count: months.length,
5820
5849
  sessions_count: totalSessions
5821
5850
  };
@@ -5828,14 +5857,14 @@ function computeToolRelationship(aiUsage) {
5828
5857
  }
5829
5858
  const grandTotal = [...totalsByTool.values()].reduce((a, b) => a + b, 0);
5830
5859
  const tools = [...totalsByTool.entries()].sort(([, a], [, b]) => b - a).map(([tool, n]) => ({
5831
- tool: TOOL_LABELS[tool] ?? tool,
5860
+ tool: SOURCE_LABELS[tool] ?? tool,
5832
5861
  share: grandTotal > 0 ? n / grandTotal : 0
5833
5862
  }));
5834
5863
  return { kind: "polyglot", tools };
5835
5864
  }
5836
5865
  function computeModels(aiUsage) {
5837
5866
  const allModelCounts = /* @__PURE__ */ new Map();
5838
- for (const src of SOURCES3) {
5867
+ for (const src of SOURCES) {
5839
5868
  const stats = aiUsage[src];
5840
5869
  if (!stats?.model_session_counts) continue;
5841
5870
  for (const [model, n] of Object.entries(stats.model_session_counts)) {
@@ -5905,7 +5934,7 @@ function computeCodeFootprint(aiCoding, totalCommits, line) {
5905
5934
  function computeConcurrency2(aiUsage, line) {
5906
5935
  let best = null;
5907
5936
  let bestSessions = -1;
5908
- for (const src of SOURCES3) {
5937
+ for (const src of SOURCES) {
5909
5938
  const stats = aiUsage[src];
5910
5939
  if (!stats?.concurrency) continue;
5911
5940
  const sessions = stats.total_sessions ?? 0;
@@ -5927,7 +5956,7 @@ function computeConversation(aiUsage, line, themes) {
5927
5956
  let totalPrompts = 0;
5928
5957
  let questionWeighted = 0;
5929
5958
  let charsWeighted = 0;
5930
- for (const src of SOURCES3) {
5959
+ for (const src of SOURCES) {
5931
5960
  const stats = aiUsage[src];
5932
5961
  const c = stats?.conversation;
5933
5962
  if (!c) continue;
@@ -6836,14 +6865,14 @@ function describeError(err) {
6836
6865
  }
6837
6866
  return parts.join(" <- ") || String(err);
6838
6867
  }
6839
- var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
6868
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
6840
6869
  function isReady(computed) {
6841
6870
  if (!computed) return false;
6842
6871
  return computed.status === "ready" || computed.archetype != null;
6843
6872
  }
6844
6873
  async function pollComputed(id) {
6845
6874
  const deadline = Date.now() + POLL_TIMEOUT_MS;
6846
- await sleep2(POLL_INTERVAL_MS);
6875
+ await sleep(POLL_INTERVAL_MS);
6847
6876
  while (Date.now() < deadline) {
6848
6877
  try {
6849
6878
  const res = await fetch(`${STANDOUT_API_URL}/api/public/wrapped/${id}`, {
@@ -6856,7 +6885,7 @@ async function pollComputed(id) {
6856
6885
  }
6857
6886
  } catch {
6858
6887
  }
6859
- await sleep2(POLL_INTERVAL_MS);
6888
+ await sleep(POLL_INTERVAL_MS);
6860
6889
  }
6861
6890
  return null;
6862
6891
  }
@@ -6890,7 +6919,7 @@ async function createWrapped(args) {
6890
6919
  break;
6891
6920
  } catch (err) {
6892
6921
  if (isPreConnectError(err) && attempt < MAX_ATTEMPTS) {
6893
- await sleep2(RETRY_BASE_DELAY_MS * attempt);
6922
+ await sleep(RETRY_BASE_DELAY_MS * attempt);
6894
6923
  continue;
6895
6924
  }
6896
6925
  const detail = describeError(err);
@@ -7261,7 +7290,7 @@ async function runAgent(jobId) {
7261
7290
  console.log(` Applying to: ${STANDOUT_API_URL}/jobs/${jobId}
7262
7291
  `);
7263
7292
  }
7264
- const client = new Anthropic2({
7293
+ const client = new Anthropic({
7265
7294
  apiKey: "proxied-by-standout",
7266
7295
  baseURL: `${STANDOUT_API_URL}/api/public/agent-chat`,
7267
7296
  defaultHeaders: {
@@ -7365,7 +7394,7 @@ async function runAgent(jobId) {
7365
7394
  messages,
7366
7395
  // Cache the entire tools array via cache_control on the last tool definition.
7367
7396
  tools: [
7368
- ...TOOLS2,
7397
+ ...TOOLS,
7369
7398
  {
7370
7399
  type: "web_search_20250305",
7371
7400
  name: "web_search",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "standout",
3
- "version": "0.5.35",
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",