arbiter-ai 1.0.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.
Files changed (51) hide show
  1. package/README.md +41 -0
  2. package/assets/jerom_16x16.png +0 -0
  3. package/dist/arbiter.d.ts +43 -0
  4. package/dist/arbiter.js +486 -0
  5. package/dist/context-analyzer.d.ts +15 -0
  6. package/dist/context-analyzer.js +603 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +165 -0
  9. package/dist/orchestrator.d.ts +31 -0
  10. package/dist/orchestrator.js +227 -0
  11. package/dist/router.d.ts +187 -0
  12. package/dist/router.js +1135 -0
  13. package/dist/router.test.d.ts +15 -0
  14. package/dist/router.test.js +95 -0
  15. package/dist/session-persistence.d.ts +9 -0
  16. package/dist/session-persistence.js +63 -0
  17. package/dist/session-persistence.test.d.ts +1 -0
  18. package/dist/session-persistence.test.js +165 -0
  19. package/dist/sound.d.ts +31 -0
  20. package/dist/sound.js +50 -0
  21. package/dist/state.d.ts +72 -0
  22. package/dist/state.js +107 -0
  23. package/dist/state.test.d.ts +1 -0
  24. package/dist/state.test.js +194 -0
  25. package/dist/test-headless.d.ts +1 -0
  26. package/dist/test-headless.js +155 -0
  27. package/dist/tui/index.d.ts +14 -0
  28. package/dist/tui/index.js +17 -0
  29. package/dist/tui/layout.d.ts +30 -0
  30. package/dist/tui/layout.js +200 -0
  31. package/dist/tui/render.d.ts +57 -0
  32. package/dist/tui/render.js +266 -0
  33. package/dist/tui/scene.d.ts +64 -0
  34. package/dist/tui/scene.js +366 -0
  35. package/dist/tui/screens/CharacterSelect-termkit.d.ts +18 -0
  36. package/dist/tui/screens/CharacterSelect-termkit.js +216 -0
  37. package/dist/tui/screens/ForestIntro-termkit.d.ts +15 -0
  38. package/dist/tui/screens/ForestIntro-termkit.js +856 -0
  39. package/dist/tui/screens/GitignoreCheck-termkit.d.ts +14 -0
  40. package/dist/tui/screens/GitignoreCheck-termkit.js +185 -0
  41. package/dist/tui/screens/TitleScreen-termkit.d.ts +14 -0
  42. package/dist/tui/screens/TitleScreen-termkit.js +132 -0
  43. package/dist/tui/screens/index.d.ts +9 -0
  44. package/dist/tui/screens/index.js +10 -0
  45. package/dist/tui/tileset.d.ts +97 -0
  46. package/dist/tui/tileset.js +237 -0
  47. package/dist/tui/tui-termkit.d.ts +34 -0
  48. package/dist/tui/tui-termkit.js +2602 -0
  49. package/dist/tui/types.d.ts +41 -0
  50. package/dist/tui/types.js +4 -0
  51. package/package.json +71 -0
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Gitignore Check Screen (terminal-kit version)
3
+ *
4
+ * Checks if Arbiter's generated files are gitignored in the user's project.
5
+ * If not, prompts user to add them. Skips silently if not in a git repo.
6
+ */
7
+ /**
8
+ * Shows the gitignore check prompt using terminal-kit.
9
+ * Skips silently if not in a git repo or files are already ignored.
10
+ *
11
+ * @returns Promise<void> - Resolves when check is complete
12
+ */
13
+ export declare function checkGitignore(): Promise<void>;
14
+ export default checkGitignore;
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Gitignore Check Screen (terminal-kit version)
3
+ *
4
+ * Checks if Arbiter's generated files are gitignored in the user's project.
5
+ * If not, prompts user to add them. Skips silently if not in a git repo.
6
+ */
7
+ import { execSync } from 'node:child_process';
8
+ import * as fs from 'node:fs';
9
+ import * as path from 'node:path';
10
+ import termKit from 'terminal-kit';
11
+ const term = termKit.terminal;
12
+ // ANSI codes
13
+ const DIM = '\x1b[2m';
14
+ const RESET = '\x1b[0m';
15
+ const YELLOW = '\x1b[33m';
16
+ const GREEN = '\x1b[32m';
17
+ // Files that Arbiter creates and should be gitignored
18
+ const ARBITER_FILES = ['.claude/.arbiter-session.json', '.claude/arbiter.tmp.log'];
19
+ // What to append to .gitignore
20
+ const GITIGNORE_ENTRIES = `
21
+ # Arbiter
22
+ .claude/.arbiter-session.json
23
+ .claude/arbiter.tmp.log
24
+ `;
25
+ /**
26
+ * Check if we're in a git repository
27
+ * @returns The git root path, or null if not in a repo
28
+ */
29
+ function getGitRoot() {
30
+ try {
31
+ const root = execSync('git rev-parse --show-toplevel', {
32
+ encoding: 'utf-8',
33
+ stdio: ['pipe', 'pipe', 'pipe'],
34
+ }).trim();
35
+ return root;
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ /**
42
+ * Check if a file is gitignored
43
+ * @param filePath - Path to check (relative to git root)
44
+ * @returns true if ignored, false if not
45
+ */
46
+ function isGitignored(filePath) {
47
+ try {
48
+ execSync(`git check-ignore -q ${filePath}`, {
49
+ stdio: ['pipe', 'pipe', 'pipe'],
50
+ });
51
+ return true; // exit code 0 = ignored
52
+ }
53
+ catch {
54
+ return false; // exit code 1 = not ignored
55
+ }
56
+ }
57
+ /**
58
+ * Append entries to .gitignore file
59
+ * @param gitRoot - Git repository root path
60
+ */
61
+ function addToGitignore(gitRoot) {
62
+ const gitignorePath = path.join(gitRoot, '.gitignore');
63
+ // Append to existing or create new
64
+ fs.appendFileSync(gitignorePath, GITIGNORE_ENTRIES, 'utf-8');
65
+ }
66
+ /**
67
+ * Shows the gitignore check prompt using terminal-kit.
68
+ * Skips silently if not in a git repo or files are already ignored.
69
+ *
70
+ * @returns Promise<void> - Resolves when check is complete
71
+ */
72
+ export async function checkGitignore() {
73
+ // Check if we're in a git repo
74
+ const gitRoot = getGitRoot();
75
+ if (!gitRoot) {
76
+ return; // Not a git repo, skip silently
77
+ }
78
+ // Check which files are not gitignored
79
+ const unignoredFiles = ARBITER_FILES.filter((file) => !isGitignored(file));
80
+ if (unignoredFiles.length === 0) {
81
+ return; // All files already ignored, skip silently
82
+ }
83
+ return new Promise((resolve) => {
84
+ // Initialize terminal
85
+ term.fullscreen(true);
86
+ term.hideCursor();
87
+ term.grabInput({ mouse: 'button' });
88
+ // Get terminal dimensions
89
+ let width = 180;
90
+ let height = 50;
91
+ if (typeof term.width === 'number' && Number.isFinite(term.width) && term.width > 0) {
92
+ width = term.width;
93
+ }
94
+ if (typeof term.height === 'number' && Number.isFinite(term.height) && term.height > 0) {
95
+ height = term.height;
96
+ }
97
+ // Content lines
98
+ const lines = [
99
+ "The Arbiter creates files that shouldn't be committed:",
100
+ '',
101
+ ...unignoredFiles.map((f) => ` ${f}`),
102
+ '',
103
+ 'Add to .gitignore? [y/n]',
104
+ ];
105
+ // Calculate centering
106
+ const maxLineWidth = Math.max(...lines.map((l) => l.length));
107
+ const contentHeight = lines.length;
108
+ const startX = Math.max(1, Math.floor((width - maxLineWidth) / 2));
109
+ const startY = Math.max(1, Math.floor((height - contentHeight) / 2));
110
+ // Clear screen
111
+ term.clear();
112
+ // Draw content
113
+ lines.forEach((line, idx) => {
114
+ term.moveTo(startX, startY + idx);
115
+ if (idx === 0) {
116
+ // Header in yellow
117
+ process.stdout.write(`${YELLOW}${line}${RESET}`);
118
+ }
119
+ else if (line.startsWith(' ')) {
120
+ // File paths dimmed
121
+ process.stdout.write(`${DIM}${line}${RESET}`);
122
+ }
123
+ else if (line.includes('[y/n]')) {
124
+ // Prompt
125
+ process.stdout.write(line);
126
+ }
127
+ else {
128
+ process.stdout.write(line);
129
+ }
130
+ });
131
+ /**
132
+ * Cleanup and restore terminal
133
+ */
134
+ function cleanup() {
135
+ term.removeAllListeners('key');
136
+ term.grabInput(false);
137
+ term.fullscreen(false);
138
+ term.hideCursor(false);
139
+ }
140
+ /**
141
+ * Show brief success message
142
+ */
143
+ function showSuccess() {
144
+ const msg = 'Added to .gitignore';
145
+ const msgX = Math.max(1, Math.floor((width - msg.length) / 2));
146
+ const msgY = startY + lines.length + 1;
147
+ term.moveTo(msgX, msgY);
148
+ process.stdout.write(`${GREEN}${msg}${RESET}`);
149
+ // Brief pause so user sees the message
150
+ setTimeout(() => {
151
+ cleanup();
152
+ resolve();
153
+ }, 800);
154
+ }
155
+ // Handle key press
156
+ term.on('key', (key) => {
157
+ // Exit on quit keys
158
+ if (key === 'CTRL_C' || key === 'CTRL_Z') {
159
+ cleanup();
160
+ process.exit(0);
161
+ }
162
+ // Yes - add to gitignore
163
+ if (key === 'y' || key === 'Y') {
164
+ try {
165
+ addToGitignore(gitRoot);
166
+ showSuccess();
167
+ }
168
+ catch {
169
+ // Failed to write, just continue
170
+ cleanup();
171
+ resolve();
172
+ }
173
+ return;
174
+ }
175
+ // No - skip
176
+ if (key === 'n' || key === 'N') {
177
+ cleanup();
178
+ resolve();
179
+ return;
180
+ }
181
+ // Ignore other keys - wait for y/n
182
+ });
183
+ });
184
+ }
185
+ export default checkGitignore;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Title Screen (terminal-kit version)
3
+ *
4
+ * Displays ASCII art title "The Arbiter" and continues on any key press.
5
+ * Uses terminal-kit for fullscreen rendering.
6
+ */
7
+ /**
8
+ * Shows the title screen using terminal-kit.
9
+ * Continues to character select on any key press.
10
+ *
11
+ * @returns Promise<void> - Resolves when user presses any key
12
+ */
13
+ export declare function showTitleScreen(): Promise<void>;
14
+ export default showTitleScreen;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Title Screen (terminal-kit version)
3
+ *
4
+ * Displays ASCII art title "The Arbiter" and continues on any key press.
5
+ * Uses terminal-kit for fullscreen rendering.
6
+ */
7
+ import termKit from 'terminal-kit';
8
+ import { playSfx } from '../../sound.js';
9
+ const term = termKit.terminal;
10
+ // ANSI codes
11
+ const _BOLD = '\x1b[1m';
12
+ const DIM = '\x1b[2m';
13
+ const RESET = '\x1b[0m';
14
+ // Fire gradient colors (top = bright, bottom = dark)
15
+ const FIRE_COLORS = [
16
+ '\x1b[97;1m', // bright white
17
+ '\x1b[93;1m', // bright yellow
18
+ '\x1b[93;1m', // bright yellow
19
+ '\x1b[33;1m', // yellow
20
+ '\x1b[33m', // dark yellow
21
+ '\x1b[38;5;208m', // orange
22
+ '\x1b[38;5;208m', // orange
23
+ '\x1b[38;5;202m', // dark orange
24
+ '\x1b[91m', // bright red
25
+ '\x1b[91m', // bright red
26
+ '\x1b[31m', // red
27
+ '\x1b[31m', // red
28
+ '\x1b[38;5;124m', // dark red
29
+ '\x1b[38;5;124m', // dark red
30
+ ];
31
+ // ASCII art title (raw lines)
32
+ const TITLE_ART = [
33
+ ' ',
34
+ ' /### / / ## / ',
35
+ ' / ############/ #/ /#### #/ # ',
36
+ '/ ######### ## / ### ## ### # ',
37
+ '# / # ## /## ## # ## ',
38
+ ' ## / ## ## / ## ## ## ',
39
+ ' / ### ## /## /## / ## ### /### ## /### ### ######## /## ### /### ',
40
+ ' ## ## ## / ### / ### / ## ###/ #### / ##/ ### / ### ######## / ### ###/ #### / ',
41
+ ' ## ## ##/ ### / ### / ## ## ###/ ## ###/ ## ## / ### ## ###/ ',
42
+ ' ## ## ## ## ## ### / ## ## ## ## ## ## ## ### ## ',
43
+ ' ## ## ## ## ######## /######## ## ## ## ## ## ######## ## ',
44
+ ' ## ## ## ## ####### / ## ## ## ## ## ## ####### ## ',
45
+ ' ## # / ## ## ## # ## ## ## ## ## ## ## ## ',
46
+ ' ### / ## ## #### / /#### ## ## ## /# ## ## #### / ## ',
47
+ ' ######/ ## ## ######/ / #### ## / ### ####/ ### / ## ######/ ### ',
48
+ ' ### ## ## ##### / ## #/ ### ### ##/ ## ##### ### ',
49
+ ' / # ',
50
+ ' / ## ',
51
+ ' / ',
52
+ ' / ',
53
+ ];
54
+ /**
55
+ * Shows the title screen using terminal-kit.
56
+ * Continues to character select on any key press.
57
+ *
58
+ * @returns Promise<void> - Resolves when user presses any key
59
+ */
60
+ export async function showTitleScreen() {
61
+ return new Promise((resolve) => {
62
+ // Initialize terminal
63
+ term.fullscreen(true);
64
+ term.hideCursor();
65
+ term.grabInput({ mouse: 'button' });
66
+ // Get terminal dimensions
67
+ let width = 180;
68
+ let height = 50;
69
+ if (typeof term.width === 'number' && Number.isFinite(term.width) && term.width > 0) {
70
+ width = term.width;
71
+ }
72
+ if (typeof term.height === 'number' && Number.isFinite(term.height) && term.height > 0) {
73
+ height = term.height;
74
+ }
75
+ // Calculate centering
76
+ const artWidth = TITLE_ART[0].length;
77
+ const artHeight = TITLE_ART.length;
78
+ const contentHeight = artHeight + 4; // art + spacing + prompt
79
+ const startX = Math.max(1, Math.floor((width - artWidth) / 2));
80
+ const startY = Math.max(1, Math.floor((height - contentHeight) / 2));
81
+ // Clear screen
82
+ term.clear();
83
+ // Draw title art (diagonal fire gradient - ~25 degrees, top-left to bottom-right)
84
+ // Weight row more than col for shallower angle (row * 3 ≈ 25 degrees)
85
+ const rowWeight = 6;
86
+ const maxDiagonal = TITLE_ART.length * rowWeight + TITLE_ART[0].length;
87
+ for (let row = 0; row < TITLE_ART.length; row++) {
88
+ term.moveTo(startX, startY + row);
89
+ let line = '';
90
+ let lastColorIdx = -1;
91
+ for (let col = 0; col < TITLE_ART[row].length; col++) {
92
+ const diagonal = row * rowWeight + col;
93
+ const colorIdx = Math.min(Math.floor((diagonal / maxDiagonal) * FIRE_COLORS.length), FIRE_COLORS.length - 1);
94
+ // Only add color code when it changes
95
+ if (colorIdx !== lastColorIdx) {
96
+ line += FIRE_COLORS[colorIdx];
97
+ lastColorIdx = colorIdx;
98
+ }
99
+ line += TITLE_ART[row][col];
100
+ }
101
+ process.stdout.write(line + RESET);
102
+ }
103
+ // Draw prompt (dim, centered below art)
104
+ const prompt = 'Press any key to continue...';
105
+ const promptX = Math.max(1, Math.floor((width - prompt.length) / 2));
106
+ const promptY = startY + artHeight + 3;
107
+ term.moveTo(promptX, promptY);
108
+ process.stdout.write(`${DIM}${prompt}${RESET}`);
109
+ /**
110
+ * Cleanup and restore terminal
111
+ */
112
+ function cleanup() {
113
+ term.removeAllListeners('key');
114
+ term.grabInput(false);
115
+ term.fullscreen(false);
116
+ term.hideCursor(false);
117
+ }
118
+ // Handle any key press
119
+ term.on('key', (key) => {
120
+ // Exit on quit keys
121
+ if (key === 'CTRL_C' || key === 'CTRL_Z') {
122
+ cleanup();
123
+ process.exit(0);
124
+ }
125
+ // Any other key continues to character select
126
+ playSfx('menuSelect');
127
+ cleanup();
128
+ resolve();
129
+ });
130
+ });
131
+ }
132
+ export default showTitleScreen;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Screen components re-export
3
+ *
4
+ * Terminal-kit based screen components for the TUI.
5
+ */
6
+ export { showCharacterSelect } from './CharacterSelect-termkit.js';
7
+ export { showForestIntro } from './ForestIntro-termkit.js';
8
+ export { checkGitignore } from './GitignoreCheck-termkit.js';
9
+ export { showTitleScreen } from './TitleScreen-termkit.js';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Screen components re-export
3
+ *
4
+ * Terminal-kit based screen components for the TUI.
5
+ */
6
+ export { showCharacterSelect } from './CharacterSelect-termkit.js';
7
+ export { showForestIntro } from './ForestIntro-termkit.js';
8
+ export { checkGitignore } from './GitignoreCheck-termkit.js';
9
+ // Terminal-kit screen components
10
+ export { showTitleScreen } from './TitleScreen-termkit.js';
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Tileset loading and rendering utilities for the Arbiter TUI
3
+ *
4
+ * Provides functions for loading PNG tilesets, extracting individual tiles,
5
+ * compositing tiles together, and rendering them to ANSI terminal output.
6
+ */
7
+ export declare const TILE_SIZE = 16;
8
+ export declare const TILES_PER_ROW = 10;
9
+ export declare const CHAR_HEIGHT = 8;
10
+ export declare const RESET = "\u001B[0m";
11
+ export declare const CLEAR_SCREEN = "\u001B[2J";
12
+ export declare const CURSOR_HOME = "\u001B[H";
13
+ export declare const HIDE_CURSOR = "\u001B[?25l";
14
+ export declare const SHOW_CURSOR = "\u001B[?25h";
15
+ export declare const TILE: {
16
+ readonly GRASS: 50;
17
+ readonly GRASS_SPARSE: 51;
18
+ readonly PINE_TREE: 57;
19
+ readonly BARE_TREE: 58;
20
+ readonly CAULDRON: 86;
21
+ readonly CAMPFIRE: 87;
22
+ readonly SMOKE: 90;
23
+ readonly SPELLBOOK: 102;
24
+ readonly HUMAN_1: 190;
25
+ readonly HUMAN_2: 191;
26
+ readonly HUMAN_3: 192;
27
+ readonly HUMAN_4: 193;
28
+ readonly HUMAN_5: 194;
29
+ readonly HUMAN_6: 195;
30
+ readonly HUMAN_7: 196;
31
+ readonly HUMAN_8: 197;
32
+ readonly ARBITER: 205;
33
+ readonly DEMON_1: 220;
34
+ readonly DEMON_2: 221;
35
+ readonly DEMON_3: 222;
36
+ readonly DEMON_4: 223;
37
+ readonly DEMON_5: 224;
38
+ readonly FOCUS: 270;
39
+ readonly CHAT_BUBBLE_QUARTERS: 267;
40
+ readonly ALERT_QUARTERS: 268;
41
+ readonly SCROLL: 124;
42
+ };
43
+ export type QuarterPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
44
+ export interface RGB {
45
+ r: number;
46
+ g: number;
47
+ b: number;
48
+ a: number;
49
+ }
50
+ export interface Tileset {
51
+ width: number;
52
+ height: number;
53
+ data: Buffer;
54
+ }
55
+ /**
56
+ * Load the tileset PNG and return cached data
57
+ */
58
+ export declare function loadTileset(): Promise<Tileset>;
59
+ /**
60
+ * Extract a 16x16 tile by index from the tileset
61
+ */
62
+ export declare function extractTile(tileset: Tileset, index: number): RGB[][];
63
+ /**
64
+ * Composite foreground tile on background tile using alpha threshold
65
+ * Pixels with alpha below threshold use the background pixel
66
+ */
67
+ export declare function compositeTiles(fg: RGB[][], bg: RGB[][], alphaThreshold: number): RGB[][];
68
+ /**
69
+ * Composite focus overlay on character tile
70
+ * The focus overlay has transparent center, only the corner brackets show
71
+ */
72
+ export declare function compositeWithFocus(charPixels: RGB[][], focusPixels: RGB[][], alphaThreshold?: number): RGB[][];
73
+ /**
74
+ * Mirror a tile horizontally (flip left-right)
75
+ */
76
+ export declare function mirrorTile(pixels: RGB[][]): RGB[][];
77
+ /**
78
+ * Extract an 8x8 quarter from a 16x16 tile
79
+ * @param pixels The full 16x16 tile pixels
80
+ * @param quarter Which quarter to extract
81
+ * @returns 8x8 pixel array
82
+ */
83
+ export declare function extractQuarterTile(pixels: RGB[][], quarter: QuarterPosition): RGB[][];
84
+ /**
85
+ * Composite an 8x8 quarter tile onto a specific corner of a 16x16 tile
86
+ * @param base The full 16x16 tile pixels (will be cloned, not mutated)
87
+ * @param quarter The 8x8 quarter tile to overlay
88
+ * @param position Where to place the quarter tile
89
+ * @param alphaThreshold Pixels with alpha below this use the base pixel
90
+ * @returns New 16x16 pixel array with quarter composited
91
+ */
92
+ export declare function compositeQuarterTile(base: RGB[][], quarter: RGB[][], position: QuarterPosition, alphaThreshold?: number): RGB[][];
93
+ /**
94
+ * Render a tile to ANSI string array (16 chars wide x 8 rows)
95
+ * Uses half-block characters for 2:1 vertical compression
96
+ */
97
+ export declare function renderTile(pixels: RGB[][]): string[];