tokengolf 0.4.1 → 0.4.2
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 +1360 -643
- package/docs/index.html +844 -375
- package/package.json +1 -1
- package/scripts/demo-to-html.js +104 -0
- package/src/cli.js +45 -5
- package/src/lib/demo-active.js +56 -0
- package/src/lib/demo-fixtures.js +496 -0
- package/src/lib/demo-hud.js +3 -0
- package/src/lib/demo-render.js +16 -0
- package/src/lib/demo-scorecard.js +56 -0
- package/src/lib/demo-stats.js +56 -0
package/dist/cli.js
CHANGED
|
@@ -9,425 +9,46 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// src/lib/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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}`;
|
|
12
|
+
// src/lib/state.js
|
|
13
|
+
import fs from "fs";
|
|
14
|
+
import path from "path";
|
|
15
|
+
import os from "os";
|
|
16
|
+
function ensureDir() {
|
|
17
|
+
if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
85
18
|
}
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
for (const { title, hud } of SCENARIOS) {
|
|
92
|
-
console.log(`${DIM}${title}${RESET}`);
|
|
93
|
-
console.log(hudLine(hud));
|
|
94
|
-
console.log("");
|
|
19
|
+
function getCurrentRun() {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(fs.readFileSync(STATE_FILE, "utf8"));
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
95
24
|
}
|
|
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
25
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
];
|
|
26
|
+
function setCurrentRun(run) {
|
|
27
|
+
ensureDir();
|
|
28
|
+
fs.writeFileSync(STATE_FILE, JSON.stringify(run, null, 2));
|
|
29
|
+
}
|
|
30
|
+
function updateCurrentRun(updates) {
|
|
31
|
+
const run = getCurrentRun();
|
|
32
|
+
if (!run) return null;
|
|
33
|
+
const updated = { ...run, ...updates };
|
|
34
|
+
setCurrentRun(updated);
|
|
35
|
+
return updated;
|
|
36
|
+
}
|
|
37
|
+
function clearCurrentRun() {
|
|
38
|
+
if (fs.existsSync(STATE_FILE)) fs.unlinkSync(STATE_FILE);
|
|
39
|
+
}
|
|
40
|
+
var STATE_DIR, STATE_FILE;
|
|
41
|
+
var init_state = __esm({
|
|
42
|
+
"src/lib/state.js"() {
|
|
43
|
+
STATE_DIR = path.join(os.homedir(), ".tokengolf");
|
|
44
|
+
STATE_FILE = path.join(STATE_DIR, "current-run.json");
|
|
196
45
|
}
|
|
197
46
|
});
|
|
198
47
|
|
|
199
|
-
// src/lib/install.js
|
|
200
|
-
var install_exports = {};
|
|
201
|
-
__export(install_exports, {
|
|
202
|
-
installHooks: () => installHooks
|
|
203
|
-
});
|
|
204
|
-
import fs4 from "fs";
|
|
205
|
-
import path4 from "path";
|
|
206
|
-
import os3 from "os";
|
|
207
|
-
function installHooks() {
|
|
208
|
-
console.log("\n\u26F3 TokenGolf \u2014 Installing Claude Code hooks\n");
|
|
209
|
-
let settings = {};
|
|
210
|
-
if (fs4.existsSync(CLAUDE_SETTINGS)) {
|
|
211
|
-
try {
|
|
212
|
-
settings = JSON.parse(fs4.readFileSync(CLAUDE_SETTINGS, "utf8"));
|
|
213
|
-
console.log(" \u2713 Found ~/.claude/settings.json");
|
|
214
|
-
} catch {
|
|
215
|
-
console.log(" \u26A0\uFE0F Could not parse settings.json \u2014 starting fresh");
|
|
216
|
-
}
|
|
217
|
-
} else {
|
|
218
|
-
fs4.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
219
|
-
console.log(" \u2139\uFE0F Creating ~/.claude/settings.json");
|
|
220
|
-
}
|
|
221
|
-
if (!settings.hooks) settings.hooks = {};
|
|
222
|
-
function upsertHook(event, entry) {
|
|
223
|
-
const existing2 = settings.hooks[event] || [];
|
|
224
|
-
const filtered = existing2.filter(
|
|
225
|
-
(h) => !h._tg && !h.hooks?.some(
|
|
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")
|
|
227
|
-
)
|
|
228
|
-
);
|
|
229
|
-
settings.hooks[event] = [...filtered, { _tg: true, ...entry }];
|
|
230
|
-
}
|
|
231
|
-
if (settings.hooks.Stop) {
|
|
232
|
-
settings.hooks.Stop = (settings.hooks.Stop || []).filter(
|
|
233
|
-
(h) => !h._tg && !h.hooks?.some((e) => e.command?.includes("session-stop.js"))
|
|
234
|
-
);
|
|
235
|
-
if (settings.hooks.Stop.length === 0) delete settings.hooks.Stop;
|
|
236
|
-
}
|
|
237
|
-
upsertHook("SessionStart", {
|
|
238
|
-
hooks: [
|
|
239
|
-
{
|
|
240
|
-
type: "command",
|
|
241
|
-
command: `node ${path4.join(HOOKS_DIR, "session-start.js")}`,
|
|
242
|
-
timeout: 5
|
|
243
|
-
}
|
|
244
|
-
]
|
|
245
|
-
});
|
|
246
|
-
upsertHook("PostToolUse", {
|
|
247
|
-
matcher: "",
|
|
248
|
-
hooks: [
|
|
249
|
-
{
|
|
250
|
-
type: "command",
|
|
251
|
-
command: `node ${path4.join(HOOKS_DIR, "post-tool-use.js")}`,
|
|
252
|
-
timeout: 5
|
|
253
|
-
}
|
|
254
|
-
]
|
|
255
|
-
});
|
|
256
|
-
upsertHook("UserPromptSubmit", {
|
|
257
|
-
hooks: [
|
|
258
|
-
{
|
|
259
|
-
type: "command",
|
|
260
|
-
command: `node ${path4.join(HOOKS_DIR, "user-prompt-submit.js")}`,
|
|
261
|
-
timeout: 5
|
|
262
|
-
}
|
|
263
|
-
]
|
|
264
|
-
});
|
|
265
|
-
upsertHook("SessionEnd", {
|
|
266
|
-
hooks: [
|
|
267
|
-
{
|
|
268
|
-
type: "command",
|
|
269
|
-
command: `node ${path4.join(HOOKS_DIR, "session-end.js")}`,
|
|
270
|
-
timeout: 30
|
|
271
|
-
}
|
|
272
|
-
]
|
|
273
|
-
});
|
|
274
|
-
upsertHook("PreCompact", {
|
|
275
|
-
hooks: [
|
|
276
|
-
{
|
|
277
|
-
type: "command",
|
|
278
|
-
command: `node ${path4.join(HOOKS_DIR, "pre-compact.js")}`,
|
|
279
|
-
timeout: 5
|
|
280
|
-
}
|
|
281
|
-
]
|
|
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
|
-
});
|
|
311
|
-
try {
|
|
312
|
-
fs4.chmodSync(STATUSLINE_PATH, 493);
|
|
313
|
-
} catch {
|
|
314
|
-
}
|
|
315
|
-
const existing = settings.statusLine;
|
|
316
|
-
const existingCmd = typeof existing === "string" ? existing : existing?.command ?? null;
|
|
317
|
-
const alreadyOurs = existingCmd === STATUSLINE_PATH || existingCmd === WRAPPER_PATH;
|
|
318
|
-
if (!alreadyOurs && existingCmd) {
|
|
319
|
-
fs4.writeFileSync(
|
|
320
|
-
WRAPPER_PATH,
|
|
321
|
-
[
|
|
322
|
-
"#!/usr/bin/env bash",
|
|
323
|
-
"SESSION_JSON=$(cat)",
|
|
324
|
-
`echo "$SESSION_JSON" | ${existingCmd} 2>/dev/null || true`,
|
|
325
|
-
`echo "$SESSION_JSON" | bash ${STATUSLINE_PATH}`
|
|
326
|
-
].join("\n") + "\n"
|
|
327
|
-
);
|
|
328
|
-
fs4.chmodSync(WRAPPER_PATH, 493);
|
|
329
|
-
settings.statusLine = {
|
|
330
|
-
type: "command",
|
|
331
|
-
command: WRAPPER_PATH,
|
|
332
|
-
padding: 1
|
|
333
|
-
};
|
|
334
|
-
console.log(" \u2713 statusLine \u2192 wrapped your existing statusline + tokengolf HUD");
|
|
335
|
-
} else if (!alreadyOurs) {
|
|
336
|
-
settings.statusLine = {
|
|
337
|
-
type: "command",
|
|
338
|
-
command: STATUSLINE_PATH,
|
|
339
|
-
padding: 1
|
|
340
|
-
};
|
|
341
|
-
console.log(" \u2713 statusLine \u2192 live HUD in every Claude session");
|
|
342
|
-
} else {
|
|
343
|
-
console.log(" \u2713 statusLine \u2192 already installed");
|
|
344
|
-
}
|
|
345
|
-
fs4.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2));
|
|
346
|
-
console.log(" \u2713 SessionStart \u2192 injects run context into Claude");
|
|
347
|
-
console.log(" \u2713 PostToolUse \u2192 tracks tool calls + 80% budget warning");
|
|
348
|
-
console.log(" \u2713 UserPromptSubmit \u2192 counts prompts + 50% nudge");
|
|
349
|
-
console.log(" \u2713 SessionEnd \u2192 auto-displays scorecard on /exit");
|
|
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");
|
|
354
|
-
console.log("\n \u2705 Done! Start a run: tokengolf start\n");
|
|
355
|
-
}
|
|
356
|
-
var realEntry, HOOKS_DIR, STATUSLINE_PATH, WRAPPER_PATH, CLAUDE_DIR, CLAUDE_SETTINGS;
|
|
357
|
-
var init_install = __esm({
|
|
358
|
-
"src/lib/install.js"() {
|
|
359
|
-
realEntry = fs4.realpathSync(process.argv[1]);
|
|
360
|
-
HOOKS_DIR = path4.resolve(path4.dirname(realEntry), "../hooks");
|
|
361
|
-
STATUSLINE_PATH = path4.join(HOOKS_DIR, "statusline.sh");
|
|
362
|
-
WRAPPER_PATH = path4.join(HOOKS_DIR, "statusline-wrapper.sh");
|
|
363
|
-
CLAUDE_DIR = path4.join(os3.homedir(), ".claude");
|
|
364
|
-
CLAUDE_SETTINGS = path4.join(CLAUDE_DIR, "settings.json");
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
// src/cli.js
|
|
369
|
-
import { program } from "commander";
|
|
370
|
-
import { render } from "ink";
|
|
371
|
-
import React5 from "react";
|
|
372
|
-
|
|
373
|
-
// src/lib/state.js
|
|
374
|
-
import fs from "fs";
|
|
375
|
-
import path from "path";
|
|
376
|
-
import os from "os";
|
|
377
|
-
var STATE_DIR = path.join(os.homedir(), ".tokengolf");
|
|
378
|
-
var STATE_FILE = path.join(STATE_DIR, "current-run.json");
|
|
379
|
-
function ensureDir() {
|
|
380
|
-
if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
381
|
-
}
|
|
382
|
-
function getCurrentRun() {
|
|
383
|
-
try {
|
|
384
|
-
return JSON.parse(fs.readFileSync(STATE_FILE, "utf8"));
|
|
385
|
-
} catch {
|
|
386
|
-
return null;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
function setCurrentRun(run) {
|
|
390
|
-
ensureDir();
|
|
391
|
-
fs.writeFileSync(STATE_FILE, JSON.stringify(run, null, 2));
|
|
392
|
-
}
|
|
393
|
-
function updateCurrentRun(updates) {
|
|
394
|
-
const run = getCurrentRun();
|
|
395
|
-
if (!run) return null;
|
|
396
|
-
const updated = { ...run, ...updates };
|
|
397
|
-
setCurrentRun(updated);
|
|
398
|
-
return updated;
|
|
399
|
-
}
|
|
400
|
-
function clearCurrentRun() {
|
|
401
|
-
if (fs.existsSync(STATE_FILE)) fs.unlinkSync(STATE_FILE);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// src/lib/store.js
|
|
405
|
-
import fs2 from "fs";
|
|
406
|
-
import path2 from "path";
|
|
407
|
-
|
|
408
48
|
// src/lib/score.js
|
|
409
|
-
var BUDGET_TIERS = [
|
|
410
|
-
{ label: "Diamond", emoji: "\u{1F48E}", max: 0.1, color: "cyan" },
|
|
411
|
-
{ label: "Gold", emoji: "\u{1F947}", max: 0.3, color: "yellow" },
|
|
412
|
-
{ label: "Silver", emoji: "\u{1F948}", max: 1, color: "white" },
|
|
413
|
-
{ label: "Bronze", emoji: "\u{1F949}", max: 3, color: "yellow" },
|
|
414
|
-
{ label: "Reckless", emoji: "\u{1F4B8}", max: Infinity, color: "red" }
|
|
415
|
-
];
|
|
416
|
-
var EFFORT_LEVELS = {
|
|
417
|
-
low: { label: "Low", emoji: "\u{1FAB6}", color: "green" },
|
|
418
|
-
medium: { label: "Medium", emoji: "\u2696\uFE0F", color: "white" },
|
|
419
|
-
high: { label: "High", emoji: "\u{1F525}", color: "yellow" },
|
|
420
|
-
max: { label: "Max", emoji: "\u{1F4A5}", color: "magenta", opusOnly: true }
|
|
421
|
-
};
|
|
422
49
|
function getEffortLevel(effort) {
|
|
423
50
|
return EFFORT_LEVELS[effort] || null;
|
|
424
51
|
}
|
|
425
|
-
var MODEL_BUDGET_TIERS = {
|
|
426
|
-
haiku: { diamond: 0.15, gold: 0.4, silver: 1, bronze: 2.5 },
|
|
427
|
-
sonnet: { diamond: 0.5, gold: 1.5, silver: 4, bronze: 10 },
|
|
428
|
-
opusplan: { diamond: 1.5, gold: 4.5, silver: 12, bronze: 30 },
|
|
429
|
-
opus: { diamond: 2.5, gold: 7.5, silver: 20, bronze: 50 }
|
|
430
|
-
};
|
|
431
52
|
function getModelBudgets(model) {
|
|
432
53
|
const m = (model || "").toLowerCase();
|
|
433
54
|
if (m.includes("haiku")) return MODEL_BUDGET_TIERS.haiku;
|
|
@@ -435,43 +56,6 @@ function getModelBudgets(model) {
|
|
|
435
56
|
if (m.includes("opus")) return MODEL_BUDGET_TIERS.opus;
|
|
436
57
|
return MODEL_BUDGET_TIERS.sonnet;
|
|
437
58
|
}
|
|
438
|
-
var MODEL_CLASSES = {
|
|
439
|
-
haiku: {
|
|
440
|
-
name: "Haiku",
|
|
441
|
-
label: "Rogue",
|
|
442
|
-
emoji: "\u{1F3F9}",
|
|
443
|
-
difficulty: "Hard",
|
|
444
|
-
color: "red"
|
|
445
|
-
},
|
|
446
|
-
sonnet: {
|
|
447
|
-
name: "Sonnet",
|
|
448
|
-
label: "Fighter",
|
|
449
|
-
emoji: "\u2694\uFE0F",
|
|
450
|
-
difficulty: "Normal",
|
|
451
|
-
color: "cyan"
|
|
452
|
-
},
|
|
453
|
-
opusplan: {
|
|
454
|
-
name: "Paladin",
|
|
455
|
-
label: "Paladin",
|
|
456
|
-
emoji: "\u269C\uFE0F",
|
|
457
|
-
difficulty: "Calculated",
|
|
458
|
-
color: "yellow"
|
|
459
|
-
},
|
|
460
|
-
opus: {
|
|
461
|
-
name: "Opus",
|
|
462
|
-
label: "Warlock",
|
|
463
|
-
emoji: "\u{1F9D9}",
|
|
464
|
-
difficulty: "Easy",
|
|
465
|
-
color: "magenta"
|
|
466
|
-
}
|
|
467
|
-
};
|
|
468
|
-
var FLOORS = [
|
|
469
|
-
"Write the code",
|
|
470
|
-
"Write the tests",
|
|
471
|
-
"Fix failing tests",
|
|
472
|
-
"Code review pass",
|
|
473
|
-
"PR merged \u2014 BOSS \u{1F3C6}"
|
|
474
|
-
];
|
|
475
59
|
function getTier(spent) {
|
|
476
60
|
return BUDGET_TIERS.find((t) => spent <= t.max) || BUDGET_TIERS[BUDGET_TIERS.length - 1];
|
|
477
61
|
}
|
|
@@ -929,43 +513,1292 @@ function calculateAchievements(run) {
|
|
|
929
513
|
emoji: "\u{1FAE0}"
|
|
930
514
|
});
|
|
931
515
|
}
|
|
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}" });
|
|
965
|
-
return achievements;
|
|
516
|
+
const failedToolCalls = run.failedToolCalls ?? 0;
|
|
517
|
+
const subagentSpawns = run.subagentSpawns ?? 0;
|
|
518
|
+
const turnCount = run.turnCount ?? 0;
|
|
519
|
+
if (failedToolCalls === 0 && totalToolCalls >= 5)
|
|
520
|
+
achievements.push({ key: "clean_run", label: "Clean Run \u2014 No tool failures", emoji: "\u2705" });
|
|
521
|
+
if (failedToolCalls >= 10)
|
|
522
|
+
achievements.push({
|
|
523
|
+
key: "stubborn",
|
|
524
|
+
label: `Stubborn \u2014 ${failedToolCalls} failed tool calls, still won`,
|
|
525
|
+
emoji: "\u{1F402}"
|
|
526
|
+
});
|
|
527
|
+
if (subagentSpawns === 0)
|
|
528
|
+
achievements.push({ key: "lone_wolf", label: "Lone Wolf \u2014 No subagents spawned", emoji: "\u{1F43A}" });
|
|
529
|
+
if (subagentSpawns >= 5)
|
|
530
|
+
achievements.push({
|
|
531
|
+
key: "summoner",
|
|
532
|
+
label: `Summoner \u2014 ${subagentSpawns} subagents spawned`,
|
|
533
|
+
emoji: "\u{1F4E1}"
|
|
534
|
+
});
|
|
535
|
+
if (subagentSpawns >= 10 && pct !== null && pct < 0.5)
|
|
536
|
+
achievements.push({
|
|
537
|
+
key: "army",
|
|
538
|
+
label: `Army of One \u2014 ${subagentSpawns} subagents, EFFICIENT cost`,
|
|
539
|
+
emoji: "\u{1FA96}"
|
|
540
|
+
});
|
|
541
|
+
if (promptCount >= 2 && turnCount >= 1 && turnCount / promptCount >= 3)
|
|
542
|
+
achievements.push({
|
|
543
|
+
key: "agentic",
|
|
544
|
+
label: `Agentic \u2014 ${(turnCount / promptCount).toFixed(1)} turns per prompt`,
|
|
545
|
+
emoji: "\u{1F916}"
|
|
546
|
+
});
|
|
547
|
+
if (promptCount >= 3 && turnCount === promptCount)
|
|
548
|
+
achievements.push({ key: "obedient", label: "Obedient \u2014 One turn per prompt", emoji: "\u{1F415}" });
|
|
549
|
+
return achievements;
|
|
550
|
+
}
|
|
551
|
+
var BUDGET_TIERS, EFFORT_LEVELS, MODEL_BUDGET_TIERS, MODEL_CLASSES, FLOORS;
|
|
552
|
+
var init_score = __esm({
|
|
553
|
+
"src/lib/score.js"() {
|
|
554
|
+
BUDGET_TIERS = [
|
|
555
|
+
{ label: "Diamond", emoji: "\u{1F48E}", max: 0.1, color: "cyan" },
|
|
556
|
+
{ label: "Gold", emoji: "\u{1F947}", max: 0.3, color: "yellow" },
|
|
557
|
+
{ label: "Silver", emoji: "\u{1F948}", max: 1, color: "white" },
|
|
558
|
+
{ label: "Bronze", emoji: "\u{1F949}", max: 3, color: "yellow" },
|
|
559
|
+
{ label: "Reckless", emoji: "\u{1F4B8}", max: Infinity, color: "red" }
|
|
560
|
+
];
|
|
561
|
+
EFFORT_LEVELS = {
|
|
562
|
+
low: { label: "Low", emoji: "\u{1FAB6}", color: "green" },
|
|
563
|
+
medium: { label: "Medium", emoji: "\u2696\uFE0F", color: "white" },
|
|
564
|
+
high: { label: "High", emoji: "\u{1F525}", color: "yellow" },
|
|
565
|
+
max: { label: "Max", emoji: "\u{1F4A5}", color: "magenta", opusOnly: true }
|
|
566
|
+
};
|
|
567
|
+
MODEL_BUDGET_TIERS = {
|
|
568
|
+
haiku: { diamond: 0.15, gold: 0.4, silver: 1, bronze: 2.5 },
|
|
569
|
+
sonnet: { diamond: 0.5, gold: 1.5, silver: 4, bronze: 10 },
|
|
570
|
+
opusplan: { diamond: 1.5, gold: 4.5, silver: 12, bronze: 30 },
|
|
571
|
+
opus: { diamond: 2.5, gold: 7.5, silver: 20, bronze: 50 }
|
|
572
|
+
};
|
|
573
|
+
MODEL_CLASSES = {
|
|
574
|
+
haiku: {
|
|
575
|
+
name: "Haiku",
|
|
576
|
+
label: "Rogue",
|
|
577
|
+
emoji: "\u{1F3F9}",
|
|
578
|
+
difficulty: "Hard",
|
|
579
|
+
color: "red"
|
|
580
|
+
},
|
|
581
|
+
sonnet: {
|
|
582
|
+
name: "Sonnet",
|
|
583
|
+
label: "Fighter",
|
|
584
|
+
emoji: "\u2694\uFE0F",
|
|
585
|
+
difficulty: "Normal",
|
|
586
|
+
color: "cyan"
|
|
587
|
+
},
|
|
588
|
+
opusplan: {
|
|
589
|
+
name: "Paladin",
|
|
590
|
+
label: "Paladin",
|
|
591
|
+
emoji: "\u269C\uFE0F",
|
|
592
|
+
difficulty: "Calculated",
|
|
593
|
+
color: "yellow"
|
|
594
|
+
},
|
|
595
|
+
opus: {
|
|
596
|
+
name: "Opus",
|
|
597
|
+
label: "Warlock",
|
|
598
|
+
emoji: "\u{1F9D9}",
|
|
599
|
+
difficulty: "Easy",
|
|
600
|
+
color: "magenta"
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
FLOORS = [
|
|
604
|
+
"Write the code",
|
|
605
|
+
"Write the tests",
|
|
606
|
+
"Fix failing tests",
|
|
607
|
+
"Code review pass",
|
|
608
|
+
"PR merged \u2014 BOSS \u{1F3C6}"
|
|
609
|
+
];
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// src/components/ActiveRun.js
|
|
614
|
+
import React2, { useState as useState2, useEffect } from "react";
|
|
615
|
+
import { Box as Box2, Text as Text2, useApp as useApp2, useInput } from "ink";
|
|
616
|
+
import { ProgressBar } from "@inkjs/ui";
|
|
617
|
+
function ActiveRun({ run: initialRun }) {
|
|
618
|
+
const { exit } = useApp2();
|
|
619
|
+
const [run, setRun] = useState2(initialRun);
|
|
620
|
+
const [tick, setTick] = useState2(0);
|
|
621
|
+
useEffect(() => {
|
|
622
|
+
const interval = setInterval(() => {
|
|
623
|
+
const latest = getCurrentRun();
|
|
624
|
+
if (latest) setRun(latest);
|
|
625
|
+
setTick((t) => t + 1);
|
|
626
|
+
}, 2e3);
|
|
627
|
+
return () => clearInterval(interval);
|
|
628
|
+
}, []);
|
|
629
|
+
useInput((input) => {
|
|
630
|
+
if (input === "q") exit();
|
|
631
|
+
});
|
|
632
|
+
const flowMode = !run.budget;
|
|
633
|
+
const mc = getModelClass(run.model);
|
|
634
|
+
const pct = flowMode ? null : getBudgetPct(run.spent, run.budget);
|
|
635
|
+
const efficiency = flowMode ? null : getEfficiencyRating(run.spent, run.budget);
|
|
636
|
+
const barColor = !pct ? "green" : pct >= 80 ? "red" : pct >= 50 ? "yellow" : "green";
|
|
637
|
+
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(
|
|
638
|
+
Box2,
|
|
639
|
+
{
|
|
640
|
+
borderStyle: "round",
|
|
641
|
+
borderColor: "yellow",
|
|
642
|
+
paddingX: 1,
|
|
643
|
+
paddingY: 1,
|
|
644
|
+
flexDirection: "column",
|
|
645
|
+
gap: 1
|
|
646
|
+
},
|
|
647
|
+
/* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "white" }, run.quest),
|
|
648
|
+
/* @__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)),
|
|
649
|
+
!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, "%")),
|
|
650
|
+
/* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", gap: 0, marginTop: 1 }, FLOORS.map((floor, i) => {
|
|
651
|
+
const n = i + 1;
|
|
652
|
+
const done = n < run.floor;
|
|
653
|
+
const active = n === run.floor;
|
|
654
|
+
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(
|
|
655
|
+
Text2,
|
|
656
|
+
{
|
|
657
|
+
color: done ? "green" : active ? "white" : "gray",
|
|
658
|
+
dimColor: !done && !active
|
|
659
|
+
},
|
|
660
|
+
"Floor ",
|
|
661
|
+
n,
|
|
662
|
+
": ",
|
|
663
|
+
floor
|
|
664
|
+
));
|
|
665
|
+
})),
|
|
666
|
+
/* @__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))),
|
|
667
|
+
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"))
|
|
668
|
+
), /* @__PURE__ */ React2.createElement(Text2, { color: "gray", dimColor: true }, "tokengolf win [--spent 0.18] \xB7 tokengolf bust \xB7 q to close"));
|
|
669
|
+
}
|
|
670
|
+
var init_ActiveRun = __esm({
|
|
671
|
+
"src/components/ActiveRun.js"() {
|
|
672
|
+
init_state();
|
|
673
|
+
init_score();
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
// src/components/ScoreCard.js
|
|
678
|
+
import React3, { useEffect as useEffect2 } from "react";
|
|
679
|
+
import { Box as Box3, Text as Text3, useApp as useApp3, useInput as useInput2 } from "ink";
|
|
680
|
+
function ScoreCard({ run }) {
|
|
681
|
+
const { exit } = useApp3();
|
|
682
|
+
const won = run.status === "won";
|
|
683
|
+
useInput2((input) => {
|
|
684
|
+
if (input === "q") exit();
|
|
685
|
+
});
|
|
686
|
+
useEffect2(() => {
|
|
687
|
+
const t = setTimeout(() => exit(), 6e4);
|
|
688
|
+
return () => clearTimeout(t);
|
|
689
|
+
}, [exit]);
|
|
690
|
+
const tier = getTier(run.spent);
|
|
691
|
+
const mc = getModelClass(run.model);
|
|
692
|
+
const flowMode = !run.budget;
|
|
693
|
+
const efficiency = flowMode ? null : getEfficiencyRating(run.spent, run.budget);
|
|
694
|
+
const pct = flowMode ? null : getBudgetPct(run.spent, run.budget);
|
|
695
|
+
const haikuPct = getHaikuPct(run.modelBreakdown, run.spent);
|
|
696
|
+
const opusPct = getOpusPct(run.modelBreakdown, run.spent);
|
|
697
|
+
return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", paddingX: 1, paddingY: 1, gap: 1 }, /* @__PURE__ */ React3.createElement(
|
|
698
|
+
Box3,
|
|
699
|
+
{
|
|
700
|
+
borderStyle: "double",
|
|
701
|
+
borderColor: won ? "yellow" : "red",
|
|
702
|
+
paddingX: 2,
|
|
703
|
+
paddingY: 1,
|
|
704
|
+
flexDirection: "column",
|
|
705
|
+
gap: 1
|
|
706
|
+
},
|
|
707
|
+
/* @__PURE__ */ React3.createElement(Text3, { bold: true, color: won ? "yellow" : "red" }, won ? "\u{1F3C6} SESSION COMPLETE" : "\u{1F480} BUDGET BUSTED"),
|
|
708
|
+
/* @__PURE__ */ React3.createElement(Text3, { color: "white", bold: true }, run.quest ?? /* @__PURE__ */ React3.createElement(Text3, { color: "gray" }, "Flow Mode")),
|
|
709
|
+
/* @__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, [
|
|
710
|
+
run.effort && run.effort !== "medium" ? getEffortLevel(run.effort)?.label : null,
|
|
711
|
+
run.fastMode ? "Fast" : null
|
|
712
|
+
].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))),
|
|
713
|
+
efficiency && /* @__PURE__ */ React3.createElement(Box3, { gap: 2 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: efficiency.color }, efficiency.emoji, " ", efficiency.label)),
|
|
714
|
+
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))),
|
|
715
|
+
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"))),
|
|
716
|
+
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]) => {
|
|
717
|
+
const m = model.toLowerCase();
|
|
718
|
+
const short = m.includes("haiku") ? "Haiku" : m.includes("sonnet") ? "Sonnet" : m.includes("opusplan") || m.includes("paladin") ? "Paladin" : "Opus";
|
|
719
|
+
const pctOfTotal = Math.round(cost / run.spent * 100);
|
|
720
|
+
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)));
|
|
721
|
+
}))),
|
|
722
|
+
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)))),
|
|
723
|
+
!won && run.budget && /* @__PURE__ */ React3.createElement(
|
|
724
|
+
Box3,
|
|
725
|
+
{
|
|
726
|
+
borderStyle: "single",
|
|
727
|
+
borderColor: "red",
|
|
728
|
+
paddingX: 1,
|
|
729
|
+
marginTop: 1,
|
|
730
|
+
flexDirection: "column"
|
|
731
|
+
},
|
|
732
|
+
/* @__PURE__ */ React3.createElement(Text3, { color: "red", bold: true }, "Cause of death: Budget exceeded by ", formatCost(run.spent - run.budget)),
|
|
733
|
+
/* @__PURE__ */ React3.createElement(Text3, { color: "gray", dimColor: true }, "Tip: Use Read with line ranges instead of full file reads.")
|
|
734
|
+
)
|
|
735
|
+
), /* @__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")));
|
|
736
|
+
}
|
|
737
|
+
var init_ScoreCard = __esm({
|
|
738
|
+
"src/components/ScoreCard.js"() {
|
|
739
|
+
init_score();
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
// src/components/StatsView.js
|
|
744
|
+
import React4 from "react";
|
|
745
|
+
import { Box as Box4, Text as Text4, useApp as useApp4, useInput as useInput3 } from "ink";
|
|
746
|
+
function StatsView({ stats }) {
|
|
747
|
+
const { exit } = useApp4();
|
|
748
|
+
useInput3((input) => {
|
|
749
|
+
if (input === "q") exit();
|
|
750
|
+
});
|
|
751
|
+
if (stats.total === 0) {
|
|
752
|
+
return /* @__PURE__ */ React4.createElement(Box4, { paddingX: 1, paddingY: 1, flexDirection: "column", gap: 1 }, /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "yellow" }, "\u26F3 TokenGolf Stats"), /* @__PURE__ */ React4.createElement(Text4, { color: "gray" }, "No completed runs yet."), /* @__PURE__ */ React4.createElement(Text4, { color: "gray" }, "Start one: ", /* @__PURE__ */ React4.createElement(Text4, { color: "cyan" }, "tokengolf start")));
|
|
753
|
+
}
|
|
754
|
+
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 && (() => {
|
|
755
|
+
const bestTier = getTier(stats.bestRun.spent);
|
|
756
|
+
const bestMc = getModelClass(stats.bestRun.model);
|
|
757
|
+
return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", gap: 0 }, /* @__PURE__ */ React4.createElement(Text4, { color: "yellow" }, "\u{1F3C6} Personal Best"), /* @__PURE__ */ React4.createElement(
|
|
758
|
+
Box4,
|
|
759
|
+
{
|
|
760
|
+
borderStyle: "round",
|
|
761
|
+
borderColor: "yellow",
|
|
762
|
+
paddingX: 1,
|
|
763
|
+
paddingY: 1,
|
|
764
|
+
flexDirection: "column"
|
|
765
|
+
},
|
|
766
|
+
/* @__PURE__ */ React4.createElement(Text4, { color: "white" }, stats.bestRun.quest),
|
|
767
|
+
/* @__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))
|
|
768
|
+
));
|
|
769
|
+
})(), /* @__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) => {
|
|
770
|
+
const won = run.status === "won";
|
|
771
|
+
const tier = getTier(run.spent);
|
|
772
|
+
const mc = getModelClass(run.model);
|
|
773
|
+
const pct = run.budget ? getBudgetPct(run.spent, run.budget) : null;
|
|
774
|
+
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, "%"));
|
|
775
|
+
})), 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"));
|
|
776
|
+
}
|
|
777
|
+
var init_StatsView = __esm({
|
|
778
|
+
"src/components/StatsView.js"() {
|
|
779
|
+
init_score();
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
// src/lib/demo.js
|
|
784
|
+
var demo_exports = {};
|
|
785
|
+
__export(demo_exports, {
|
|
786
|
+
runDemo: () => runDemo
|
|
787
|
+
});
|
|
788
|
+
function hudLine({ quest, model, cost, budget, ctxPct, effort, fainted, floor }) {
|
|
789
|
+
const m = (model || "").toLowerCase();
|
|
790
|
+
let modelName, modelEmoji;
|
|
791
|
+
if (m.includes("haiku")) {
|
|
792
|
+
modelName = "Haiku";
|
|
793
|
+
modelEmoji = "\u{1F3F9}";
|
|
794
|
+
} else if (m.includes("sonnet")) {
|
|
795
|
+
modelName = "Sonnet";
|
|
796
|
+
modelEmoji = "\u2694\uFE0F";
|
|
797
|
+
} else if (m.includes("opus")) {
|
|
798
|
+
modelName = "Opus";
|
|
799
|
+
modelEmoji = "\u{1F9D9}";
|
|
800
|
+
} else {
|
|
801
|
+
modelName = "?";
|
|
802
|
+
modelEmoji = "?";
|
|
803
|
+
}
|
|
804
|
+
const labelParts = [`${modelEmoji} ${modelName}`];
|
|
805
|
+
if (effort) labelParts.push(effort.charAt(0).toUpperCase() + effort.slice(1));
|
|
806
|
+
const modelLabel = labelParts.join("\xB7");
|
|
807
|
+
let tierEmoji;
|
|
808
|
+
if (cost < 0.1) tierEmoji = "\u{1F48E}";
|
|
809
|
+
else if (cost < 0.3) tierEmoji = "\u{1F947}";
|
|
810
|
+
else if (cost < 1) tierEmoji = "\u{1F948}";
|
|
811
|
+
else if (cost < 3) tierEmoji = "\u{1F949}";
|
|
812
|
+
else tierEmoji = "\u{1F4B8}";
|
|
813
|
+
const sep = ` ${DIM}|${RESET} `;
|
|
814
|
+
let costStr, ratingStr;
|
|
815
|
+
if (budget) {
|
|
816
|
+
const pct = cost / budget * 100;
|
|
817
|
+
let rating, rc;
|
|
818
|
+
if (pct <= 25) {
|
|
819
|
+
rating = "LEGENDARY";
|
|
820
|
+
rc = M;
|
|
821
|
+
} else if (pct <= 50) {
|
|
822
|
+
rating = "EFFICIENT";
|
|
823
|
+
rc = C;
|
|
824
|
+
} else if (pct <= 75) {
|
|
825
|
+
rating = "SOLID";
|
|
826
|
+
rc = G;
|
|
827
|
+
} else if (pct <= 100) {
|
|
828
|
+
rating = "CLOSE CALL";
|
|
829
|
+
rc = Y;
|
|
830
|
+
} else {
|
|
831
|
+
rating = "BUSTED";
|
|
832
|
+
rc = R;
|
|
833
|
+
}
|
|
834
|
+
costStr = `${tierEmoji} $${cost.toFixed(4)}/$${budget.toFixed(2)} ${pct.toFixed(0)}%`;
|
|
835
|
+
ratingStr = `${rc}${rating}${RESET}`;
|
|
836
|
+
} else {
|
|
837
|
+
costStr = `${tierEmoji} $${cost.toFixed(4)}`;
|
|
838
|
+
ratingStr = null;
|
|
839
|
+
}
|
|
840
|
+
let ctxStr = null;
|
|
841
|
+
if (ctxPct != null) {
|
|
842
|
+
if (ctxPct >= 90) ctxStr = `${R}\u{1F4E6} ${ctxPct}%${RESET}`;
|
|
843
|
+
else if (ctxPct >= 75) ctxStr = `${Y}\u{1F392} ${ctxPct}%${RESET}`;
|
|
844
|
+
else if (ctxPct >= 50) ctxStr = `${G}\u{1FAB6} ${ctxPct}%${RESET}`;
|
|
845
|
+
}
|
|
846
|
+
const icon = fainted ? "\u{1F4A4}" : "\u26F3";
|
|
847
|
+
const prefix = `${BOLD}${C}${icon}${RESET}`;
|
|
848
|
+
const parts = [`${prefix} ${quest}`, costStr];
|
|
849
|
+
if (ratingStr) parts.push(ratingStr);
|
|
850
|
+
if (ctxStr) parts.push(ctxStr);
|
|
851
|
+
parts.push(`${C}${modelLabel}${RESET}`);
|
|
852
|
+
if (budget && floor) parts.push(`Floor ${floor}`);
|
|
853
|
+
return `${DIM} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${RESET}
|
|
854
|
+
${parts.join(sep)}
|
|
855
|
+
${DIM} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${RESET}`;
|
|
856
|
+
}
|
|
857
|
+
function runDemo() {
|
|
858
|
+
console.log("");
|
|
859
|
+
console.log(`${BOLD}${C}\u26F3 TokenGolf \u2014 HUD Demo${RESET}`);
|
|
860
|
+
console.log(`${DIM}Live statusline shown in every Claude Code session${RESET}`);
|
|
861
|
+
console.log("");
|
|
862
|
+
for (const { title, hud } of SCENARIOS) {
|
|
863
|
+
console.log(`${DIM}${title}${RESET}`);
|
|
864
|
+
console.log(hudLine(hud));
|
|
865
|
+
console.log("");
|
|
866
|
+
}
|
|
867
|
+
console.log(
|
|
868
|
+
`${DIM}Run ${RESET}tokengolf start${DIM} to begin a roguelike run, or just open Claude Code \u2014 flow mode tracks automatically.${RESET}`
|
|
869
|
+
);
|
|
870
|
+
console.log("");
|
|
871
|
+
}
|
|
872
|
+
var R, G, Y, M, C, DIM, BOLD, RESET, SCENARIOS;
|
|
873
|
+
var init_demo = __esm({
|
|
874
|
+
"src/lib/demo.js"() {
|
|
875
|
+
R = "\x1B[31m";
|
|
876
|
+
G = "\x1B[32m";
|
|
877
|
+
Y = "\x1B[33m";
|
|
878
|
+
M = "\x1B[35m";
|
|
879
|
+
C = "\x1B[36m";
|
|
880
|
+
DIM = "\x1B[2m";
|
|
881
|
+
BOLD = "\x1B[1m";
|
|
882
|
+
RESET = "\x1B[0m";
|
|
883
|
+
SCENARIOS = [
|
|
884
|
+
{
|
|
885
|
+
title: "Flow mode (passive \u2014 no quest, no budget)",
|
|
886
|
+
hud: { quest: "Flow", model: "claude-sonnet-4-6", cost: 34e-4 }
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
title: "Roguelike \xB7 Sonnet \xB7 EFFICIENT",
|
|
890
|
+
hud: {
|
|
891
|
+
quest: "add pagination to /users",
|
|
892
|
+
model: "claude-sonnet-4-6",
|
|
893
|
+
cost: 0.54,
|
|
894
|
+
budget: 1.5,
|
|
895
|
+
ctxPct: 34,
|
|
896
|
+
floor: "2/5"
|
|
897
|
+
}
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
title: "Roguelike \xB7 Sonnet\xB7High \xB7 LEGENDARY",
|
|
901
|
+
hud: {
|
|
902
|
+
quest: "implement SSO with SAML",
|
|
903
|
+
model: "claude-sonnet-4-6",
|
|
904
|
+
cost: 0.41,
|
|
905
|
+
budget: 2,
|
|
906
|
+
ctxPct: 29,
|
|
907
|
+
effort: "high",
|
|
908
|
+
floor: "1/5"
|
|
909
|
+
}
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
title: "Roguelike \xB7 Opus \xB7 LEGENDARY \xB7 \u{1FAB6} context",
|
|
913
|
+
hud: {
|
|
914
|
+
quest: "refactor auth middleware",
|
|
915
|
+
model: "claude-opus-4-6",
|
|
916
|
+
cost: 0.82,
|
|
917
|
+
budget: 4,
|
|
918
|
+
ctxPct: 52,
|
|
919
|
+
floor: "3/5"
|
|
920
|
+
}
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
title: "Roguelike \xB7 Haiku \xB7 CLOSE CALL \xB7 \u{1F392} context",
|
|
924
|
+
hud: {
|
|
925
|
+
quest: "fix N+1 query in dashboard",
|
|
926
|
+
model: "claude-haiku-4-5-20251001",
|
|
927
|
+
cost: 0.46,
|
|
928
|
+
budget: 0.5,
|
|
929
|
+
ctxPct: 78,
|
|
930
|
+
floor: "4/5"
|
|
931
|
+
}
|
|
932
|
+
},
|
|
933
|
+
{
|
|
934
|
+
title: "Roguelike \xB7 BUSTED \u2014 over budget",
|
|
935
|
+
hud: {
|
|
936
|
+
quest: "migrate postgres schema",
|
|
937
|
+
model: "claude-sonnet-4-6",
|
|
938
|
+
cost: 2.41,
|
|
939
|
+
budget: 2,
|
|
940
|
+
floor: "2/5"
|
|
941
|
+
}
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
title: "Roguelike \xB7 Opus \xB7 EFFICIENT \xB7 \u{1F4E6} overencumbered",
|
|
945
|
+
hud: {
|
|
946
|
+
quest: "refactor entire API layer",
|
|
947
|
+
model: "claude-opus-4-6",
|
|
948
|
+
cost: 3.1,
|
|
949
|
+
budget: 10,
|
|
950
|
+
ctxPct: 91,
|
|
951
|
+
floor: "3/5"
|
|
952
|
+
}
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
title: "Fainted \u{1F4A4} \u2014 usage limit hit, run resumes next session",
|
|
956
|
+
hud: {
|
|
957
|
+
quest: "write test suite for payments",
|
|
958
|
+
model: "claude-sonnet-4-6",
|
|
959
|
+
cost: 1.22,
|
|
960
|
+
budget: 3,
|
|
961
|
+
ctxPct: 67,
|
|
962
|
+
fainted: true,
|
|
963
|
+
floor: "2/5"
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
];
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
// src/lib/demo-render.js
|
|
971
|
+
import { Readable } from "stream";
|
|
972
|
+
import { render } from "ink";
|
|
973
|
+
function makeFakeStdin() {
|
|
974
|
+
const s = new Readable({ read() {
|
|
975
|
+
} });
|
|
976
|
+
s.setRawMode = () => s;
|
|
977
|
+
s.ref = () => s;
|
|
978
|
+
s.unref = () => s;
|
|
979
|
+
s.isTTY = true;
|
|
980
|
+
return s;
|
|
981
|
+
}
|
|
982
|
+
function demoRender(element) {
|
|
983
|
+
return render(element, { stdin: makeFakeStdin() });
|
|
984
|
+
}
|
|
985
|
+
var init_demo_render = __esm({
|
|
986
|
+
"src/lib/demo-render.js"() {
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
// src/lib/demo-fixtures.js
|
|
991
|
+
var NOW, AGO, SCORECARD_FIXTURES, ACTIVERUN_FIXTURES, STATS_FIXTURES;
|
|
992
|
+
var init_demo_fixtures = __esm({
|
|
993
|
+
"src/lib/demo-fixtures.js"() {
|
|
994
|
+
NOW = "2026-03-10T14:00:00Z";
|
|
995
|
+
AGO = (mins) => new Date(Date.now() - mins * 6e4).toISOString();
|
|
996
|
+
SCORECARD_FIXTURES = [
|
|
997
|
+
{
|
|
998
|
+
title: "Won \xB7 Sonnet \xB7 LEGENDARY \xB7 multi-model \xB7 achievements",
|
|
999
|
+
run: {
|
|
1000
|
+
id: "run_demo_1",
|
|
1001
|
+
quest: "add pagination to /users endpoint",
|
|
1002
|
+
model: "claude-sonnet-4-6",
|
|
1003
|
+
budget: 1.5,
|
|
1004
|
+
spent: 0.23,
|
|
1005
|
+
status: "won",
|
|
1006
|
+
mode: "roguelike",
|
|
1007
|
+
effort: "high",
|
|
1008
|
+
fastMode: false,
|
|
1009
|
+
floor: 5,
|
|
1010
|
+
totalFloors: 5,
|
|
1011
|
+
promptCount: 6,
|
|
1012
|
+
totalToolCalls: 18,
|
|
1013
|
+
toolCalls: { Read: 8, Edit: 4, Bash: 3, Grep: 2, Glob: 1 },
|
|
1014
|
+
failedToolCalls: 0,
|
|
1015
|
+
subagentSpawns: 0,
|
|
1016
|
+
turnCount: 14,
|
|
1017
|
+
thinkingInvocations: 0,
|
|
1018
|
+
thinkingTokens: 0,
|
|
1019
|
+
sessionCount: 1,
|
|
1020
|
+
fainted: false,
|
|
1021
|
+
compactionEvents: [],
|
|
1022
|
+
modelBreakdown: { "claude-sonnet-4-6": 0.19, "claude-haiku-4-5-20251001": 0.04 },
|
|
1023
|
+
achievements: [
|
|
1024
|
+
{ key: "sniper", label: "Sniper \u2014 Under 25% budget", emoji: "\u{1F3AF}" },
|
|
1025
|
+
{ key: "silver_sonnet", label: "Silver \u2014 Completed with Sonnet", emoji: "\u{1F948}" },
|
|
1026
|
+
{ key: "no_rest", label: "No Rest for the Wicked \u2014 One session", emoji: "\u{1F525}" },
|
|
1027
|
+
{ key: "clean_run", label: "Clean Run \u2014 Zero failed tool calls", emoji: "\u2705" },
|
|
1028
|
+
{ key: "toolbox", label: "Toolbox \u2014 5+ distinct tool types", emoji: "\u{1F9F0}" },
|
|
1029
|
+
{ key: "silent_run", label: "Silent Run \u2014 No extended thinking", emoji: "\u{1F92B}" }
|
|
1030
|
+
],
|
|
1031
|
+
startedAt: AGO(12),
|
|
1032
|
+
endedAt: NOW
|
|
1033
|
+
}
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
title: "Won \xB7 Flow mode \xB7 minimal",
|
|
1037
|
+
run: {
|
|
1038
|
+
id: "run_demo_2",
|
|
1039
|
+
quest: null,
|
|
1040
|
+
model: "claude-sonnet-4-6",
|
|
1041
|
+
budget: null,
|
|
1042
|
+
spent: 34e-4,
|
|
1043
|
+
status: "won",
|
|
1044
|
+
mode: "flow",
|
|
1045
|
+
effort: null,
|
|
1046
|
+
fastMode: false,
|
|
1047
|
+
floor: 1,
|
|
1048
|
+
totalFloors: 5,
|
|
1049
|
+
promptCount: 2,
|
|
1050
|
+
totalToolCalls: 5,
|
|
1051
|
+
toolCalls: { Read: 3, Edit: 2 },
|
|
1052
|
+
failedToolCalls: 0,
|
|
1053
|
+
subagentSpawns: 0,
|
|
1054
|
+
turnCount: 3,
|
|
1055
|
+
thinkingInvocations: 0,
|
|
1056
|
+
thinkingTokens: 0,
|
|
1057
|
+
sessionCount: 1,
|
|
1058
|
+
fainted: false,
|
|
1059
|
+
compactionEvents: [],
|
|
1060
|
+
modelBreakdown: { "claude-sonnet-4-6": 34e-4 },
|
|
1061
|
+
achievements: [
|
|
1062
|
+
{ key: "penny", label: "Penny Pincher \u2014 Under $0.10", emoji: "\u{1FA99}" },
|
|
1063
|
+
{ key: "cheap_shots", label: "Cheap Shots \u2014 Under $0.01/prompt", emoji: "\u{1F4B2}" }
|
|
1064
|
+
],
|
|
1065
|
+
startedAt: AGO(3),
|
|
1066
|
+
endedAt: NOW
|
|
1067
|
+
}
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
title: "Won \xB7 Opus \xB7 ultrathink \xB7 EFFICIENT",
|
|
1071
|
+
run: {
|
|
1072
|
+
id: "run_demo_3",
|
|
1073
|
+
quest: "refactor auth middleware to support SAML SSO",
|
|
1074
|
+
model: "claude-opus-4-6",
|
|
1075
|
+
budget: 7.5,
|
|
1076
|
+
spent: 3.41,
|
|
1077
|
+
status: "won",
|
|
1078
|
+
mode: "roguelike",
|
|
1079
|
+
effort: "max",
|
|
1080
|
+
fastMode: false,
|
|
1081
|
+
floor: 5,
|
|
1082
|
+
totalFloors: 5,
|
|
1083
|
+
promptCount: 8,
|
|
1084
|
+
totalToolCalls: 32,
|
|
1085
|
+
toolCalls: { Read: 12, Edit: 8, Bash: 6, Grep: 4, Write: 2 },
|
|
1086
|
+
failedToolCalls: 1,
|
|
1087
|
+
subagentSpawns: 3,
|
|
1088
|
+
turnCount: 24,
|
|
1089
|
+
thinkingInvocations: 4,
|
|
1090
|
+
thinkingTokens: 12400,
|
|
1091
|
+
sessionCount: 2,
|
|
1092
|
+
fainted: true,
|
|
1093
|
+
compactionEvents: [{ trigger: "auto", context_window: { used_percentage: 92 } }],
|
|
1094
|
+
modelBreakdown: { "claude-opus-4-6": 3.22, "claude-haiku-4-5-20251001": 0.19 },
|
|
1095
|
+
achievements: [
|
|
1096
|
+
{ key: "efficient", label: "Efficient \u2014 Under 50% budget", emoji: "\u26A1" },
|
|
1097
|
+
{ key: "bronze_opus", label: "Bronze \u2014 Completed with Opus", emoji: "\u{1F949}" },
|
|
1098
|
+
{ key: "archmagus", label: "Archmagus \u2014 Opus max effort, completed", emoji: "\u{1F451}" },
|
|
1099
|
+
{ key: "deep_thinker", label: "Deep Thinker \u2014 3+ ultrathink invocations", emoji: "\u{1F300}" },
|
|
1100
|
+
{ key: "spell_cast", label: "Spell Cast \u2014 Used extended thinking", emoji: "\u{1F52E}" },
|
|
1101
|
+
{ key: "came_back", label: "Came Back \u2014 Fainted and finished", emoji: "\u{1F9DF}" },
|
|
1102
|
+
{ key: "overencumbered", label: "Overencumbered \u2014 Auto-compacted", emoji: "\u{1F4E6}" },
|
|
1103
|
+
{ key: "summoner", label: "Summoner \u2014 3+ subagents spawned", emoji: "\u{1F4E1}" }
|
|
1104
|
+
],
|
|
1105
|
+
startedAt: AGO(95),
|
|
1106
|
+
endedAt: NOW
|
|
1107
|
+
}
|
|
1108
|
+
},
|
|
1109
|
+
{
|
|
1110
|
+
title: "Won \xB7 Haiku \xB7 Diamond \xB7 one-shot",
|
|
1111
|
+
run: {
|
|
1112
|
+
id: "run_demo_4",
|
|
1113
|
+
quest: "fix typo in README badge URL",
|
|
1114
|
+
model: "claude-haiku-4-5-20251001",
|
|
1115
|
+
budget: 0.15,
|
|
1116
|
+
spent: 89e-4,
|
|
1117
|
+
status: "won",
|
|
1118
|
+
mode: "roguelike",
|
|
1119
|
+
effort: null,
|
|
1120
|
+
fastMode: false,
|
|
1121
|
+
floor: 5,
|
|
1122
|
+
totalFloors: 5,
|
|
1123
|
+
promptCount: 1,
|
|
1124
|
+
totalToolCalls: 3,
|
|
1125
|
+
toolCalls: { Read: 1, Edit: 1, Bash: 1 },
|
|
1126
|
+
failedToolCalls: 0,
|
|
1127
|
+
subagentSpawns: 0,
|
|
1128
|
+
turnCount: 2,
|
|
1129
|
+
thinkingInvocations: 0,
|
|
1130
|
+
thinkingTokens: 0,
|
|
1131
|
+
sessionCount: 1,
|
|
1132
|
+
fainted: false,
|
|
1133
|
+
compactionEvents: [],
|
|
1134
|
+
modelBreakdown: { "claude-haiku-4-5-20251001": 89e-4 },
|
|
1135
|
+
achievements: [
|
|
1136
|
+
{ key: "diamond", label: "Diamond \u2014 Haiku under $0.10", emoji: "\u{1F48E}" },
|
|
1137
|
+
{ key: "gold_haiku", label: "Gold \u2014 Completed with Haiku", emoji: "\u{1F947}" },
|
|
1138
|
+
{ key: "one_shot", label: "One Shot \u2014 Single prompt", emoji: "\u{1F94A}" },
|
|
1139
|
+
{ key: "sniper", label: "Sniper \u2014 Under 25% budget", emoji: "\u{1F3AF}" },
|
|
1140
|
+
{ key: "penny", label: "Penny Pincher \u2014 Under $0.10", emoji: "\u{1FA99}" },
|
|
1141
|
+
{ key: "speedrun", label: "Speedrun \u2014 Under 5 minutes", emoji: "\u23F1\uFE0F" },
|
|
1142
|
+
{ key: "lone_wolf", label: "Lone Wolf \u2014 No subagents", emoji: "\u{1F43A}" },
|
|
1143
|
+
{ key: "surgeon", label: "Surgeon \u2014 1-3 edits, under budget", emoji: "\u{1F52A}" }
|
|
1144
|
+
],
|
|
1145
|
+
startedAt: AGO(2),
|
|
1146
|
+
endedAt: NOW
|
|
1147
|
+
}
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
title: "Died \xB7 Sonnet \xB7 BUSTED \xB7 death marks",
|
|
1151
|
+
run: {
|
|
1152
|
+
id: "run_demo_5",
|
|
1153
|
+
quest: "migrate postgres schema to v3",
|
|
1154
|
+
model: "claude-sonnet-4-6",
|
|
1155
|
+
budget: 1.5,
|
|
1156
|
+
spent: 3.87,
|
|
1157
|
+
status: "died",
|
|
1158
|
+
mode: "roguelike",
|
|
1159
|
+
effort: "high",
|
|
1160
|
+
fastMode: false,
|
|
1161
|
+
floor: 3,
|
|
1162
|
+
totalFloors: 5,
|
|
1163
|
+
promptCount: 22,
|
|
1164
|
+
totalToolCalls: 48,
|
|
1165
|
+
toolCalls: { Read: 18, Edit: 12, Bash: 10, Grep: 5, Write: 3 },
|
|
1166
|
+
failedToolCalls: 7,
|
|
1167
|
+
subagentSpawns: 2,
|
|
1168
|
+
turnCount: 34,
|
|
1169
|
+
thinkingInvocations: 2,
|
|
1170
|
+
thinkingTokens: 5200,
|
|
1171
|
+
sessionCount: 1,
|
|
1172
|
+
fainted: false,
|
|
1173
|
+
compactionEvents: [
|
|
1174
|
+
{ trigger: "auto", context_window: { used_percentage: 94 } },
|
|
1175
|
+
{ trigger: "auto", context_window: { used_percentage: 91 } }
|
|
1176
|
+
],
|
|
1177
|
+
modelBreakdown: { "claude-sonnet-4-6": 3.52, "claude-haiku-4-5-20251001": 0.35 },
|
|
1178
|
+
achievements: [
|
|
1179
|
+
{ key: "hubris", label: "Hubris \u2014 Ultrathink, busted anyway", emoji: "\u{1F926}" },
|
|
1180
|
+
{ key: "blowout", label: "Blowout \u2014 Spent 2\xD7 budget", emoji: "\u{1F4A5}" },
|
|
1181
|
+
{ key: "fumble", label: "Fumble \u2014 5+ failed tool calls", emoji: "\u{1F921}" },
|
|
1182
|
+
{ key: "tool_happy", label: "Tool Happy \u2014 30+ tool calls, died", emoji: "\u{1F528}" },
|
|
1183
|
+
{ key: "expensive_taste", label: "Expensive Taste \u2014 $0.50+/prompt", emoji: "\u{1F377}" }
|
|
1184
|
+
],
|
|
1185
|
+
startedAt: AGO(45),
|
|
1186
|
+
endedAt: NOW
|
|
1187
|
+
}
|
|
1188
|
+
},
|
|
1189
|
+
{
|
|
1190
|
+
title: "Died \xB7 Haiku \xB7 SO CLOSE",
|
|
1191
|
+
run: {
|
|
1192
|
+
id: "run_demo_6",
|
|
1193
|
+
quest: "fix N+1 query in dashboard loader",
|
|
1194
|
+
model: "claude-haiku-4-5-20251001",
|
|
1195
|
+
budget: 0.4,
|
|
1196
|
+
spent: 0.43,
|
|
1197
|
+
status: "died",
|
|
1198
|
+
mode: "roguelike",
|
|
1199
|
+
effort: null,
|
|
1200
|
+
fastMode: false,
|
|
1201
|
+
floor: 4,
|
|
1202
|
+
totalFloors: 5,
|
|
1203
|
+
promptCount: 9,
|
|
1204
|
+
totalToolCalls: 15,
|
|
1205
|
+
toolCalls: { Read: 7, Edit: 4, Bash: 3, Grep: 1 },
|
|
1206
|
+
failedToolCalls: 1,
|
|
1207
|
+
subagentSpawns: 0,
|
|
1208
|
+
turnCount: 12,
|
|
1209
|
+
thinkingInvocations: 0,
|
|
1210
|
+
thinkingTokens: 0,
|
|
1211
|
+
sessionCount: 1,
|
|
1212
|
+
fainted: false,
|
|
1213
|
+
compactionEvents: [],
|
|
1214
|
+
modelBreakdown: { "claude-haiku-4-5-20251001": 0.43 },
|
|
1215
|
+
achievements: [
|
|
1216
|
+
{ key: "so_close", label: "So Close \u2014 Died within 10% of budget", emoji: "\u{1F62D}" },
|
|
1217
|
+
{ key: "silent_death", label: "Silent Death \u2014 Died with 2 prompts", emoji: "\u{1FAA6}" }
|
|
1218
|
+
],
|
|
1219
|
+
startedAt: AGO(18),
|
|
1220
|
+
endedAt: NOW
|
|
1221
|
+
}
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
title: "Won \xB7 Paladin \xB7 Architect \xB7 multi-model breakdown",
|
|
1225
|
+
run: {
|
|
1226
|
+
id: "run_demo_7",
|
|
1227
|
+
quest: "design and implement webhook retry system",
|
|
1228
|
+
model: "opusplan",
|
|
1229
|
+
budget: 4.5,
|
|
1230
|
+
spent: 2.18,
|
|
1231
|
+
status: "won",
|
|
1232
|
+
mode: "roguelike",
|
|
1233
|
+
effort: "high",
|
|
1234
|
+
fastMode: false,
|
|
1235
|
+
floor: 5,
|
|
1236
|
+
totalFloors: 5,
|
|
1237
|
+
promptCount: 10,
|
|
1238
|
+
totalToolCalls: 28,
|
|
1239
|
+
toolCalls: { Read: 10, Edit: 7, Bash: 5, Grep: 3, Write: 2, Agent: 1 },
|
|
1240
|
+
failedToolCalls: 0,
|
|
1241
|
+
subagentSpawns: 1,
|
|
1242
|
+
turnCount: 20,
|
|
1243
|
+
thinkingInvocations: 2,
|
|
1244
|
+
thinkingTokens: 8100,
|
|
1245
|
+
sessionCount: 1,
|
|
1246
|
+
fainted: false,
|
|
1247
|
+
compactionEvents: [],
|
|
1248
|
+
modelBreakdown: {
|
|
1249
|
+
"claude-opus-4-6": 1.42,
|
|
1250
|
+
"claude-sonnet-4-6": 0.68,
|
|
1251
|
+
"claude-haiku-4-5-20251001": 0.08
|
|
1252
|
+
},
|
|
1253
|
+
achievements: [
|
|
1254
|
+
{ key: "paladin", label: "Paladin \u2014 Opus planning mode", emoji: "\u269C\uFE0F" },
|
|
1255
|
+
{ key: "efficient", label: "Efficient \u2014 Under 50% budget", emoji: "\u26A1" },
|
|
1256
|
+
{ key: "architect", label: "Architect \u2014 Opus handled 65% of cost", emoji: "\u{1F3DB}\uFE0F" },
|
|
1257
|
+
{ key: "spell_cast", label: "Spell Cast \u2014 Used extended thinking", emoji: "\u{1F52E}" },
|
|
1258
|
+
{ key: "clean_run", label: "Clean Run \u2014 Zero failed tool calls", emoji: "\u2705" },
|
|
1259
|
+
{ key: "toolbox", label: "Toolbox \u2014 6 distinct tool types", emoji: "\u{1F9F0}" }
|
|
1260
|
+
],
|
|
1261
|
+
startedAt: AGO(35),
|
|
1262
|
+
endedAt: NOW
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
];
|
|
1266
|
+
ACTIVERUN_FIXTURES = [
|
|
1267
|
+
{
|
|
1268
|
+
title: "Roguelike \xB7 Sonnet \xB7 early run \xB7 Floor 1",
|
|
1269
|
+
run: {
|
|
1270
|
+
id: "run_demo_a1",
|
|
1271
|
+
quest: "implement rate limiting middleware",
|
|
1272
|
+
model: "claude-sonnet-4-6",
|
|
1273
|
+
budget: 1.5,
|
|
1274
|
+
spent: 0.12,
|
|
1275
|
+
status: "active",
|
|
1276
|
+
mode: "roguelike",
|
|
1277
|
+
effort: "high",
|
|
1278
|
+
floor: 1,
|
|
1279
|
+
totalFloors: 5,
|
|
1280
|
+
promptCount: 3,
|
|
1281
|
+
totalToolCalls: 8,
|
|
1282
|
+
toolCalls: { Read: 4, Grep: 2, Glob: 2 },
|
|
1283
|
+
startedAt: AGO(8)
|
|
1284
|
+
}
|
|
1285
|
+
},
|
|
1286
|
+
{
|
|
1287
|
+
title: "Roguelike \xB7 Opus \xB7 mid-run \xB7 Floor 3 \xB7 budget warning",
|
|
1288
|
+
run: {
|
|
1289
|
+
id: "run_demo_a2",
|
|
1290
|
+
quest: "refactor database connection pooling",
|
|
1291
|
+
model: "claude-opus-4-6",
|
|
1292
|
+
budget: 7.5,
|
|
1293
|
+
spent: 6.34,
|
|
1294
|
+
status: "active",
|
|
1295
|
+
mode: "roguelike",
|
|
1296
|
+
effort: "max",
|
|
1297
|
+
floor: 3,
|
|
1298
|
+
totalFloors: 5,
|
|
1299
|
+
promptCount: 12,
|
|
1300
|
+
totalToolCalls: 35,
|
|
1301
|
+
toolCalls: { Read: 14, Edit: 9, Bash: 8, Grep: 3, Write: 1 },
|
|
1302
|
+
startedAt: AGO(42)
|
|
1303
|
+
}
|
|
1304
|
+
},
|
|
1305
|
+
{
|
|
1306
|
+
title: "Flow mode \xB7 Haiku \xB7 light session",
|
|
1307
|
+
run: {
|
|
1308
|
+
id: "run_demo_a3",
|
|
1309
|
+
quest: "Flow",
|
|
1310
|
+
model: "claude-haiku-4-5-20251001",
|
|
1311
|
+
budget: null,
|
|
1312
|
+
spent: 0.018,
|
|
1313
|
+
status: "active",
|
|
1314
|
+
mode: "flow",
|
|
1315
|
+
effort: null,
|
|
1316
|
+
floor: 1,
|
|
1317
|
+
totalFloors: 5,
|
|
1318
|
+
promptCount: 4,
|
|
1319
|
+
totalToolCalls: 11,
|
|
1320
|
+
toolCalls: { Read: 6, Grep: 3, Bash: 2 },
|
|
1321
|
+
startedAt: AGO(5)
|
|
1322
|
+
}
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
title: "Roguelike \xB7 Haiku \xB7 Floor 4 \xB7 CLOSE CALL",
|
|
1326
|
+
run: {
|
|
1327
|
+
id: "run_demo_a4",
|
|
1328
|
+
quest: "fix flaky test in CI pipeline",
|
|
1329
|
+
model: "claude-haiku-4-5-20251001",
|
|
1330
|
+
budget: 0.4,
|
|
1331
|
+
spent: 0.31,
|
|
1332
|
+
status: "active",
|
|
1333
|
+
mode: "roguelike",
|
|
1334
|
+
effort: null,
|
|
1335
|
+
floor: 4,
|
|
1336
|
+
totalFloors: 5,
|
|
1337
|
+
promptCount: 7,
|
|
1338
|
+
totalToolCalls: 16,
|
|
1339
|
+
toolCalls: { Read: 6, Bash: 5, Edit: 3, Grep: 2 },
|
|
1340
|
+
startedAt: AGO(15)
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
];
|
|
1344
|
+
STATS_FIXTURES = [
|
|
1345
|
+
{
|
|
1346
|
+
title: "Veteran player \u2014 24 runs, mixed results",
|
|
1347
|
+
stats: {
|
|
1348
|
+
total: 24,
|
|
1349
|
+
wins: 18,
|
|
1350
|
+
deaths: 6,
|
|
1351
|
+
winRate: 75,
|
|
1352
|
+
avgSpend: 1.42,
|
|
1353
|
+
bestRun: {
|
|
1354
|
+
quest: "fix typo in README badge URL",
|
|
1355
|
+
model: "claude-haiku-4-5-20251001",
|
|
1356
|
+
spent: 89e-4,
|
|
1357
|
+
budget: 0.15
|
|
1358
|
+
},
|
|
1359
|
+
recentRuns: [
|
|
1360
|
+
{
|
|
1361
|
+
status: "won",
|
|
1362
|
+
quest: "add pagination to /users",
|
|
1363
|
+
spent: 0.23,
|
|
1364
|
+
budget: 1.5,
|
|
1365
|
+
model: "claude-sonnet-4-6"
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
status: "won",
|
|
1369
|
+
quest: "refactor auth middleware",
|
|
1370
|
+
spent: 3.41,
|
|
1371
|
+
budget: 7.5,
|
|
1372
|
+
model: "claude-opus-4-6"
|
|
1373
|
+
},
|
|
1374
|
+
{
|
|
1375
|
+
status: "died",
|
|
1376
|
+
quest: "migrate postgres schema",
|
|
1377
|
+
spent: 3.87,
|
|
1378
|
+
budget: 1.5,
|
|
1379
|
+
model: "claude-sonnet-4-6"
|
|
1380
|
+
},
|
|
1381
|
+
{
|
|
1382
|
+
status: "won",
|
|
1383
|
+
quest: "fix N+1 query",
|
|
1384
|
+
spent: 0.08,
|
|
1385
|
+
budget: 0.4,
|
|
1386
|
+
model: "claude-haiku-4-5-20251001"
|
|
1387
|
+
},
|
|
1388
|
+
{ status: "won", quest: null, spent: 34e-4, model: "claude-sonnet-4-6" },
|
|
1389
|
+
{
|
|
1390
|
+
status: "won",
|
|
1391
|
+
quest: "webhook retry system",
|
|
1392
|
+
spent: 2.18,
|
|
1393
|
+
budget: 4.5,
|
|
1394
|
+
model: "opusplan"
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
status: "died",
|
|
1398
|
+
quest: "rewrite billing module",
|
|
1399
|
+
spent: 12.4,
|
|
1400
|
+
budget: 7.5,
|
|
1401
|
+
model: "claude-opus-4-6"
|
|
1402
|
+
},
|
|
1403
|
+
{
|
|
1404
|
+
status: "won",
|
|
1405
|
+
quest: "add dark mode toggle",
|
|
1406
|
+
spent: 0.41,
|
|
1407
|
+
budget: 1.5,
|
|
1408
|
+
model: "claude-sonnet-4-6"
|
|
1409
|
+
}
|
|
1410
|
+
],
|
|
1411
|
+
achievements: [
|
|
1412
|
+
{ emoji: "\u{1F3AF}", label: "Sniper" },
|
|
1413
|
+
{ emoji: "\u{1F948}", label: "Silver" },
|
|
1414
|
+
{ emoji: "\u{1F525}", label: "No Rest" },
|
|
1415
|
+
{ emoji: "\u2705", label: "Clean Run" },
|
|
1416
|
+
{ emoji: "\u{1F9F0}", label: "Toolbox" },
|
|
1417
|
+
{ emoji: "\u{1F48E}", label: "Diamond" },
|
|
1418
|
+
{ emoji: "\u{1F94A}", label: "One Shot" },
|
|
1419
|
+
{ emoji: "\u{1FA99}", label: "Penny Pincher" },
|
|
1420
|
+
{ emoji: "\u23F1\uFE0F", label: "Speedrun" },
|
|
1421
|
+
{ emoji: "\u{1F43A}", label: "Lone Wolf" },
|
|
1422
|
+
{ emoji: "\u{1F52A}", label: "Surgeon" },
|
|
1423
|
+
{ emoji: "\u26A1", label: "Efficient" }
|
|
1424
|
+
]
|
|
1425
|
+
}
|
|
1426
|
+
},
|
|
1427
|
+
{
|
|
1428
|
+
title: "New player \u2014 3 runs",
|
|
1429
|
+
stats: {
|
|
1430
|
+
total: 3,
|
|
1431
|
+
wins: 2,
|
|
1432
|
+
deaths: 1,
|
|
1433
|
+
winRate: 67,
|
|
1434
|
+
avgSpend: 0.89,
|
|
1435
|
+
bestRun: {
|
|
1436
|
+
quest: "add loading spinner",
|
|
1437
|
+
model: "claude-sonnet-4-6",
|
|
1438
|
+
spent: 0.34,
|
|
1439
|
+
budget: 1.5
|
|
1440
|
+
},
|
|
1441
|
+
recentRuns: [
|
|
1442
|
+
{
|
|
1443
|
+
status: "won",
|
|
1444
|
+
quest: "add loading spinner",
|
|
1445
|
+
spent: 0.34,
|
|
1446
|
+
budget: 1.5,
|
|
1447
|
+
model: "claude-sonnet-4-6"
|
|
1448
|
+
},
|
|
1449
|
+
{
|
|
1450
|
+
status: "died",
|
|
1451
|
+
quest: "setup auth flow",
|
|
1452
|
+
spent: 1.92,
|
|
1453
|
+
budget: 1.5,
|
|
1454
|
+
model: "claude-sonnet-4-6"
|
|
1455
|
+
},
|
|
1456
|
+
{ status: "won", quest: null, spent: 0.41, model: "claude-sonnet-4-6" }
|
|
1457
|
+
],
|
|
1458
|
+
achievements: [
|
|
1459
|
+
{ emoji: "\u{1F948}", label: "Silver" },
|
|
1460
|
+
{ emoji: "\u{1F525}", label: "No Rest" }
|
|
1461
|
+
]
|
|
1462
|
+
}
|
|
1463
|
+
},
|
|
1464
|
+
{
|
|
1465
|
+
title: "Empty \u2014 no runs yet",
|
|
1466
|
+
stats: {
|
|
1467
|
+
total: 0,
|
|
1468
|
+
wins: 0,
|
|
1469
|
+
deaths: 0,
|
|
1470
|
+
winRate: 0,
|
|
1471
|
+
avgSpend: 0,
|
|
1472
|
+
bestRun: null,
|
|
1473
|
+
recentRuns: [],
|
|
1474
|
+
achievements: []
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
];
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
1480
|
+
|
|
1481
|
+
// src/lib/demo-scorecard.js
|
|
1482
|
+
var demo_scorecard_exports = {};
|
|
1483
|
+
__export(demo_scorecard_exports, {
|
|
1484
|
+
runScoreCardDemo: () => runScoreCardDemo
|
|
1485
|
+
});
|
|
1486
|
+
import React5 from "react";
|
|
1487
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
1488
|
+
function DemoScoreCard({ fixture }) {
|
|
1489
|
+
return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box5, { paddingX: 1 }, /* @__PURE__ */ React5.createElement(Text5, { color: "gray", dimColor: true }, "\u2500".repeat(50))), /* @__PURE__ */ React5.createElement(Box5, { paddingX: 1 }, /* @__PURE__ */ React5.createElement(Text5, { color: "gray", dimColor: true, italic: true }, fixture.title)), /* @__PURE__ */ React5.createElement(ScoreCard, { run: fixture.run }));
|
|
1490
|
+
}
|
|
1491
|
+
function runScoreCardDemo(index) {
|
|
1492
|
+
const fixtures = index != null ? [SCORECARD_FIXTURES[index]] : SCORECARD_FIXTURES;
|
|
1493
|
+
if (!fixtures[0]) {
|
|
1494
|
+
console.log(
|
|
1495
|
+
`Invalid index. ${SCORECARD_FIXTURES.length} scorecards available (0-${SCORECARD_FIXTURES.length - 1}).`
|
|
1496
|
+
);
|
|
1497
|
+
process.exit(1);
|
|
1498
|
+
}
|
|
1499
|
+
console.log("");
|
|
1500
|
+
console.log("\x1B[1m\x1B[36m\u26F3 TokenGolf \u2014 ScoreCard Demo\x1B[0m");
|
|
1501
|
+
console.log(`\x1B[2m${fixtures.length} variant${fixtures.length > 1 ? "s" : ""}\x1B[0m`);
|
|
1502
|
+
return new Promise((resolve) => {
|
|
1503
|
+
let i = 0;
|
|
1504
|
+
function renderNext() {
|
|
1505
|
+
if (i >= fixtures.length) {
|
|
1506
|
+
resolve();
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
const fixture = fixtures[i];
|
|
1510
|
+
const inst = demoRender(React5.createElement(DemoScoreCard, { fixture }));
|
|
1511
|
+
setTimeout(() => {
|
|
1512
|
+
inst.unmount();
|
|
1513
|
+
i++;
|
|
1514
|
+
renderNext();
|
|
1515
|
+
}, 100);
|
|
1516
|
+
}
|
|
1517
|
+
renderNext();
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
var init_demo_scorecard = __esm({
|
|
1521
|
+
"src/lib/demo-scorecard.js"() {
|
|
1522
|
+
init_demo_render();
|
|
1523
|
+
init_ScoreCard();
|
|
1524
|
+
init_demo_fixtures();
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
// src/lib/demo-active.js
|
|
1529
|
+
var demo_active_exports = {};
|
|
1530
|
+
__export(demo_active_exports, {
|
|
1531
|
+
runActiveDemo: () => runActiveDemo
|
|
1532
|
+
});
|
|
1533
|
+
import React6 from "react";
|
|
1534
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
1535
|
+
function DemoActiveRun({ fixture }) {
|
|
1536
|
+
return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Box6, { paddingX: 1 }, /* @__PURE__ */ React6.createElement(Text6, { color: "gray", dimColor: true }, "\u2500".repeat(50))), /* @__PURE__ */ React6.createElement(Box6, { paddingX: 1 }, /* @__PURE__ */ React6.createElement(Text6, { color: "gray", dimColor: true, italic: true }, fixture.title)), /* @__PURE__ */ React6.createElement(ActiveRun, { run: fixture.run }));
|
|
1537
|
+
}
|
|
1538
|
+
function runActiveDemo(index) {
|
|
1539
|
+
const fixtures = index != null ? [ACTIVERUN_FIXTURES[index]] : ACTIVERUN_FIXTURES;
|
|
1540
|
+
if (!fixtures[0]) {
|
|
1541
|
+
console.log(
|
|
1542
|
+
`Invalid index. ${ACTIVERUN_FIXTURES.length} variants available (0-${ACTIVERUN_FIXTURES.length - 1}).`
|
|
1543
|
+
);
|
|
1544
|
+
process.exit(1);
|
|
1545
|
+
}
|
|
1546
|
+
console.log("");
|
|
1547
|
+
console.log("\x1B[1m\x1B[36m\u26F3 TokenGolf \u2014 ActiveRun Demo\x1B[0m");
|
|
1548
|
+
console.log(`\x1B[2m${fixtures.length} variant${fixtures.length > 1 ? "s" : ""}\x1B[0m`);
|
|
1549
|
+
return new Promise((resolve) => {
|
|
1550
|
+
let i = 0;
|
|
1551
|
+
function renderNext() {
|
|
1552
|
+
if (i >= fixtures.length) {
|
|
1553
|
+
resolve();
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
const fixture = fixtures[i];
|
|
1557
|
+
const inst = demoRender(React6.createElement(DemoActiveRun, { fixture }));
|
|
1558
|
+
setTimeout(() => {
|
|
1559
|
+
inst.unmount();
|
|
1560
|
+
i++;
|
|
1561
|
+
renderNext();
|
|
1562
|
+
}, 100);
|
|
1563
|
+
}
|
|
1564
|
+
renderNext();
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
var init_demo_active = __esm({
|
|
1568
|
+
"src/lib/demo-active.js"() {
|
|
1569
|
+
init_demo_render();
|
|
1570
|
+
init_ActiveRun();
|
|
1571
|
+
init_demo_fixtures();
|
|
1572
|
+
}
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
// src/lib/demo-stats.js
|
|
1576
|
+
var demo_stats_exports = {};
|
|
1577
|
+
__export(demo_stats_exports, {
|
|
1578
|
+
runStatsDemo: () => runStatsDemo
|
|
1579
|
+
});
|
|
1580
|
+
import React7 from "react";
|
|
1581
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
1582
|
+
function DemoStatsView({ fixture }) {
|
|
1583
|
+
return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Box7, { paddingX: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "gray", dimColor: true }, "\u2500".repeat(50))), /* @__PURE__ */ React7.createElement(Box7, { paddingX: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "gray", dimColor: true, italic: true }, fixture.title)), /* @__PURE__ */ React7.createElement(StatsView, { stats: fixture.stats }));
|
|
1584
|
+
}
|
|
1585
|
+
function runStatsDemo(index) {
|
|
1586
|
+
const fixtures = index != null ? [STATS_FIXTURES[index]] : STATS_FIXTURES;
|
|
1587
|
+
if (!fixtures[0]) {
|
|
1588
|
+
console.log(
|
|
1589
|
+
`Invalid index. ${STATS_FIXTURES.length} variants available (0-${STATS_FIXTURES.length - 1}).`
|
|
1590
|
+
);
|
|
1591
|
+
process.exit(1);
|
|
1592
|
+
}
|
|
1593
|
+
console.log("");
|
|
1594
|
+
console.log("\x1B[1m\x1B[36m\u26F3 TokenGolf \u2014 StatsView Demo\x1B[0m");
|
|
1595
|
+
console.log(`\x1B[2m${fixtures.length} variant${fixtures.length > 1 ? "s" : ""}\x1B[0m`);
|
|
1596
|
+
return new Promise((resolve) => {
|
|
1597
|
+
let i = 0;
|
|
1598
|
+
function renderNext() {
|
|
1599
|
+
if (i >= fixtures.length) {
|
|
1600
|
+
resolve();
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
const fixture = fixtures[i];
|
|
1604
|
+
const inst = demoRender(React7.createElement(DemoStatsView, { fixture }));
|
|
1605
|
+
setTimeout(() => {
|
|
1606
|
+
inst.unmount();
|
|
1607
|
+
i++;
|
|
1608
|
+
renderNext();
|
|
1609
|
+
}, 100);
|
|
1610
|
+
}
|
|
1611
|
+
renderNext();
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
var init_demo_stats = __esm({
|
|
1615
|
+
"src/lib/demo-stats.js"() {
|
|
1616
|
+
init_demo_render();
|
|
1617
|
+
init_StatsView();
|
|
1618
|
+
init_demo_fixtures();
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
|
|
1622
|
+
// src/lib/install.js
|
|
1623
|
+
var install_exports = {};
|
|
1624
|
+
__export(install_exports, {
|
|
1625
|
+
installHooks: () => installHooks
|
|
1626
|
+
});
|
|
1627
|
+
import fs4 from "fs";
|
|
1628
|
+
import path4 from "path";
|
|
1629
|
+
import os3 from "os";
|
|
1630
|
+
function installHooks() {
|
|
1631
|
+
console.log("\n\u26F3 TokenGolf \u2014 Installing Claude Code hooks\n");
|
|
1632
|
+
let settings = {};
|
|
1633
|
+
if (fs4.existsSync(CLAUDE_SETTINGS)) {
|
|
1634
|
+
try {
|
|
1635
|
+
settings = JSON.parse(fs4.readFileSync(CLAUDE_SETTINGS, "utf8"));
|
|
1636
|
+
console.log(" \u2713 Found ~/.claude/settings.json");
|
|
1637
|
+
} catch {
|
|
1638
|
+
console.log(" \u26A0\uFE0F Could not parse settings.json \u2014 starting fresh");
|
|
1639
|
+
}
|
|
1640
|
+
} else {
|
|
1641
|
+
fs4.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
1642
|
+
console.log(" \u2139\uFE0F Creating ~/.claude/settings.json");
|
|
1643
|
+
}
|
|
1644
|
+
if (!settings.hooks) settings.hooks = {};
|
|
1645
|
+
function upsertHook(event, entry) {
|
|
1646
|
+
const existing2 = settings.hooks[event] || [];
|
|
1647
|
+
const filtered = existing2.filter(
|
|
1648
|
+
(h) => !h._tg && !h.hooks?.some(
|
|
1649
|
+
(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")
|
|
1650
|
+
)
|
|
1651
|
+
);
|
|
1652
|
+
settings.hooks[event] = [...filtered, { _tg: true, ...entry }];
|
|
1653
|
+
}
|
|
1654
|
+
if (settings.hooks.Stop) {
|
|
1655
|
+
settings.hooks.Stop = (settings.hooks.Stop || []).filter(
|
|
1656
|
+
(h) => !h._tg && !h.hooks?.some((e) => e.command?.includes("session-stop.js"))
|
|
1657
|
+
);
|
|
1658
|
+
if (settings.hooks.Stop.length === 0) delete settings.hooks.Stop;
|
|
1659
|
+
}
|
|
1660
|
+
upsertHook("SessionStart", {
|
|
1661
|
+
hooks: [
|
|
1662
|
+
{
|
|
1663
|
+
type: "command",
|
|
1664
|
+
command: `node ${path4.join(HOOKS_DIR, "session-start.js")}`,
|
|
1665
|
+
timeout: 5
|
|
1666
|
+
}
|
|
1667
|
+
]
|
|
1668
|
+
});
|
|
1669
|
+
upsertHook("PostToolUse", {
|
|
1670
|
+
matcher: "",
|
|
1671
|
+
hooks: [
|
|
1672
|
+
{
|
|
1673
|
+
type: "command",
|
|
1674
|
+
command: `node ${path4.join(HOOKS_DIR, "post-tool-use.js")}`,
|
|
1675
|
+
timeout: 5
|
|
1676
|
+
}
|
|
1677
|
+
]
|
|
1678
|
+
});
|
|
1679
|
+
upsertHook("UserPromptSubmit", {
|
|
1680
|
+
hooks: [
|
|
1681
|
+
{
|
|
1682
|
+
type: "command",
|
|
1683
|
+
command: `node ${path4.join(HOOKS_DIR, "user-prompt-submit.js")}`,
|
|
1684
|
+
timeout: 5
|
|
1685
|
+
}
|
|
1686
|
+
]
|
|
1687
|
+
});
|
|
1688
|
+
upsertHook("SessionEnd", {
|
|
1689
|
+
hooks: [
|
|
1690
|
+
{
|
|
1691
|
+
type: "command",
|
|
1692
|
+
command: `node ${path4.join(HOOKS_DIR, "session-end.js")}`,
|
|
1693
|
+
timeout: 30
|
|
1694
|
+
}
|
|
1695
|
+
]
|
|
1696
|
+
});
|
|
1697
|
+
upsertHook("PreCompact", {
|
|
1698
|
+
hooks: [
|
|
1699
|
+
{
|
|
1700
|
+
type: "command",
|
|
1701
|
+
command: `node ${path4.join(HOOKS_DIR, "pre-compact.js")}`,
|
|
1702
|
+
timeout: 5
|
|
1703
|
+
}
|
|
1704
|
+
]
|
|
1705
|
+
});
|
|
1706
|
+
upsertHook("PostToolUseFailure", {
|
|
1707
|
+
matcher: "",
|
|
1708
|
+
hooks: [
|
|
1709
|
+
{
|
|
1710
|
+
type: "command",
|
|
1711
|
+
command: `node ${path4.join(HOOKS_DIR, "post-tool-use-failure.js")}`,
|
|
1712
|
+
timeout: 5
|
|
1713
|
+
}
|
|
1714
|
+
]
|
|
1715
|
+
});
|
|
1716
|
+
upsertHook("SubagentStart", {
|
|
1717
|
+
hooks: [
|
|
1718
|
+
{
|
|
1719
|
+
type: "command",
|
|
1720
|
+
command: `node ${path4.join(HOOKS_DIR, "subagent-start.js")}`,
|
|
1721
|
+
timeout: 5
|
|
1722
|
+
}
|
|
1723
|
+
]
|
|
1724
|
+
});
|
|
1725
|
+
upsertHook("Stop", {
|
|
1726
|
+
hooks: [
|
|
1727
|
+
{
|
|
1728
|
+
type: "command",
|
|
1729
|
+
command: `node ${path4.join(HOOKS_DIR, "stop.js")}`,
|
|
1730
|
+
timeout: 5
|
|
1731
|
+
}
|
|
1732
|
+
]
|
|
1733
|
+
});
|
|
1734
|
+
try {
|
|
1735
|
+
fs4.chmodSync(STATUSLINE_PATH, 493);
|
|
1736
|
+
} catch {
|
|
1737
|
+
}
|
|
1738
|
+
const existing = settings.statusLine;
|
|
1739
|
+
const existingCmd = typeof existing === "string" ? existing : existing?.command ?? null;
|
|
1740
|
+
const alreadyOurs = existingCmd === STATUSLINE_PATH || existingCmd === WRAPPER_PATH;
|
|
1741
|
+
if (!alreadyOurs && existingCmd) {
|
|
1742
|
+
fs4.writeFileSync(
|
|
1743
|
+
WRAPPER_PATH,
|
|
1744
|
+
[
|
|
1745
|
+
"#!/usr/bin/env bash",
|
|
1746
|
+
"SESSION_JSON=$(cat)",
|
|
1747
|
+
`echo "$SESSION_JSON" | ${existingCmd} 2>/dev/null || true`,
|
|
1748
|
+
`echo "$SESSION_JSON" | bash ${STATUSLINE_PATH}`
|
|
1749
|
+
].join("\n") + "\n"
|
|
1750
|
+
);
|
|
1751
|
+
fs4.chmodSync(WRAPPER_PATH, 493);
|
|
1752
|
+
settings.statusLine = {
|
|
1753
|
+
type: "command",
|
|
1754
|
+
command: WRAPPER_PATH,
|
|
1755
|
+
padding: 1
|
|
1756
|
+
};
|
|
1757
|
+
console.log(" \u2713 statusLine \u2192 wrapped your existing statusline + tokengolf HUD");
|
|
1758
|
+
} else if (!alreadyOurs) {
|
|
1759
|
+
settings.statusLine = {
|
|
1760
|
+
type: "command",
|
|
1761
|
+
command: STATUSLINE_PATH,
|
|
1762
|
+
padding: 1
|
|
1763
|
+
};
|
|
1764
|
+
console.log(" \u2713 statusLine \u2192 live HUD in every Claude session");
|
|
1765
|
+
} else {
|
|
1766
|
+
console.log(" \u2713 statusLine \u2192 already installed");
|
|
1767
|
+
}
|
|
1768
|
+
fs4.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2));
|
|
1769
|
+
console.log(" \u2713 SessionStart \u2192 injects run context into Claude");
|
|
1770
|
+
console.log(" \u2713 PostToolUse \u2192 tracks tool calls + 80% budget warning");
|
|
1771
|
+
console.log(" \u2713 UserPromptSubmit \u2192 counts prompts + 50% nudge");
|
|
1772
|
+
console.log(" \u2713 SessionEnd \u2192 auto-displays scorecard on /exit");
|
|
1773
|
+
console.log(" \u2713 PreCompact \u2192 tracks compaction events for gear achievements");
|
|
1774
|
+
console.log(" \u2713 PostToolUseFailure \u2192 tracks failed tool calls");
|
|
1775
|
+
console.log(" \u2713 SubagentStart \u2192 tracks subagent spawns");
|
|
1776
|
+
console.log(" \u2713 Stop \u2192 tracks turn count");
|
|
1777
|
+
console.log("\n \u2705 Done! Start a run: tokengolf start\n");
|
|
966
1778
|
}
|
|
1779
|
+
var realEntry, HOOKS_DIR, STATUSLINE_PATH, WRAPPER_PATH, CLAUDE_DIR, CLAUDE_SETTINGS;
|
|
1780
|
+
var init_install = __esm({
|
|
1781
|
+
"src/lib/install.js"() {
|
|
1782
|
+
realEntry = fs4.realpathSync(process.argv[1]);
|
|
1783
|
+
HOOKS_DIR = path4.resolve(path4.dirname(realEntry), "../hooks");
|
|
1784
|
+
STATUSLINE_PATH = path4.join(HOOKS_DIR, "statusline.sh");
|
|
1785
|
+
WRAPPER_PATH = path4.join(HOOKS_DIR, "statusline-wrapper.sh");
|
|
1786
|
+
CLAUDE_DIR = path4.join(os3.homedir(), ".claude");
|
|
1787
|
+
CLAUDE_SETTINGS = path4.join(CLAUDE_DIR, "settings.json");
|
|
1788
|
+
}
|
|
1789
|
+
});
|
|
1790
|
+
|
|
1791
|
+
// src/cli.js
|
|
1792
|
+
init_state();
|
|
1793
|
+
import { program } from "commander";
|
|
1794
|
+
import { render as render2 } from "ink";
|
|
1795
|
+
import React8 from "react";
|
|
967
1796
|
|
|
968
1797
|
// src/lib/store.js
|
|
1798
|
+
init_score();
|
|
1799
|
+
init_state();
|
|
1800
|
+
import fs2 from "fs";
|
|
1801
|
+
import path2 from "path";
|
|
969
1802
|
var RUNS_FILE = path2.join(STATE_DIR, "runs.json");
|
|
970
1803
|
function ensureDir2() {
|
|
971
1804
|
if (!fs2.existsSync(STATE_DIR)) fs2.mkdirSync(STATE_DIR, { recursive: true });
|
|
@@ -1185,6 +2018,8 @@ function autoDetectCost(run) {
|
|
|
1185
2018
|
}
|
|
1186
2019
|
|
|
1187
2020
|
// src/components/StartRun.js
|
|
2021
|
+
init_state();
|
|
2022
|
+
init_score();
|
|
1188
2023
|
import React, { useState } from "react";
|
|
1189
2024
|
import { Box, Text, useApp } from "ink";
|
|
1190
2025
|
import { TextInput, Select, ConfirmInput } from "@inkjs/ui";
|
|
@@ -1347,164 +2182,13 @@ function StartRun() {
|
|
|
1347
2182
|
), 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."));
|
|
1348
2183
|
}
|
|
1349
2184
|
|
|
1350
|
-
// src/components/ActiveRun.js
|
|
1351
|
-
import React2, { useState as useState2, useEffect } from "react";
|
|
1352
|
-
import { Box as Box2, Text as Text2, useApp as useApp2, useInput } from "ink";
|
|
1353
|
-
import { ProgressBar } from "@inkjs/ui";
|
|
1354
|
-
function ActiveRun({ run: initialRun }) {
|
|
1355
|
-
const { exit } = useApp2();
|
|
1356
|
-
const [run, setRun] = useState2(initialRun);
|
|
1357
|
-
const [tick, setTick] = useState2(0);
|
|
1358
|
-
useEffect(() => {
|
|
1359
|
-
const interval = setInterval(() => {
|
|
1360
|
-
const latest = getCurrentRun();
|
|
1361
|
-
if (latest) setRun(latest);
|
|
1362
|
-
setTick((t) => t + 1);
|
|
1363
|
-
}, 2e3);
|
|
1364
|
-
return () => clearInterval(interval);
|
|
1365
|
-
}, []);
|
|
1366
|
-
useInput((input) => {
|
|
1367
|
-
if (input === "q") exit();
|
|
1368
|
-
});
|
|
1369
|
-
const flowMode = !run.budget;
|
|
1370
|
-
const mc = getModelClass(run.model);
|
|
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"));
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
// src/components/ScoreCard.js
|
|
1409
|
-
import React3, { useEffect as useEffect2 } from "react";
|
|
1410
|
-
import { Box as Box3, Text as Text3, useApp as useApp3, useInput as useInput2 } from "ink";
|
|
1411
|
-
function ScoreCard({ run }) {
|
|
1412
|
-
const { exit } = useApp3();
|
|
1413
|
-
const won = run.status === "won";
|
|
1414
|
-
useInput2((input) => {
|
|
1415
|
-
if (input === "q") exit();
|
|
1416
|
-
});
|
|
1417
|
-
useEffect2(() => {
|
|
1418
|
-
const t = setTimeout(() => exit(), 6e4);
|
|
1419
|
-
return () => clearTimeout(t);
|
|
1420
|
-
}, [exit]);
|
|
1421
|
-
const tier = getTier(run.spent);
|
|
1422
|
-
const mc = getModelClass(run.model);
|
|
1423
|
-
const flowMode = !run.budget;
|
|
1424
|
-
const efficiency = flowMode ? null : getEfficiencyRating(run.spent, run.budget);
|
|
1425
|
-
const pct = flowMode ? null : getBudgetPct(run.spent, run.budget);
|
|
1426
|
-
const haikuPct = getHaikuPct(run.modelBreakdown, run.spent);
|
|
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")));
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
// src/components/StatsView.js
|
|
1470
|
-
import React4 from "react";
|
|
1471
|
-
import { Box as Box4, Text as Text4, useApp as useApp4, useInput as useInput3 } from "ink";
|
|
1472
|
-
function StatsView({ stats }) {
|
|
1473
|
-
const { exit } = useApp4();
|
|
1474
|
-
useInput3((input) => {
|
|
1475
|
-
if (input === "q") exit();
|
|
1476
|
-
});
|
|
1477
|
-
if (stats.total === 0) {
|
|
1478
|
-
return /* @__PURE__ */ React4.createElement(Box4, { paddingX: 1, paddingY: 1, flexDirection: "column", gap: 1 }, /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: "yellow" }, "\u26F3 TokenGolf Stats"), /* @__PURE__ */ React4.createElement(Text4, { color: "gray" }, "No completed runs yet."), /* @__PURE__ */ React4.createElement(Text4, { color: "gray" }, "Start one: ", /* @__PURE__ */ React4.createElement(Text4, { color: "cyan" }, "tokengolf start")));
|
|
1479
|
-
}
|
|
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 && (() => {
|
|
1481
|
-
const bestTier = getTier(stats.bestRun.spent);
|
|
1482
|
-
const bestMc = getModelClass(stats.bestRun.model);
|
|
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
|
-
));
|
|
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) => {
|
|
1496
|
-
const won = run.status === "won";
|
|
1497
|
-
const tier = getTier(run.spent);
|
|
1498
|
-
const mc = getModelClass(run.model);
|
|
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, "%"));
|
|
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"));
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
2185
|
// src/cli.js
|
|
2186
|
+
init_ActiveRun();
|
|
2187
|
+
init_ScoreCard();
|
|
2188
|
+
init_StatsView();
|
|
1505
2189
|
program.name("tokengolf").description("\u26F3 Gamify your Claude Code sessions").version("0.1.0");
|
|
1506
2190
|
program.command("start").description("Declare a quest and start a new run").action(() => {
|
|
1507
|
-
|
|
2191
|
+
render2(React8.createElement(StartRun));
|
|
1508
2192
|
});
|
|
1509
2193
|
program.command("status").description("Show current run status").action(() => {
|
|
1510
2194
|
const run = getCurrentRun();
|
|
@@ -1512,7 +2196,7 @@ program.command("status").description("Show current run status").action(() => {
|
|
|
1512
2196
|
console.log("No active run. Start one with: tokengolf start");
|
|
1513
2197
|
process.exit(0);
|
|
1514
2198
|
}
|
|
1515
|
-
|
|
2199
|
+
render2(React8.createElement(ActiveRun, { run }));
|
|
1516
2200
|
});
|
|
1517
2201
|
program.command("win").description("Mark current run as complete (won)").option("--spent <amount>", "How much did you spend? (e.g. 0.18)").action((opts) => {
|
|
1518
2202
|
const run = getCurrentRun();
|
|
@@ -1531,7 +2215,7 @@ program.command("win").description("Mark current run as complete (won)").option(
|
|
|
1531
2215
|
};
|
|
1532
2216
|
const saved = saveRun(completed);
|
|
1533
2217
|
clearCurrentRun();
|
|
1534
|
-
|
|
2218
|
+
render2(React8.createElement(ScoreCard, { run: saved }));
|
|
1535
2219
|
});
|
|
1536
2220
|
program.command("bust").description("Mark current run as budget busted (died)").option("--spent <amount>", "How much did you spend? (e.g. 0.45)").action((opts) => {
|
|
1537
2221
|
const run = getCurrentRun();
|
|
@@ -1550,7 +2234,7 @@ program.command("bust").description("Mark current run as budget busted (died)").
|
|
|
1550
2234
|
};
|
|
1551
2235
|
const saved = saveRun(died);
|
|
1552
2236
|
clearCurrentRun();
|
|
1553
|
-
|
|
2237
|
+
render2(React8.createElement(ScoreCard, { run: saved }));
|
|
1554
2238
|
});
|
|
1555
2239
|
program.command("floor").description("Advance to the next floor").action(() => {
|
|
1556
2240
|
const run = getCurrentRun();
|
|
@@ -1568,14 +2252,47 @@ program.command("scorecard").description("Show the last run scorecard").action((
|
|
|
1568
2252
|
console.log("No runs yet. Start one with: tokengolf start");
|
|
1569
2253
|
process.exit(0);
|
|
1570
2254
|
}
|
|
1571
|
-
|
|
2255
|
+
render2(React8.createElement(ScoreCard, { run }));
|
|
1572
2256
|
});
|
|
1573
2257
|
program.command("stats").description("Show career stats dashboard").action(() => {
|
|
1574
|
-
|
|
2258
|
+
render2(React8.createElement(StatsView, { stats: getStats() }));
|
|
1575
2259
|
});
|
|
1576
|
-
program.command("demo").description("Show
|
|
1577
|
-
const
|
|
1578
|
-
|
|
2260
|
+
program.command("demo [component]").description("Show UI demos \u2014 all, hud, scorecard, active, stats").option("-i, --index <n>", "Show only the Nth variant (0-based)").action(async (component, opts) => {
|
|
2261
|
+
const idx = opts.index != null ? parseInt(opts.index) : void 0;
|
|
2262
|
+
const c = (component || "all").toLowerCase();
|
|
2263
|
+
if (c === "all") {
|
|
2264
|
+
const { runDemo: runDemo2 } = await Promise.resolve().then(() => (init_demo(), demo_exports));
|
|
2265
|
+
runDemo2();
|
|
2266
|
+
const { runScoreCardDemo: runScoreCardDemo2 } = await Promise.resolve().then(() => (init_demo_scorecard(), demo_scorecard_exports));
|
|
2267
|
+
await runScoreCardDemo2(idx);
|
|
2268
|
+
const { runActiveDemo: runActiveDemo2 } = await Promise.resolve().then(() => (init_demo_active(), demo_active_exports));
|
|
2269
|
+
await runActiveDemo2(idx);
|
|
2270
|
+
const { runStatsDemo: runStatsDemo2 } = await Promise.resolve().then(() => (init_demo_stats(), demo_stats_exports));
|
|
2271
|
+
await runStatsDemo2(idx);
|
|
2272
|
+
process.exit(0);
|
|
2273
|
+
}
|
|
2274
|
+
if (c === "hud") {
|
|
2275
|
+
const { runDemo: runDemo2 } = await Promise.resolve().then(() => (init_demo(), demo_exports));
|
|
2276
|
+
runDemo2();
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
if (c === "scorecard") {
|
|
2280
|
+
const { runScoreCardDemo: runScoreCardDemo2 } = await Promise.resolve().then(() => (init_demo_scorecard(), demo_scorecard_exports));
|
|
2281
|
+
await runScoreCardDemo2(idx);
|
|
2282
|
+
process.exit(0);
|
|
2283
|
+
}
|
|
2284
|
+
if (c === "active") {
|
|
2285
|
+
const { runActiveDemo: runActiveDemo2 } = await Promise.resolve().then(() => (init_demo_active(), demo_active_exports));
|
|
2286
|
+
await runActiveDemo2(idx);
|
|
2287
|
+
process.exit(0);
|
|
2288
|
+
}
|
|
2289
|
+
if (c === "stats") {
|
|
2290
|
+
const { runStatsDemo: runStatsDemo2 } = await Promise.resolve().then(() => (init_demo_stats(), demo_stats_exports));
|
|
2291
|
+
await runStatsDemo2(idx);
|
|
2292
|
+
process.exit(0);
|
|
2293
|
+
}
|
|
2294
|
+
console.log("Unknown demo component. Choose: all, hud, scorecard, active, stats");
|
|
2295
|
+
process.exit(1);
|
|
1579
2296
|
});
|
|
1580
2297
|
program.command("install").description("Install Claude Code hooks into ~/.claude/settings.json").action(async () => {
|
|
1581
2298
|
const { installHooks: installHooks2 } = await Promise.resolve().then(() => (init_install(), install_exports));
|