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.
- package/dist/cli.js +266 -55
- 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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
2921
|
-
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
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] =
|
|
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(
|
|
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 =
|
|
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") ?
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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[
|
|
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
|
|
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:
|
|
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:
|
|
5669
|
-
to_tool:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
-
...
|
|
7448
|
+
...TOOLS,
|
|
7238
7449
|
{
|
|
7239
7450
|
type: "web_search_20250305",
|
|
7240
7451
|
name: "web_search",
|