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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokengolf",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Gamify your Claude Code sessions. Flow mode tracks you. Roguelike mode trains you.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ // Captures demo output and converts ANSI escape codes to HTML spans
3
+ // Usage: node scripts/demo-to-html.js [scorecard|active|stats|hud] [index]
4
+
5
+ import { execSync } from 'child_process';
6
+
7
+ const component = process.argv[2] || 'scorecard';
8
+ const index = process.argv[3] != null ? `-i ${process.argv[3]}` : '';
9
+
10
+ const raw = execSync(`node dist/cli.js demo ${component} ${index}`, {
11
+ encoding: 'utf8',
12
+ env: { ...process.env, FORCE_COLOR: '1' },
13
+ });
14
+
15
+ // ANSI color code → CSS class mapping
16
+ const ANSI_MAP = {
17
+ '1': 'b', // bold
18
+ '2': 'dim', // dim
19
+ '3': 'i', // italic
20
+ '31': 'red',
21
+ '32': 'green',
22
+ '33': 'yellow',
23
+ '34': 'blue',
24
+ '35': 'magenta',
25
+ '36': 'cyan',
26
+ '37': 'white',
27
+ '39': '', // default fg
28
+ '90': 'dim', // bright black = dim
29
+ };
30
+
31
+ function ansiToHtml(str) {
32
+ // Remove the demo header lines (first 2 lines) and separator/title lines
33
+ const lines = str.split('\n');
34
+
35
+ // Find where actual content starts (skip "TokenGolf — X Demo" + "N variants" + separator + title)
36
+ let startIdx = 0;
37
+ for (let i = 0; i < lines.length; i++) {
38
+ if (lines[i].includes('TokenGolf —') || lines[i].includes('variant')) {
39
+ startIdx = i + 1;
40
+ }
41
+ }
42
+
43
+ // Also skip the leading separator + title line
44
+ while (startIdx < lines.length && lines[startIdx].trim() === '') startIdx++;
45
+ // Skip "──────" separator
46
+ const stripped = lines[startIdx]?.replace(/\x1b\[[0-9;]*m/g, '').trim();
47
+ if (stripped && stripped.match(/^─+$/)) startIdx++;
48
+ // Skip title line
49
+ if (lines[startIdx]) startIdx++;
50
+
51
+ let content = lines.slice(startIdx).join('\n');
52
+
53
+ // Remove trailing empty lines
54
+ content = content.replace(/\n+$/, '');
55
+
56
+ // Also remove Ink's cleanup sequences ([2K[1A patterns)
57
+ content = content.replace(/(\x1b\[2K\x1b\[1A)+\x1b\[2K\x1b\[G/g, '');
58
+ content = content.replace(/\x1b\[2K/g, '');
59
+ content = content.replace(/\x1b\[1A/g, '');
60
+ content = content.replace(/\x1b\[\d*G/g, '');
61
+
62
+ // Remove trailing cleanup
63
+ content = content.replace(/\n+$/, '');
64
+
65
+ // Convert ANSI to HTML spans
66
+ let html = '';
67
+ let openTags = [];
68
+
69
+ const parts = content.split(/(\x1b\[[0-9;]*m)/);
70
+
71
+ for (const part of parts) {
72
+ const match = part.match(/^\x1b\[([0-9;]*)m$/);
73
+ if (match) {
74
+ const codes = match[1].split(';');
75
+ for (const code of codes) {
76
+ if (code === '0' || code === '') {
77
+ // Reset — close all open tags
78
+ html += openTags.map(() => '</span>').join('');
79
+ openTags = [];
80
+ } else {
81
+ const cls = ANSI_MAP[code];
82
+ if (cls) {
83
+ html += `<span class="t-${cls}">`;
84
+ openTags.push(cls);
85
+ }
86
+ }
87
+ }
88
+ } else {
89
+ // Escape HTML entities
90
+ html += part
91
+ .replace(/&/g, '&amp;')
92
+ .replace(/</g, '&lt;')
93
+ .replace(/>/g, '&gt;');
94
+ }
95
+ }
96
+
97
+ // Close remaining tags
98
+ html += openTags.map(() => '</span>').join('');
99
+
100
+ return html;
101
+ }
102
+
103
+ const html = ansiToHtml(raw);
104
+ console.log(html);
package/src/cli.js CHANGED
@@ -114,11 +114,51 @@ program
114
114
  });
115
115
 
116
116
  program
117
- .command('demo')
118
- .description('Show HUD examples for all game states (great for screenshots)')
119
- .action(async () => {
120
- const { runDemo } = await import('./lib/demo.js');
121
- runDemo();
117
+ .command('demo [component]')
118
+ .description('Show UI demos all, hud, scorecard, active, stats')
119
+ .option('-i, --index <n>', 'Show only the Nth variant (0-based)')
120
+ .action(async (component, opts) => {
121
+ const idx = opts.index != null ? parseInt(opts.index) : undefined;
122
+ const c = (component || 'all').toLowerCase();
123
+
124
+ if (c === 'all') {
125
+ const { runDemo } = await import('./lib/demo.js');
126
+ runDemo();
127
+ const { runScoreCardDemo } = await import('./lib/demo-scorecard.js');
128
+ await runScoreCardDemo(idx);
129
+ const { runActiveDemo } = await import('./lib/demo-active.js');
130
+ await runActiveDemo(idx);
131
+ const { runStatsDemo } = await import('./lib/demo-stats.js');
132
+ await runStatsDemo(idx);
133
+ process.exit(0);
134
+ }
135
+
136
+ if (c === 'hud') {
137
+ const { runDemo } = await import('./lib/demo.js');
138
+ runDemo();
139
+ return;
140
+ }
141
+
142
+ if (c === 'scorecard') {
143
+ const { runScoreCardDemo } = await import('./lib/demo-scorecard.js');
144
+ await runScoreCardDemo(idx);
145
+ process.exit(0);
146
+ }
147
+
148
+ if (c === 'active') {
149
+ const { runActiveDemo } = await import('./lib/demo-active.js');
150
+ await runActiveDemo(idx);
151
+ process.exit(0);
152
+ }
153
+
154
+ if (c === 'stats') {
155
+ const { runStatsDemo } = await import('./lib/demo-stats.js');
156
+ await runStatsDemo(idx);
157
+ process.exit(0);
158
+ }
159
+
160
+ console.log('Unknown demo component. Choose: all, hud, scorecard, active, stats');
161
+ process.exit(1);
122
162
  });
123
163
 
124
164
  program
@@ -0,0 +1,56 @@
1
+ // Demo: ActiveRun variants (Ink)
2
+ import React from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { demoRender } from './demo-render.js';
5
+ import { ActiveRun } from '../components/ActiveRun.js';
6
+ import { ACTIVERUN_FIXTURES } from './demo-fixtures.js';
7
+
8
+ function DemoActiveRun({ fixture }) {
9
+ return (
10
+ <Box flexDirection="column">
11
+ <Box paddingX={1}>
12
+ <Text color="gray" dimColor>
13
+ {'─'.repeat(50)}
14
+ </Text>
15
+ </Box>
16
+ <Box paddingX={1}>
17
+ <Text color="gray" dimColor italic>
18
+ {fixture.title}
19
+ </Text>
20
+ </Box>
21
+ <ActiveRun run={fixture.run} />
22
+ </Box>
23
+ );
24
+ }
25
+
26
+ export function runActiveDemo(index) {
27
+ const fixtures = index != null ? [ACTIVERUN_FIXTURES[index]] : ACTIVERUN_FIXTURES;
28
+ if (!fixtures[0]) {
29
+ console.log(
30
+ `Invalid index. ${ACTIVERUN_FIXTURES.length} variants available (0-${ACTIVERUN_FIXTURES.length - 1}).`
31
+ );
32
+ process.exit(1);
33
+ }
34
+
35
+ console.log('');
36
+ console.log('\x1b[1m\x1b[36m⛳ TokenGolf — ActiveRun Demo\x1b[0m');
37
+ console.log(`\x1b[2m${fixtures.length} variant${fixtures.length > 1 ? 's' : ''}\x1b[0m`);
38
+
39
+ return new Promise((resolve) => {
40
+ let i = 0;
41
+ function renderNext() {
42
+ if (i >= fixtures.length) {
43
+ resolve();
44
+ return;
45
+ }
46
+ const fixture = fixtures[i];
47
+ const inst = demoRender(React.createElement(DemoActiveRun, { fixture }));
48
+ setTimeout(() => {
49
+ inst.unmount();
50
+ i++;
51
+ renderNext();
52
+ }, 100);
53
+ }
54
+ renderNext();
55
+ });
56
+ }