tokengolf 0.3.0 → 0.4.0
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/.husky/pre-commit +4 -0
- package/.prettierignore +2 -0
- package/.prettierrc +6 -0
- package/.vscode/settings.json +15 -0
- package/CHANGELOG.md +254 -0
- package/CLAUDE.md +136 -10
- package/README.md +89 -47
- package/assets/demo-hud.png +0 -0
- package/assets/scorecard.png +0 -0
- package/dist/cli.js +790 -103
- package/docs/assets/demo-hud.png +0 -0
- package/docs/assets/scorecard.png +0 -0
- package/docs/assets/tokengolf-bg-min.jpg +0 -0
- package/docs/index.html +1080 -0
- package/eslint.config.js +39 -0
- package/hooks/post-tool-use-failure.js +27 -0
- package/hooks/post-tool-use.js +11 -7
- package/hooks/pre-compact.js +9 -3
- package/hooks/session-end.js +168 -42
- package/hooks/session-start.js +31 -11
- package/hooks/session-stop.js +6 -2
- package/hooks/statusline.sh +16 -7
- package/hooks/stop.js +27 -0
- package/hooks/subagent-start.js +27 -0
- package/hooks/user-prompt-submit.js +8 -6
- package/package.json +16 -3
- package/src/cli.js +23 -6
- package/src/components/ActiveRun.js +76 -24
- package/src/components/ScoreCard.js +132 -37
- package/src/components/StartRun.js +156 -53
- package/src/components/StatsView.js +89 -37
- package/src/lib/__tests__/score.test.js +596 -0
- package/src/lib/cost.js +84 -21
- package/src/lib/demo.js +186 -0
- package/src/lib/install.js +92 -62
- package/src/lib/score.js +433 -136
- package/src/lib/store.js +11 -11
- package/.claude/settings.local.json +0 -36
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,193 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/lib/demo.js
|
|
13
|
+
var demo_exports = {};
|
|
14
|
+
__export(demo_exports, {
|
|
15
|
+
runDemo: () => runDemo
|
|
16
|
+
});
|
|
17
|
+
function hudLine({ quest, model, cost, budget, ctxPct, effort, fainted, floor }) {
|
|
18
|
+
const m = (model || "").toLowerCase();
|
|
19
|
+
let modelName, modelEmoji;
|
|
20
|
+
if (m.includes("haiku")) {
|
|
21
|
+
modelName = "Haiku";
|
|
22
|
+
modelEmoji = "\u{1F3F9}";
|
|
23
|
+
} else if (m.includes("sonnet")) {
|
|
24
|
+
modelName = "Sonnet";
|
|
25
|
+
modelEmoji = "\u2694\uFE0F";
|
|
26
|
+
} else if (m.includes("opus")) {
|
|
27
|
+
modelName = "Opus";
|
|
28
|
+
modelEmoji = "\u{1F9D9}";
|
|
29
|
+
} else {
|
|
30
|
+
modelName = "?";
|
|
31
|
+
modelEmoji = "?";
|
|
32
|
+
}
|
|
33
|
+
const labelParts = [`${modelEmoji} ${modelName}`];
|
|
34
|
+
if (effort) labelParts.push(effort.charAt(0).toUpperCase() + effort.slice(1));
|
|
35
|
+
const modelLabel = labelParts.join("\xB7");
|
|
36
|
+
let tierEmoji;
|
|
37
|
+
if (cost < 0.1) tierEmoji = "\u{1F48E}";
|
|
38
|
+
else if (cost < 0.3) tierEmoji = "\u{1F947}";
|
|
39
|
+
else if (cost < 1) tierEmoji = "\u{1F948}";
|
|
40
|
+
else if (cost < 3) tierEmoji = "\u{1F949}";
|
|
41
|
+
else tierEmoji = "\u{1F4B8}";
|
|
42
|
+
const sep = ` ${DIM}|${RESET} `;
|
|
43
|
+
let costStr, ratingStr;
|
|
44
|
+
if (budget) {
|
|
45
|
+
const pct = cost / budget * 100;
|
|
46
|
+
let rating, rc;
|
|
47
|
+
if (pct <= 25) {
|
|
48
|
+
rating = "LEGENDARY";
|
|
49
|
+
rc = M;
|
|
50
|
+
} else if (pct <= 50) {
|
|
51
|
+
rating = "EFFICIENT";
|
|
52
|
+
rc = C;
|
|
53
|
+
} else if (pct <= 75) {
|
|
54
|
+
rating = "SOLID";
|
|
55
|
+
rc = G;
|
|
56
|
+
} else if (pct <= 100) {
|
|
57
|
+
rating = "CLOSE CALL";
|
|
58
|
+
rc = Y;
|
|
59
|
+
} else {
|
|
60
|
+
rating = "BUSTED";
|
|
61
|
+
rc = R;
|
|
62
|
+
}
|
|
63
|
+
costStr = `${tierEmoji} $${cost.toFixed(4)}/$${budget.toFixed(2)} ${pct.toFixed(0)}%`;
|
|
64
|
+
ratingStr = `${rc}${rating}${RESET}`;
|
|
65
|
+
} else {
|
|
66
|
+
costStr = `${tierEmoji} $${cost.toFixed(4)}`;
|
|
67
|
+
ratingStr = null;
|
|
68
|
+
}
|
|
69
|
+
let ctxStr = null;
|
|
70
|
+
if (ctxPct != null) {
|
|
71
|
+
if (ctxPct >= 90) ctxStr = `${R}\u{1F4E6} ${ctxPct}%${RESET}`;
|
|
72
|
+
else if (ctxPct >= 75) ctxStr = `${Y}\u{1F392} ${ctxPct}%${RESET}`;
|
|
73
|
+
else if (ctxPct >= 50) ctxStr = `${G}\u{1FAB6} ${ctxPct}%${RESET}`;
|
|
74
|
+
}
|
|
75
|
+
const icon = fainted ? "\u{1F4A4}" : "\u26F3";
|
|
76
|
+
const prefix = `${BOLD}${C}${icon}${RESET}`;
|
|
77
|
+
const parts = [`${prefix} ${quest}`, costStr];
|
|
78
|
+
if (ratingStr) parts.push(ratingStr);
|
|
79
|
+
if (ctxStr) parts.push(ctxStr);
|
|
80
|
+
parts.push(`${C}${modelLabel}${RESET}`);
|
|
81
|
+
if (budget && floor) parts.push(`Floor ${floor}`);
|
|
82
|
+
return `${DIM} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${RESET}
|
|
83
|
+
${parts.join(sep)}
|
|
84
|
+
${DIM} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${RESET}`;
|
|
85
|
+
}
|
|
86
|
+
function runDemo() {
|
|
87
|
+
console.log("");
|
|
88
|
+
console.log(`${BOLD}${C}\u26F3 TokenGolf \u2014 HUD Demo${RESET}`);
|
|
89
|
+
console.log(`${DIM}Live statusline shown in every Claude Code session${RESET}`);
|
|
90
|
+
console.log("");
|
|
91
|
+
for (const { title, hud } of SCENARIOS) {
|
|
92
|
+
console.log(`${DIM}${title}${RESET}`);
|
|
93
|
+
console.log(hudLine(hud));
|
|
94
|
+
console.log("");
|
|
95
|
+
}
|
|
96
|
+
console.log(
|
|
97
|
+
`${DIM}Run ${RESET}tokengolf start${DIM} to begin a roguelike run, or just open Claude Code \u2014 flow mode tracks automatically.${RESET}`
|
|
98
|
+
);
|
|
99
|
+
console.log("");
|
|
100
|
+
}
|
|
101
|
+
var R, G, Y, M, C, DIM, BOLD, RESET, SCENARIOS;
|
|
102
|
+
var init_demo = __esm({
|
|
103
|
+
"src/lib/demo.js"() {
|
|
104
|
+
R = "\x1B[31m";
|
|
105
|
+
G = "\x1B[32m";
|
|
106
|
+
Y = "\x1B[33m";
|
|
107
|
+
M = "\x1B[35m";
|
|
108
|
+
C = "\x1B[36m";
|
|
109
|
+
DIM = "\x1B[2m";
|
|
110
|
+
BOLD = "\x1B[1m";
|
|
111
|
+
RESET = "\x1B[0m";
|
|
112
|
+
SCENARIOS = [
|
|
113
|
+
{
|
|
114
|
+
title: "Flow mode (passive \u2014 no quest, no budget)",
|
|
115
|
+
hud: { quest: "Flow", model: "claude-sonnet-4-6", cost: 34e-4 }
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
title: "Roguelike \xB7 Sonnet \xB7 EFFICIENT",
|
|
119
|
+
hud: {
|
|
120
|
+
quest: "add pagination to /users",
|
|
121
|
+
model: "claude-sonnet-4-6",
|
|
122
|
+
cost: 0.54,
|
|
123
|
+
budget: 1.5,
|
|
124
|
+
ctxPct: 34,
|
|
125
|
+
floor: "2/5"
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
title: "Roguelike \xB7 Sonnet\xB7High \xB7 LEGENDARY",
|
|
130
|
+
hud: {
|
|
131
|
+
quest: "implement SSO with SAML",
|
|
132
|
+
model: "claude-sonnet-4-6",
|
|
133
|
+
cost: 0.41,
|
|
134
|
+
budget: 2,
|
|
135
|
+
ctxPct: 29,
|
|
136
|
+
effort: "high",
|
|
137
|
+
floor: "1/5"
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
title: "Roguelike \xB7 Opus \xB7 LEGENDARY \xB7 \u{1FAB6} context",
|
|
142
|
+
hud: {
|
|
143
|
+
quest: "refactor auth middleware",
|
|
144
|
+
model: "claude-opus-4-6",
|
|
145
|
+
cost: 0.82,
|
|
146
|
+
budget: 4,
|
|
147
|
+
ctxPct: 52,
|
|
148
|
+
floor: "3/5"
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
title: "Roguelike \xB7 Haiku \xB7 CLOSE CALL \xB7 \u{1F392} context",
|
|
153
|
+
hud: {
|
|
154
|
+
quest: "fix N+1 query in dashboard",
|
|
155
|
+
model: "claude-haiku-4-5-20251001",
|
|
156
|
+
cost: 0.46,
|
|
157
|
+
budget: 0.5,
|
|
158
|
+
ctxPct: 78,
|
|
159
|
+
floor: "4/5"
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
title: "Roguelike \xB7 BUSTED \u2014 over budget",
|
|
164
|
+
hud: {
|
|
165
|
+
quest: "migrate postgres schema",
|
|
166
|
+
model: "claude-sonnet-4-6",
|
|
167
|
+
cost: 2.41,
|
|
168
|
+
budget: 2,
|
|
169
|
+
floor: "2/5"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
title: "Roguelike \xB7 Opus \xB7 EFFICIENT \xB7 \u{1F4E6} overencumbered",
|
|
174
|
+
hud: {
|
|
175
|
+
quest: "refactor entire API layer",
|
|
176
|
+
model: "claude-opus-4-6",
|
|
177
|
+
cost: 3.1,
|
|
178
|
+
budget: 10,
|
|
179
|
+
ctxPct: 91,
|
|
180
|
+
floor: "3/5"
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
title: "Fainted \u{1F4A4} \u2014 usage limit hit, run resumes next session",
|
|
185
|
+
hud: {
|
|
186
|
+
quest: "write test suite for payments",
|
|
187
|
+
model: "claude-sonnet-4-6",
|
|
188
|
+
cost: 1.22,
|
|
189
|
+
budget: 3,
|
|
190
|
+
ctxPct: 67,
|
|
191
|
+
fainted: true,
|
|
192
|
+
floor: "2/5"
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
12
199
|
// src/lib/install.js
|
|
13
200
|
var install_exports = {};
|
|
14
201
|
__export(install_exports, {
|
|
@@ -36,7 +223,7 @@ function installHooks() {
|
|
|
36
223
|
const existing2 = settings.hooks[event] || [];
|
|
37
224
|
const filtered = existing2.filter(
|
|
38
225
|
(h) => !h._tg && !h.hooks?.some(
|
|
39
|
-
(e) => e.command?.includes("tokengolf") || e.command?.includes("session-start.js") || e.command?.includes("session-stop.js") || e.command?.includes("session-end.js") || e.command?.includes("pre-compact.js") || e.command?.includes("post-tool-use.js") || e.command?.includes("user-prompt-submit.js")
|
|
226
|
+
(e) => e.command?.includes("tokengolf") || e.command?.includes("session-start.js") || e.command?.includes("session-stop.js") || e.command?.includes("session-end.js") || e.command?.includes("pre-compact.js") || e.command?.includes("post-tool-use.js") || e.command?.includes("post-tool-use-failure.js") || e.command?.includes("subagent-start.js") || e.command?.includes("stop.js") || e.command?.includes("user-prompt-submit.js")
|
|
40
227
|
)
|
|
41
228
|
);
|
|
42
229
|
settings.hooks[event] = [...filtered, { _tg: true, ...entry }];
|
|
@@ -93,6 +280,34 @@ function installHooks() {
|
|
|
93
280
|
}
|
|
94
281
|
]
|
|
95
282
|
});
|
|
283
|
+
upsertHook("PostToolUseFailure", {
|
|
284
|
+
matcher: "",
|
|
285
|
+
hooks: [
|
|
286
|
+
{
|
|
287
|
+
type: "command",
|
|
288
|
+
command: `node ${path4.join(HOOKS_DIR, "post-tool-use-failure.js")}`,
|
|
289
|
+
timeout: 5
|
|
290
|
+
}
|
|
291
|
+
]
|
|
292
|
+
});
|
|
293
|
+
upsertHook("SubagentStart", {
|
|
294
|
+
hooks: [
|
|
295
|
+
{
|
|
296
|
+
type: "command",
|
|
297
|
+
command: `node ${path4.join(HOOKS_DIR, "subagent-start.js")}`,
|
|
298
|
+
timeout: 5
|
|
299
|
+
}
|
|
300
|
+
]
|
|
301
|
+
});
|
|
302
|
+
upsertHook("Stop", {
|
|
303
|
+
hooks: [
|
|
304
|
+
{
|
|
305
|
+
type: "command",
|
|
306
|
+
command: `node ${path4.join(HOOKS_DIR, "stop.js")}`,
|
|
307
|
+
timeout: 5
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
});
|
|
96
311
|
try {
|
|
97
312
|
fs4.chmodSync(STATUSLINE_PATH, 493);
|
|
98
313
|
} catch {
|
|
@@ -116,9 +331,7 @@ function installHooks() {
|
|
|
116
331
|
command: WRAPPER_PATH,
|
|
117
332
|
padding: 1
|
|
118
333
|
};
|
|
119
|
-
console.log(
|
|
120
|
-
" \u2713 statusLine \u2192 wrapped your existing statusline + tokengolf HUD"
|
|
121
|
-
);
|
|
334
|
+
console.log(" \u2713 statusLine \u2192 wrapped your existing statusline + tokengolf HUD");
|
|
122
335
|
} else if (!alreadyOurs) {
|
|
123
336
|
settings.statusLine = {
|
|
124
337
|
type: "command",
|
|
@@ -134,9 +347,10 @@ function installHooks() {
|
|
|
134
347
|
console.log(" \u2713 PostToolUse \u2192 tracks tool calls + 80% budget warning");
|
|
135
348
|
console.log(" \u2713 UserPromptSubmit \u2192 counts prompts + 50% nudge");
|
|
136
349
|
console.log(" \u2713 SessionEnd \u2192 auto-displays scorecard on /exit");
|
|
137
|
-
console.log(
|
|
138
|
-
|
|
139
|
-
);
|
|
350
|
+
console.log(" \u2713 PreCompact \u2192 tracks compaction events for gear achievements");
|
|
351
|
+
console.log(" \u2713 PostToolUseFailure \u2192 tracks failed tool calls");
|
|
352
|
+
console.log(" \u2713 SubagentStart \u2192 tracks subagent spawns");
|
|
353
|
+
console.log(" \u2713 Stop \u2192 tracks turn count");
|
|
140
354
|
console.log("\n \u2705 Done! Start a run: tokengolf start\n");
|
|
141
355
|
}
|
|
142
356
|
var realEntry, HOOKS_DIR, STATUSLINE_PATH, WRAPPER_PATH, CLAUDE_DIR, CLAUDE_SETTINGS;
|
|
@@ -211,11 +425,13 @@ function getEffortLevel(effort) {
|
|
|
211
425
|
var MODEL_BUDGET_TIERS = {
|
|
212
426
|
haiku: { diamond: 0.15, gold: 0.4, silver: 1, bronze: 2.5 },
|
|
213
427
|
sonnet: { diamond: 0.5, gold: 1.5, silver: 4, bronze: 10 },
|
|
428
|
+
opusplan: { diamond: 1.5, gold: 4.5, silver: 12, bronze: 30 },
|
|
214
429
|
opus: { diamond: 2.5, gold: 7.5, silver: 20, bronze: 50 }
|
|
215
430
|
};
|
|
216
431
|
function getModelBudgets(model) {
|
|
217
432
|
const m = (model || "").toLowerCase();
|
|
218
433
|
if (m.includes("haiku")) return MODEL_BUDGET_TIERS.haiku;
|
|
434
|
+
if (m.includes("opusplan")) return MODEL_BUDGET_TIERS.opusplan;
|
|
219
435
|
if (m.includes("opus")) return MODEL_BUDGET_TIERS.opus;
|
|
220
436
|
return MODEL_BUDGET_TIERS.sonnet;
|
|
221
437
|
}
|
|
@@ -234,6 +450,13 @@ var MODEL_CLASSES = {
|
|
|
234
450
|
difficulty: "Normal",
|
|
235
451
|
color: "cyan"
|
|
236
452
|
},
|
|
453
|
+
opusplan: {
|
|
454
|
+
name: "Paladin",
|
|
455
|
+
label: "Paladin",
|
|
456
|
+
emoji: "\u269C\uFE0F",
|
|
457
|
+
difficulty: "Calculated",
|
|
458
|
+
color: "yellow"
|
|
459
|
+
},
|
|
237
460
|
opus: {
|
|
238
461
|
name: "Opus",
|
|
239
462
|
label: "Warlock",
|
|
@@ -253,9 +476,9 @@ function getTier(spent) {
|
|
|
253
476
|
return BUDGET_TIERS.find((t) => spent <= t.max) || BUDGET_TIERS[BUDGET_TIERS.length - 1];
|
|
254
477
|
}
|
|
255
478
|
function getModelClass(model = "") {
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
);
|
|
479
|
+
const m = model.toLowerCase();
|
|
480
|
+
if (m.includes("opusplan")) return MODEL_CLASSES.opusplan;
|
|
481
|
+
const key = Object.keys(MODEL_CLASSES).find((k) => m.includes(k));
|
|
259
482
|
return MODEL_CLASSES[key] || MODEL_CLASSES.sonnet;
|
|
260
483
|
}
|
|
261
484
|
function getEfficiencyRating(spent, budget) {
|
|
@@ -271,7 +494,7 @@ function getBudgetPct(spent, budget) {
|
|
|
271
494
|
}
|
|
272
495
|
function formatCost(amount = 0) {
|
|
273
496
|
if (amount === 0) return "$0.00";
|
|
274
|
-
if (amount < 0.01) return `$${
|
|
497
|
+
if (amount < 0.01) return `$${amount.toFixed(5)}`;
|
|
275
498
|
return `$${amount.toFixed(4)}`;
|
|
276
499
|
}
|
|
277
500
|
function formatElapsed(startedAt) {
|
|
@@ -284,6 +507,12 @@ function formatElapsed(startedAt) {
|
|
|
284
507
|
if (m > 0) return `${m}m ${s % 60}s`;
|
|
285
508
|
return `${s}s`;
|
|
286
509
|
}
|
|
510
|
+
function getOpusPct(modelBreakdown, totalSpent) {
|
|
511
|
+
if (!modelBreakdown || !totalSpent) return null;
|
|
512
|
+
const opusCost = Object.entries(modelBreakdown).filter(([m]) => m.toLowerCase().includes("opus")).reduce((sum, [, c]) => sum + c, 0);
|
|
513
|
+
if (opusCost === 0) return null;
|
|
514
|
+
return Math.round(opusCost / totalSpent * 100);
|
|
515
|
+
}
|
|
287
516
|
function getHaikuPct(modelBreakdown, totalSpent) {
|
|
288
517
|
if (!modelBreakdown || !totalSpent) return null;
|
|
289
518
|
const haikuCost = Object.entries(modelBreakdown).filter(([m]) => m.toLowerCase().includes("haiku")).reduce((sum, [, c]) => sum + c, 0);
|
|
@@ -295,13 +524,56 @@ function calculateAchievements(run) {
|
|
|
295
524
|
const won = run.status === "won";
|
|
296
525
|
const pct = run.budget ? run.spent / run.budget : null;
|
|
297
526
|
const mc = getModelClass(run.model);
|
|
527
|
+
const isPaladin = mc === MODEL_CLASSES.opusplan;
|
|
528
|
+
if ((run.modelSwitches ?? 0) >= 3)
|
|
529
|
+
achievements.push({
|
|
530
|
+
key: "indecisive",
|
|
531
|
+
label: `Indecisive \u2014 ${run.modelSwitches} model switches mid-session`,
|
|
532
|
+
emoji: "\u{1F3B2}"
|
|
533
|
+
});
|
|
298
534
|
if (run.thinkingInvocations > 0 && run.status === "died")
|
|
299
535
|
achievements.push({
|
|
300
536
|
key: "hubris",
|
|
301
537
|
label: "Hubris \u2014 Used ultrathink, busted anyway",
|
|
302
538
|
emoji: "\u{1F926}"
|
|
303
539
|
});
|
|
304
|
-
if (!won)
|
|
540
|
+
if (!won) {
|
|
541
|
+
if (run.budget && run.spent / run.budget >= 2)
|
|
542
|
+
achievements.push({ key: "blowout", label: "Blowout \u2014 Spent 2\xD7 budget", emoji: "\u{1F4A5}" });
|
|
543
|
+
else if (run.budget && run.spent / run.budget > 1 && run.spent / run.budget <= 1.1)
|
|
544
|
+
achievements.push({
|
|
545
|
+
key: "so_close",
|
|
546
|
+
label: "So Close \u2014 Died within 10% of budget",
|
|
547
|
+
emoji: "\u{1F62D}"
|
|
548
|
+
});
|
|
549
|
+
if ((run.totalToolCalls || 0) >= 30)
|
|
550
|
+
achievements.push({
|
|
551
|
+
key: "tool_happy",
|
|
552
|
+
label: `Tool Happy \u2014 Died with ${run.totalToolCalls} tool calls`,
|
|
553
|
+
emoji: "\u{1F528}"
|
|
554
|
+
});
|
|
555
|
+
if ((run.promptCount || 0) <= 2)
|
|
556
|
+
achievements.push({
|
|
557
|
+
key: "silent_death",
|
|
558
|
+
label: "Silent Death \u2014 Died with \u22642 prompts",
|
|
559
|
+
emoji: "\u{1FAA6}"
|
|
560
|
+
});
|
|
561
|
+
if ((run.failedToolCalls ?? 0) >= 5)
|
|
562
|
+
achievements.push({
|
|
563
|
+
key: "fumble",
|
|
564
|
+
label: `Fumble \u2014 Died with ${run.failedToolCalls} failed tool calls`,
|
|
565
|
+
emoji: "\u{1F921}"
|
|
566
|
+
});
|
|
567
|
+
if (run.budget && run.spent / run.budget >= 0.5) {
|
|
568
|
+
if ((run.promptCount || 0) >= 3 && run.spent / (run.promptCount || 1) >= 0.5)
|
|
569
|
+
achievements.push({
|
|
570
|
+
key: "expensive_taste",
|
|
571
|
+
label: "Expensive Taste \u2014 Over $0.50 per prompt",
|
|
572
|
+
emoji: "\u{1F377}"
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
return achievements;
|
|
576
|
+
}
|
|
305
577
|
if (mc === MODEL_CLASSES.haiku) {
|
|
306
578
|
achievements.push({
|
|
307
579
|
key: "gold_haiku",
|
|
@@ -320,6 +592,18 @@ function calculateAchievements(run) {
|
|
|
320
592
|
label: "Silver \u2014 Completed with Sonnet",
|
|
321
593
|
emoji: "\u{1F948}"
|
|
322
594
|
});
|
|
595
|
+
} else if (mc === MODEL_CLASSES.opusplan) {
|
|
596
|
+
achievements.push({
|
|
597
|
+
key: "paladin",
|
|
598
|
+
label: "Paladin \u2014 Completed a run as Paladin",
|
|
599
|
+
emoji: "\u269C\uFE0F"
|
|
600
|
+
});
|
|
601
|
+
if (pct !== null && pct <= 0.25)
|
|
602
|
+
achievements.push({
|
|
603
|
+
key: "grand_strategist",
|
|
604
|
+
label: "Grand Strategist \u2014 LEGENDARY efficiency as Paladin",
|
|
605
|
+
emoji: "\u265F\uFE0F"
|
|
606
|
+
});
|
|
323
607
|
} else if (mc === MODEL_CLASSES.opus) {
|
|
324
608
|
achievements.push({
|
|
325
609
|
key: "bronze_opus",
|
|
@@ -352,13 +636,13 @@ function calculateAchievements(run) {
|
|
|
352
636
|
achievements.push({
|
|
353
637
|
key: "speedrunner",
|
|
354
638
|
label: "Speedrunner \u2014 Low effort, completed under budget",
|
|
355
|
-
emoji: "\u{
|
|
639
|
+
emoji: "\u{1F3CE}\uFE0F"
|
|
356
640
|
});
|
|
357
641
|
if ((run.effort === "high" || run.effort === "max") && pct !== null && pct <= 0.25)
|
|
358
642
|
achievements.push({
|
|
359
643
|
key: "tryhard",
|
|
360
644
|
label: "Tryhard \u2014 High effort, LEGENDARY efficiency",
|
|
361
|
-
emoji: "\u{
|
|
645
|
+
emoji: "\u{1F3CB}\uFE0F"
|
|
362
646
|
});
|
|
363
647
|
if (run.effort === "max" && mc === MODEL_CLASSES.opus)
|
|
364
648
|
achievements.push({
|
|
@@ -372,7 +656,7 @@ function calculateAchievements(run) {
|
|
|
372
656
|
achievements.push({
|
|
373
657
|
key: "lightning",
|
|
374
658
|
label: "Lightning Run \u2014 Opus fast mode, completed under budget",
|
|
375
|
-
emoji: "\
|
|
659
|
+
emoji: "\u26C8\uFE0F"
|
|
376
660
|
});
|
|
377
661
|
if (pct !== null && pct <= 0.25)
|
|
378
662
|
achievements.push({
|
|
@@ -392,18 +676,16 @@ function calculateAchievements(run) {
|
|
|
392
676
|
achievements.push({
|
|
393
677
|
key: "no_rest",
|
|
394
678
|
label: "No Rest for the Wicked \u2014 Completed in one session",
|
|
395
|
-
emoji: "\
|
|
679
|
+
emoji: "\u{1F525}"
|
|
396
680
|
});
|
|
397
681
|
if (run.fainted)
|
|
398
682
|
achievements.push({
|
|
399
683
|
key: "came_back",
|
|
400
684
|
label: "Came Back \u2014 Fainted and finished anyway",
|
|
401
|
-
emoji: "\u{
|
|
685
|
+
emoji: "\u{1F9DF}"
|
|
402
686
|
});
|
|
403
687
|
const compactionEvents = run.compactionEvents || [];
|
|
404
|
-
const manualCompactions = compactionEvents.filter(
|
|
405
|
-
(e) => e.trigger === "manual"
|
|
406
|
-
);
|
|
688
|
+
const manualCompactions = compactionEvents.filter((e) => e.trigger === "manual");
|
|
407
689
|
const autoCompactions = compactionEvents.filter((e) => e.trigger === "auto");
|
|
408
690
|
if (autoCompactions.length > 0)
|
|
409
691
|
achievements.push({
|
|
@@ -412,9 +694,7 @@ function calculateAchievements(run) {
|
|
|
412
694
|
emoji: "\u{1F4E6}"
|
|
413
695
|
});
|
|
414
696
|
if (manualCompactions.length > 0) {
|
|
415
|
-
const minPct = Math.min(
|
|
416
|
-
...manualCompactions.map((e) => e.contextPct ?? 100)
|
|
417
|
-
);
|
|
697
|
+
const minPct = Math.min(...manualCompactions.map((e) => e.contextPct ?? 100));
|
|
418
698
|
if (minPct <= 30)
|
|
419
699
|
achievements.push({
|
|
420
700
|
key: "ghost_run",
|
|
@@ -445,7 +725,7 @@ function calculateAchievements(run) {
|
|
|
445
725
|
achievements.push({
|
|
446
726
|
key: "calculated_risk",
|
|
447
727
|
label: "Calculated Risk \u2014 Ultrathink + LEGENDARY efficiency",
|
|
448
|
-
emoji: "\u{
|
|
728
|
+
emoji: "\u{1F9EE}"
|
|
449
729
|
});
|
|
450
730
|
if (ti >= 3)
|
|
451
731
|
achievements.push({
|
|
@@ -460,6 +740,77 @@ function calculateAchievements(run) {
|
|
|
460
740
|
label: "Silent Run \u2014 No extended thinking, completed under budget",
|
|
461
741
|
emoji: "\u{1F92B}"
|
|
462
742
|
});
|
|
743
|
+
if (mc === MODEL_CLASSES.opusplan) {
|
|
744
|
+
const opusPct = getOpusPct(run.modelBreakdown, run.spent);
|
|
745
|
+
if (opusPct !== null) {
|
|
746
|
+
if (opusPct > 60)
|
|
747
|
+
achievements.push({
|
|
748
|
+
key: "architect",
|
|
749
|
+
label: `Architect \u2014 Opus handled ${opusPct}% of cost (heavy planner)`,
|
|
750
|
+
emoji: "\u{1F3DB}\uFE0F"
|
|
751
|
+
});
|
|
752
|
+
if (opusPct < 25)
|
|
753
|
+
achievements.push({
|
|
754
|
+
key: "blitz",
|
|
755
|
+
label: `Blitz \u2014 Opus handled only ${opusPct}% of cost (light plan, fast execution)`,
|
|
756
|
+
emoji: "\u{1F4A8}"
|
|
757
|
+
});
|
|
758
|
+
if (opusPct >= 40 && opusPct <= 60)
|
|
759
|
+
achievements.push({
|
|
760
|
+
key: "equilibrium",
|
|
761
|
+
label: `Equilibrium \u2014 Opus and Sonnet balanced at ${opusPct}% / ${100 - opusPct}%`,
|
|
762
|
+
emoji: "\u2696\uFE0F"
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
const switches = run.modelSwitches ?? 0;
|
|
767
|
+
const distinct = run.distinctModels ?? 0;
|
|
768
|
+
if (!isPaladin) {
|
|
769
|
+
if (distinct === 1)
|
|
770
|
+
achievements.push({
|
|
771
|
+
key: "purist",
|
|
772
|
+
label: "Purist \u2014 Single model family throughout",
|
|
773
|
+
emoji: "\u{1F537}"
|
|
774
|
+
});
|
|
775
|
+
if (distinct >= 2 && pct !== null && pct < 1)
|
|
776
|
+
achievements.push({
|
|
777
|
+
key: "chameleon",
|
|
778
|
+
label: `Chameleon \u2014 ${distinct} model families used, completed under budget`,
|
|
779
|
+
emoji: "\u{1F98E}"
|
|
780
|
+
});
|
|
781
|
+
if (switches === 1 && pct !== null && pct < 1)
|
|
782
|
+
achievements.push({
|
|
783
|
+
key: "tactical_switch",
|
|
784
|
+
label: "Tactical Switch \u2014 Exactly 1 model switch, completed under budget",
|
|
785
|
+
emoji: "\u{1F500}"
|
|
786
|
+
});
|
|
787
|
+
if (switches === 0 && distinct <= 1)
|
|
788
|
+
achievements.push({
|
|
789
|
+
key: "committed",
|
|
790
|
+
label: "Committed \u2014 No model switches, one model family",
|
|
791
|
+
emoji: "\u{1F512}"
|
|
792
|
+
});
|
|
793
|
+
if (run.modelBreakdown && run.spent > 0) {
|
|
794
|
+
const declared = (run.model || "").toLowerCase();
|
|
795
|
+
const isHaikuRun = declared.includes("haiku");
|
|
796
|
+
const isSonnetRun = declared.includes("sonnet") && !declared.includes("opus");
|
|
797
|
+
const opusPct2 = getOpusPct(run.modelBreakdown, run.spent) ?? 0;
|
|
798
|
+
const haikuPct2 = getHaikuPct(run.modelBreakdown, run.spent) ?? 0;
|
|
799
|
+
const nonHaikuPct = 100 - haikuPct2;
|
|
800
|
+
if (isHaikuRun && nonHaikuPct > 50)
|
|
801
|
+
achievements.push({
|
|
802
|
+
key: "class_defection",
|
|
803
|
+
label: `Class Defection \u2014 Declared Haiku but ${nonHaikuPct}% cost on heavier models`,
|
|
804
|
+
emoji: "\u26A0\uFE0F"
|
|
805
|
+
});
|
|
806
|
+
else if (isSonnetRun && opusPct2 > 40)
|
|
807
|
+
achievements.push({
|
|
808
|
+
key: "class_defection",
|
|
809
|
+
label: `Class Defection \u2014 Declared Sonnet but ${opusPct2}% cost on Opus`,
|
|
810
|
+
emoji: "\u26A0\uFE0F"
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
}
|
|
463
814
|
const haikuPct = getHaikuPct(run.modelBreakdown, run.spent);
|
|
464
815
|
if (haikuPct !== null) {
|
|
465
816
|
if (haikuPct >= 50)
|
|
@@ -475,6 +826,142 @@ function calculateAchievements(run) {
|
|
|
475
826
|
emoji: "\u{1F3B2}"
|
|
476
827
|
});
|
|
477
828
|
}
|
|
829
|
+
const promptCount = run.promptCount || 0;
|
|
830
|
+
const totalToolCalls = run.totalToolCalls || 0;
|
|
831
|
+
if (promptCount === 1)
|
|
832
|
+
achievements.push({
|
|
833
|
+
key: "one_shot",
|
|
834
|
+
label: "One Shot \u2014 Completed in a single prompt",
|
|
835
|
+
emoji: "\u{1F94A}"
|
|
836
|
+
});
|
|
837
|
+
if (promptCount >= 20)
|
|
838
|
+
achievements.push({
|
|
839
|
+
key: "conversationalist",
|
|
840
|
+
label: `Conversationalist \u2014 ${promptCount} prompts`,
|
|
841
|
+
emoji: "\u{1F4AC}"
|
|
842
|
+
});
|
|
843
|
+
if (promptCount <= 3 && totalToolCalls >= 10)
|
|
844
|
+
achievements.push({
|
|
845
|
+
key: "terse",
|
|
846
|
+
label: `Terse \u2014 ${promptCount} prompts, ${totalToolCalls} tool calls`,
|
|
847
|
+
emoji: "\u{1F910}"
|
|
848
|
+
});
|
|
849
|
+
if (promptCount >= 15 && totalToolCalls / promptCount < 1)
|
|
850
|
+
achievements.push({
|
|
851
|
+
key: "backseat_driver",
|
|
852
|
+
label: "Backseat Driver \u2014 Many prompts, few tool calls",
|
|
853
|
+
emoji: "\u{1FA91}"
|
|
854
|
+
});
|
|
855
|
+
if (promptCount >= 2 && totalToolCalls / promptCount >= 5)
|
|
856
|
+
achievements.push({
|
|
857
|
+
key: "high_leverage",
|
|
858
|
+
label: `High Leverage \u2014 ${(totalToolCalls / promptCount).toFixed(1)}\xD7 tools per prompt`,
|
|
859
|
+
emoji: "\u{1F3D7}\uFE0F"
|
|
860
|
+
});
|
|
861
|
+
const toolCalls = run.toolCalls || {};
|
|
862
|
+
const editCount = toolCalls["Edit"] || 0;
|
|
863
|
+
const writeCount = toolCalls["Write"] || 0;
|
|
864
|
+
const readCount = toolCalls["Read"] || 0;
|
|
865
|
+
const bashCount = toolCalls["Bash"] || 0;
|
|
866
|
+
const distinctTools = Object.keys(toolCalls).filter((k) => toolCalls[k] > 0).length;
|
|
867
|
+
if (editCount === 0 && writeCount === 0 && readCount >= 1)
|
|
868
|
+
achievements.push({ key: "read_only", label: "Read Only \u2014 No edits or writes", emoji: "\u{1F441}\uFE0F" });
|
|
869
|
+
if (editCount >= 10)
|
|
870
|
+
achievements.push({ key: "editor", label: `Editor \u2014 ${editCount} Edit calls`, emoji: "\u270F\uFE0F" });
|
|
871
|
+
if (bashCount >= 10 && totalToolCalls >= 1 && bashCount / totalToolCalls >= 0.5)
|
|
872
|
+
achievements.push({
|
|
873
|
+
key: "bash_warrior",
|
|
874
|
+
label: `Bash Warrior \u2014 ${bashCount} Bash calls (${Math.round(bashCount / totalToolCalls * 100)}% of tools)`,
|
|
875
|
+
emoji: "\u{1F41A}"
|
|
876
|
+
});
|
|
877
|
+
if (totalToolCalls >= 5 && readCount / totalToolCalls >= 0.6)
|
|
878
|
+
achievements.push({
|
|
879
|
+
key: "scout",
|
|
880
|
+
label: `Scout \u2014 ${Math.round(readCount / totalToolCalls * 100)}% Read calls`,
|
|
881
|
+
emoji: "\u{1F50D}"
|
|
882
|
+
});
|
|
883
|
+
if (editCount >= 1 && editCount <= 3 && pct !== null && pct < 1)
|
|
884
|
+
achievements.push({
|
|
885
|
+
key: "surgeon",
|
|
886
|
+
label: `Surgeon \u2014 Only ${editCount} Edit call${editCount > 1 ? "s" : ""}, under budget`,
|
|
887
|
+
emoji: "\u{1F52A}"
|
|
888
|
+
});
|
|
889
|
+
if (distinctTools >= 5)
|
|
890
|
+
achievements.push({
|
|
891
|
+
key: "toolbox",
|
|
892
|
+
label: `Toolbox \u2014 ${distinctTools} distinct tools used`,
|
|
893
|
+
emoji: "\u{1F9F0}"
|
|
894
|
+
});
|
|
895
|
+
if (promptCount >= 3) {
|
|
896
|
+
const costPerPrompt = run.spent / promptCount;
|
|
897
|
+
if (costPerPrompt < 0.01)
|
|
898
|
+
achievements.push({
|
|
899
|
+
key: "cheap_shots",
|
|
900
|
+
label: `Cheap Shots \u2014 $${costPerPrompt.toFixed(4)} per prompt`,
|
|
901
|
+
emoji: "\u{1F4B2}"
|
|
902
|
+
});
|
|
903
|
+
if (costPerPrompt >= 0.5)
|
|
904
|
+
achievements.push({
|
|
905
|
+
key: "expensive_taste",
|
|
906
|
+
label: `Expensive Taste \u2014 $${costPerPrompt.toFixed(2)} per prompt`,
|
|
907
|
+
emoji: "\u{1F377}"
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
if (run.startedAt && run.endedAt) {
|
|
911
|
+
const elapsedMs = new Date(run.endedAt).getTime() - new Date(run.startedAt).getTime();
|
|
912
|
+
const elapsedMin = elapsedMs / 6e4;
|
|
913
|
+
if (elapsedMin < 5)
|
|
914
|
+
achievements.push({
|
|
915
|
+
key: "speedrun",
|
|
916
|
+
label: `Speedrun \u2014 Completed in ${Math.round(elapsedMin * 60)}s`,
|
|
917
|
+
emoji: "\u23F1\uFE0F"
|
|
918
|
+
});
|
|
919
|
+
if (elapsedMin > 60 && elapsedMin <= 180)
|
|
920
|
+
achievements.push({
|
|
921
|
+
key: "marathon",
|
|
922
|
+
label: `Marathon \u2014 ${Math.round(elapsedMin)}m session`,
|
|
923
|
+
emoji: "\u{1F3C3}"
|
|
924
|
+
});
|
|
925
|
+
if (elapsedMin > 180)
|
|
926
|
+
achievements.push({
|
|
927
|
+
key: "endurance",
|
|
928
|
+
label: `Endurance \u2014 ${Math.round(elapsedMin / 60)}h session`,
|
|
929
|
+
emoji: "\u{1FAE0}"
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
const failedToolCalls = run.failedToolCalls ?? 0;
|
|
933
|
+
const subagentSpawns = run.subagentSpawns ?? 0;
|
|
934
|
+
const turnCount = run.turnCount ?? 0;
|
|
935
|
+
if (failedToolCalls === 0 && totalToolCalls >= 5)
|
|
936
|
+
achievements.push({ key: "clean_run", label: "Clean Run \u2014 No tool failures", emoji: "\u2705" });
|
|
937
|
+
if (failedToolCalls >= 10)
|
|
938
|
+
achievements.push({
|
|
939
|
+
key: "stubborn",
|
|
940
|
+
label: `Stubborn \u2014 ${failedToolCalls} failed tool calls, still won`,
|
|
941
|
+
emoji: "\u{1F402}"
|
|
942
|
+
});
|
|
943
|
+
if (subagentSpawns === 0)
|
|
944
|
+
achievements.push({ key: "lone_wolf", label: "Lone Wolf \u2014 No subagents spawned", emoji: "\u{1F43A}" });
|
|
945
|
+
if (subagentSpawns >= 5)
|
|
946
|
+
achievements.push({
|
|
947
|
+
key: "summoner",
|
|
948
|
+
label: `Summoner \u2014 ${subagentSpawns} subagents spawned`,
|
|
949
|
+
emoji: "\u{1F4E1}"
|
|
950
|
+
});
|
|
951
|
+
if (subagentSpawns >= 10 && pct !== null && pct < 0.5)
|
|
952
|
+
achievements.push({
|
|
953
|
+
key: "army",
|
|
954
|
+
label: `Army of One \u2014 ${subagentSpawns} subagents, EFFICIENT cost`,
|
|
955
|
+
emoji: "\u{1FA96}"
|
|
956
|
+
});
|
|
957
|
+
if (promptCount >= 2 && turnCount >= 1 && turnCount / promptCount >= 3)
|
|
958
|
+
achievements.push({
|
|
959
|
+
key: "agentic",
|
|
960
|
+
label: `Agentic \u2014 ${(turnCount / promptCount).toFixed(1)} turns per prompt`,
|
|
961
|
+
emoji: "\u{1F916}"
|
|
962
|
+
});
|
|
963
|
+
if (promptCount >= 3 && turnCount === promptCount)
|
|
964
|
+
achievements.push({ key: "obedient", label: "Obedient \u2014 One turn per prompt", emoji: "\u{1F415}" });
|
|
478
965
|
return achievements;
|
|
479
966
|
}
|
|
480
967
|
|
|
@@ -517,11 +1004,13 @@ function getStats() {
|
|
|
517
1004
|
const deaths = runs.filter((r) => r.status === "died");
|
|
518
1005
|
const avgSpend = wins.length ? wins.reduce((sum, r) => sum + (r.spent || 0), 0) / wins.length : 0;
|
|
519
1006
|
const bestRun = wins.length ? wins.reduce((best, r) => !best || r.spent < best.spent ? r : best, null) : null;
|
|
520
|
-
const allAchievements = runs.flatMap(
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
1007
|
+
const allAchievements = runs.flatMap(
|
|
1008
|
+
(r) => (r.achievements || []).map((a) => ({
|
|
1009
|
+
...a,
|
|
1010
|
+
quest: r.quest,
|
|
1011
|
+
earnedAt: r.endedAt
|
|
1012
|
+
}))
|
|
1013
|
+
);
|
|
525
1014
|
return {
|
|
526
1015
|
total: runs.length,
|
|
527
1016
|
wins: wins.length,
|
|
@@ -579,7 +1068,10 @@ function parseCostFromTranscript(transcriptPath) {
|
|
|
579
1068
|
}
|
|
580
1069
|
function findTranscriptsSince(projectDir, sinceMs) {
|
|
581
1070
|
try {
|
|
582
|
-
return fs3.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({
|
|
1071
|
+
return fs3.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({
|
|
1072
|
+
p: path3.join(projectDir, f),
|
|
1073
|
+
mtime: fs3.statSync(path3.join(projectDir, f)).mtimeMs
|
|
1074
|
+
})).filter(({ mtime }) => mtime >= sinceMs).map(({ p }) => p);
|
|
583
1075
|
} catch {
|
|
584
1076
|
return [];
|
|
585
1077
|
}
|
|
@@ -623,6 +1115,34 @@ function parseAllTranscripts(paths) {
|
|
|
623
1115
|
}
|
|
624
1116
|
return total > 0 ? { total, byModel } : null;
|
|
625
1117
|
}
|
|
1118
|
+
function modelFamily(model) {
|
|
1119
|
+
const m = (model || "").toLowerCase();
|
|
1120
|
+
if (m.includes("haiku")) return "haiku";
|
|
1121
|
+
if (m.includes("sonnet")) return "sonnet";
|
|
1122
|
+
if (m.includes("opus")) return "opus";
|
|
1123
|
+
return "unknown";
|
|
1124
|
+
}
|
|
1125
|
+
function parseModelSwitches(transcriptPath) {
|
|
1126
|
+
try {
|
|
1127
|
+
const lines = fs3.readFileSync(transcriptPath, "utf8").trim().split("\n");
|
|
1128
|
+
let lastFamily = null;
|
|
1129
|
+
let switches = 0;
|
|
1130
|
+
for (const line of lines) {
|
|
1131
|
+
try {
|
|
1132
|
+
const entry = JSON.parse(line);
|
|
1133
|
+
if (entry.type === "assistant" && entry.message?.model) {
|
|
1134
|
+
const family = modelFamily(entry.message.model);
|
|
1135
|
+
if (lastFamily !== null && family !== lastFamily) switches++;
|
|
1136
|
+
lastFamily = family;
|
|
1137
|
+
}
|
|
1138
|
+
} catch {
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return { switches };
|
|
1142
|
+
} catch {
|
|
1143
|
+
return { switches: 0 };
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
626
1146
|
function findTranscript(sessionId, projectDir) {
|
|
627
1147
|
if (sessionId) {
|
|
628
1148
|
try {
|
|
@@ -648,11 +1168,19 @@ function autoDetectCost(run) {
|
|
|
648
1168
|
if (spent === null) return null;
|
|
649
1169
|
const modelBreakdown = parsed?.byModel ?? run.modelBreakdown ?? null;
|
|
650
1170
|
const thinking = parseThinkingFromTranscripts(paths);
|
|
1171
|
+
const primaryPath = findTranscript(run.sessionId, projectDir);
|
|
1172
|
+
const { switches: modelSwitches } = primaryPath ? parseModelSwitches(primaryPath) : { switches: 0 };
|
|
1173
|
+
const families = new Set(
|
|
1174
|
+
Object.keys(parsed?.byModel ?? {}).map(modelFamily).filter((f) => f !== "unknown")
|
|
1175
|
+
);
|
|
1176
|
+
const distinctModels = families.size;
|
|
651
1177
|
return {
|
|
652
1178
|
spent,
|
|
653
1179
|
modelBreakdown,
|
|
654
1180
|
thinkingInvocations: thinking?.thinkingInvocations ?? 0,
|
|
655
|
-
thinkingTokens: thinking?.thinkingTokens ?? 0
|
|
1181
|
+
thinkingTokens: thinking?.thinkingTokens ?? 0,
|
|
1182
|
+
modelSwitches,
|
|
1183
|
+
distinctModels
|
|
656
1184
|
};
|
|
657
1185
|
}
|
|
658
1186
|
|
|
@@ -663,6 +1191,7 @@ import { TextInput, Select, ConfirmInput } from "@inkjs/ui";
|
|
|
663
1191
|
var MODEL_OPTIONS = [
|
|
664
1192
|
{ label: "\u2694\uFE0F Sonnet \u2014 Balanced. The default run. [Normal]", value: "claude-sonnet-4-6" },
|
|
665
1193
|
{ label: "\u{1F3F9} Haiku \u2014 Glass cannon. Hard mode. [Hard]", value: "claude-haiku-4-5-20251001" },
|
|
1194
|
+
{ label: "\u269C\uFE0F Paladin \u2014 Opus plans, Sonnet executes. [Calculated]", value: "opusplan" },
|
|
666
1195
|
{ label: "\u{1F9D9} Opus \u2014 Powerful but expensive. [Easy]", value: "claude-opus-4-6" }
|
|
667
1196
|
];
|
|
668
1197
|
var EFFORT_OPTIONS_BASE = [
|
|
@@ -674,11 +1203,14 @@ var EFFORT_OPTIONS_OPUS = [
|
|
|
674
1203
|
...EFFORT_OPTIONS_BASE,
|
|
675
1204
|
{ label: "\u{1F4A5} Max \u2014 Absolute max, no token constraints (Opus only)", value: "max" }
|
|
676
1205
|
];
|
|
677
|
-
var getEffortOptions = (model) => model.toLowerCase().includes("opus") ? EFFORT_OPTIONS_OPUS : EFFORT_OPTIONS_BASE;
|
|
1206
|
+
var getEffortOptions = (model) => model.toLowerCase().includes("opus") && !model.toLowerCase().includes("opusplan") ? EFFORT_OPTIONS_OPUS : EFFORT_OPTIONS_BASE;
|
|
678
1207
|
function getBudgetOptions(model) {
|
|
679
1208
|
const b = getModelBudgets(model);
|
|
680
1209
|
return [
|
|
681
|
-
{
|
|
1210
|
+
{
|
|
1211
|
+
label: `\u{1F48E} Diamond \u2014 $${b.diamond.toFixed(2)} surgical micro-task`,
|
|
1212
|
+
value: String(b.diamond)
|
|
1213
|
+
},
|
|
682
1214
|
{ label: `\u{1F947} Gold \u2014 $${b.gold.toFixed(2)} focused small task`, value: String(b.gold) },
|
|
683
1215
|
{ label: `\u{1F948} Silver \u2014 $${b.silver.toFixed(2)} medium task`, value: String(b.silver) },
|
|
684
1216
|
{ label: `\u{1F949} Bronze \u2014 $${b.bronze.toFixed(2)} heavy / complex`, value: String(b.bronze) },
|
|
@@ -694,56 +1226,125 @@ function StartRun() {
|
|
|
694
1226
|
const [budgetVal, setBudgetVal] = useState("");
|
|
695
1227
|
const budget = parseFloat(budgetVal) || 0;
|
|
696
1228
|
const mc = model ? getModelClass(model) : null;
|
|
697
|
-
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1, paddingX: 1, paddingY: 1 }, /* @__PURE__ */ React.createElement(Box, { gap: 2 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "yellow" }, "\u26F3 TokenGolf"), /* @__PURE__ */ React.createElement(Text, { color: "gray" }, "New Run")), /* @__PURE__ */ React.createElement(
|
|
698
|
-
|
|
699
|
-
setQuest(v.trim());
|
|
700
|
-
setStep("model");
|
|
701
|
-
}
|
|
702
|
-
} }) : /* @__PURE__ */ React.createElement(Text, { color: "white" }, quest)), step !== "quest" && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 0 }, /* @__PURE__ */ React.createElement(Box, { gap: 2 }, /* @__PURE__ */ React.createElement(Text, { color: step === "model" ? "cyan" : "gray" }, "\u{1F3AE} Class "), step !== "model" && /* @__PURE__ */ React.createElement(Text, { color: "white" }, mc?.emoji, " ", mc?.name, " [", mc?.difficulty, "]")), step === "model" && /* @__PURE__ */ React.createElement(Select, { options: MODEL_OPTIONS, onChange: (v) => {
|
|
703
|
-
setModel(v);
|
|
704
|
-
if (v.toLowerCase().includes("haiku")) {
|
|
705
|
-
setEffort(null);
|
|
706
|
-
setStep("budget");
|
|
707
|
-
} else setStep("effort");
|
|
708
|
-
} })), (step === "effort" || step === "budget" || step === "custom" || step === "confirm") && effort !== null && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 0 }, /* @__PURE__ */ React.createElement(Box, { gap: 2 }, /* @__PURE__ */ React.createElement(Text, { color: step === "effort" ? "cyan" : "gray" }, "\u26A1 Effort "), step !== "effort" && effort && /* @__PURE__ */ React.createElement(Text, { color: "white" }, getEffortLevel(effort)?.emoji, " ", getEffortLevel(effort)?.label)), step === "effort" && /* @__PURE__ */ React.createElement(Select, { options: getEffortOptions(model), onChange: (v) => {
|
|
709
|
-
setEffort(v);
|
|
710
|
-
setStep("budget");
|
|
711
|
-
} })), (step === "budget" || step === "custom" || step === "confirm") && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 0 }, /* @__PURE__ */ React.createElement(Box, { gap: 2 }, /* @__PURE__ */ React.createElement(Text, { color: step === "budget" || step === "custom" ? "cyan" : "gray" }, "\u{1F4B0} Budget "), step === "confirm" && /* @__PURE__ */ React.createElement(Text, { color: "green" }, "$", budget.toFixed(2))), step === "budget" && /* @__PURE__ */ React.createElement(Select, { options: getBudgetOptions(model), onChange: (v) => {
|
|
712
|
-
if (v === "custom") {
|
|
713
|
-
setStep("custom");
|
|
714
|
-
} else {
|
|
715
|
-
setBudgetVal(v);
|
|
716
|
-
setStep("confirm");
|
|
717
|
-
}
|
|
718
|
-
} }), step === "custom" && /* @__PURE__ */ React.createElement(TextInput, { placeholder: "Enter amount e.g. 0.50", onSubmit: (v) => {
|
|
719
|
-
const n = parseFloat(v);
|
|
720
|
-
if (!isNaN(n) && n > 0) {
|
|
721
|
-
setBudgetVal(String(n));
|
|
722
|
-
setStep("confirm");
|
|
723
|
-
}
|
|
724
|
-
} })), step === "confirm" && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1, marginTop: 1 }, /* @__PURE__ */ React.createElement(Box, { borderStyle: "round", borderColor: "yellow", paddingX: 1, paddingY: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "yellow" }, "Ready?"), /* @__PURE__ */ React.createElement(Text, { color: "gray" }, "Quest ", /* @__PURE__ */ React.createElement(Text, { color: "white" }, quest)), /* @__PURE__ */ React.createElement(Text, { color: "gray" }, "Model ", /* @__PURE__ */ React.createElement(Text, { color: "white" }, mc?.emoji, " ", mc?.name, " [", mc?.difficulty, "]")), effort && /* @__PURE__ */ React.createElement(Text, { color: "gray" }, "Effort ", /* @__PURE__ */ React.createElement(Text, { color: "white" }, getEffortLevel(effort)?.emoji, " ", getEffortLevel(effort)?.label)), /* @__PURE__ */ React.createElement(Text, { color: "gray" }, "Budget ", /* @__PURE__ */ React.createElement(Text, { color: "green" }, "$", budget.toFixed(2)))), /* @__PURE__ */ React.createElement(Box, { gap: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "gray" }, "Confirm? "), /* @__PURE__ */ React.createElement(
|
|
725
|
-
ConfirmInput,
|
|
1229
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1, paddingX: 1, paddingY: 1 }, /* @__PURE__ */ React.createElement(Box, { gap: 2 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "yellow" }, "\u26F3 TokenGolf"), /* @__PURE__ */ React.createElement(Text, { color: "gray" }, "New Run")), /* @__PURE__ */ React.createElement(
|
|
1230
|
+
Box,
|
|
726
1231
|
{
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1232
|
+
flexDirection: "column",
|
|
1233
|
+
gap: 1,
|
|
1234
|
+
borderStyle: "single",
|
|
1235
|
+
borderColor: "gray",
|
|
1236
|
+
paddingX: 1,
|
|
1237
|
+
paddingY: 1
|
|
1238
|
+
},
|
|
1239
|
+
/* @__PURE__ */ React.createElement(Box, { gap: 2, alignItems: "flex-start" }, /* @__PURE__ */ React.createElement(Text, { color: step === "quest" ? "cyan" : "gray" }, "\u{1F4CB} Quest "), step === "quest" ? /* @__PURE__ */ React.createElement(
|
|
1240
|
+
TextInput,
|
|
1241
|
+
{
|
|
1242
|
+
placeholder: "What are you shipping?",
|
|
1243
|
+
onSubmit: (v) => {
|
|
1244
|
+
if (v.trim()) {
|
|
1245
|
+
setQuest(v.trim());
|
|
1246
|
+
setStep("model");
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
) : /* @__PURE__ */ React.createElement(Text, { color: "white" }, quest)),
|
|
1251
|
+
step !== "quest" && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 0 }, /* @__PURE__ */ React.createElement(Box, { gap: 2 }, /* @__PURE__ */ React.createElement(Text, { color: step === "model" ? "cyan" : "gray" }, "\u{1F3AE} Class "), step !== "model" && /* @__PURE__ */ React.createElement(Text, { color: "white" }, mc?.emoji, " ", mc?.name, " [", mc?.difficulty, "]")), step === "model" && /* @__PURE__ */ React.createElement(
|
|
1252
|
+
Select,
|
|
1253
|
+
{
|
|
1254
|
+
options: MODEL_OPTIONS,
|
|
1255
|
+
onChange: (v) => {
|
|
1256
|
+
setModel(v);
|
|
1257
|
+
if (v.toLowerCase().includes("haiku")) {
|
|
1258
|
+
setEffort(null);
|
|
1259
|
+
setStep("budget");
|
|
1260
|
+
} else setStep("effort");
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
)),
|
|
1264
|
+
(step === "effort" || step === "budget" || step === "custom" || step === "confirm") && effort !== null && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 0 }, /* @__PURE__ */ React.createElement(Box, { gap: 2 }, /* @__PURE__ */ React.createElement(Text, { color: step === "effort" ? "cyan" : "gray" }, "\u26A1 Effort "), step !== "effort" && effort && /* @__PURE__ */ React.createElement(Text, { color: "white" }, getEffortLevel(effort)?.emoji, " ", getEffortLevel(effort)?.label)), step === "effort" && /* @__PURE__ */ React.createElement(
|
|
1265
|
+
Select,
|
|
1266
|
+
{
|
|
1267
|
+
options: getEffortOptions(model),
|
|
1268
|
+
onChange: (v) => {
|
|
1269
|
+
setEffort(v);
|
|
1270
|
+
setStep("budget");
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
)),
|
|
1274
|
+
(step === "budget" || step === "custom" || step === "confirm") && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 0 }, /* @__PURE__ */ React.createElement(Box, { gap: 2 }, /* @__PURE__ */ React.createElement(Text, { color: step === "budget" || step === "custom" ? "cyan" : "gray" }, "\u{1F4B0} Budget", " "), step === "confirm" && /* @__PURE__ */ React.createElement(Text, { color: "green" }, "$", budget.toFixed(2))), step === "budget" && /* @__PURE__ */ React.createElement(
|
|
1275
|
+
Select,
|
|
1276
|
+
{
|
|
1277
|
+
options: getBudgetOptions(model),
|
|
1278
|
+
onChange: (v) => {
|
|
1279
|
+
if (v === "custom") {
|
|
1280
|
+
setStep("custom");
|
|
1281
|
+
} else {
|
|
1282
|
+
setBudgetVal(v);
|
|
1283
|
+
setStep("confirm");
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
), step === "custom" && /* @__PURE__ */ React.createElement(
|
|
1288
|
+
TextInput,
|
|
1289
|
+
{
|
|
1290
|
+
placeholder: "Enter amount e.g. 0.50",
|
|
1291
|
+
onSubmit: (v) => {
|
|
1292
|
+
const n = parseFloat(v);
|
|
1293
|
+
if (!isNaN(n) && n > 0) {
|
|
1294
|
+
setBudgetVal(String(n));
|
|
1295
|
+
setStep("confirm");
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
)),
|
|
1300
|
+
step === "confirm" && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1, marginTop: 1 }, /* @__PURE__ */ React.createElement(
|
|
1301
|
+
Box,
|
|
1302
|
+
{
|
|
1303
|
+
borderStyle: "round",
|
|
1304
|
+
borderColor: "yellow",
|
|
1305
|
+
paddingX: 1,
|
|
1306
|
+
paddingY: 1,
|
|
1307
|
+
flexDirection: "column"
|
|
743
1308
|
},
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
1309
|
+
/* @__PURE__ */ React.createElement(Text, { bold: true, color: "yellow" }, "Ready?"),
|
|
1310
|
+
/* @__PURE__ */ React.createElement(Text, { color: "gray" }, "Quest ", /* @__PURE__ */ React.createElement(Text, { color: "white" }, quest)),
|
|
1311
|
+
/* @__PURE__ */ React.createElement(Text, { color: "gray" }, "Model", " ", /* @__PURE__ */ React.createElement(Text, { color: "white" }, mc?.emoji, " ", mc?.name, " [", mc?.difficulty, "]")),
|
|
1312
|
+
effort && /* @__PURE__ */ React.createElement(Text, { color: "gray" }, "Effort", " ", /* @__PURE__ */ React.createElement(Text, { color: "white" }, getEffortLevel(effort)?.emoji, " ", getEffortLevel(effort)?.label)),
|
|
1313
|
+
/* @__PURE__ */ React.createElement(Text, { color: "gray" }, "Budget ", /* @__PURE__ */ React.createElement(Text, { color: "green" }, "$", budget.toFixed(2)))
|
|
1314
|
+
), /* @__PURE__ */ React.createElement(Box, { gap: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "gray" }, "Confirm? "), /* @__PURE__ */ React.createElement(
|
|
1315
|
+
ConfirmInput,
|
|
1316
|
+
{
|
|
1317
|
+
onConfirm: () => {
|
|
1318
|
+
setCurrentRun({
|
|
1319
|
+
id: `run_${Date.now()}`,
|
|
1320
|
+
quest,
|
|
1321
|
+
model,
|
|
1322
|
+
budget,
|
|
1323
|
+
effort,
|
|
1324
|
+
spent: 0,
|
|
1325
|
+
status: "active",
|
|
1326
|
+
mode: "roguelike",
|
|
1327
|
+
floor: 1,
|
|
1328
|
+
totalFloors: FLOORS.length,
|
|
1329
|
+
promptCount: 0,
|
|
1330
|
+
totalToolCalls: 0,
|
|
1331
|
+
toolCalls: {},
|
|
1332
|
+
failedToolCalls: 0,
|
|
1333
|
+
subagentSpawns: 0,
|
|
1334
|
+
turnCount: 0,
|
|
1335
|
+
thinkingInvocations: 0,
|
|
1336
|
+
thinkingTokens: 0,
|
|
1337
|
+
fainted: false,
|
|
1338
|
+
sessionCount: 1,
|
|
1339
|
+
compactionEvents: [],
|
|
1340
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1341
|
+
});
|
|
1342
|
+
exit();
|
|
1343
|
+
},
|
|
1344
|
+
onCancel: () => setStep("quest")
|
|
1345
|
+
}
|
|
1346
|
+
)))
|
|
1347
|
+
), step !== "confirm" && /* @__PURE__ */ React.createElement(Text, { color: "gray", dimColor: true }, "Use \u2191\u2193 to navigate, Enter to select"), step === "confirm" && /* @__PURE__ */ React.createElement(Text, { color: "gray", dimColor: true }, "After confirming, work normally in Claude Code. Run `tokengolf win` or `tokengolf bust` when done."));
|
|
747
1348
|
}
|
|
748
1349
|
|
|
749
1350
|
// src/components/ActiveRun.js
|
|
@@ -765,16 +1366,43 @@ function ActiveRun({ run: initialRun }) {
|
|
|
765
1366
|
useInput((input) => {
|
|
766
1367
|
if (input === "q") exit();
|
|
767
1368
|
});
|
|
1369
|
+
const flowMode = !run.budget;
|
|
768
1370
|
const mc = getModelClass(run.model);
|
|
769
|
-
const pct = getBudgetPct(run.spent, run.budget);
|
|
770
|
-
const efficiency = getEfficiencyRating(run.spent, run.budget);
|
|
771
|
-
const barColor = pct >= 80 ? "red" : pct >= 50 ? "yellow" : "green";
|
|
772
|
-
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", gap: 1, paddingX: 1, paddingY: 1 }, /* @__PURE__ */ React2.createElement(Box2, { gap: 2 }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "yellow" }, "\u26F3 TokenGolf"), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "Active Run"), /* @__PURE__ */ React2.createElement(Text2, { color: "gray", dimColor: true }, formatElapsed(run.startedAt))), /* @__PURE__ */ React2.createElement(
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
1371
|
+
const pct = flowMode ? null : getBudgetPct(run.spent, run.budget);
|
|
1372
|
+
const efficiency = flowMode ? null : getEfficiencyRating(run.spent, run.budget);
|
|
1373
|
+
const barColor = !pct ? "green" : pct >= 80 ? "red" : pct >= 50 ? "yellow" : "green";
|
|
1374
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", gap: 1, paddingX: 1, paddingY: 1 }, /* @__PURE__ */ React2.createElement(Box2, { gap: 2 }, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "yellow" }, "\u26F3 TokenGolf"), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "Active Run"), /* @__PURE__ */ React2.createElement(Text2, { color: "gray", dimColor: true }, formatElapsed(run.startedAt))), /* @__PURE__ */ React2.createElement(
|
|
1375
|
+
Box2,
|
|
1376
|
+
{
|
|
1377
|
+
borderStyle: "round",
|
|
1378
|
+
borderColor: "yellow",
|
|
1379
|
+
paddingX: 1,
|
|
1380
|
+
paddingY: 1,
|
|
1381
|
+
flexDirection: "column",
|
|
1382
|
+
gap: 1
|
|
1383
|
+
},
|
|
1384
|
+
/* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, run.quest),
|
|
1385
|
+
/* @__PURE__ */ React2.createElement(Box2, { gap: 3 }, /* @__PURE__ */ React2.createElement(Text2, null, mc.emoji, " ", /* @__PURE__ */ React2.createElement(Text2, { color: "cyan" }, mc.name)), flowMode ? /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "Flow Mode") : /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "Budget ", /* @__PURE__ */ React2.createElement(Text2, { color: "green" }, "$", run.budget.toFixed(2))), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "Spent ", /* @__PURE__ */ React2.createElement(Text2, { color: barColor }, formatCost(run.spent))), !flowMode && /* @__PURE__ */ React2.createElement(Text2, { color: efficiency.color }, efficiency.emoji, " ", efficiency.label)),
|
|
1386
|
+
!flowMode && /* @__PURE__ */ React2.createElement(Box2, { gap: 1, alignItems: "center" }, /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "\u{1F4B0} "), /* @__PURE__ */ React2.createElement(Box2, { width: 24 }, /* @__PURE__ */ React2.createElement(ProgressBar, { value: Math.min(pct, 100) })), /* @__PURE__ */ React2.createElement(Text2, { color: barColor }, " ", pct, "%")),
|
|
1387
|
+
/* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", gap: 0, marginTop: 1 }, FLOORS.map((floor, i) => {
|
|
1388
|
+
const n = i + 1;
|
|
1389
|
+
const done = n < run.floor;
|
|
1390
|
+
const active = n === run.floor;
|
|
1391
|
+
return /* @__PURE__ */ React2.createElement(Box2, { key: i, gap: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: done ? "green" : active ? "yellow" : "gray" }, done ? "\u2713" : active ? "\u25B6" : "\u25CB"), /* @__PURE__ */ React2.createElement(
|
|
1392
|
+
Text2,
|
|
1393
|
+
{
|
|
1394
|
+
color: done ? "green" : active ? "white" : "gray",
|
|
1395
|
+
dimColor: !done && !active
|
|
1396
|
+
},
|
|
1397
|
+
"Floor ",
|
|
1398
|
+
n,
|
|
1399
|
+
": ",
|
|
1400
|
+
floor
|
|
1401
|
+
));
|
|
1402
|
+
})),
|
|
1403
|
+
/* @__PURE__ */ React2.createElement(Box2, { gap: 3, marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "Prompts ", /* @__PURE__ */ React2.createElement(Text2, { color: "white" }, run.promptCount || 0)), /* @__PURE__ */ React2.createElement(Text2, { color: "gray" }, "Tools ", /* @__PURE__ */ React2.createElement(Text2, { color: "white" }, run.totalToolCalls || 0))),
|
|
1404
|
+
pct >= 80 && pct < 100 && /* @__PURE__ */ React2.createElement(Box2, { borderStyle: "single", borderColor: "red", paddingX: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: "red", bold: true }, "\u26A0\uFE0F BUDGET WARNING \u2014 ", formatCost(run.budget - run.spent), " left"))
|
|
1405
|
+
), /* @__PURE__ */ React2.createElement(Text2, { color: "gray", dimColor: true }, "tokengolf win [--spent 0.18] \xB7 tokengolf bust \xB7 q to close"));
|
|
778
1406
|
}
|
|
779
1407
|
|
|
780
1408
|
// src/components/ScoreCard.js
|
|
@@ -796,14 +1424,46 @@ function ScoreCard({ run }) {
|
|
|
796
1424
|
const efficiency = flowMode ? null : getEfficiencyRating(run.spent, run.budget);
|
|
797
1425
|
const pct = flowMode ? null : getBudgetPct(run.spent, run.budget);
|
|
798
1426
|
const haikuPct = getHaikuPct(run.modelBreakdown, run.spent);
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
1427
|
+
const opusPct = getOpusPct(run.modelBreakdown, run.spent);
|
|
1428
|
+
return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", paddingX: 1, paddingY: 1, gap: 1 }, /* @__PURE__ */ React3.createElement(
|
|
1429
|
+
Box3,
|
|
1430
|
+
{
|
|
1431
|
+
borderStyle: "double",
|
|
1432
|
+
borderColor: won ? "yellow" : "red",
|
|
1433
|
+
paddingX: 2,
|
|
1434
|
+
paddingY: 1,
|
|
1435
|
+
flexDirection: "column",
|
|
1436
|
+
gap: 1
|
|
1437
|
+
},
|
|
1438
|
+
/* @__PURE__ */ React3.createElement(Text3, { bold: true, color: won ? "yellow" : "red" }, won ? "\u{1F3C6} SESSION COMPLETE" : "\u{1F480} BUDGET BUSTED"),
|
|
1439
|
+
/* @__PURE__ */ React3.createElement(Text3, { color: "white", bold: true }, run.quest ?? /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, "Flow Mode")),
|
|
1440
|
+
/* @__PURE__ */ React3.createElement(Box3, { gap: 4, flexWrap: "wrap", marginTop: 1 }, /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "SPENT"), /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: won ? "green" : "red" }, formatCost(run.spent))), !flowMode && /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "BUDGET"), /* @__PURE__ */ React3.createElement(Text3, { color: "white" }, "$", run.budget.toFixed(2))), /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "USED"), /* @__PURE__ */ React3.createElement(Text3, { color: pct > 100 ? "red" : pct > 80 ? "yellow" : "green" }, pct, "%"))), /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "MODEL"), /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, mc.emoji, " ", mc.name, [
|
|
1441
|
+
run.effort && run.effort !== "medium" ? getEffortLevel(run.effort)?.label : null,
|
|
1442
|
+
run.fastMode ? "Fast" : null
|
|
1443
|
+
].filter(Boolean).map((s) => `\xB7${s}`).join(""))), run.effort && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "EFFORT"), /* @__PURE__ */ React3.createElement(Text3, { color: getEffortLevel(run.effort)?.color }, getEffortLevel(run.effort)?.emoji, " ", getEffortLevel(run.effort)?.label)), run.fastMode && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "MODE"), /* @__PURE__ */ React3.createElement(Text3, { color: "yellow" }, "\u21AF Fast")), /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "TIER"), /* @__PURE__ */ React3.createElement(Text3, { color: tier.color }, tier.emoji, " ", tier.label))),
|
|
1444
|
+
efficiency && /* @__PURE__ */ React3.createElement(Box3, { gap: 2 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: efficiency.color }, efficiency.emoji, " ", efficiency.label)),
|
|
1445
|
+
run.achievements?.length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", gap: 0, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "Achievements unlocked:"), run.achievements.map((a, i) => /* @__PURE__ */ React3.createElement(Text3, { key: i, color: "yellow" }, " ", a.emoji, " ", a.label))),
|
|
1446
|
+
run.thinkingInvocations > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", gap: 0, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Box3, { gap: 3, alignItems: "center" }, /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "Extended thinking:"), /* @__PURE__ */ React3.createElement(Text3, { color: "magenta" }, "\u{1F52E} ", run.thinkingInvocations, "\xD7 invoked"))),
|
|
1447
|
+
run.modelBreakdown && Object.keys(run.modelBreakdown).length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", gap: 0, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Box3, { gap: 2, alignItems: "center" }, /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "Model usage:"), haikuPct !== null && /* @__PURE__ */ React3.createElement(Text3, { color: haikuPct >= 75 ? "magenta" : haikuPct >= 50 ? "cyan" : "yellow" }, "\u{1F3F9} ", haikuPct, "% Haiku"), mc === MODEL_CLASSES.opusplan && opusPct !== null && /* @__PURE__ */ React3.createElement(Text3, { color: "yellow" }, "\u269C\uFE0F ", opusPct, "% Opus (planning)")), /* @__PURE__ */ React3.createElement(Box3, { gap: 3, flexWrap: "wrap" }, Object.entries(run.modelBreakdown).map(([model, cost]) => {
|
|
1448
|
+
const m = model.toLowerCase();
|
|
1449
|
+
const short = m.includes("haiku") ? "Haiku" : m.includes("sonnet") ? "Sonnet" : m.includes("opusplan") || m.includes("paladin") ? "Paladin" : "Opus";
|
|
1450
|
+
const pctOfTotal = Math.round(cost / run.spent * 100);
|
|
1451
|
+
return /* @__PURE__ */ React3.createElement(Text3, { key: model, color: "gray" }, short, " ", /* @__PURE__ */ React3.createElement(Text3, { color: "white" }, pctOfTotal, "%"), " ", /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, formatCost(cost)));
|
|
1452
|
+
}))),
|
|
1453
|
+
run.toolCalls && Object.keys(run.toolCalls).length > 0 && /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", gap: 0, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "Tool calls:"), /* @__PURE__ */ React3.createElement(Box3, { gap: 2, flexWrap: "wrap" }, Object.entries(run.toolCalls).map(([tool, count]) => /* @__PURE__ */ React3.createElement(Text3, { key: tool, color: "gray" }, /* @__PURE__ */ React3.createElement(Text3, { color: "white" }, tool), " \xD7", count)))),
|
|
1454
|
+
!won && run.budget && /* @__PURE__ */ React3.createElement(
|
|
1455
|
+
Box3,
|
|
1456
|
+
{
|
|
1457
|
+
borderStyle: "single",
|
|
1458
|
+
borderColor: "red",
|
|
1459
|
+
paddingX: 1,
|
|
1460
|
+
marginTop: 1,
|
|
1461
|
+
flexDirection: "column"
|
|
1462
|
+
},
|
|
1463
|
+
/* @__PURE__ */ React3.createElement(Text3, { color: "red", bold: true }, "Cause of death: Budget exceeded by ", formatCost(run.spent - run.budget)),
|
|
1464
|
+
/* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "Tip: Use Read with line ranges instead of full file reads.")
|
|
1465
|
+
)
|
|
1466
|
+
), /* @__PURE__ */ React3.createElement(Box3, { gap: 2 }, /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "tokengolf start \u2014 run again"), /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "\xB7"), /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "tokengolf stats \u2014 career stats"), /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "\xB7"), /* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "q to exit")));
|
|
807
1467
|
}
|
|
808
1468
|
|
|
809
1469
|
// src/components/StatsView.js
|
|
@@ -820,13 +1480,24 @@ function StatsView({ stats }) {
|
|
|
820
1480
|
return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", paddingX: 1, paddingY: 1, gap: 1 }, /* @__PURE__ */ React4.createElement(Box4, { gap: 2 }, /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "yellow" }, "\u26F3 TokenGolf"), /* @__PURE__ */ React4.createElement(Text4, { color: "gray" }, "Career Stats")), /* @__PURE__ */ React4.createElement(Box4, { borderStyle: "single", borderColor: "gray", paddingX: 1, paddingY: 1, gap: 4 }, /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Text4, { color: "gray", dimColor: true }, "RUNS"), /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "white" }, stats.total)), /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Text4, { color: "gray", dimColor: true }, "WINS"), /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "green" }, stats.wins)), /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Text4, { color: "gray", dimColor: true }, "DEATHS"), /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "red" }, stats.deaths)), /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Text4, { color: "gray", dimColor: true }, "WIN RATE"), /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: stats.winRate >= 70 ? "green" : stats.winRate >= 40 ? "yellow" : "red" }, stats.winRate, "%")), /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Text4, { color: "gray", dimColor: true }, "AVG SPEND"), /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "cyan" }, formatCost(stats.avgSpend)))), stats.bestRun && (() => {
|
|
821
1481
|
const bestTier = getTier(stats.bestRun.spent);
|
|
822
1482
|
const bestMc = getModelClass(stats.bestRun.model);
|
|
823
|
-
return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", gap: 0 }, /* @__PURE__ */ React4.createElement(Text4, { color: "yellow" }, "\u{1F3C6} Personal Best"), /* @__PURE__ */ React4.createElement(
|
|
1483
|
+
return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", gap: 0 }, /* @__PURE__ */ React4.createElement(Text4, { color: "yellow" }, "\u{1F3C6} Personal Best"), /* @__PURE__ */ React4.createElement(
|
|
1484
|
+
Box4,
|
|
1485
|
+
{
|
|
1486
|
+
borderStyle: "round",
|
|
1487
|
+
borderColor: "yellow",
|
|
1488
|
+
paddingX: 1,
|
|
1489
|
+
paddingY: 1,
|
|
1490
|
+
flexDirection: "column"
|
|
1491
|
+
},
|
|
1492
|
+
/* @__PURE__ */ React4.createElement(Text4, { color: "white" }, stats.bestRun.quest),
|
|
1493
|
+
/* @__PURE__ */ React4.createElement(Box4, { gap: 3, marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { color: "green" }, formatCost(stats.bestRun.spent)), stats.bestRun.budget ? /* @__PURE__ */ React4.createElement(Text4, { color: "gray" }, "of $", stats.bestRun.budget.toFixed(2)) : null, /* @__PURE__ */ React4.createElement(Text4, null, bestMc.emoji), /* @__PURE__ */ React4.createElement(Text4, { color: bestTier.color }, bestTier.emoji, " ", bestTier.label))
|
|
1494
|
+
));
|
|
824
1495
|
})(), /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", gap: 0 }, /* @__PURE__ */ React4.createElement(Text4, { color: "gray", dimColor: true }, "Recent runs:"), stats.recentRuns.slice(0, 8).map((run, i) => {
|
|
825
1496
|
const won = run.status === "won";
|
|
826
1497
|
const tier = getTier(run.spent);
|
|
827
1498
|
const mc = getModelClass(run.model);
|
|
828
|
-
const pct = getBudgetPct(run.spent, run.budget);
|
|
829
|
-
return /* @__PURE__ */ React4.createElement(Box4, { key: i, gap: 2 }, /* @__PURE__ */ React4.createElement(Text4, { color: won ? "green" : "red" }, won ? "\u2713" : "\u2717"), /* @__PURE__ */ React4.createElement(Text4, { color: "white" }, (run.quest || "").slice(0, 34).padEnd(34)), /* @__PURE__ */ React4.createElement(Text4, { color: won ? "green" : "red" }, formatCost(run.spent)), /* @__PURE__ */ React4.createElement(Text4, { color: "gray" }, "/", formatCost(run.budget)), /* @__PURE__ */ React4.createElement(Text4, null, mc.emoji), /* @__PURE__ */ React4.createElement(Text4, { color: tier.color }, tier.emoji), /* @__PURE__ */ React4.createElement(Text4, { color: "gray", dimColor: true }, pct, "%"));
|
|
1499
|
+
const pct = run.budget ? getBudgetPct(run.spent, run.budget) : null;
|
|
1500
|
+
return /* @__PURE__ */ React4.createElement(Box4, { key: i, gap: 2 }, /* @__PURE__ */ React4.createElement(Text4, { color: won ? "green" : "red" }, won ? "\u2713" : "\u2717"), /* @__PURE__ */ React4.createElement(Text4, { color: "white" }, (run.quest || "Flow").slice(0, 34).padEnd(34)), /* @__PURE__ */ React4.createElement(Text4, { color: won ? "green" : "red" }, formatCost(run.spent)), run.budget ? /* @__PURE__ */ React4.createElement(Text4, { color: "gray" }, "/", formatCost(run.budget)) : null, /* @__PURE__ */ React4.createElement(Text4, null, mc.emoji), /* @__PURE__ */ React4.createElement(Text4, { color: tier.color }, tier.emoji), pct !== null && /* @__PURE__ */ React4.createElement(Text4, { color: "gray", dimColor: true }, pct, "%"));
|
|
830
1501
|
})), stats.achievements.length > 0 && /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", gap: 0 }, /* @__PURE__ */ React4.createElement(Text4, { color: "gray", dimColor: true }, "Recent achievements:"), /* @__PURE__ */ React4.createElement(Box4, { flexWrap: "wrap", gap: 1 }, stats.achievements.slice(0, 12).map((a, i) => /* @__PURE__ */ React4.createElement(Box4, { key: i, borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React4.createElement(Text4, null, a.emoji, " ", /* @__PURE__ */ React4.createElement(Text4, { color: "gray", dimColor: true }, a.label)))))), /* @__PURE__ */ React4.createElement(Text4, { color: "gray", dimColor: true }, "q to exit"));
|
|
831
1502
|
}
|
|
832
1503
|
|
|
@@ -851,7 +1522,13 @@ program.command("win").description("Mark current run as complete (won)").option(
|
|
|
851
1522
|
}
|
|
852
1523
|
const detected = opts.spent ? null : autoDetectCost(run);
|
|
853
1524
|
const spent = opts.spent ? parseFloat(opts.spent) : detected?.spent ?? run.spent;
|
|
854
|
-
const completed = {
|
|
1525
|
+
const completed = {
|
|
1526
|
+
...run,
|
|
1527
|
+
spent,
|
|
1528
|
+
status: "won",
|
|
1529
|
+
modelBreakdown: detected?.modelBreakdown ?? run.modelBreakdown ?? null,
|
|
1530
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1531
|
+
};
|
|
855
1532
|
const saved = saveRun(completed);
|
|
856
1533
|
clearCurrentRun();
|
|
857
1534
|
render(React5.createElement(ScoreCard, { run: saved }));
|
|
@@ -864,7 +1541,13 @@ program.command("bust").description("Mark current run as budget busted (died)").
|
|
|
864
1541
|
}
|
|
865
1542
|
const detected = opts.spent ? null : autoDetectCost(run);
|
|
866
1543
|
const spent = opts.spent ? parseFloat(opts.spent) : detected?.spent ?? run.budget + 0.01;
|
|
867
|
-
const died = {
|
|
1544
|
+
const died = {
|
|
1545
|
+
...run,
|
|
1546
|
+
spent,
|
|
1547
|
+
status: "died",
|
|
1548
|
+
modelBreakdown: detected?.modelBreakdown ?? run.modelBreakdown ?? null,
|
|
1549
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1550
|
+
};
|
|
868
1551
|
const saved = saveRun(died);
|
|
869
1552
|
clearCurrentRun();
|
|
870
1553
|
render(React5.createElement(ScoreCard, { run: saved }));
|
|
@@ -890,6 +1573,10 @@ program.command("scorecard").description("Show the last run scorecard").action((
|
|
|
890
1573
|
program.command("stats").description("Show career stats dashboard").action(() => {
|
|
891
1574
|
render(React5.createElement(StatsView, { stats: getStats() }));
|
|
892
1575
|
});
|
|
1576
|
+
program.command("demo").description("Show HUD examples for all game states (great for screenshots)").action(async () => {
|
|
1577
|
+
const { runDemo: runDemo2 } = await Promise.resolve().then(() => (init_demo(), demo_exports));
|
|
1578
|
+
runDemo2();
|
|
1579
|
+
});
|
|
893
1580
|
program.command("install").description("Install Claude Code hooks into ~/.claude/settings.json").action(async () => {
|
|
894
1581
|
const { installHooks: installHooks2 } = await Promise.resolve().then(() => (init_install(), install_exports));
|
|
895
1582
|
installHooks2();
|