tokengolf 0.4.2 → 0.5.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/CHANGELOG.md +9 -0
- package/CLAUDE.md +10 -5
- package/LICENSE +21 -0
- package/README.md +228 -109
- package/dist/cli.js +97 -33
- package/docs/assets/banner.svg +45 -0
- package/docs/index.html +376 -197
- package/hooks/session-end.js +12 -27
- package/install.sh +69 -0
- package/package.json +3 -2
- package/scripts/update-homebrew.sh +39 -0
- package/src/components/ActiveRun.js +7 -3
- package/src/components/ScoreCard.js +7 -9
- package/src/components/StartRun.js +27 -8
- package/src/components/StatsView.js +31 -11
- package/src/lib/score.js +4 -4
- package/src/lib/ui.js +16 -0
- package/assets/demo-hud.png +0 -0
- package/assets/scorecard.png +0 -0
- package/docs/assets/demo-hud.png +0 -0
- package/docs/assets/scorecard.png +0 -0
package/hooks/session-end.js
CHANGED
|
@@ -23,10 +23,10 @@ function writeTTY(text) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function termWidth(str) {
|
|
26
|
-
// Compute display width of a string
|
|
26
|
+
// Compute display width of a string for achievement line wrapping.
|
|
27
|
+
// Handles emoji variation selectors and surrogates:
|
|
27
28
|
// - Supplementary plane chars (> U+FFFF) → 2 cols
|
|
28
|
-
// - U+FE0F (emoji variation selector after BMP char) → upgrades prev from 1→2
|
|
29
|
-
// - U+FE0F after supplementary → 0 (already 2)
|
|
29
|
+
// - U+FE0F (emoji variation selector after BMP char) → upgrades prev from 1→2
|
|
30
30
|
// - U+FE0E, ZWJ, zero-width chars → 0
|
|
31
31
|
// - Everything else → 1
|
|
32
32
|
/* eslint-disable no-control-regex */
|
|
@@ -52,7 +52,7 @@ function termWidth(str) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function renderScorecard(run) {
|
|
55
|
-
const W = Math.min(Math.max((process.stdout.columns || 88) -
|
|
55
|
+
const W = Math.min(Math.max((process.stdout.columns || 88) - 8, 40), 80);
|
|
56
56
|
const won = run.status === 'won';
|
|
57
57
|
const flowMode = !run.budget;
|
|
58
58
|
|
|
@@ -65,28 +65,13 @@ function renderScorecard(run) {
|
|
|
65
65
|
RESET = '\x1b[0m',
|
|
66
66
|
BOLD = '\x1b[1m';
|
|
67
67
|
const bc = won ? Y : R;
|
|
68
|
+
const BLK = '██';
|
|
68
69
|
|
|
69
|
-
const tl = '╔',
|
|
70
|
-
tr = '╗',
|
|
71
|
-
bl = '╚',
|
|
72
|
-
br = '╝';
|
|
73
|
-
const h = '═',
|
|
74
|
-
v = '║';
|
|
75
|
-
const ml = '╠',
|
|
76
|
-
mr = '╣';
|
|
77
|
-
|
|
78
|
-
function bar() {
|
|
79
|
-
return bc + ml + h.repeat(W) + mr + RESET;
|
|
80
|
-
}
|
|
81
|
-
function top() {
|
|
82
|
-
return bc + tl + h.repeat(W) + tr + RESET;
|
|
83
|
-
}
|
|
84
|
-
function bot() {
|
|
85
|
-
return bc + bl + h.repeat(W) + br + RESET;
|
|
86
|
-
}
|
|
87
70
|
function row(content) {
|
|
88
|
-
|
|
89
|
-
|
|
71
|
+
return bc + BLK + RESET + ' ' + content;
|
|
72
|
+
}
|
|
73
|
+
function bar() {
|
|
74
|
+
return bc + BLK + RESET + ' ' + DIM + '─'.repeat(W) + RESET;
|
|
90
75
|
}
|
|
91
76
|
|
|
92
77
|
const mc = getModelClass(run.model);
|
|
@@ -147,7 +132,7 @@ function renderScorecard(run) {
|
|
|
147
132
|
for (const token of achTokens) {
|
|
148
133
|
const sep = currentLine ? ' ' : '';
|
|
149
134
|
const testLen = termWidth(currentLine + sep + token);
|
|
150
|
-
if (currentLine && testLen > W
|
|
135
|
+
if (currentLine && testLen > W) {
|
|
151
136
|
achLines.push(currentLine);
|
|
152
137
|
currentLine = token;
|
|
153
138
|
} else {
|
|
@@ -160,7 +145,7 @@ function renderScorecard(run) {
|
|
|
160
145
|
const thinkRow =
|
|
161
146
|
ti > 0 ? `${M}🔮 ${ti} ultrathink${ti > 1 ? ' invocations' : ' invocation'}${RESET}` : null;
|
|
162
147
|
|
|
163
|
-
const lines = [
|
|
148
|
+
const lines = ['', row(header), row(questStr), bar(), row(midRow)];
|
|
164
149
|
|
|
165
150
|
if (thinkRow) {
|
|
166
151
|
lines.push(bar());
|
|
@@ -180,7 +165,7 @@ function renderScorecard(run) {
|
|
|
180
165
|
`${DIM}tokengolf scorecard${RESET} · ${DIM}tokengolf start${RESET} · ${DIM}tokengolf stats${RESET}`
|
|
181
166
|
)
|
|
182
167
|
);
|
|
183
|
-
lines.push(
|
|
168
|
+
lines.push('');
|
|
184
169
|
|
|
185
170
|
return lines.join('\n');
|
|
186
171
|
}
|
package/install.sh
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# TokenGolf installer
|
|
3
|
+
# Usage: curl -fsSL https://raw.githubusercontent.com/josheche/tokengolf/main/install.sh | bash
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
BOLD='\033[1m'
|
|
7
|
+
DIM='\033[2m'
|
|
8
|
+
GREEN='\033[32m'
|
|
9
|
+
YELLOW='\033[33m'
|
|
10
|
+
RED='\033[31m'
|
|
11
|
+
CYAN='\033[36m'
|
|
12
|
+
RESET='\033[0m'
|
|
13
|
+
|
|
14
|
+
info() { printf "${BOLD}${CYAN}▶${RESET} %s\n" "$1"; }
|
|
15
|
+
ok() { printf "${BOLD}${GREEN}✓${RESET} %s\n" "$1"; }
|
|
16
|
+
warn() { printf "${BOLD}${YELLOW}!${RESET} %s\n" "$1"; }
|
|
17
|
+
err() { printf "${BOLD}${RED}✗${RESET} %s\n" "$1" >&2; }
|
|
18
|
+
|
|
19
|
+
echo ""
|
|
20
|
+
printf "${YELLOW}██${RESET} ${BOLD}TokenGolf Installer${RESET}\n"
|
|
21
|
+
printf "${YELLOW}██${RESET} ${DIM}Every token matters.${RESET}\n"
|
|
22
|
+
echo ""
|
|
23
|
+
|
|
24
|
+
# Check Node.js
|
|
25
|
+
if ! command -v node &>/dev/null; then
|
|
26
|
+
err "Node.js is required but not installed."
|
|
27
|
+
echo ""
|
|
28
|
+
echo " Install Node.js (v18+) from:"
|
|
29
|
+
echo " https://nodejs.org"
|
|
30
|
+
echo " brew install node"
|
|
31
|
+
echo " curl -fsSL https://fnm.vercel.app/install | bash"
|
|
32
|
+
echo ""
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
NODE_VERSION=$(node -v | sed 's/v//' | cut -d. -f1)
|
|
37
|
+
if [ "$NODE_VERSION" -lt 18 ]; then
|
|
38
|
+
err "Node.js v18+ required (found v$(node -v | sed 's/v//'))"
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
ok "Node.js $(node -v) detected"
|
|
43
|
+
|
|
44
|
+
# Check npm
|
|
45
|
+
if ! command -v npm &>/dev/null; then
|
|
46
|
+
err "npm is required but not installed."
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Install tokengolf globally
|
|
51
|
+
info "Installing tokengolf via npm..."
|
|
52
|
+
npm install -g tokengolf
|
|
53
|
+
|
|
54
|
+
ok "tokengolf installed ($(tokengolf --version 2>/dev/null || echo 'unknown version'))"
|
|
55
|
+
|
|
56
|
+
# Patch Claude Code hooks (only if Claude Code is installed)
|
|
57
|
+
if [ -d "$HOME/.claude" ]; then
|
|
58
|
+
info "Installing Claude Code hooks..."
|
|
59
|
+
tokengolf install
|
|
60
|
+
else
|
|
61
|
+
warn "Claude Code not detected — skipping hook setup."
|
|
62
|
+
printf " Run ${CYAN}tokengolf install${RESET} after installing Claude Code.\n"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
echo ""
|
|
66
|
+
printf "${YELLOW}██${RESET} ${BOLD}${GREEN}Ready to play!${RESET}\n"
|
|
67
|
+
printf "${YELLOW}██${RESET} ${DIM}Open Claude Code and /exit to see your first scorecard.${RESET}\n"
|
|
68
|
+
printf "${YELLOW}██${RESET} ${DIM}Or run: tokengolf start${RESET}\n"
|
|
69
|
+
echo ""
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokengolf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Gamify your Claude Code sessions. Flow mode tracks you. Roguelike mode trains you.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,5 +36,6 @@
|
|
|
36
36
|
},
|
|
37
37
|
"engines": {
|
|
38
38
|
"node": ">=18.0.0"
|
|
39
|
-
}
|
|
39
|
+
},
|
|
40
|
+
"license": "MIT"
|
|
40
41
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Updates the Homebrew formula after npm publish.
|
|
3
|
+
# Usage: ./scripts/update-homebrew.sh
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
7
|
+
TARBALL="https://registry.npmjs.org/tokengolf/-/tokengolf-${VERSION}.tgz"
|
|
8
|
+
TAP_DIR="${TAP_DIR:-../homebrew-tokengolf}"
|
|
9
|
+
FORMULA="${TAP_DIR}/Formula/tokengolf.rb"
|
|
10
|
+
|
|
11
|
+
echo "Updating Homebrew formula to v${VERSION}..."
|
|
12
|
+
|
|
13
|
+
# Fetch SHA256 of the published tarball
|
|
14
|
+
SHA=$(curl -sfL "$TARBALL" | shasum -a 256 | cut -d' ' -f1)
|
|
15
|
+
if [ -z "$SHA" ]; then
|
|
16
|
+
echo "Error: could not fetch tarball at ${TARBALL}" >&2
|
|
17
|
+
echo "Has the version been published to npm?" >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
echo " tarball: ${TARBALL}"
|
|
22
|
+
echo " sha256: ${SHA}"
|
|
23
|
+
|
|
24
|
+
# Update formula (BSD sed on macOS; GNU sed on Linux omits the '')
|
|
25
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
26
|
+
sed -i '' "s|url \".*\"|url \"${TARBALL}\"|" "$FORMULA"
|
|
27
|
+
sed -i '' "s|sha256 \".*\"|sha256 \"${SHA}\"|" "$FORMULA"
|
|
28
|
+
else
|
|
29
|
+
sed -i "s|url \".*\"|url \"${TARBALL}\"|" "$FORMULA"
|
|
30
|
+
sed -i "s|sha256 \".*\"|sha256 \"${SHA}\"|" "$FORMULA"
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Commit and push
|
|
34
|
+
cd "$TAP_DIR"
|
|
35
|
+
git add Formula/tokengolf.rb
|
|
36
|
+
git commit -m "tokengolf ${VERSION}"
|
|
37
|
+
git push
|
|
38
|
+
|
|
39
|
+
echo "Done — brew upgrade tokengolf will now pull v${VERSION}"
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
formatElapsed,
|
|
11
11
|
FLOORS,
|
|
12
12
|
} from '../lib/score.js';
|
|
13
|
+
import { ACCENT_BORDER, ACCENT_PADDING } from '../lib/ui.js';
|
|
13
14
|
|
|
14
15
|
export function ActiveRun({ run: initialRun }) {
|
|
15
16
|
const { exit } = useApp();
|
|
@@ -48,9 +49,12 @@ export function ActiveRun({ run: initialRun }) {
|
|
|
48
49
|
</Box>
|
|
49
50
|
|
|
50
51
|
<Box
|
|
51
|
-
borderStyle=
|
|
52
|
+
borderStyle={ACCENT_BORDER}
|
|
52
53
|
borderColor="yellow"
|
|
53
|
-
|
|
54
|
+
borderRight={false}
|
|
55
|
+
borderTop={false}
|
|
56
|
+
borderBottom={false}
|
|
57
|
+
paddingLeft={ACCENT_PADDING}
|
|
54
58
|
paddingY={1}
|
|
55
59
|
flexDirection="column"
|
|
56
60
|
gap={1}
|
|
@@ -121,7 +125,7 @@ export function ActiveRun({ run: initialRun }) {
|
|
|
121
125
|
</Box>
|
|
122
126
|
|
|
123
127
|
{pct >= 80 && pct < 100 && (
|
|
124
|
-
<Box
|
|
128
|
+
<Box paddingLeft={1}>
|
|
125
129
|
<Text color="red" bold>
|
|
126
130
|
⚠️ BUDGET WARNING — {formatCost(run.budget - run.spent)} left
|
|
127
131
|
</Text>
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getOpusPct,
|
|
12
12
|
MODEL_CLASSES,
|
|
13
13
|
} from '../lib/score.js';
|
|
14
|
+
import { ACCENT_BORDER, ACCENT_PADDING } from '../lib/ui.js';
|
|
14
15
|
|
|
15
16
|
export function ScoreCard({ run }) {
|
|
16
17
|
const { exit } = useApp();
|
|
@@ -37,9 +38,12 @@ export function ScoreCard({ run }) {
|
|
|
37
38
|
<Box flexDirection="column" paddingX={1} paddingY={1} gap={1}>
|
|
38
39
|
{/* Big status header */}
|
|
39
40
|
<Box
|
|
40
|
-
borderStyle=
|
|
41
|
+
borderStyle={ACCENT_BORDER}
|
|
41
42
|
borderColor={won ? 'yellow' : 'red'}
|
|
42
|
-
|
|
43
|
+
borderRight={false}
|
|
44
|
+
borderTop={false}
|
|
45
|
+
borderBottom={false}
|
|
46
|
+
paddingLeft={ACCENT_PADDING}
|
|
43
47
|
paddingY={1}
|
|
44
48
|
flexDirection="column"
|
|
45
49
|
gap={1}
|
|
@@ -213,13 +217,7 @@ export function ScoreCard({ run }) {
|
|
|
213
217
|
|
|
214
218
|
{/* Death tip */}
|
|
215
219
|
{!won && run.budget && (
|
|
216
|
-
<Box
|
|
217
|
-
borderStyle="single"
|
|
218
|
-
borderColor="red"
|
|
219
|
-
paddingX={1}
|
|
220
|
-
marginTop={1}
|
|
221
|
-
flexDirection="column"
|
|
222
|
-
>
|
|
220
|
+
<Box marginTop={1} flexDirection="column" paddingLeft={1}>
|
|
223
221
|
<Text color="red" bold>
|
|
224
222
|
Cause of death: Budget exceeded by {formatCost(run.spent - run.budget)}
|
|
225
223
|
</Text>
|
|
@@ -3,12 +3,16 @@ import { Box, Text, useApp } from 'ink';
|
|
|
3
3
|
import { TextInput, Select, ConfirmInput } from '@inkjs/ui';
|
|
4
4
|
import { setCurrentRun } from '../lib/state.js';
|
|
5
5
|
import { getModelClass, getEffortLevel, getModelBudgets, FLOORS } from '../lib/score.js';
|
|
6
|
+
import { ACCENT_BORDER, ACCENT_PADDING } from '../lib/ui.js';
|
|
6
7
|
|
|
7
8
|
const MODEL_OPTIONS = [
|
|
8
|
-
{ label: '⚔️ Sonnet — Balanced. The default run. [
|
|
9
|
-
{
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
{ label: '⚔️ Sonnet — Balanced. The default run. [Standard]', value: 'claude-sonnet-4-6' },
|
|
10
|
+
{
|
|
11
|
+
label: '🏹 Haiku — Glass cannon. Hard mode. [Nightmare]',
|
|
12
|
+
value: 'claude-haiku-4-5-20251001',
|
|
13
|
+
},
|
|
14
|
+
{ label: '⚜️ Paladin — Opus plans, Sonnet executes. [Tactical]', value: 'opusplan' },
|
|
15
|
+
{ label: '🧙 Opus — Powerful but expensive. [Casual]', value: 'claude-opus-4-6' },
|
|
12
16
|
];
|
|
13
17
|
|
|
14
18
|
const EFFORT_OPTIONS_BASE = [
|
|
@@ -62,9 +66,12 @@ export function StartRun() {
|
|
|
62
66
|
<Box
|
|
63
67
|
flexDirection="column"
|
|
64
68
|
gap={1}
|
|
65
|
-
borderStyle=
|
|
69
|
+
borderStyle={ACCENT_BORDER}
|
|
66
70
|
borderColor="gray"
|
|
67
|
-
|
|
71
|
+
borderRight={false}
|
|
72
|
+
borderTop={false}
|
|
73
|
+
borderBottom={false}
|
|
74
|
+
paddingLeft={ACCENT_PADDING}
|
|
68
75
|
paddingY={1}
|
|
69
76
|
>
|
|
70
77
|
{/* Quest */}
|
|
@@ -176,9 +183,21 @@ export function StartRun() {
|
|
|
176
183
|
{step === 'confirm' && (
|
|
177
184
|
<Box flexDirection="column" gap={1} marginTop={1}>
|
|
178
185
|
<Box
|
|
179
|
-
borderStyle=
|
|
186
|
+
borderStyle={{
|
|
187
|
+
topLeft: ' ',
|
|
188
|
+
top: ' ',
|
|
189
|
+
topRight: ' ',
|
|
190
|
+
left: '██',
|
|
191
|
+
right: ' ',
|
|
192
|
+
bottomLeft: ' ',
|
|
193
|
+
bottom: ' ',
|
|
194
|
+
bottomRight: ' ',
|
|
195
|
+
}}
|
|
180
196
|
borderColor="yellow"
|
|
181
|
-
|
|
197
|
+
borderRight={false}
|
|
198
|
+
borderTop={false}
|
|
199
|
+
borderBottom={false}
|
|
200
|
+
paddingLeft={ACCENT_PADDING}
|
|
182
201
|
paddingY={1}
|
|
183
202
|
flexDirection="column"
|
|
184
203
|
>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Box, Text, useApp, useInput } from 'ink';
|
|
3
3
|
import { getTier, getModelClass, getBudgetPct, formatCost } from '../lib/score.js';
|
|
4
|
+
import { ACCENT_BORDER, ACCENT_PADDING } from '../lib/ui.js';
|
|
4
5
|
|
|
5
6
|
export function StatsView({ stats }) {
|
|
6
7
|
const { exit } = useApp();
|
|
@@ -33,7 +34,16 @@ export function StatsView({ stats }) {
|
|
|
33
34
|
</Box>
|
|
34
35
|
|
|
35
36
|
{/* Top line */}
|
|
36
|
-
<Box
|
|
37
|
+
<Box
|
|
38
|
+
borderStyle={ACCENT_BORDER}
|
|
39
|
+
borderColor="gray"
|
|
40
|
+
borderRight={false}
|
|
41
|
+
borderTop={false}
|
|
42
|
+
borderBottom={false}
|
|
43
|
+
paddingLeft={ACCENT_PADDING}
|
|
44
|
+
paddingY={1}
|
|
45
|
+
gap={4}
|
|
46
|
+
>
|
|
37
47
|
<Box flexDirection="column">
|
|
38
48
|
<Text color="gray" dimColor>
|
|
39
49
|
RUNS
|
|
@@ -85,9 +95,21 @@ export function StatsView({ stats }) {
|
|
|
85
95
|
<Box flexDirection="column" gap={0}>
|
|
86
96
|
<Text color="yellow">🏆 Personal Best</Text>
|
|
87
97
|
<Box
|
|
88
|
-
borderStyle=
|
|
98
|
+
borderStyle={{
|
|
99
|
+
topLeft: ' ',
|
|
100
|
+
top: ' ',
|
|
101
|
+
topRight: ' ',
|
|
102
|
+
left: '██',
|
|
103
|
+
right: ' ',
|
|
104
|
+
bottomLeft: ' ',
|
|
105
|
+
bottom: ' ',
|
|
106
|
+
bottomRight: ' ',
|
|
107
|
+
}}
|
|
89
108
|
borderColor="yellow"
|
|
90
|
-
|
|
109
|
+
borderRight={false}
|
|
110
|
+
borderTop={false}
|
|
111
|
+
borderBottom={false}
|
|
112
|
+
paddingLeft={ACCENT_PADDING}
|
|
91
113
|
paddingY={1}
|
|
92
114
|
flexDirection="column"
|
|
93
115
|
>
|
|
@@ -141,16 +163,14 @@ export function StatsView({ stats }) {
|
|
|
141
163
|
<Text color="gray" dimColor>
|
|
142
164
|
Recent achievements:
|
|
143
165
|
</Text>
|
|
144
|
-
<Box flexWrap="wrap"
|
|
166
|
+
<Box flexWrap="wrap" columnGap={2}>
|
|
145
167
|
{stats.achievements.slice(0, 12).map((a, i) => (
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
{a.label}
|
|
151
|
-
</Text>
|
|
168
|
+
<Text key={i}>
|
|
169
|
+
{a.emoji}{' '}
|
|
170
|
+
<Text color="gray" dimColor>
|
|
171
|
+
{a.label}
|
|
152
172
|
</Text>
|
|
153
|
-
</
|
|
173
|
+
</Text>
|
|
154
174
|
))}
|
|
155
175
|
</Box>
|
|
156
176
|
</Box>
|
package/src/lib/score.js
CHANGED
|
@@ -37,28 +37,28 @@ export const MODEL_CLASSES = {
|
|
|
37
37
|
name: 'Haiku',
|
|
38
38
|
label: 'Rogue',
|
|
39
39
|
emoji: '🏹',
|
|
40
|
-
difficulty: '
|
|
40
|
+
difficulty: 'Nightmare',
|
|
41
41
|
color: 'red',
|
|
42
42
|
},
|
|
43
43
|
sonnet: {
|
|
44
44
|
name: 'Sonnet',
|
|
45
45
|
label: 'Fighter',
|
|
46
46
|
emoji: '⚔️',
|
|
47
|
-
difficulty: '
|
|
47
|
+
difficulty: 'Standard',
|
|
48
48
|
color: 'cyan',
|
|
49
49
|
},
|
|
50
50
|
opusplan: {
|
|
51
51
|
name: 'Paladin',
|
|
52
52
|
label: 'Paladin',
|
|
53
53
|
emoji: '⚜️',
|
|
54
|
-
difficulty: '
|
|
54
|
+
difficulty: 'Tactical',
|
|
55
55
|
color: 'yellow',
|
|
56
56
|
},
|
|
57
57
|
opus: {
|
|
58
58
|
name: 'Opus',
|
|
59
59
|
label: 'Warlock',
|
|
60
60
|
emoji: '🧙',
|
|
61
|
-
difficulty: '
|
|
61
|
+
difficulty: 'Casual',
|
|
62
62
|
color: 'magenta',
|
|
63
63
|
},
|
|
64
64
|
};
|
package/src/lib/ui.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Shared UI constants for Ink components
|
|
2
|
+
|
|
3
|
+
// Design D: left-only ██ block accent bar.
|
|
4
|
+
// paddingLeft: 3 = 2 visible spaces after ██ (border eats 1)
|
|
5
|
+
export const ACCENT_BORDER = {
|
|
6
|
+
topLeft: ' ',
|
|
7
|
+
top: ' ',
|
|
8
|
+
topRight: ' ',
|
|
9
|
+
left: '██',
|
|
10
|
+
right: ' ',
|
|
11
|
+
bottomLeft: ' ',
|
|
12
|
+
bottom: ' ',
|
|
13
|
+
bottomRight: ' ',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const ACCENT_PADDING = 3;
|
package/assets/demo-hud.png
DELETED
|
Binary file
|
package/assets/scorecard.png
DELETED
|
Binary file
|
package/docs/assets/demo-hud.png
DELETED
|
Binary file
|
|
Binary file
|