tokengolf 0.5.0 → 0.5.1
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/CHANGELOG.md +1 -0
- package/CLAUDE.md +6 -3
- package/README.md +4 -7
- package/dist/cli.js +35 -21
- package/docs/index.html +10 -21
- package/hooks/statusline.sh +36 -20
- package/package.json +3 -2
- package/src/lib/demo.js +37 -19
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,7 @@ TokenGolf patch notes — what changed, what it measures, and why the mechanic e
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
9
|
### Changed
|
|
10
|
+
- **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.
|
|
10
11
|
- **Design D block accent UI** — All bordered boxes replaced with left-only `██` block accent bars. Eliminates persistent right-border misalignment caused by emoji/unicode width differences across terminals. Color-coded: yellow for won, red for died, gray for neutral.
|
|
11
12
|
- ScoreCard, StatsView, ActiveRun, StartRun components all use custom Ink `borderStyle` with `left: '██'`, no right/top/bottom borders
|
|
12
13
|
- session-end.js ANSI scorecard uses `██` prefix per line with `─` horizontal separators
|
package/CLAUDE.md
CHANGED
|
@@ -347,10 +347,13 @@ Nine hooks in `hooks/` directory, installed via `tokengolf install`. Most comple
|
|
|
347
347
|
### `StatusLine` (`statusline.sh`)
|
|
348
348
|
- Bash script; uses `TG_SESSION_JSON=... python3 - "$STATE_FILE" <<'PYEOF'` pattern to avoid heredoc/stdin conflict
|
|
349
349
|
- Receives live session JSON (cost, context %, model) via stdin
|
|
350
|
-
-
|
|
350
|
+
- **Design D accent bar**: `██` prefix on each line, color-coded (yellow normal, red when budget >75%)
|
|
351
|
+
- Line 1: `██ ⛳ quest $cost/budget ▓▓▓░░░ pct% RATING model F1/5`
|
|
352
|
+
- Line 2 (optional, when context ≥50%): `██ 🧠 ▓▓▓▓░░░ ctx% 🪶/🎒/📦`
|
|
353
|
+
- Budget progress bar: `▓` filled, `░` empty, 11 chars wide. Red when >75%, yellow otherwise
|
|
354
|
+
- Context progress bar: `▓░` 10 chars wide. Green (50–74%), yellow (75–89%), red (90%+); hidden below 50%
|
|
351
355
|
- Model label: `⚔️ Sonnet`, `⚔️ Sonnet·High`, `🏹 Haiku`, `🧙 Opus·Max`, etc. Effort appended only when explicitly set in settings.json (medium omitted — it's the default)
|
|
352
|
-
-
|
|
353
|
-
- Separator lines (`───────────────`) above and below HUD row
|
|
356
|
+
- 1 line when context <50% (flow mode is often just 1 line), 2 lines when context visible
|
|
354
357
|
- statusLine config must be an object: `{type:"command", command:"...statusline.sh", padding:1}`
|
|
355
358
|
|
|
356
359
|
### Hook installation
|
package/README.md
CHANGED
|
@@ -253,16 +253,13 @@ Budget presets are model-calibrated — Haiku Diamond is $0.15, Opus Diamond is
|
|
|
253
253
|
After `tokengolf install`, a status line appears in every Claude Code session showing quest, cost, efficiency, context load, and model class.
|
|
254
254
|
|
|
255
255
|
```
|
|
256
|
-
|
|
257
|
-
⛳ add pagination to /users | 🥈 $0.54/$1.50 36% | EFFICIENT | ⚔️ Sonnet | Floor 2/5
|
|
258
|
-
───────────────
|
|
256
|
+
██ ⛳ add pagination to /users $0.54/1.50 ▓▓▓▓░░░░░░░ 36% EFFICIENT ⚔️ Sonnet F2/5
|
|
259
257
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
───────────────
|
|
258
|
+
██ ⛳ refactor auth middleware $0.82/4.00 ▓▓░░░░░░░░░ 21% LEGENDARY 🧙 Opus F3/5
|
|
259
|
+
██ 🧠 ▓▓▓▓▓░░░░░ 52% 🪶
|
|
263
260
|
```
|
|
264
261
|
|
|
265
|
-
Context
|
|
262
|
+
Budget bar turns red above 75%. Context bar (line 2) appears at 50%+: **🪶** green · **🎒** yellow · **📦** red. Accent `██` turns red in danger. **💤** replaces ⛳ when fainted.
|
|
266
263
|
|
|
267
264
|
---
|
|
268
265
|
|
package/dist/cli.js
CHANGED
|
@@ -847,7 +847,8 @@ function hudLine({ quest, model, cost, budget, ctxPct, effort, fainted, floor })
|
|
|
847
847
|
modelEmoji = "?";
|
|
848
848
|
}
|
|
849
849
|
const labelParts = [`${modelEmoji} ${modelName}`];
|
|
850
|
-
if (effort
|
|
850
|
+
if (effort && effort !== "medium")
|
|
851
|
+
labelParts.push(effort.charAt(0).toUpperCase() + effort.slice(1));
|
|
851
852
|
const modelLabel = labelParts.join("\xB7");
|
|
852
853
|
let tierEmoji;
|
|
853
854
|
if (cost < 0.1) tierEmoji = "\u{1F48E}";
|
|
@@ -855,8 +856,7 @@ function hudLine({ quest, model, cost, budget, ctxPct, effort, fainted, floor })
|
|
|
855
856
|
else if (cost < 1) tierEmoji = "\u{1F948}";
|
|
856
857
|
else if (cost < 3) tierEmoji = "\u{1F949}";
|
|
857
858
|
else tierEmoji = "\u{1F4B8}";
|
|
858
|
-
|
|
859
|
-
let costStr, ratingStr;
|
|
859
|
+
let costStr, ratingStr, accent;
|
|
860
860
|
if (budget) {
|
|
861
861
|
const pct = cost / budget * 100;
|
|
862
862
|
let rating, rc;
|
|
@@ -876,28 +876,42 @@ function hudLine({ quest, model, cost, budget, ctxPct, effort, fainted, floor })
|
|
|
876
876
|
rating = "BUSTED";
|
|
877
877
|
rc = R;
|
|
878
878
|
}
|
|
879
|
-
|
|
880
|
-
|
|
879
|
+
accent = pct > 75 ? R : Y;
|
|
880
|
+
const barW = 11;
|
|
881
|
+
const barFilled = Math.min(barW, Math.round(pct / 100 * barW));
|
|
882
|
+
const barEmpty = barW - barFilled;
|
|
883
|
+
const bar = `${accent}${"\u2593".repeat(barFilled)}${"\u2591".repeat(barEmpty)}${RESET}`;
|
|
884
|
+
costStr = `${DIM}$${RESET}${cost.toFixed(2)}${DIM}/${budget.toFixed(2)}${RESET} ${bar} ${pct.toFixed(0)}%`;
|
|
885
|
+
ratingStr = ` ${rc}${rating}${RESET}`;
|
|
881
886
|
} else {
|
|
882
|
-
|
|
883
|
-
|
|
887
|
+
accent = Y;
|
|
888
|
+
costStr = `${tierEmoji} $${cost.toFixed(2)}`;
|
|
889
|
+
ratingStr = "";
|
|
884
890
|
}
|
|
885
|
-
let
|
|
886
|
-
if (ctxPct != null) {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
891
|
+
let ctxLine = null;
|
|
892
|
+
if (ctxPct != null && ctxPct >= 50) {
|
|
893
|
+
const ctxW = 10;
|
|
894
|
+
const ctxFilled = Math.min(ctxW, Math.round(ctxPct / 100 * ctxW));
|
|
895
|
+
const ctxEmpty = ctxW - ctxFilled;
|
|
896
|
+
let ctxColor, ctxIcon;
|
|
897
|
+
if (ctxPct >= 90) {
|
|
898
|
+
ctxColor = R;
|
|
899
|
+
ctxIcon = "\u{1F4E6}";
|
|
900
|
+
} else if (ctxPct >= 75) {
|
|
901
|
+
ctxColor = Y;
|
|
902
|
+
ctxIcon = "\u{1F392}";
|
|
903
|
+
} else {
|
|
904
|
+
ctxColor = G;
|
|
905
|
+
ctxIcon = "\u{1FAB6}";
|
|
906
|
+
}
|
|
907
|
+
const ctxBar = `${ctxColor}${"\u2593".repeat(ctxFilled)}${"\u2591".repeat(ctxEmpty)}${RESET}`;
|
|
908
|
+
ctxLine = ` ${accent}\u2588\u2588${RESET} \u{1F9E0} ${ctxBar} ${ctxPct}% ${ctxIcon}`;
|
|
890
909
|
}
|
|
891
910
|
const icon = fainted ? "\u{1F4A4}" : "\u26F3";
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
parts.push(`${C}${modelLabel}${RESET}`);
|
|
897
|
-
if (budget && floor) parts.push(`Floor ${floor}`);
|
|
898
|
-
return `${DIM} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${RESET}
|
|
899
|
-
${parts.join(sep)}
|
|
900
|
-
${DIM} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${RESET}`;
|
|
911
|
+
let line1 = ` ${accent}\u2588\u2588${RESET} ${icon} ${quest} ${costStr}${ratingStr} ${modelLabel}`;
|
|
912
|
+
if (budget && floor) line1 += ` ${DIM}F${floor}${RESET}`;
|
|
913
|
+
return ctxLine ? `${line1}
|
|
914
|
+
${ctxLine}` : line1;
|
|
901
915
|
}
|
|
902
916
|
function runDemo() {
|
|
903
917
|
console.log("");
|
package/docs/index.html
CHANGED
|
@@ -1145,33 +1145,22 @@
|
|
|
1145
1145
|
<div class="term" data-glow="cyan">
|
|
1146
1146
|
<div class="term-header">tokengolf demo hud</div>
|
|
1147
1147
|
<pre>
|
|
1148
|
-
<span class="
|
|
1149
|
-
<span class="t-b t-cyan">⛳</span> Flow <span class="t-dim">|</span> 💎 $0.0034 <span class="t-dim">|</span> <span class="t-cyan">⚔️ Sonnet</span>
|
|
1150
|
-
<span class="t-dim"> ───────────────</span>
|
|
1148
|
+
<span class="border-yellow">██</span> ⛳ Flow 💎 $0.0034 ⚔️ Sonnet
|
|
1151
1149
|
|
|
1152
|
-
<span class="t-dim">
|
|
1153
|
-
<span class="t-b t-cyan">⛳</span> add pagination to /users <span class="t-dim">|</span> 🥈 $0.5400/$1.50 36% <span class="t-dim">|</span> <span class="t-cyan">EFFICIENT</span> <span class="t-dim">|</span> <span class="t-cyan">⚔️ Sonnet</span> <span class="t-dim">|</span> Floor 2/5
|
|
1154
|
-
<span class="t-dim"> ───────────────</span>
|
|
1150
|
+
<span class="border-yellow">██</span> ⛳ add pagination to /users <span class="t-dim">$</span>0.54<span class="t-dim">/1.50</span> <span class="t-yellow">▓▓▓▓░░░░░░░</span> 36% <span class="t-cyan">EFFICIENT</span> ⚔️ Sonnet <span class="t-dim">F2/5</span>
|
|
1155
1151
|
|
|
1156
|
-
<span class="t-dim">
|
|
1157
|
-
<span class="t-b t-cyan">⛳</span> implement SSO with SAML <span class="t-dim">|</span> 🥈 $0.4100/$2.00 21% <span class="t-dim">|</span> <span class="t-magenta">LEGENDARY</span> <span class="t-dim">|</span> <span class="t-cyan">⚔️ Sonnet·High</span> <span class="t-dim">|</span> Floor 1/5
|
|
1158
|
-
<span class="t-dim"> ───────────────</span>
|
|
1152
|
+
<span class="border-yellow">██</span> ⛳ implement SSO with SAML <span class="t-dim">$</span>0.41<span class="t-dim">/2.00</span> <span class="t-yellow">▓▓░░░░░░░░░</span> 21% <span class="t-magenta">LEGENDARY</span> ⚔️ Sonnet·High <span class="t-dim">F1/5</span>
|
|
1159
1153
|
|
|
1160
|
-
<span class="t-dim">
|
|
1161
|
-
<span class="
|
|
1162
|
-
<span class="t-dim"> ───────────────</span>
|
|
1154
|
+
<span class="border-yellow">██</span> ⛳ refactor auth middleware <span class="t-dim">$</span>0.82<span class="t-dim">/4.00</span> <span class="t-yellow">▓▓░░░░░░░░░</span> 21% <span class="t-magenta">LEGENDARY</span> 🧙 Opus <span class="t-dim">F3/5</span>
|
|
1155
|
+
<span class="border-yellow">██</span> 🧠 <span class="t-green">▓▓▓▓▓░░░░░</span> 52% 🪶
|
|
1163
1156
|
|
|
1164
|
-
<span class="t-dim">
|
|
1165
|
-
<span class="
|
|
1166
|
-
<span class="t-dim"> ───────────────</span>
|
|
1157
|
+
<span class="border-red">██</span> ⛳ fix N+1 query in dashboard <span class="t-dim">$</span>0.46<span class="t-dim">/0.50</span> <span class="t-red">▓▓▓▓▓▓▓▓▓▓░</span> 92% <span class="t-yellow">CLOSE CALL</span> 🏹 Haiku <span class="t-dim">F4/5</span>
|
|
1158
|
+
<span class="border-red">██</span> 🧠 <span class="t-yellow">▓▓▓▓▓▓▓▓░░</span> 78% 🎒
|
|
1167
1159
|
|
|
1168
|
-
<span class="t-dim">
|
|
1169
|
-
<span class="t-b t-cyan">⛳</span> migrate postgres schema <span class="t-dim">|</span> 🥉 $2.4100/$2.00 121% <span class="t-dim">|</span> <span class="t-red">BUSTED</span> <span class="t-dim">|</span> <span class="t-cyan">⚔️ Sonnet</span> <span class="t-dim">|</span> Floor 2/5
|
|
1170
|
-
<span class="t-dim"> ───────────────</span>
|
|
1160
|
+
<span class="border-red">██</span> ⛳ migrate postgres schema <span class="t-dim">$</span>2.41<span class="t-dim">/2.00</span> <span class="t-red">▓▓▓▓▓▓▓▓▓▓▓</span> 121% <span class="t-red">BUSTED</span> ⚔️ Sonnet <span class="t-dim">F2/5</span>
|
|
1171
1161
|
|
|
1172
|
-
<span class="t-dim">
|
|
1173
|
-
<span class="
|
|
1174
|
-
<span class="t-dim"> ───────────────</span></pre>
|
|
1162
|
+
<span class="border-yellow">██</span> 💤 write test suite for payments <span class="t-dim">$</span>1.22<span class="t-dim">/3.00</span> <span class="t-yellow">▓▓▓▓▓░░░░░░</span> 41% <span class="t-cyan">EFFICIENT</span> ⚔️ Sonnet <span class="t-dim">F2/5</span>
|
|
1163
|
+
<span class="border-yellow">██</span> 🧠 <span class="t-green">▓▓▓▓▓▓▓░░░</span> 67% 🪶</pre>
|
|
1175
1164
|
</div>
|
|
1176
1165
|
<div class="demo-label">
|
|
1177
1166
|
<strong>Live HUD</strong> appears in every Claude Code session. Shows quest, cost, efficiency rating, context load, and model class at a glance.
|
package/hooks/statusline.sh
CHANGED
|
@@ -22,7 +22,7 @@ except: pass
|
|
|
22
22
|
ctx_pct = (session.get('context_window') or {}).get('used_percentage') or None
|
|
23
23
|
quest = (run.get('quest') or 'Flow')[:32]
|
|
24
24
|
budget = run.get('budget')
|
|
25
|
-
floor = f"{run.get('floor',1)}/{run.get('totalFloors',5)}"
|
|
25
|
+
floor = f"F{run.get('floor',1)}/{run.get('totalFloors',5)}"
|
|
26
26
|
sm = session.get('model') or {}; m = (sm.get('id','') or run.get('model','') if isinstance(sm,dict) else sm or run.get('model','')).lower()
|
|
27
27
|
# opusplan must be checked before opus (opusplan contains 'opus' as substring)
|
|
28
28
|
if 'opusplan' in m: model, model_emoji = 'Paladin', '⚜️'
|
|
@@ -38,11 +38,11 @@ fast = run.get('fastMode', False)
|
|
|
38
38
|
fainted = run.get('fainted', False)
|
|
39
39
|
|
|
40
40
|
label_parts = [f'{model_emoji} {model}']
|
|
41
|
-
if effort: label_parts.append(effort.capitalize())
|
|
41
|
+
if effort and effort != 'medium': label_parts.append(effort.capitalize())
|
|
42
42
|
if fast: label_parts.append('⚡Fast')
|
|
43
43
|
model_label = '·'.join(label_parts)
|
|
44
44
|
|
|
45
|
-
R,
|
|
45
|
+
R, G, Y, M, C, DIM, RESET = '\033[31m','\033[32m','\033[33m','\033[35m','\033[36m','\033[2m','\033[0m'
|
|
46
46
|
BOLD = '\033[1m'
|
|
47
47
|
|
|
48
48
|
if cost < 0.10: tier_emoji = '💎'
|
|
@@ -51,6 +51,7 @@ elif cost < 1.00: tier_emoji = '🥈'
|
|
|
51
51
|
elif cost < 3.00: tier_emoji = '🥉'
|
|
52
52
|
else: tier_emoji = '💸'
|
|
53
53
|
|
|
54
|
+
# Accent bar color: red in danger, yellow otherwise
|
|
54
55
|
if budget:
|
|
55
56
|
pct = cost / budget * 100
|
|
56
57
|
if pct <= 25: rating, rc = 'LEGENDARY', M
|
|
@@ -58,24 +59,39 @@ if budget:
|
|
|
58
59
|
elif pct <= 75: rating, rc = 'SOLID', G
|
|
59
60
|
elif pct <= 100: rating, rc = 'CLOSE CALL', Y
|
|
60
61
|
else: rating, rc = 'BUSTED', R
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
accent = R if pct > 75 else Y
|
|
63
|
+
# Budget progress bar
|
|
64
|
+
bar_w = 11
|
|
65
|
+
bar_filled = min(bar_w, int(pct / 100 * bar_w + 0.5))
|
|
66
|
+
bar_empty = bar_w - bar_filled
|
|
67
|
+
bar = f"{accent}{'▓' * bar_filled}{'░' * bar_empty}{RESET}"
|
|
68
|
+
cost_str = f"{DIM}${RESET}{cost:.2f}{DIM}/{budget:.2f}{RESET} {bar} {pct:.0f}%"
|
|
69
|
+
rating_str = f" {rc}{rating}{RESET}"
|
|
63
70
|
else:
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
accent = Y
|
|
72
|
+
cost_str = f"{tier_emoji} ${cost:.2f}"
|
|
73
|
+
rating_str = ''
|
|
66
74
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if ctx_pct is not None:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
# Context bar (line 2, only shown when >= 50%)
|
|
76
|
+
ctx_line = None
|
|
77
|
+
if ctx_pct is not None and ctx_pct >= 50:
|
|
78
|
+
ctx_w = 10
|
|
79
|
+
ctx_filled = min(ctx_w, int(ctx_pct / 100 * ctx_w + 0.5))
|
|
80
|
+
ctx_empty = ctx_w - ctx_filled
|
|
81
|
+
if ctx_pct >= 90: ctx_color, ctx_icon = R, '📦'
|
|
82
|
+
elif ctx_pct >= 75: ctx_color, ctx_icon = Y, '🎒'
|
|
83
|
+
else: ctx_color, ctx_icon = G, '🪶'
|
|
84
|
+
ctx_bar = f"{ctx_color}{'▓' * ctx_filled}{'░' * ctx_empty}{RESET}"
|
|
85
|
+
ctx_line = f" {accent}██{RESET} 🧠 {ctx_bar} {ctx_pct:.0f}% {ctx_icon}"
|
|
73
86
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
# Line 1: accent bar + quest + cost bar + rating + model + floor
|
|
88
|
+
icon = '💤' if fainted else '⛳'
|
|
89
|
+
parts = [f" {accent}██{RESET} {icon} {quest} {cost_str}{rating_str} {model_label}"]
|
|
90
|
+
if budget: parts.append(f" {DIM}{floor}{RESET}")
|
|
91
|
+
line1 = ''.join(parts)
|
|
92
|
+
|
|
93
|
+
# Output (leading blank line separates from any existing statusline above)
|
|
94
|
+
print()
|
|
95
|
+
print(line1)
|
|
96
|
+
if ctx_line: print(ctx_line)
|
|
81
97
|
PYEOF
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokengolf",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Gamify your Claude Code sessions. Flow mode tracks you. Roguelike mode trains you.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"format": "prettier --write src/ hooks/",
|
|
18
18
|
"format:check": "prettier --check src/ hooks/",
|
|
19
19
|
"version": "git add CHANGELOG.md",
|
|
20
|
-
"postversion": "git push && git push --tags"
|
|
20
|
+
"postversion": "git push && git push --tags",
|
|
21
|
+
"postpublish": "./scripts/update-homebrew.sh"
|
|
21
22
|
},
|
|
22
23
|
"dependencies": {
|
|
23
24
|
"@inkjs/ui": "^2.0.0",
|
package/src/lib/demo.js
CHANGED
|
@@ -25,7 +25,8 @@ function hudLine({ quest, model, cost, budget, ctxPct, effort, fainted, floor })
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const labelParts = [`${modelEmoji} ${modelName}`];
|
|
28
|
-
if (effort
|
|
28
|
+
if (effort && effort !== 'medium')
|
|
29
|
+
labelParts.push(effort.charAt(0).toUpperCase() + effort.slice(1));
|
|
29
30
|
const modelLabel = labelParts.join('·');
|
|
30
31
|
|
|
31
32
|
let tierEmoji;
|
|
@@ -35,8 +36,7 @@ function hudLine({ quest, model, cost, budget, ctxPct, effort, fainted, floor })
|
|
|
35
36
|
else if (cost < 3.0) tierEmoji = '🥉';
|
|
36
37
|
else tierEmoji = '💸';
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
let costStr, ratingStr;
|
|
39
|
+
let costStr, ratingStr, accent;
|
|
40
40
|
|
|
41
41
|
if (budget) {
|
|
42
42
|
const pct = (cost / budget) * 100;
|
|
@@ -57,29 +57,47 @@ function hudLine({ quest, model, cost, budget, ctxPct, effort, fainted, floor })
|
|
|
57
57
|
rating = 'BUSTED';
|
|
58
58
|
rc = R;
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
accent = pct > 75 ? R : Y;
|
|
61
|
+
// Budget progress bar
|
|
62
|
+
const barW = 11;
|
|
63
|
+
const barFilled = Math.min(barW, Math.round((pct / 100) * barW));
|
|
64
|
+
const barEmpty = barW - barFilled;
|
|
65
|
+
const bar = `${accent}${'▓'.repeat(barFilled)}${'░'.repeat(barEmpty)}${RESET}`;
|
|
66
|
+
costStr = `${DIM}$${RESET}${cost.toFixed(2)}${DIM}/${budget.toFixed(2)}${RESET} ${bar} ${pct.toFixed(0)}%`;
|
|
67
|
+
ratingStr = ` ${rc}${rating}${RESET}`;
|
|
62
68
|
} else {
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
accent = Y;
|
|
70
|
+
costStr = `${tierEmoji} $${cost.toFixed(2)}`;
|
|
71
|
+
ratingStr = '';
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
// Context bar (line 2, only shown when >= 50%)
|
|
75
|
+
let ctxLine = null;
|
|
76
|
+
if (ctxPct != null && ctxPct >= 50) {
|
|
77
|
+
const ctxW = 10;
|
|
78
|
+
const ctxFilled = Math.min(ctxW, Math.round((ctxPct / 100) * ctxW));
|
|
79
|
+
const ctxEmpty = ctxW - ctxFilled;
|
|
80
|
+
let ctxColor, ctxIcon;
|
|
81
|
+
if (ctxPct >= 90) {
|
|
82
|
+
ctxColor = R;
|
|
83
|
+
ctxIcon = '📦';
|
|
84
|
+
} else if (ctxPct >= 75) {
|
|
85
|
+
ctxColor = Y;
|
|
86
|
+
ctxIcon = '🎒';
|
|
87
|
+
} else {
|
|
88
|
+
ctxColor = G;
|
|
89
|
+
ctxIcon = '🪶';
|
|
90
|
+
}
|
|
91
|
+
const ctxBar = `${ctxColor}${'▓'.repeat(ctxFilled)}${'░'.repeat(ctxEmpty)}${RESET}`;
|
|
92
|
+
ctxLine = ` ${accent}██${RESET} 🧠 ${ctxBar} ${ctxPct}% ${ctxIcon}`;
|
|
72
93
|
}
|
|
73
94
|
|
|
95
|
+
// Line 1: accent bar + quest + cost bar + rating + model + floor
|
|
74
96
|
const icon = fainted ? '💤' : '⛳';
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (ratingStr) parts.push(ratingStr);
|
|
78
|
-
if (ctxStr) parts.push(ctxStr);
|
|
79
|
-
parts.push(`${C}${modelLabel}${RESET}`);
|
|
80
|
-
if (budget && floor) parts.push(`Floor ${floor}`);
|
|
97
|
+
let line1 = ` ${accent}██${RESET} ${icon} ${quest} ${costStr}${ratingStr} ${modelLabel}`;
|
|
98
|
+
if (budget && floor) line1 += ` ${DIM}F${floor}${RESET}`;
|
|
81
99
|
|
|
82
|
-
return `${
|
|
100
|
+
return ctxLine ? `${line1}\n${ctxLine}` : line1;
|
|
83
101
|
}
|
|
84
102
|
|
|
85
103
|
const SCENARIOS = [
|