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,237 @@
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
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import sharp from 'sharp';
10
+ // ============================================================================
11
+ // Constants
12
+ // ============================================================================
13
+ export const TILE_SIZE = 16;
14
+ export const TILES_PER_ROW = 10;
15
+ export const CHAR_HEIGHT = 8; // 16 pixels / 2 due to half-block rendering
16
+ // Get package root directory (works when installed globally or locally)
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = path.dirname(__filename);
19
+ const PACKAGE_ROOT = path.resolve(__dirname, '..', '..'); // From dist/tui/ to package root
20
+ const TILESET_PATH = path.join(PACKAGE_ROOT, 'assets/jerom_16x16.png');
21
+ // ANSI escape sequences
22
+ export const RESET = '\x1b[0m';
23
+ export const CLEAR_SCREEN = '\x1b[2J';
24
+ export const CURSOR_HOME = '\x1b[H';
25
+ export const HIDE_CURSOR = '\x1b[?25l';
26
+ export const SHOW_CURSOR = '\x1b[?25h';
27
+ // Key tile indices from the tileset
28
+ export const TILE = {
29
+ GRASS: 50,
30
+ GRASS_SPARSE: 51,
31
+ PINE_TREE: 57,
32
+ BARE_TREE: 58,
33
+ CAULDRON: 86,
34
+ CAMPFIRE: 87,
35
+ SMOKE: 90,
36
+ SPELLBOOK: 102,
37
+ HUMAN_1: 190,
38
+ HUMAN_2: 191,
39
+ HUMAN_3: 192,
40
+ HUMAN_4: 193,
41
+ HUMAN_5: 194,
42
+ HUMAN_6: 195,
43
+ HUMAN_7: 196,
44
+ HUMAN_8: 197,
45
+ ARBITER: 205,
46
+ DEMON_1: 220,
47
+ DEMON_2: 221,
48
+ DEMON_3: 222,
49
+ DEMON_4: 223,
50
+ DEMON_5: 224,
51
+ FOCUS: 270, // Focus overlay - corner brackets to highlight active speaker
52
+ CHAT_BUBBLE_QUARTERS: 267, // Quarter tiles - top-right is chat bubble indicator
53
+ ALERT_QUARTERS: 268, // Quarter tiles - top-left is exclamation/alert indicator
54
+ SCROLL: 124,
55
+ };
56
+ // ============================================================================
57
+ // Core Functions
58
+ // ============================================================================
59
+ /**
60
+ * Load the tileset PNG and return cached data
61
+ */
62
+ export async function loadTileset() {
63
+ const image = sharp(TILESET_PATH);
64
+ const { data, info } = await image.raw().ensureAlpha().toBuffer({ resolveWithObject: true });
65
+ return { width: info.width, height: info.height, data };
66
+ }
67
+ /**
68
+ * Get a single pixel from the tileset data buffer
69
+ */
70
+ function getPixel(data, width, x, y) {
71
+ const idx = (y * width + x) * 4;
72
+ return { r: data[idx], g: data[idx + 1], b: data[idx + 2], a: data[idx + 3] };
73
+ }
74
+ /**
75
+ * Extract a 16x16 tile by index from the tileset
76
+ */
77
+ export function extractTile(tileset, index) {
78
+ const tileX = (index % TILES_PER_ROW) * TILE_SIZE;
79
+ const tileY = Math.floor(index / TILES_PER_ROW) * TILE_SIZE;
80
+ const pixels = [];
81
+ for (let y = 0; y < TILE_SIZE; y++) {
82
+ const row = [];
83
+ for (let x = 0; x < TILE_SIZE; x++) {
84
+ row.push(getPixel(tileset.data, tileset.width, tileX + x, tileY + y));
85
+ }
86
+ pixels.push(row);
87
+ }
88
+ return pixels;
89
+ }
90
+ /**
91
+ * Composite foreground tile on background tile using alpha threshold
92
+ * Pixels with alpha below threshold use the background pixel
93
+ */
94
+ export function compositeTiles(fg, bg, alphaThreshold) {
95
+ const size = fg.length;
96
+ const result = [];
97
+ for (let y = 0; y < size; y++) {
98
+ const row = [];
99
+ for (let x = 0; x < size; x++) {
100
+ const fgPx = fg[y][x];
101
+ const bgPx = bg[y]?.[x] || fgPx;
102
+ row.push(fgPx.a < alphaThreshold ? bgPx : fgPx);
103
+ }
104
+ result.push(row);
105
+ }
106
+ return result;
107
+ }
108
+ /**
109
+ * Composite focus overlay on character tile
110
+ * The focus overlay has transparent center, only the corner brackets show
111
+ */
112
+ export function compositeWithFocus(charPixels, focusPixels, alphaThreshold = 1) {
113
+ const size = charPixels.length;
114
+ const result = [];
115
+ for (let y = 0; y < size; y++) {
116
+ const row = [];
117
+ for (let x = 0; x < size; x++) {
118
+ const focusPx = focusPixels[y][x];
119
+ const charPx = charPixels[y][x];
120
+ // Focus overlay: if focus pixel is opaque, use it; otherwise use character
121
+ row.push(focusPx.a >= alphaThreshold ? focusPx : charPx);
122
+ }
123
+ result.push(row);
124
+ }
125
+ return result;
126
+ }
127
+ /**
128
+ * Mirror a tile horizontally (flip left-right)
129
+ */
130
+ export function mirrorTile(pixels) {
131
+ return pixels.map((row) => [...row].reverse());
132
+ }
133
+ /**
134
+ * Extract an 8x8 quarter from a 16x16 tile
135
+ * @param pixels The full 16x16 tile pixels
136
+ * @param quarter Which quarter to extract
137
+ * @returns 8x8 pixel array
138
+ */
139
+ export function extractQuarterTile(pixels, quarter) {
140
+ const halfSize = TILE_SIZE / 2; // 8
141
+ let startX = 0;
142
+ let startY = 0;
143
+ switch (quarter) {
144
+ case 'top-left':
145
+ startX = 0;
146
+ startY = 0;
147
+ break;
148
+ case 'top-right':
149
+ startX = halfSize;
150
+ startY = 0;
151
+ break;
152
+ case 'bottom-left':
153
+ startX = 0;
154
+ startY = halfSize;
155
+ break;
156
+ case 'bottom-right':
157
+ startX = halfSize;
158
+ startY = halfSize;
159
+ break;
160
+ }
161
+ const result = [];
162
+ for (let y = 0; y < halfSize; y++) {
163
+ const row = [];
164
+ for (let x = 0; x < halfSize; x++) {
165
+ row.push(pixels[startY + y][startX + x]);
166
+ }
167
+ result.push(row);
168
+ }
169
+ return result;
170
+ }
171
+ /**
172
+ * Composite an 8x8 quarter tile onto a specific corner of a 16x16 tile
173
+ * @param base The full 16x16 tile pixels (will be cloned, not mutated)
174
+ * @param quarter The 8x8 quarter tile to overlay
175
+ * @param position Where to place the quarter tile
176
+ * @param alphaThreshold Pixels with alpha below this use the base pixel
177
+ * @returns New 16x16 pixel array with quarter composited
178
+ */
179
+ export function compositeQuarterTile(base, quarter, position, alphaThreshold = 1) {
180
+ const halfSize = TILE_SIZE / 2; // 8
181
+ let startX = 0;
182
+ let startY = 0;
183
+ switch (position) {
184
+ case 'top-left':
185
+ startX = 0;
186
+ startY = 0;
187
+ break;
188
+ case 'top-right':
189
+ startX = halfSize;
190
+ startY = 0;
191
+ break;
192
+ case 'bottom-left':
193
+ startX = 0;
194
+ startY = halfSize;
195
+ break;
196
+ case 'bottom-right':
197
+ startX = halfSize;
198
+ startY = halfSize;
199
+ break;
200
+ }
201
+ // Clone the base tile
202
+ const result = base.map((row) => row.map((px) => ({ ...px })));
203
+ // Overlay the quarter
204
+ for (let y = 0; y < halfSize; y++) {
205
+ for (let x = 0; x < halfSize; x++) {
206
+ const quarterPx = quarter[y][x];
207
+ if (quarterPx.a >= alphaThreshold) {
208
+ result[startY + y][startX + x] = quarterPx;
209
+ }
210
+ }
211
+ }
212
+ return result;
213
+ }
214
+ /**
215
+ * Generate ANSI true color escape sequence
216
+ */
217
+ function tc(r, g, b, bg) {
218
+ return bg ? `\x1b[48;2;${r};${g};${b}m` : `\x1b[38;2;${r};${g};${b}m`;
219
+ }
220
+ /**
221
+ * Render a tile to ANSI string array (16 chars wide x 8 rows)
222
+ * Uses half-block characters for 2:1 vertical compression
223
+ */
224
+ export function renderTile(pixels) {
225
+ const lines = [];
226
+ for (let y = 0; y < TILE_SIZE; y += 2) {
227
+ let line = '';
228
+ for (let x = 0; x < TILE_SIZE; x++) {
229
+ const top = pixels[y][x];
230
+ const bot = pixels[y + 1]?.[x] || top;
231
+ line += `${tc(top.r, top.g, top.b, true) + tc(bot.r, bot.g, bot.b, false)}▄`;
232
+ }
233
+ line += RESET;
234
+ lines.push(line);
235
+ }
236
+ return lines;
237
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Terminal-Kit based TUI using Strategy 5 (Minimal Redraws)
3
+ *
4
+ * This replaces the Ink-based TUI with a terminal-kit implementation
5
+ * that uses minimal redraws for flicker-free animation and input handling.
6
+ */
7
+ import type { RouterCallbacks } from '../router.js';
8
+ import type { AppState } from '../state.js';
9
+ /**
10
+ * TUI interface - main entry point for the terminal UI
11
+ * Matches the interface expected by the Router
12
+ */
13
+ export interface TUI {
14
+ /** Starts the TUI and takes over the terminal */
15
+ start(): void;
16
+ /** Stops the TUI and restores the terminal */
17
+ stop(): void;
18
+ /** Returns router callbacks for updating the display */
19
+ getRouterCallbacks(): RouterCallbacks;
20
+ /** Registers a callback for user input */
21
+ onInput(callback: (text: string) => void): void;
22
+ /** Registers a callback for when user confirms exit */
23
+ onExit(callback: () => void): void;
24
+ /** Registers a callback for when requirements selection is complete */
25
+ onRequirementsReady(callback: () => void): void;
26
+ /** Start the loading animation for arbiter or orchestrator */
27
+ startWaiting(waitingFor: 'arbiter' | 'orchestrator'): void;
28
+ /** Stop the loading animation */
29
+ stopWaiting(): void;
30
+ }
31
+ /**
32
+ * Creates a TUI instance using terminal-kit with Strategy 5 (minimal redraws)
33
+ */
34
+ export declare function createTUI(appState: AppState, selectedCharacter?: number): TUI;