tokengolf 1.0.4 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +15 -0
- package/README.md +13 -6
- package/dist/cli.js +104 -46
- package/docs/index.html +20 -12
- package/hooks/post-tool-use-failure.js +2 -1
- package/hooks/post-tool-use.js +2 -1
- package/hooks/pre-compact.js +2 -1
- package/hooks/session-end.js +4 -4
- package/hooks/session-start.js +38 -13
- package/hooks/statusline.sh +22 -6
- package/hooks/stop.js +2 -1
- package/hooks/subagent-start.js +2 -1
- package/hooks/user-prompt-submit.js +2 -1
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/scripts/post-tool-use-failure.js +2 -1
- package/plugin/scripts/post-tool-use.js +2 -1
- package/plugin/scripts/pre-compact.js +2 -1
- package/plugin/scripts/session-end.js +6 -3
- package/plugin/scripts/session-start.js +38 -13
- package/plugin/scripts/statusline.sh +22 -6
- package/plugin/scripts/stop.js +2 -1
- package/plugin/scripts/subagent-start.js +2 -1
- package/plugin/scripts/user-prompt-submit.js +2 -1
- package/src/lib/demo.js +75 -19
- package/src/lib/install.js +29 -21
- package/src/lib/state.js +2 -1
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"name": "tokengolf",
|
|
12
12
|
"source": "./plugin",
|
|
13
13
|
"description": "Gamify your Claude Code sessions — track token efficiency, earn achievements, level up your prompting.",
|
|
14
|
-
"version": "1.0
|
|
14
|
+
"version": "1.1.0",
|
|
15
15
|
"homepage": "https://josheche.github.io/tokengolf/",
|
|
16
16
|
"license": "MIT"
|
|
17
17
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@ TokenGolf patch notes — what changed, what it measures, and why the mechanic e
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## [1.1.0] — 2026-03-18
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- **3-line statusline HUD** — New top line shows efficiency rating + project name + git branch/dirty status (`📂 myapp ⎇ main ✓`). Rating moved from line 1 to its own line for cleaner layout.
|
|
15
|
+
- **Per-project state isolation** — Run state is now keyed by working directory (`current-run-{cwd}.json`). Concurrent Claude Code sessions in different repos no longer collide. Migration from global `current-run.json` is automatic.
|
|
16
|
+
- **Stable statusline path** — `statusline.sh` is copied to `~/.tokengolf/statusline.sh` on every session start. Survives plugin uninstall and npm removal (exits cleanly with no active run). Wrapper script also writes to stable path.
|
|
17
|
+
- **python3 availability guard** — `statusline.sh` checks for python3 before invoking it. Graceful silent exit on systems without python3.
|
|
18
|
+
- **Plugin build in CI pipeline** — `npm run prepare` now includes `build:plugin` so the plugin bundle can't ship stale.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **npm-only auto-sync guard** — Early bail via `fs.existsSync(pkgPath)` so plugin users skip 50 lines of dead auto-sync code on every session start.
|
|
22
|
+
- **install.js error handling** — `copyFileSync` and version stamp wrapped in try-catch with user-facing warnings instead of raw stack traces.
|
|
23
|
+
|
|
9
24
|
### Changed
|
|
10
25
|
- **Sublinear par scaling (sqrt)** — Par formula changed from `prompts × rate` to `rate × sqrt(prompts)`. Early prompts have headroom for exploration; pressure builds as the session goes on. Long wasteful sessions bust. Rates recalibrated: Haiku $0.55, Sonnet $7.00, Paladin $22.00, Opus $45.00. Floors unchanged. All models bust around 20 prompts at typical per-prompt spend.
|
|
11
26
|
- **Design D HUD** — StatusLine HUD redesigned with `██` accent bar, inline `▓░` progress bars for budget and context, no separator lines. 1 line when context <50%, 2 lines when context visible. Accent bar turns red when budget >75%. Matches Design D across all UI surfaces.
|
package/README.md
CHANGED
|
@@ -289,17 +289,21 @@ Thresholds scale per model — Haiku Diamond is $0.15, Opus Diamond is $2.50. Sa
|
|
|
289
289
|
|
|
290
290
|
## Live HUD
|
|
291
291
|
|
|
292
|
-
After install, a status
|
|
292
|
+
After install, a 3-line status bar appears in every Claude Code session showing efficiency rating, project info, cost, emotion, model, and context load.
|
|
293
293
|
|
|
294
294
|
```
|
|
295
|
-
██
|
|
295
|
+
██ 🌟 LEGENDARY 📂 myapp ⎇ main ✓
|
|
296
|
+
██ 😎 VIBING 💎 $0.42/9.90 ▓░░░░░░░░░░ 4%
|
|
296
297
|
██ ⚔️ Sonnet 🪶 ▓░░░░░░░░░ 8%
|
|
297
298
|
|
|
298
|
-
██
|
|
299
|
+
██ 💪 PRO 📂 api-server ⎇ feat/auth ●
|
|
300
|
+
██ 😤 GRINDING 🥉 $6.80/19.80 ▓▓▓▓░░░░░░░ 34%
|
|
299
301
|
██ ⚔️ Sonnet 📚 ▓▓▓░░░░░░░ 34%
|
|
300
302
|
```
|
|
301
303
|
|
|
302
|
-
|
|
304
|
+
**Line 1**: Efficiency rating + project name + git branch (✓ clean, ● dirty). **Line 2**: Emotion + cost vs par + progress bar. **Line 3**: Model class + context weight (**🪶** · **📚** · **🎒** · **🧱** · **🪨** · **🗿** — feather to stone). Accent `██` color matches your efficiency tier. **💤** replaces the emotion icon when fainted.
|
|
305
|
+
|
|
306
|
+
Emotions are a composite signal: par %, context %, failed tools, prompt count. From 😎 VIBING through 😤 GRINDING to 🧟 ZOMBIE (past bust). Three display modes: `tokengolf config emotions emoji` (default), `ascii` (kaomoji + label on a 4th line), or `off`.
|
|
303
307
|
|
|
304
308
|
---
|
|
305
309
|
|
|
@@ -407,8 +411,11 @@ Installed automatically via plugin, or manually via `tokengolf install` (npm):
|
|
|
407
411
|
|
|
408
412
|
All data lives in `~/.tokengolf/`:
|
|
409
413
|
|
|
410
|
-
- `current-run.json` — active run
|
|
411
|
-
- `
|
|
414
|
+
- `current-run-{cwd}.json` — active run (per-project, so concurrent sessions in different repos don't collide)
|
|
415
|
+
- `session-cost-{cwd}` — cost sidecar from statusline (per-project)
|
|
416
|
+
- `runs.json` — completed run history (shared)
|
|
417
|
+
- `config.json` — user preferences (emotion mode, custom par rates)
|
|
418
|
+
- `statusline.sh` — stable copy of the HUD script (survives plugin/npm uninstall)
|
|
412
419
|
|
|
413
420
|
No database, no native deps, no compilation.
|
|
414
421
|
|
package/dist/cli.js
CHANGED
|
@@ -144,11 +144,12 @@ var init_score = __esm({
|
|
|
144
144
|
// src/lib/state.js
|
|
145
145
|
import path from "path";
|
|
146
146
|
import os from "os";
|
|
147
|
-
var STATE_DIR, STATE_FILE;
|
|
147
|
+
var STATE_DIR, cwdKey, STATE_FILE;
|
|
148
148
|
var init_state = __esm({
|
|
149
149
|
"src/lib/state.js"() {
|
|
150
150
|
STATE_DIR = path.join(os.homedir(), ".tokengolf");
|
|
151
|
-
|
|
151
|
+
cwdKey = (process.env.PWD || process.cwd()).replace(/\//g, "-");
|
|
152
|
+
STATE_FILE = path.join(STATE_DIR, `current-run${cwdKey}.json`);
|
|
152
153
|
}
|
|
153
154
|
});
|
|
154
155
|
|
|
@@ -367,7 +368,18 @@ function getPar(modelName, promptCount) {
|
|
|
367
368
|
PAR_FLOORS[modelName] || 3
|
|
368
369
|
);
|
|
369
370
|
}
|
|
370
|
-
function hudLine({
|
|
371
|
+
function hudLine({
|
|
372
|
+
model,
|
|
373
|
+
cost,
|
|
374
|
+
prompts,
|
|
375
|
+
ctxPct,
|
|
376
|
+
effort,
|
|
377
|
+
fainted,
|
|
378
|
+
emotionKey,
|
|
379
|
+
project,
|
|
380
|
+
gitBranch,
|
|
381
|
+
gitDirty
|
|
382
|
+
}) {
|
|
371
383
|
const m = (model || "").toLowerCase();
|
|
372
384
|
let modelName, modelEmoji;
|
|
373
385
|
if (m.includes("haiku")) {
|
|
@@ -432,7 +444,6 @@ function hudLine({ model, cost, prompts, ctxPct, effort, fainted, emotionKey })
|
|
|
432
444
|
const barEmpty = barW - barFilled;
|
|
433
445
|
const bar = `${accent}${"\u2593".repeat(barFilled)}${"\u2591".repeat(barEmpty)}${RESET}`;
|
|
434
446
|
const costStr = `${tierEmoji} ${DIM}$${RESET}${cost.toFixed(2)}${DIM}/${par.toFixed(2)}${RESET} ${bar} ${pct.toFixed(0)}%`;
|
|
435
|
-
const ratingStr = ` ${effEmoji} ${rc}${rating}${RESET}`;
|
|
436
447
|
const ctxPctVal = ctxPct != null ? ctxPct : 0;
|
|
437
448
|
const ctxW = 10;
|
|
438
449
|
const ctxFilled = Math.min(ctxW, Math.round(ctxPctVal / 100 * ctxW));
|
|
@@ -465,10 +476,14 @@ function hudLine({ model, cost, prompts, ctxPct, effort, fainted, emotionKey })
|
|
|
465
476
|
} else {
|
|
466
477
|
emotionDisplay = `${G}VIBING${RESET}`;
|
|
467
478
|
}
|
|
468
|
-
const line1 = ` ${accent}\u2588\u2588${RESET} ${icon} ${emotionDisplay} ${costStr}
|
|
469
|
-
const
|
|
470
|
-
const
|
|
471
|
-
|
|
479
|
+
const line1 = ` ${accent}\u2588\u2588${RESET} ${icon} ${emotionDisplay} ${costStr}`;
|
|
480
|
+
const line2 = ` ${accent}\u2588\u2588${RESET} ${modelLabel} ${ctxIcon} ${ctxBar} ${ctxPctVal}%`;
|
|
481
|
+
const projName = project || "myproject";
|
|
482
|
+
const branch = gitBranch || "main";
|
|
483
|
+
const dirtyIcon = gitDirty ? `${Y}\u25CF${RESET}` : `${G}\u2713${RESET}`;
|
|
484
|
+
const line3 = ` ${accent}\u2588\u2588${RESET} ${effEmoji} ${rc}${rating}${RESET} ${DIM}\u{1F4C2}${RESET} ${projName} ${DIM}\u2387${RESET} ${branch} ${dirtyIcon}`;
|
|
485
|
+
return `${line3}
|
|
486
|
+
${line1}
|
|
472
487
|
${line2}`;
|
|
473
488
|
}
|
|
474
489
|
function runDemo() {
|
|
@@ -517,79 +532,117 @@ var init_demo = __esm({
|
|
|
517
532
|
};
|
|
518
533
|
SCENARIOS = [
|
|
519
534
|
{
|
|
520
|
-
title: "Fresh session \xB7 Sonnet \xB7 2 prompts \xB7
|
|
521
|
-
|
|
535
|
+
title: "Fresh session \xB7 Sonnet \xB7 2 prompts \xB7 LEGENDARY",
|
|
536
|
+
// par = 1.5*sqrt(2) = $2.12, cost $0.18 → 8% = LEGENDARY
|
|
537
|
+
hud: {
|
|
538
|
+
model: "claude-sonnet-4-6",
|
|
539
|
+
cost: 0.18,
|
|
540
|
+
prompts: 2,
|
|
541
|
+
ctxPct: 8,
|
|
542
|
+
emotionKey: "VIBING",
|
|
543
|
+
project: "myapp",
|
|
544
|
+
gitBranch: "main",
|
|
545
|
+
gitDirty: false
|
|
546
|
+
}
|
|
522
547
|
},
|
|
523
548
|
{
|
|
524
|
-
title: "Sonnet \xB7 8 prompts \xB7 efficient \xB7
|
|
549
|
+
title: "Sonnet \xB7 8 prompts \xB7 efficient \xB7 PRO",
|
|
550
|
+
// par = 1.5*sqrt(8) = $4.24, cost $1.50 → 35% = PRO
|
|
525
551
|
hud: {
|
|
526
552
|
model: "claude-sonnet-4-6",
|
|
527
|
-
cost:
|
|
553
|
+
cost: 1.5,
|
|
528
554
|
prompts: 8,
|
|
529
555
|
ctxPct: 34,
|
|
530
|
-
emotionKey: "GRINDING"
|
|
556
|
+
emotionKey: "GRINDING",
|
|
557
|
+
project: "api-server",
|
|
558
|
+
gitBranch: "feat/auth",
|
|
559
|
+
gitDirty: true
|
|
531
560
|
}
|
|
532
561
|
},
|
|
533
562
|
{
|
|
534
563
|
title: "Sonnet\xB7High \xB7 5 prompts \xB7 LEGENDARY",
|
|
564
|
+
// par = 1.5*sqrt(5) = $3.35, cost $0.41 → 12% = LEGENDARY
|
|
535
565
|
hud: {
|
|
536
566
|
model: "claude-sonnet-4-6",
|
|
537
567
|
cost: 0.41,
|
|
538
568
|
prompts: 5,
|
|
539
569
|
ctxPct: 29,
|
|
540
570
|
effort: "high",
|
|
541
|
-
emotionKey: "VIBING"
|
|
571
|
+
emotionKey: "VIBING",
|
|
572
|
+
project: "tokengolf",
|
|
573
|
+
gitBranch: "main",
|
|
574
|
+
gitDirty: false
|
|
542
575
|
}
|
|
543
576
|
},
|
|
544
577
|
{
|
|
545
578
|
title: "Opus \xB7 4 prompts \xB7 EPIC",
|
|
579
|
+
// par = 8.0*sqrt(4) = $16.00, cost $3.80 → 24% = EPIC
|
|
546
580
|
hud: {
|
|
547
581
|
model: "claude-opus-4-6",
|
|
548
|
-
cost:
|
|
582
|
+
cost: 3.8,
|
|
549
583
|
prompts: 4,
|
|
550
584
|
ctxPct: 52,
|
|
551
|
-
emotionKey: "CRUISING"
|
|
585
|
+
emotionKey: "CRUISING",
|
|
586
|
+
project: "ml-pipeline",
|
|
587
|
+
gitBranch: "refactor/v2",
|
|
588
|
+
gitDirty: false
|
|
552
589
|
}
|
|
553
590
|
},
|
|
554
591
|
{
|
|
555
|
-
title: "Haiku \xB7
|
|
592
|
+
title: "Haiku \xB7 12 prompts \xB7 CLOSE CALL",
|
|
593
|
+
// par = 0.15*sqrt(12) = $0.52, cost $0.45 → 87% = CLOSE CALL
|
|
556
594
|
hud: {
|
|
557
595
|
model: "claude-haiku-4-5-20251001",
|
|
558
|
-
cost:
|
|
559
|
-
prompts:
|
|
596
|
+
cost: 0.45,
|
|
597
|
+
prompts: 12,
|
|
560
598
|
ctxPct: 78,
|
|
561
|
-
emotionKey: "SWEATING"
|
|
599
|
+
emotionKey: "SWEATING",
|
|
600
|
+
project: "docs-site",
|
|
601
|
+
gitBranch: "fix/typos",
|
|
602
|
+
gitDirty: true
|
|
562
603
|
}
|
|
563
604
|
},
|
|
564
605
|
{
|
|
565
|
-
title: "Sonnet \xB7 10 prompts \xB7
|
|
606
|
+
title: "Sonnet \xB7 10 prompts \xB7 BUST",
|
|
607
|
+
// par = 1.5*sqrt(10) = $4.74, cost $6.20 → 131% = BUST
|
|
566
608
|
hud: {
|
|
567
609
|
model: "claude-sonnet-4-6",
|
|
568
|
-
cost:
|
|
610
|
+
cost: 6.2,
|
|
569
611
|
prompts: 10,
|
|
570
612
|
ctxPct: 45,
|
|
571
|
-
emotionKey: "ZOMBIE"
|
|
613
|
+
emotionKey: "ZOMBIE",
|
|
614
|
+
project: "monorepo",
|
|
615
|
+
gitBranch: "main",
|
|
616
|
+
gitDirty: true
|
|
572
617
|
}
|
|
573
618
|
},
|
|
574
619
|
{
|
|
575
620
|
title: "Opus \xB7 3 prompts \xB7 overencumbered context",
|
|
621
|
+
// par = 8.0*sqrt(3) = $13.86, cost $5.50 → 40% = PRO
|
|
576
622
|
hud: {
|
|
577
623
|
model: "claude-opus-4-6",
|
|
578
|
-
cost:
|
|
624
|
+
cost: 5.5,
|
|
579
625
|
prompts: 3,
|
|
580
626
|
ctxPct: 91,
|
|
581
|
-
emotionKey: "FOCUSED"
|
|
627
|
+
emotionKey: "FOCUSED",
|
|
628
|
+
project: "kernel",
|
|
629
|
+
gitBranch: "dev",
|
|
630
|
+
gitDirty: false
|
|
582
631
|
}
|
|
583
632
|
},
|
|
584
633
|
{
|
|
585
634
|
title: "Fainted \u{1F4A4} \u2014 usage limit hit, run resumes next session",
|
|
635
|
+
// par = 1.5*sqrt(6) = $3.67, cost $0.92 → 25% = EPIC
|
|
586
636
|
hud: {
|
|
587
637
|
model: "claude-sonnet-4-6",
|
|
588
|
-
cost:
|
|
638
|
+
cost: 0.92,
|
|
589
639
|
prompts: 6,
|
|
590
640
|
ctxPct: 67,
|
|
591
641
|
fainted: true,
|
|
592
|
-
emotionKey: "SLEEPING"
|
|
642
|
+
emotionKey: "SLEEPING",
|
|
643
|
+
project: "webapp",
|
|
644
|
+
gitBranch: "feat/deploy",
|
|
645
|
+
gitDirty: false
|
|
593
646
|
}
|
|
594
647
|
}
|
|
595
648
|
];
|
|
@@ -1293,13 +1346,17 @@ function installHooks() {
|
|
|
1293
1346
|
}
|
|
1294
1347
|
]
|
|
1295
1348
|
});
|
|
1349
|
+
if (!fs3.existsSync(TG_DIR)) fs3.mkdirSync(TG_DIR, { recursive: true });
|
|
1296
1350
|
try {
|
|
1297
|
-
fs3.
|
|
1298
|
-
|
|
1351
|
+
fs3.copyFileSync(SRC_STATUSLINE_PATH, STABLE_STATUSLINE);
|
|
1352
|
+
fs3.chmodSync(STABLE_STATUSLINE, 493);
|
|
1353
|
+
} catch (err) {
|
|
1354
|
+
console.log(` \u26A0\uFE0F Could not copy statusline.sh: ${err.message}`);
|
|
1355
|
+
console.log(" The HUD may not work. Try reinstalling tokengolf.");
|
|
1299
1356
|
}
|
|
1300
1357
|
const existing = settings.statusLine;
|
|
1301
1358
|
const existingCmd = typeof existing === "string" ? existing : existing?.command ?? null;
|
|
1302
|
-
const isTgStatusline = (cmd) => cmd && (cmd.includes("tokengolf/hooks/statusline") || cmd.includes("tokengolf\\hooks\\statusline"));
|
|
1359
|
+
const isTgStatusline = (cmd) => cmd && (cmd.includes("tokengolf/hooks/statusline") || cmd.includes("tokengolf\\hooks\\statusline") || cmd.includes(".tokengolf/statusline"));
|
|
1303
1360
|
const alreadyOurs = isTgStatusline(existingCmd);
|
|
1304
1361
|
function extractUserStatusline(wrapperPath, visited = /* @__PURE__ */ new Set()) {
|
|
1305
1362
|
if (!wrapperPath || visited.has(wrapperPath)) return null;
|
|
@@ -1325,64 +1382,63 @@ function installHooks() {
|
|
|
1325
1382
|
const userStatusline = existingCmd.includes("statusline-wrapper") ? extractUserStatusline(existingCmd) : null;
|
|
1326
1383
|
if (userStatusline) {
|
|
1327
1384
|
fs3.writeFileSync(
|
|
1328
|
-
|
|
1385
|
+
STABLE_WRAPPER,
|
|
1329
1386
|
[
|
|
1330
1387
|
"#!/usr/bin/env bash",
|
|
1331
1388
|
"SESSION_JSON=$(cat)",
|
|
1332
1389
|
`echo "$SESSION_JSON" | ${userStatusline} 2>/dev/null || true`,
|
|
1333
|
-
`echo "$SESSION_JSON" | bash ${
|
|
1390
|
+
`echo "$SESSION_JSON" | bash ${STABLE_STATUSLINE}`
|
|
1334
1391
|
].join("\n") + "\n"
|
|
1335
1392
|
);
|
|
1336
|
-
fs3.chmodSync(
|
|
1393
|
+
fs3.chmodSync(STABLE_WRAPPER, 493);
|
|
1337
1394
|
settings.statusLine = {
|
|
1338
1395
|
type: "command",
|
|
1339
|
-
command:
|
|
1396
|
+
command: STABLE_WRAPPER,
|
|
1340
1397
|
padding: existing?.padding ?? 1
|
|
1341
1398
|
};
|
|
1342
1399
|
console.log(" \u2713 statusLine \u2192 updated paths (kept your existing statusline)");
|
|
1343
1400
|
} else {
|
|
1344
1401
|
settings.statusLine = {
|
|
1345
1402
|
type: "command",
|
|
1346
|
-
command:
|
|
1403
|
+
command: STABLE_STATUSLINE,
|
|
1347
1404
|
padding: existing?.padding ?? 1
|
|
1348
1405
|
};
|
|
1349
1406
|
console.log(" \u2713 statusLine \u2192 updated to current install path");
|
|
1350
1407
|
}
|
|
1351
1408
|
} else if (existingCmd) {
|
|
1352
1409
|
fs3.writeFileSync(
|
|
1353
|
-
|
|
1410
|
+
STABLE_WRAPPER,
|
|
1354
1411
|
[
|
|
1355
1412
|
"#!/usr/bin/env bash",
|
|
1356
1413
|
"SESSION_JSON=$(cat)",
|
|
1357
1414
|
`echo "$SESSION_JSON" | ${existingCmd} 2>/dev/null || true`,
|
|
1358
|
-
`echo "$SESSION_JSON" | bash ${
|
|
1415
|
+
`echo "$SESSION_JSON" | bash ${STABLE_STATUSLINE}`
|
|
1359
1416
|
].join("\n") + "\n"
|
|
1360
1417
|
);
|
|
1361
|
-
fs3.chmodSync(
|
|
1418
|
+
fs3.chmodSync(STABLE_WRAPPER, 493);
|
|
1362
1419
|
settings.statusLine = {
|
|
1363
1420
|
type: "command",
|
|
1364
|
-
command:
|
|
1421
|
+
command: STABLE_WRAPPER,
|
|
1365
1422
|
padding: 1
|
|
1366
1423
|
};
|
|
1367
1424
|
console.log(" \u2713 statusLine \u2192 wrapped your existing statusline + tokengolf HUD");
|
|
1368
1425
|
} else {
|
|
1369
1426
|
settings.statusLine = {
|
|
1370
1427
|
type: "command",
|
|
1371
|
-
command:
|
|
1428
|
+
command: STABLE_STATUSLINE,
|
|
1372
1429
|
padding: 1
|
|
1373
1430
|
};
|
|
1374
1431
|
console.log(" \u2713 statusLine \u2192 live HUD in every Claude session");
|
|
1375
1432
|
}
|
|
1376
1433
|
fs3.writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2));
|
|
1377
|
-
const TG_DIR = path4.join(os2.homedir(), ".tokengolf");
|
|
1378
|
-
if (!fs3.existsSync(TG_DIR)) fs3.mkdirSync(TG_DIR, { recursive: true });
|
|
1379
1434
|
try {
|
|
1380
1435
|
const pkgVersion = JSON.parse(
|
|
1381
1436
|
fs3.readFileSync(path4.resolve(path4.dirname(realEntry), "../package.json"), "utf8")
|
|
1382
1437
|
).version;
|
|
1383
1438
|
fs3.writeFileSync(path4.join(TG_DIR, "installed-version"), pkgVersion);
|
|
1384
1439
|
console.log(` \u2713 installed-version \u2192 ${pkgVersion}`);
|
|
1385
|
-
} catch {
|
|
1440
|
+
} catch (err) {
|
|
1441
|
+
console.log(` \u26A0\uFE0F Could not stamp installed version: ${err.message}`);
|
|
1386
1442
|
}
|
|
1387
1443
|
const CONFIG_FILE2 = path4.join(TG_DIR, "config.json");
|
|
1388
1444
|
if (!fs3.existsSync(CONFIG_FILE2)) {
|
|
@@ -1400,13 +1456,15 @@ function installHooks() {
|
|
|
1400
1456
|
console.log(" \u2713 Stop \u2192 tracks turn count");
|
|
1401
1457
|
console.log("\n \u2705 Done! Start a run: tokengolf start\n");
|
|
1402
1458
|
}
|
|
1403
|
-
var realEntry, HOOKS_DIR,
|
|
1459
|
+
var realEntry, HOOKS_DIR, SRC_STATUSLINE_PATH, TG_DIR, STABLE_STATUSLINE, STABLE_WRAPPER, CLAUDE_DIR, CLAUDE_SETTINGS;
|
|
1404
1460
|
var init_install = __esm({
|
|
1405
1461
|
"src/lib/install.js"() {
|
|
1406
1462
|
realEntry = fs3.realpathSync(process.argv[1]);
|
|
1407
1463
|
HOOKS_DIR = path4.resolve(path4.dirname(realEntry), "../hooks");
|
|
1408
|
-
|
|
1409
|
-
|
|
1464
|
+
SRC_STATUSLINE_PATH = path4.join(HOOKS_DIR, "statusline.sh");
|
|
1465
|
+
TG_DIR = path4.join(os2.homedir(), ".tokengolf");
|
|
1466
|
+
STABLE_STATUSLINE = path4.join(TG_DIR, "statusline.sh");
|
|
1467
|
+
STABLE_WRAPPER = path4.join(TG_DIR, "statusline-wrapper.sh");
|
|
1410
1468
|
CLAUDE_DIR = path4.join(os2.homedir(), ".claude");
|
|
1411
1469
|
CLAUDE_SETTINGS = path4.join(CLAUDE_DIR, "settings.json");
|
|
1412
1470
|
}
|
package/docs/index.html
CHANGED
|
@@ -1282,7 +1282,7 @@
|
|
|
1282
1282
|
<!-- ── HUD panel ── -->
|
|
1283
1283
|
<div class="tab-panel active" id="panel-hud" role="tabpanel" aria-labelledby="tab-hud">
|
|
1284
1284
|
<p class="demo-label" style="margin-bottom:1.5rem">
|
|
1285
|
-
<strong>Live HUD</strong> appears in every Claude Code session.
|
|
1285
|
+
<strong>Live HUD</strong> appears in every Claude Code session. 3-line display: efficiency rating with project/git info, emotion with cost vs par, model class with context load. Accent color matches your efficiency tier.
|
|
1286
1286
|
</p>
|
|
1287
1287
|
<div class="demos-grid">
|
|
1288
1288
|
|
|
@@ -1290,7 +1290,8 @@
|
|
|
1290
1290
|
<div class="term" data-glow="yellow">
|
|
1291
1291
|
<div class="term-header">Legendary · early session</div>
|
|
1292
1292
|
<pre>
|
|
1293
|
-
<span class="border-yellow">██</span>
|
|
1293
|
+
<span class="border-yellow">██</span> 🌟 <span class="t-yellow">LEGENDARY</span> <span class="t-dim">📂</span> myapp <span class="t-dim">⎇</span> main <span class="t-green">✓</span>
|
|
1294
|
+
<span class="border-yellow">██</span> 😎 <span class="t-green">VIBING</span> 💎 <span class="t-dim">$</span>0.18<span class="t-dim">/2.12</span> <span class="t-yellow">▓░░░░░░░░░░</span> 8%
|
|
1294
1295
|
<span class="border-yellow">██</span> ⚔️ Sonnet 🪶 <span class="t-green">▓░░░░░░░░░</span> 8%</pre>
|
|
1295
1296
|
</div>
|
|
1296
1297
|
</div>
|
|
@@ -1299,7 +1300,8 @@
|
|
|
1299
1300
|
<div class="term" data-glow="cyan">
|
|
1300
1301
|
<div class="term-header">Pro · mid session</div>
|
|
1301
1302
|
<pre>
|
|
1302
|
-
<span class="border-cyan">██</span>
|
|
1303
|
+
<span class="border-cyan">██</span> 💪 <span class="t-cyan">PRO</span> <span class="t-dim">📂</span> api-server <span class="t-dim">⎇</span> feat/auth <span class="t-yellow">●</span>
|
|
1304
|
+
<span class="border-cyan">██</span> 😤 <span class="t-yellow">GRINDING</span> 🥈 <span class="t-dim">$</span>1.50<span class="t-dim">/4.24</span> <span class="t-cyan">▓▓▓▓░░░░░░░</span> 35%
|
|
1303
1305
|
<span class="border-cyan">██</span> ⚔️ Sonnet 📚 <span class="t-green">▓▓▓░░░░░░░</span> 34%</pre>
|
|
1304
1306
|
</div>
|
|
1305
1307
|
</div>
|
|
@@ -1308,16 +1310,18 @@
|
|
|
1308
1310
|
<div class="term" data-glow="yellow">
|
|
1309
1311
|
<div class="term-header">Legendary · high effort</div>
|
|
1310
1312
|
<pre>
|
|
1311
|
-
<span class="border-yellow">██</span>
|
|
1313
|
+
<span class="border-yellow">██</span> 🌟 <span class="t-yellow">LEGENDARY</span> <span class="t-dim">📂</span> tokengolf <span class="t-dim">⎇</span> main <span class="t-green">✓</span>
|
|
1314
|
+
<span class="border-yellow">██</span> 😎 <span class="t-green">VIBING</span> 💎 <span class="t-dim">$</span>0.41<span class="t-dim">/3.35</span> <span class="t-yellow">▓░░░░░░░░░░</span> 12%
|
|
1312
1315
|
<span class="border-yellow">██</span> ⚔️ Sonnet·High 📚 <span class="t-green">▓▓▓░░░░░░░</span> 29%</pre>
|
|
1313
1316
|
</div>
|
|
1314
1317
|
</div>
|
|
1315
1318
|
|
|
1316
1319
|
<div class="demo-item">
|
|
1317
|
-
<div class="term" data-glow="
|
|
1320
|
+
<div class="term" data-glow="magenta">
|
|
1318
1321
|
<div class="term-header">Epic · Opus run</div>
|
|
1319
1322
|
<pre>
|
|
1320
|
-
<span class="border-magenta">██</span>
|
|
1323
|
+
<span class="border-magenta">██</span> 🔥 <span class="t-magenta">EPIC</span> <span class="t-dim">📂</span> ml-pipeline <span class="t-dim">⎇</span> refactor/v2 <span class="t-green">✓</span>
|
|
1324
|
+
<span class="border-magenta">██</span> 🛹 <span class="t-green">CRUISING</span> 💎 <span class="t-dim">$</span>3.80<span class="t-dim">/16.00</span> <span class="t-magenta">▓▓▓░░░░░░░░</span> 24%
|
|
1321
1325
|
<span class="border-magenta">██</span> 🧙 Opus 🎒 <span class="t-cyan">▓▓▓▓▓░░░░░</span> 52%</pre>
|
|
1322
1326
|
</div>
|
|
1323
1327
|
</div>
|
|
@@ -1326,7 +1330,8 @@
|
|
|
1326
1330
|
<div class="term">
|
|
1327
1331
|
<div class="term-header">Close Call · Haiku near bust</div>
|
|
1328
1332
|
<pre>
|
|
1329
|
-
<span class="border-white">██</span>
|
|
1333
|
+
<span class="border-white">██</span> ⚠️ <span class="t-white">CLOSE CALL</span> <span class="t-dim">📂</span> docs-site <span class="t-dim">⎇</span> fix/typos <span class="t-yellow">●</span>
|
|
1334
|
+
<span class="border-white">██</span> 😰 <span class="t-yellow">SWEATING</span> 🥇 <span class="t-dim">$</span>0.45<span class="t-dim">/0.52</span> <span class="t-white">▓▓▓▓▓▓▓▓▓░░</span> 87%
|
|
1330
1335
|
<span class="border-white">██</span> 🏹 Haiku 🪨 <span class="t-yellow">▓▓▓▓▓▓▓▓░░</span> 78%</pre>
|
|
1331
1336
|
</div>
|
|
1332
1337
|
</div>
|
|
@@ -1335,7 +1340,8 @@
|
|
|
1335
1340
|
<div class="term" data-glow="red">
|
|
1336
1341
|
<div class="term-header">Bust · past par, dead</div>
|
|
1337
1342
|
<pre>
|
|
1338
|
-
<span class="border-red">██</span>
|
|
1343
|
+
<span class="border-red">██</span> 💥 <span class="t-red">BUST</span> <span class="t-dim">📂</span> monorepo <span class="t-dim">⎇</span> main <span class="t-yellow">●</span>
|
|
1344
|
+
<span class="border-red">██</span> 🧟 <span class="t-red">ZOMBIE</span> 🥈 <span class="t-dim">$</span>6.20<span class="t-dim">/4.74</span> <span class="t-red">▓▓▓▓▓▓▓▓▓▓▓</span> 131%
|
|
1339
1345
|
<span class="border-red">██</span> ⚔️ Sonnet 🎒 <span class="t-cyan">▓▓▓▓▓░░░░░</span> 45%</pre>
|
|
1340
1346
|
</div>
|
|
1341
1347
|
</div>
|
|
@@ -1344,16 +1350,18 @@
|
|
|
1344
1350
|
<div class="term" data-glow="cyan">
|
|
1345
1351
|
<div class="term-header">Overwhelmed · high context</div>
|
|
1346
1352
|
<pre>
|
|
1347
|
-
<span class="border-cyan">██</span>
|
|
1353
|
+
<span class="border-cyan">██</span> 💪 <span class="t-cyan">PRO</span> <span class="t-dim">📂</span> kernel <span class="t-dim">⎇</span> dev <span class="t-green">✓</span>
|
|
1354
|
+
<span class="border-cyan">██</span> 🤯 <span class="t-red">OVERWHELMED</span> 💎 <span class="t-dim">$</span>5.50<span class="t-dim">/13.86</span> <span class="t-cyan">▓▓▓▓░░░░░░░</span> 40%
|
|
1348
1355
|
<span class="border-cyan">██</span> 🧙 Opus 🗿 <span class="t-red">▓▓▓▓▓▓▓▓▓░</span> 91%</pre>
|
|
1349
1356
|
</div>
|
|
1350
1357
|
</div>
|
|
1351
1358
|
|
|
1352
1359
|
<div class="demo-item">
|
|
1353
|
-
<div class="term" data-glow="
|
|
1354
|
-
<div class="term-header">Sleeping ·
|
|
1360
|
+
<div class="term" data-glow="magenta">
|
|
1361
|
+
<div class="term-header">Sleeping · fainted session</div>
|
|
1355
1362
|
<pre>
|
|
1356
|
-
<span class="border-magenta">██</span>
|
|
1363
|
+
<span class="border-magenta">██</span> 🔥 <span class="t-magenta">EPIC</span> <span class="t-dim">📂</span> webapp <span class="t-dim">⎇</span> feat/deploy <span class="t-green">✓</span>
|
|
1364
|
+
<span class="border-magenta">██</span> 💤 <span class="t-dim">SLEEPING</span> 💎 <span class="t-dim">$</span>0.92<span class="t-dim">/3.67</span> <span class="t-magenta">▓▓▓░░░░░░░░</span> 25%
|
|
1357
1365
|
<span class="border-magenta">██</span> ⚔️ Sonnet 🧱 <span class="t-yellow">▓▓▓▓▓▓▓░░░</span> 67%</pre>
|
|
1358
1366
|
</div>
|
|
1359
1367
|
</div>
|
|
@@ -3,7 +3,8 @@ import fs from 'fs';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import os from 'os';
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const cwdKey = (process.env.PWD || process.cwd()).replace(/\//g, '-');
|
|
7
|
+
const STATE_FILE = path.join(os.homedir(), '.tokengolf', `current-run${cwdKey}.json`);
|
|
7
8
|
|
|
8
9
|
let input = '';
|
|
9
10
|
process.stdin.setEncoding('utf8');
|
package/hooks/post-tool-use.js
CHANGED
|
@@ -3,7 +3,8 @@ import fs from 'fs';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import os from 'os';
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const cwdKey = (process.env.PWD || process.cwd()).replace(/\//g, '-');
|
|
7
|
+
const STATE_FILE = path.join(os.homedir(), '.tokengolf', `current-run${cwdKey}.json`);
|
|
7
8
|
|
|
8
9
|
let input = '';
|
|
9
10
|
process.stdin.setEncoding('utf8');
|
package/hooks/pre-compact.js
CHANGED
|
@@ -3,7 +3,8 @@ import fs from 'fs';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import os from 'os';
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const cwdKey = (process.env.PWD || process.cwd()).replace(/\//g, '-');
|
|
7
|
+
const STATE_FILE = path.join(os.homedir(), '.tokengolf', `current-run${cwdKey}.json`);
|
|
7
8
|
|
|
8
9
|
try {
|
|
9
10
|
let stdin = '';
|
package/hooks/session-end.js
CHANGED
|
@@ -32,11 +32,11 @@ try {
|
|
|
32
32
|
const reason = event.reason || 'other';
|
|
33
33
|
|
|
34
34
|
// Read authoritative cost from StatusLine sidecar (same source as the HUD)
|
|
35
|
+
const cwdKey = (process.env.PWD || process.cwd()).replace(/\//g, '-');
|
|
36
|
+
const costFile = path.join(os.homedir(), '.tokengolf', `session-cost${cwdKey}`);
|
|
35
37
|
let liveCost = null;
|
|
36
38
|
try {
|
|
37
|
-
const raw = fs
|
|
38
|
-
.readFileSync(path.join(os.homedir(), '.tokengolf', 'session-cost'), 'utf8')
|
|
39
|
-
.trim();
|
|
39
|
+
const raw = fs.readFileSync(costFile, 'utf8').trim();
|
|
40
40
|
const parsed = parseFloat(raw);
|
|
41
41
|
if (!isNaN(parsed) && parsed > 0) liveCost = parsed;
|
|
42
42
|
} catch {}
|
|
@@ -122,7 +122,7 @@ try {
|
|
|
122
122
|
clearCurrentRun();
|
|
123
123
|
// Clean up sidecar cost file
|
|
124
124
|
try {
|
|
125
|
-
fs.unlinkSync(
|
|
125
|
+
fs.unlinkSync(costFile);
|
|
126
126
|
} catch {}
|
|
127
127
|
|
|
128
128
|
writeTTY('\n' + renderScorecard(saved) + '\n\n');
|
package/hooks/session-start.js
CHANGED
|
@@ -3,12 +3,14 @@ import fs from 'fs';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import os from 'os';
|
|
5
5
|
|
|
6
|
-
const STATE_FILE = path.join(os.homedir(), '.tokengolf', 'current-run.json');
|
|
7
6
|
const STATE_DIR = path.join(os.homedir(), '.tokengolf');
|
|
7
|
+
const cwdKey = (process.env.PWD || process.cwd()).replace(/\//g, '-');
|
|
8
|
+
const STATE_FILE = path.join(STATE_DIR, `current-run${cwdKey}.json`);
|
|
8
9
|
|
|
9
|
-
// Auto-sync: if npm package version changed since last install, update hook paths
|
|
10
|
+
// Auto-sync: if npm package version changed since last install, update hook paths (npm-only)
|
|
10
11
|
try {
|
|
11
12
|
const pkgPath = path.resolve(path.dirname(process.argv[1]), '../package.json');
|
|
13
|
+
if (!fs.existsSync(pkgPath)) throw new Error('not npm install');
|
|
12
14
|
const currentVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version;
|
|
13
15
|
const stampFile = path.join(STATE_DIR, 'installed-version');
|
|
14
16
|
let installedVersion = null;
|
|
@@ -35,17 +37,7 @@ try {
|
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
|
-
//
|
|
39
|
-
const statuslinePath = path.join(hooksDir, 'statusline.sh');
|
|
40
|
-
const wrapperPath = path.join(hooksDir, 'statusline-wrapper.sh');
|
|
41
|
-
if (settings.statusLine?.command) {
|
|
42
|
-
const cmd = settings.statusLine.command;
|
|
43
|
-
if (cmd.includes('statusline-wrapper')) {
|
|
44
|
-
settings.statusLine.command = wrapperPath;
|
|
45
|
-
} else if (cmd.includes('statusline.sh')) {
|
|
46
|
-
settings.statusLine.command = statuslinePath;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
40
|
+
// statusLine paths now use stable ~/.tokengolf/statusline.sh — auto-install block handles updates
|
|
49
41
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
50
42
|
} catch {}
|
|
51
43
|
fs.writeFileSync(stampFile, currentVersion);
|
|
@@ -56,6 +48,28 @@ try {
|
|
|
56
48
|
}
|
|
57
49
|
} catch {}
|
|
58
50
|
|
|
51
|
+
// Auto-install statusLine: copy to stable ~/.tokengolf/ path (survives plugin uninstall)
|
|
52
|
+
try {
|
|
53
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
54
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
55
|
+
const scriptDir = path.dirname(fs.realpathSync(process.argv[1]));
|
|
56
|
+
const srcStatusline = path.join(scriptDir, 'statusline.sh');
|
|
57
|
+
const stablePath = path.join(STATE_DIR, 'statusline.sh');
|
|
58
|
+
if (fs.existsSync(srcStatusline)) {
|
|
59
|
+
// Always copy latest version to stable location
|
|
60
|
+
if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
61
|
+
fs.copyFileSync(srcStatusline, stablePath);
|
|
62
|
+
fs.chmodSync(stablePath, 0o755);
|
|
63
|
+
const current = settings.statusLine?.command || '';
|
|
64
|
+
const needsInstall = !current || current.includes('tokengolf');
|
|
65
|
+
const needsUpdate = needsInstall && current !== stablePath;
|
|
66
|
+
if (needsUpdate) {
|
|
67
|
+
settings.statusLine = { type: 'command', command: stablePath, padding: 1 };
|
|
68
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch {}
|
|
72
|
+
|
|
59
73
|
function detectEffort() {
|
|
60
74
|
const fromEnv = process.env.CLAUDE_CODE_EFFORT_LEVEL;
|
|
61
75
|
if (fromEnv) return fromEnv;
|
|
@@ -82,6 +96,17 @@ function detectFastMode() {
|
|
|
82
96
|
}
|
|
83
97
|
}
|
|
84
98
|
|
|
99
|
+
// Migrate from global current-run.json if this project's run doesn't exist yet
|
|
100
|
+
try {
|
|
101
|
+
const globalFile = path.join(STATE_DIR, 'current-run.json');
|
|
102
|
+
if (!fs.existsSync(STATE_FILE) && fs.existsSync(globalFile)) {
|
|
103
|
+
const globalRun = JSON.parse(fs.readFileSync(globalFile, 'utf8'));
|
|
104
|
+
if (globalRun.cwd === (process.env.PWD || process.cwd())) {
|
|
105
|
+
fs.renameSync(globalFile, STATE_FILE);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch {}
|
|
109
|
+
|
|
85
110
|
try {
|
|
86
111
|
const cwd = process.env.PWD || process.cwd();
|
|
87
112
|
|