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 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
- - Shows: quest/mode | tier emoji + cost [/budget pct%] | [efficiency rating] | [🪶/🎒/📦 ctx%] | model label | [floor]
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
- - Context load: 🪶 green (50–74%), 🎒 yellow (75–89%), 📦 red (90%+); hidden below 50%
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
- refactor auth middleware | 🥈 $0.82/$4.00 21% | LEGENDARY | 🪶 52% | 🧙 Opus | Floor 3/5
262
- ───────────────
258
+ ██ ⛳ refactor auth middleware $0.82/4.00 ▓▓░░░░░░░░░ 21% LEGENDARY 🧙 Opus F3/5
259
+ ██ 🧠 ▓▓▓▓▓░░░░░ 52% 🪶
263
260
  ```
264
261
 
265
- Context indicators: **🪶** green (50–74%) · **🎒** yellow (75–89%) · **📦** red (90%+). **💤** replaces ⛳ if the previous session fainted.
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) labelParts.push(effort.charAt(0).toUpperCase() + effort.slice(1));
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
- const sep = ` ${DIM}|${RESET} `;
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
- costStr = `${tierEmoji} $${cost.toFixed(4)}/$${budget.toFixed(2)} ${pct.toFixed(0)}%`;
880
- ratingStr = `${rc}${rating}${RESET}`;
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
- costStr = `${tierEmoji} $${cost.toFixed(4)}`;
883
- ratingStr = null;
887
+ accent = Y;
888
+ costStr = `${tierEmoji} $${cost.toFixed(2)}`;
889
+ ratingStr = "";
884
890
  }
885
- let ctxStr = null;
886
- if (ctxPct != null) {
887
- if (ctxPct >= 90) ctxStr = `${R}\u{1F4E6} ${ctxPct}%${RESET}`;
888
- else if (ctxPct >= 75) ctxStr = `${Y}\u{1F392} ${ctxPct}%${RESET}`;
889
- else if (ctxPct >= 50) ctxStr = `${G}\u{1FAB6} ${ctxPct}%${RESET}`;
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
- const prefix = `${BOLD}${C}${icon}${RESET}`;
893
- const parts = [`${prefix} ${quest}`, costStr];
894
- if (ratingStr) parts.push(ratingStr);
895
- if (ctxStr) parts.push(ctxStr);
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="t-dim"> ───────────────</span>
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"> ───────────────</span>
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"> ───────────────</span>
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"> ───────────────</span>
1161
- <span class="t-b t-cyan">⛳</span> refactor auth middleware <span class="t-dim">|</span> 🥈 $0.8200/$4.00 21% <span class="t-dim">|</span> <span class="t-magenta">LEGENDARY</span> <span class="t-dim">|</span> <span class="t-green">🪶 52%</span> <span class="t-dim">|</span> <span class="t-cyan">🧙 Opus</span> <span class="t-dim">|</span> Floor 3/5
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"> ───────────────</span>
1165
- <span class="t-b t-cyan">⛳</span> fix N+1 query in dashboard <span class="t-dim">|</span> 🥈 $0.4600/$0.50 92% <span class="t-dim">|</span> <span class="t-yellow">CLOSE CALL</span> <span class="t-dim">|</span> <span class="t-yellow">🎒 78%</span> <span class="t-dim">|</span> <span class="t-cyan">🏹 Haiku</span> <span class="t-dim">|</span> Floor 4/5
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"> ───────────────</span>
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"> ───────────────</span>
1173
- <span class="t-b t-cyan">💤</span> write test suite for payments <span class="t-dim">|</span> 🥉 $1.2200/$3.00 41% <span class="t-dim">|</span> <span class="t-cyan">EFFICIENT</span> <span class="t-dim">|</span> <span class="t-green">🪶 67%</span> <span class="t-dim">|</span> <span class="t-cyan">⚔️ Sonnet</span> <span class="t-dim">|</span> Floor 2/5
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.
@@ -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, B, G, Y, M, C, DIM, RESET = '\033[31m','\033[34m','\033[32m','\033[33m','\033[35m','\033[36m','\033[2m','\033[0m'
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
- cost_str = f"{tier_emoji} ${cost:.4f}/${budget:.2f} {pct:.0f}%"
62
- rating_str = f"{rc}{rating}{RESET}"
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
- cost_str = f"{tier_emoji} ${cost:.4f}"
65
- rating_str = None
71
+ accent = Y
72
+ cost_str = f"{tier_emoji} ${cost:.2f}"
73
+ rating_str = ''
66
74
 
67
- sep = f" {DIM}|{RESET} "
68
- ctx_str = None
69
- if ctx_pct is not None:
70
- if ctx_pct >= 90: ctx_str = f"{R}📦 {ctx_pct:.0f}%{RESET}"
71
- elif ctx_pct >= 75: ctx_str = f"{Y}🎒 {ctx_pct:.0f}%{RESET}"
72
- elif ctx_pct >= 50: ctx_str = f"{G}🪶 {ctx_pct:.0f}%{RESET}"
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
- prefix = f"{BOLD}{C}{'💤' if fainted else '⛳'}{RESET}"
75
- parts = [f"{prefix} {quest}", cost_str]
76
- if rating_str: parts.append(rating_str)
77
- if ctx_str: parts.append(ctx_str)
78
- parts.append(f"{C}{model_label}{RESET}")
79
- if budget: parts.append(f"Floor {floor}")
80
- print('\n' + f'{DIM} ───────────────{RESET}' + '\n' + sep.join(parts) + '\n' + f'{DIM} ───────────────{RESET}')
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.0",
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) labelParts.push(effort.charAt(0).toUpperCase() + effort.slice(1));
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
- const sep = ` ${DIM}|${RESET} `;
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
- costStr = `${tierEmoji} $${cost.toFixed(4)}/$${budget.toFixed(2)} ${pct.toFixed(0)}%`;
61
- ratingStr = `${rc}${rating}${RESET}`;
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
- costStr = `${tierEmoji} $${cost.toFixed(4)}`;
64
- ratingStr = null;
69
+ accent = Y;
70
+ costStr = `${tierEmoji} $${cost.toFixed(2)}`;
71
+ ratingStr = '';
65
72
  }
66
73
 
67
- let ctxStr = null;
68
- if (ctxPct != null) {
69
- if (ctxPct >= 90) ctxStr = `${R}📦 ${ctxPct}%${RESET}`;
70
- else if (ctxPct >= 75) ctxStr = `${Y}🎒 ${ctxPct}%${RESET}`;
71
- else if (ctxPct >= 50) ctxStr = `${G}🪶 ${ctxPct}%${RESET}`;
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
- const prefix = `${BOLD}${C}${icon}${RESET}`;
76
- const parts = [`${prefix} ${quest}`, costStr];
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 `${DIM} ───────────────${RESET}\n${parts.join(sep)}\n${DIM} ───────────────${RESET}`;
100
+ return ctxLine ? `${line1}\n${ctxLine}` : line1;
83
101
  }
84
102
 
85
103
  const SCENARIOS = [