autohand-cli 0.6.1 → 0.6.3

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.
@@ -0,0 +1,1135 @@
1
+ import {
2
+ AUTOHAND_FILES
3
+ } from "./chunk-737A24RB.js";
4
+
5
+ // src/ui/theme/Theme.ts
6
+ var Theme = class {
7
+ constructor(name, colors, colorMode) {
8
+ this.ansiCache = /* @__PURE__ */ new Map();
9
+ this.name = name;
10
+ this.colors = colors;
11
+ this.colorMode = colorMode ?? detectColorMode();
12
+ }
13
+ /**
14
+ * Apply foreground color to text.
15
+ */
16
+ fg(token, text) {
17
+ if (this.colorMode === "none") return text;
18
+ const color = this.colors[token];
19
+ if (!color) return text;
20
+ const ansi = this.getAnsiCode(color, "fg");
21
+ return `${ansi}${text}\x1B[39m`;
22
+ }
23
+ /**
24
+ * Apply background color to text.
25
+ */
26
+ bg(token, text) {
27
+ if (this.colorMode === "none") return text;
28
+ const color = this.colors[token];
29
+ if (!color) return text;
30
+ const ansi = this.getAnsiCode(color, "bg");
31
+ return `${ansi}${text}\x1B[49m`;
32
+ }
33
+ /**
34
+ * Apply both foreground and background colors.
35
+ */
36
+ fgBg(fgToken, bgToken, text) {
37
+ if (this.colorMode === "none") return text;
38
+ const fgColor = this.colors[fgToken];
39
+ const bgColor = this.colors[bgToken];
40
+ const fgAnsi = fgColor ? this.getAnsiCode(fgColor, "fg") : "";
41
+ const bgAnsi = bgColor ? this.getAnsiCode(bgColor, "bg") : "";
42
+ return `${fgAnsi}${bgAnsi}${text}\x1B[0m`;
43
+ }
44
+ /**
45
+ * Apply bold style.
46
+ */
47
+ bold(text) {
48
+ return `\x1B[1m${text}\x1B[22m`;
49
+ }
50
+ /**
51
+ * Apply italic style.
52
+ */
53
+ italic(text) {
54
+ return `\x1B[3m${text}\x1B[23m`;
55
+ }
56
+ /**
57
+ * Apply underline style.
58
+ */
59
+ underline(text) {
60
+ return `\x1B[4m${text}\x1B[24m`;
61
+ }
62
+ /**
63
+ * Apply dim style.
64
+ */
65
+ dimStyle(text) {
66
+ return `\x1B[2m${text}\x1B[22m`;
67
+ }
68
+ /**
69
+ * Apply strikethrough style.
70
+ */
71
+ strikethrough(text) {
72
+ return `\x1B[9m${text}\x1B[29m`;
73
+ }
74
+ /**
75
+ * Reset all styles.
76
+ */
77
+ reset(text) {
78
+ return `\x1B[0m${text}\x1B[0m`;
79
+ }
80
+ /**
81
+ * Get raw color value for a token.
82
+ */
83
+ getColor(token) {
84
+ return this.colors[token] || "";
85
+ }
86
+ /**
87
+ * Get current color mode.
88
+ */
89
+ getColorMode() {
90
+ return this.colorMode;
91
+ }
92
+ /**
93
+ * Convert color value to ANSI escape code.
94
+ */
95
+ getAnsiCode(color, type) {
96
+ const cacheKey = `${color}:${type}`;
97
+ const cached = this.ansiCache.get(cacheKey);
98
+ if (cached) return cached;
99
+ const code = this.colorToAnsi(color, type);
100
+ this.ansiCache.set(cacheKey, code);
101
+ return code;
102
+ }
103
+ /**
104
+ * Convert a color value to ANSI code based on color mode.
105
+ */
106
+ colorToAnsi(color, type) {
107
+ if (!color) return "";
108
+ if (color.startsWith("#")) {
109
+ return this.hexToAnsi(color, type);
110
+ }
111
+ const numValue = parseInt(color, 10);
112
+ if (!isNaN(numValue) && numValue >= 0 && numValue <= 255) {
113
+ return this.index256ToAnsi(numValue, type);
114
+ }
115
+ return "";
116
+ }
117
+ /**
118
+ * Convert hex color to ANSI code.
119
+ */
120
+ hexToAnsi(hex, type) {
121
+ const rgb = hexToRgb(hex);
122
+ if (!rgb) return "";
123
+ const base = type === "fg" ? 38 : 48;
124
+ if (this.colorMode === "truecolor") {
125
+ return `\x1B[${base};2;${rgb.r};${rgb.g};${rgb.b}m`;
126
+ } else if (this.colorMode === "256") {
127
+ const index = rgbTo256(rgb.r, rgb.g, rgb.b);
128
+ return `\x1B[${base};5;${index}m`;
129
+ } else if (this.colorMode === "16") {
130
+ const index = rgbTo16(rgb.r, rgb.g, rgb.b);
131
+ const code = type === "fg" ? index < 8 ? 30 + index : 90 + index - 8 : index < 8 ? 40 + index : 100 + index - 8;
132
+ return `\x1B[${code}m`;
133
+ }
134
+ return "";
135
+ }
136
+ /**
137
+ * Convert 256-color index to ANSI code.
138
+ */
139
+ index256ToAnsi(index, type) {
140
+ const base = type === "fg" ? 38 : 48;
141
+ if (this.colorMode === "truecolor" || this.colorMode === "256") {
142
+ return `\x1B[${base};5;${index}m`;
143
+ } else if (this.colorMode === "16") {
144
+ const basicIndex = index < 16 ? index : index256To16(index);
145
+ const code = type === "fg" ? basicIndex < 8 ? 30 + basicIndex : 90 + basicIndex - 8 : basicIndex < 8 ? 40 + basicIndex : 100 + basicIndex - 8;
146
+ return `\x1B[${code}m`;
147
+ }
148
+ return "";
149
+ }
150
+ };
151
+ function detectColorMode() {
152
+ if (process.env.NO_COLOR !== void 0) {
153
+ return "none";
154
+ }
155
+ if (process.env.FORCE_COLOR !== void 0) {
156
+ const level = parseInt(process.env.FORCE_COLOR, 10);
157
+ if (level === 0) return "none";
158
+ if (level === 1) return "16";
159
+ if (level === 2) return "256";
160
+ if (level >= 3) return "truecolor";
161
+ }
162
+ const colorterm = process.env.COLORTERM;
163
+ if (colorterm === "truecolor" || colorterm === "24bit") {
164
+ return "truecolor";
165
+ }
166
+ const term = process.env.TERM || "";
167
+ if (term.includes("256color") || term.includes("256-color")) {
168
+ return "256";
169
+ }
170
+ if (term.includes("color") || term.includes("ansi") || term === "xterm") {
171
+ return "16";
172
+ }
173
+ if (process.stdout.isTTY) {
174
+ return "256";
175
+ }
176
+ return "none";
177
+ }
178
+ function hexToRgb(hex) {
179
+ const match = hex.match(/^#?([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})$/);
180
+ if (match) {
181
+ return {
182
+ r: parseInt(match[1], 16),
183
+ g: parseInt(match[2], 16),
184
+ b: parseInt(match[3], 16)
185
+ };
186
+ }
187
+ const shortMatch = hex.match(/^#?([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])$/);
188
+ if (shortMatch) {
189
+ return {
190
+ r: parseInt(shortMatch[1] + shortMatch[1], 16),
191
+ g: parseInt(shortMatch[2] + shortMatch[2], 16),
192
+ b: parseInt(shortMatch[3] + shortMatch[3], 16)
193
+ };
194
+ }
195
+ return null;
196
+ }
197
+ function rgbTo256(r, g, b) {
198
+ if (r === g && g === b) {
199
+ if (r < 8) return 16;
200
+ if (r > 248) return 231;
201
+ return Math.round((r - 8) / 10) + 232;
202
+ }
203
+ const ri = Math.round(r / 51);
204
+ const gi = Math.round(g / 51);
205
+ const bi = Math.round(b / 51);
206
+ return 16 + 36 * ri + 6 * gi + bi;
207
+ }
208
+ function rgbTo16(r, g, b) {
209
+ const bright = (r + g + b) / 3 > 128 ? 8 : 0;
210
+ const ri = r > 128 ? 1 : 0;
211
+ const gi = g > 128 ? 2 : 0;
212
+ const bi = b > 128 ? 4 : 0;
213
+ return bright + ri + gi + bi;
214
+ }
215
+ function index256To16(index) {
216
+ if (index < 16) return index;
217
+ if (index >= 232) {
218
+ const gray = (index - 232) * 10 + 8;
219
+ return gray > 128 ? 15 : 0;
220
+ }
221
+ const adjusted = index - 16;
222
+ const r = Math.floor(adjusted / 36) * 51;
223
+ const g = Math.floor(adjusted % 36 / 6) * 51;
224
+ const b = adjusted % 6 * 51;
225
+ return rgbTo16(r, g, b);
226
+ }
227
+ var globalTheme = null;
228
+ function getTheme() {
229
+ if (!globalTheme) {
230
+ throw new Error("Theme not initialized. Call initTheme() first.");
231
+ }
232
+ return globalTheme;
233
+ }
234
+ function setTheme(theme) {
235
+ globalTheme = theme;
236
+ }
237
+ function isThemeInitialized() {
238
+ return globalTheme !== null;
239
+ }
240
+
241
+ // src/ui/theme/loader.ts
242
+ import { readFileSync, existsSync, readdirSync } from "fs";
243
+ import { join } from "path";
244
+ import { homedir } from "os";
245
+
246
+ // src/ui/theme/types.ts
247
+ var COLOR_TOKENS = [
248
+ // Core UI
249
+ "accent",
250
+ "border",
251
+ "borderAccent",
252
+ "borderMuted",
253
+ "success",
254
+ "error",
255
+ "warning",
256
+ "muted",
257
+ "dim",
258
+ "text",
259
+ // Backgrounds & Content
260
+ "userMessageBg",
261
+ "userMessageText",
262
+ "toolPendingBg",
263
+ "toolSuccessBg",
264
+ "toolErrorBg",
265
+ "toolTitle",
266
+ "toolOutput",
267
+ // Diff Colors
268
+ "diffAdded",
269
+ "diffRemoved",
270
+ "diffContext",
271
+ // Syntax Highlighting
272
+ "syntaxComment",
273
+ "syntaxKeyword",
274
+ "syntaxFunction",
275
+ "syntaxVariable",
276
+ "syntaxString",
277
+ "syntaxNumber",
278
+ "syntaxType",
279
+ "syntaxOperator",
280
+ "syntaxPunctuation",
281
+ // Markdown
282
+ "mdHeading",
283
+ "mdLink",
284
+ "mdLinkUrl",
285
+ "mdCode",
286
+ "mdCodeBlock",
287
+ "mdCodeBlockBorder",
288
+ "mdQuote",
289
+ "mdQuoteBorder",
290
+ "mdHr",
291
+ "mdListBullet"
292
+ ];
293
+ function isHexColor(value) {
294
+ if (typeof value !== "string") return false;
295
+ return /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(value);
296
+ }
297
+ function is256ColorIndex(value) {
298
+ return typeof value === "number" && value >= 0 && value <= 255 && Number.isInteger(value);
299
+ }
300
+
301
+ // src/ui/theme/themes.ts
302
+ var darkTheme = {
303
+ name: "dark",
304
+ vars: {
305
+ // Base colors
306
+ cyan: "#00bcd4",
307
+ green: "#4caf50",
308
+ red: "#f44336",
309
+ yellow: "#ffeb3b",
310
+ blue: "#2196f3",
311
+ magenta: "#e91e63",
312
+ orange: "#ff9800",
313
+ // Grays
314
+ gray100: "#f5f5f5",
315
+ gray200: "#eeeeee",
316
+ gray300: "#e0e0e0",
317
+ gray400: "#bdbdbd",
318
+ gray500: "#9e9e9e",
319
+ gray600: "#757575",
320
+ gray700: "#616161",
321
+ gray800: "#424242",
322
+ gray900: "#212121",
323
+ // Backgrounds
324
+ bgDark: "#1a1a1a",
325
+ bgMedium: "#2b2b2b",
326
+ bgLight: "#3a3a3a"
327
+ },
328
+ colors: {
329
+ // Core UI
330
+ accent: "cyan",
331
+ border: "gray700",
332
+ borderAccent: "cyan",
333
+ borderMuted: "gray800",
334
+ success: "green",
335
+ error: "red",
336
+ warning: "yellow",
337
+ muted: "gray500",
338
+ dim: "gray700",
339
+ text: "gray200",
340
+ // Backgrounds & Content
341
+ userMessageBg: "bgMedium",
342
+ userMessageText: "gray200",
343
+ toolPendingBg: "bgLight",
344
+ toolSuccessBg: "#1b3d1b",
345
+ toolErrorBg: "#3d1b1b",
346
+ toolTitle: "cyan",
347
+ toolOutput: "gray400",
348
+ // Diff Colors
349
+ diffAdded: "#4caf50",
350
+ diffRemoved: "#f44336",
351
+ diffContext: "gray500",
352
+ // Syntax Highlighting
353
+ syntaxComment: "gray600",
354
+ syntaxKeyword: "magenta",
355
+ syntaxFunction: "blue",
356
+ syntaxVariable: "cyan",
357
+ syntaxString: "green",
358
+ syntaxNumber: "yellow",
359
+ syntaxType: "cyan",
360
+ syntaxOperator: "gray300",
361
+ syntaxPunctuation: "gray400",
362
+ // Markdown
363
+ mdHeading: "cyan",
364
+ mdLink: "blue",
365
+ mdLinkUrl: "gray500",
366
+ mdCode: "orange",
367
+ mdCodeBlock: "gray300",
368
+ mdCodeBlockBorder: "gray700",
369
+ mdQuote: "gray400",
370
+ mdQuoteBorder: "gray600",
371
+ mdHr: "gray700",
372
+ mdListBullet: "cyan"
373
+ }
374
+ };
375
+ var draculaTheme = {
376
+ name: "dracula",
377
+ vars: {
378
+ // Dracula palette
379
+ background: "#282a36",
380
+ currentLine: "#44475a",
381
+ foreground: "#f8f8f2",
382
+ comment: "#6272a4",
383
+ cyan: "#8be9fd",
384
+ green: "#50fa7b",
385
+ orange: "#ffb86c",
386
+ pink: "#ff79c6",
387
+ purple: "#bd93f9",
388
+ red: "#ff5555",
389
+ yellow: "#f1fa8c",
390
+ // Additional grays
391
+ gray100: "#f8f8f2",
392
+ gray200: "#e6e6e6",
393
+ gray300: "#bfbfbf",
394
+ gray400: "#6272a4",
395
+ gray500: "#44475a",
396
+ gray600: "#383a46",
397
+ gray700: "#282a36",
398
+ gray800: "#21222c",
399
+ gray900: "#191a21"
400
+ },
401
+ colors: {
402
+ // Core UI
403
+ accent: "purple",
404
+ border: "comment",
405
+ borderAccent: "purple",
406
+ borderMuted: "currentLine",
407
+ success: "green",
408
+ error: "red",
409
+ warning: "orange",
410
+ muted: "comment",
411
+ dim: "currentLine",
412
+ text: "foreground",
413
+ // Backgrounds & Content
414
+ userMessageBg: "currentLine",
415
+ userMessageText: "foreground",
416
+ toolPendingBg: "gray800",
417
+ toolSuccessBg: "#1e3a1e",
418
+ toolErrorBg: "#3a1e1e",
419
+ toolTitle: "purple",
420
+ toolOutput: "foreground",
421
+ // Diff Colors
422
+ diffAdded: "green",
423
+ diffRemoved: "red",
424
+ diffContext: "comment",
425
+ // Syntax Highlighting
426
+ syntaxComment: "comment",
427
+ syntaxKeyword: "pink",
428
+ syntaxFunction: "green",
429
+ syntaxVariable: "foreground",
430
+ syntaxString: "yellow",
431
+ syntaxNumber: "purple",
432
+ syntaxType: "cyan",
433
+ syntaxOperator: "pink",
434
+ syntaxPunctuation: "foreground",
435
+ // Markdown
436
+ mdHeading: "purple",
437
+ mdLink: "cyan",
438
+ mdLinkUrl: "comment",
439
+ mdCode: "green",
440
+ mdCodeBlock: "foreground",
441
+ mdCodeBlockBorder: "comment",
442
+ mdQuote: "yellow",
443
+ mdQuoteBorder: "comment",
444
+ mdHr: "comment",
445
+ mdListBullet: "cyan"
446
+ }
447
+ };
448
+ var sandyTheme = {
449
+ name: "sandy",
450
+ vars: {
451
+ // Sandy/desert palette
452
+ sand: "#e8d5b7",
453
+ sandDark: "#d4c4a8",
454
+ sandLight: "#f5ece0",
455
+ terracotta: "#c45c3e",
456
+ rust: "#a04030",
457
+ cactus: "#6b8e23",
458
+ sage: "#8fbc8f",
459
+ clay: "#8b6914",
460
+ dune: "#c4a35a",
461
+ stone: "#7a6a5a",
462
+ adobe: "#bc8f8f",
463
+ // Grays (warm-tinted)
464
+ gray100: "#f5f0e8",
465
+ gray200: "#e8e0d5",
466
+ gray300: "#d5c8b8",
467
+ gray400: "#a89888",
468
+ gray500: "#8a7a6a",
469
+ gray600: "#6a5a4a",
470
+ gray700: "#4a3a2a",
471
+ gray800: "#3a2a1a",
472
+ gray900: "#2a1a0a"
473
+ },
474
+ colors: {
475
+ // Core UI
476
+ accent: "terracotta",
477
+ border: "stone",
478
+ borderAccent: "terracotta",
479
+ borderMuted: "gray600",
480
+ success: "cactus",
481
+ error: "rust",
482
+ warning: "dune",
483
+ muted: "stone",
484
+ dim: "gray600",
485
+ text: "gray100",
486
+ // Backgrounds & Content
487
+ userMessageBg: "gray700",
488
+ userMessageText: "gray100",
489
+ toolPendingBg: "gray800",
490
+ toolSuccessBg: "#2a3a2a",
491
+ toolErrorBg: "#3a2a2a",
492
+ toolTitle: "terracotta",
493
+ toolOutput: "gray300",
494
+ // Diff Colors
495
+ diffAdded: "cactus",
496
+ diffRemoved: "rust",
497
+ diffContext: "stone",
498
+ // Syntax Highlighting
499
+ syntaxComment: "stone",
500
+ syntaxKeyword: "terracotta",
501
+ syntaxFunction: "cactus",
502
+ syntaxVariable: "dune",
503
+ syntaxString: "sage",
504
+ syntaxNumber: "clay",
505
+ syntaxType: "adobe",
506
+ syntaxOperator: "gray300",
507
+ syntaxPunctuation: "gray400",
508
+ // Markdown
509
+ mdHeading: "terracotta",
510
+ mdLink: "cactus",
511
+ mdLinkUrl: "stone",
512
+ mdCode: "dune",
513
+ mdCodeBlock: "gray200",
514
+ mdCodeBlockBorder: "stone",
515
+ mdQuote: "sage",
516
+ mdQuoteBorder: "stone",
517
+ mdHr: "gray600",
518
+ mdListBullet: "terracotta"
519
+ }
520
+ };
521
+ var tuiTheme = {
522
+ name: "tui",
523
+ vars: {
524
+ // New Zealand palette
525
+ silverFern: "#7fb069",
526
+ // Silver fern green
527
+ fernDark: "#4a7c3f",
528
+ // Dark fern
529
+ paua: "#4b0082",
530
+ // Paua shell purple
531
+ pauaBlue: "#1e90ff",
532
+ // Paua shell blue
533
+ pauaTeal: "#20b2aa",
534
+ // Paua teal highlights
535
+ kowhai: "#ffd700",
536
+ // Kowhai yellow
537
+ pohutukawa: "#dc143c",
538
+ // Pohutukawa red
539
+ kiwi: "#8b4513",
540
+ // Kiwi brown
541
+ sky: "#87ceeb",
542
+ // NZ sky blue
543
+ snow: "#f0f8ff",
544
+ // Southern Alps snow
545
+ obsidian: "#1a1a2e",
546
+ // Maori obsidian
547
+ // Grays (cool-tinted)
548
+ gray100: "#e8f0f0",
549
+ gray200: "#c0d0d8",
550
+ gray300: "#90a8b0",
551
+ gray400: "#607880",
552
+ gray500: "#405058",
553
+ gray600: "#303840",
554
+ gray700: "#202830",
555
+ gray800: "#151c22",
556
+ gray900: "#0a1015"
557
+ },
558
+ colors: {
559
+ // Core UI
560
+ accent: "pauaBlue",
561
+ border: "gray500",
562
+ borderAccent: "pauaTeal",
563
+ borderMuted: "gray600",
564
+ success: "silverFern",
565
+ error: "pohutukawa",
566
+ warning: "kowhai",
567
+ muted: "gray400",
568
+ dim: "gray600",
569
+ text: "snow",
570
+ // Backgrounds & Content
571
+ userMessageBg: "gray700",
572
+ userMessageText: "snow",
573
+ toolPendingBg: "gray800",
574
+ toolSuccessBg: "#1a2a1a",
575
+ toolErrorBg: "#2a1a1a",
576
+ toolTitle: "pauaTeal",
577
+ toolOutput: "gray200",
578
+ // Diff Colors
579
+ diffAdded: "silverFern",
580
+ diffRemoved: "pohutukawa",
581
+ diffContext: "gray400",
582
+ // Syntax Highlighting
583
+ syntaxComment: "gray400",
584
+ syntaxKeyword: "paua",
585
+ syntaxFunction: "pauaBlue",
586
+ syntaxVariable: "pauaTeal",
587
+ syntaxString: "silverFern",
588
+ syntaxNumber: "kowhai",
589
+ syntaxType: "sky",
590
+ syntaxOperator: "gray200",
591
+ syntaxPunctuation: "gray300",
592
+ // Markdown
593
+ mdHeading: "pauaBlue",
594
+ mdLink: "pauaTeal",
595
+ mdLinkUrl: "gray400",
596
+ mdCode: "silverFern",
597
+ mdCodeBlock: "gray200",
598
+ mdCodeBlockBorder: "gray500",
599
+ mdQuote: "kowhai",
600
+ mdQuoteBorder: "gray500",
601
+ mdHr: "gray600",
602
+ mdListBullet: "pauaTeal"
603
+ }
604
+ };
605
+ var lightTheme = {
606
+ name: "light",
607
+ vars: {
608
+ // Base colors (darker for light bg)
609
+ cyan: "#0097a7",
610
+ green: "#388e3c",
611
+ red: "#d32f2f",
612
+ yellow: "#f9a825",
613
+ blue: "#1976d2",
614
+ magenta: "#c2185b",
615
+ orange: "#ef6c00",
616
+ // Grays (inverted)
617
+ gray100: "#212121",
618
+ gray200: "#424242",
619
+ gray300: "#616161",
620
+ gray400: "#757575",
621
+ gray500: "#9e9e9e",
622
+ gray600: "#bdbdbd",
623
+ gray700: "#e0e0e0",
624
+ gray800: "#eeeeee",
625
+ gray900: "#f5f5f5",
626
+ // Backgrounds
627
+ bgLight: "#ffffff",
628
+ bgMedium: "#f5f5f5",
629
+ bgDark: "#eeeeee"
630
+ },
631
+ colors: {
632
+ // Core UI
633
+ accent: "cyan",
634
+ border: "gray600",
635
+ borderAccent: "cyan",
636
+ borderMuted: "gray700",
637
+ success: "green",
638
+ error: "red",
639
+ warning: "yellow",
640
+ muted: "gray400",
641
+ dim: "gray500",
642
+ text: "gray100",
643
+ // Backgrounds & Content
644
+ userMessageBg: "bgMedium",
645
+ userMessageText: "gray100",
646
+ toolPendingBg: "bgDark",
647
+ toolSuccessBg: "#e8f5e9",
648
+ toolErrorBg: "#ffebee",
649
+ toolTitle: "cyan",
650
+ toolOutput: "gray300",
651
+ // Diff Colors
652
+ diffAdded: "#2e7d32",
653
+ diffRemoved: "#c62828",
654
+ diffContext: "gray400",
655
+ // Syntax Highlighting
656
+ syntaxComment: "gray500",
657
+ syntaxKeyword: "magenta",
658
+ syntaxFunction: "blue",
659
+ syntaxVariable: "cyan",
660
+ syntaxString: "green",
661
+ syntaxNumber: "orange",
662
+ syntaxType: "cyan",
663
+ syntaxOperator: "gray300",
664
+ syntaxPunctuation: "gray400",
665
+ // Markdown
666
+ mdHeading: "cyan",
667
+ mdLink: "blue",
668
+ mdLinkUrl: "gray400",
669
+ mdCode: "orange",
670
+ mdCodeBlock: "gray200",
671
+ mdCodeBlockBorder: "gray600",
672
+ mdQuote: "gray300",
673
+ mdQuoteBorder: "gray500",
674
+ mdHr: "gray600",
675
+ mdListBullet: "cyan"
676
+ }
677
+ };
678
+ var builtInThemes = {
679
+ dark: darkTheme,
680
+ light: lightTheme,
681
+ dracula: draculaTheme,
682
+ sandy: sandyTheme,
683
+ tui: tuiTheme
684
+ };
685
+ function getDefaultThemeName() {
686
+ return "dark";
687
+ }
688
+
689
+ // src/ui/theme/loader.ts
690
+ var CUSTOM_THEMES_DIR = join(homedir(), ".autohand", "themes");
691
+ var ThemeLoadError = class extends Error {
692
+ constructor(message, themeName, cause) {
693
+ super(message);
694
+ this.themeName = themeName;
695
+ this.cause = cause;
696
+ this.name = "ThemeLoadError";
697
+ }
698
+ };
699
+ function loadTheme(themeName) {
700
+ const definition = getThemeDefinition(themeName);
701
+ const resolvedColors = resolveThemeColors(definition);
702
+ return new Theme(definition.name, resolvedColors, detectColorMode());
703
+ }
704
+ function initTheme(themeName) {
705
+ const name = themeName || getDefaultThemeName();
706
+ try {
707
+ const theme = loadTheme(name);
708
+ setTheme(theme);
709
+ return theme;
710
+ } catch (error) {
711
+ console.warn(`Failed to load theme "${name}", falling back to dark theme:`, error instanceof Error ? error.message : error);
712
+ const theme = loadTheme("dark");
713
+ setTheme(theme);
714
+ return theme;
715
+ }
716
+ }
717
+ function getThemeDefinition(themeName) {
718
+ if (themeName in builtInThemes) {
719
+ return builtInThemes[themeName];
720
+ }
721
+ const customThemePath = join(CUSTOM_THEMES_DIR, `${themeName}.json`);
722
+ if (existsSync(customThemePath)) {
723
+ return loadCustomTheme(customThemePath, themeName);
724
+ }
725
+ throw new ThemeLoadError(`Theme "${themeName}" not found`, themeName);
726
+ }
727
+ function loadCustomTheme(filePath, themeName) {
728
+ try {
729
+ const content = readFileSync(filePath, "utf-8");
730
+ const parsed = JSON.parse(content);
731
+ return validateAndMergeTheme(parsed, themeName);
732
+ } catch (error) {
733
+ if (error instanceof SyntaxError) {
734
+ throw new ThemeLoadError(`Invalid JSON in theme file: ${filePath}`, themeName, error);
735
+ }
736
+ if (error instanceof ThemeLoadError) {
737
+ throw error;
738
+ }
739
+ throw new ThemeLoadError(
740
+ `Failed to load theme file: ${filePath}`,
741
+ themeName,
742
+ error instanceof Error ? error : new Error(String(error))
743
+ );
744
+ }
745
+ }
746
+ function validateAndMergeTheme(partial, themeName) {
747
+ const name = partial.name || themeName;
748
+ if (partial.vars) {
749
+ for (const [key, value] of Object.entries(partial.vars)) {
750
+ if (!isValidColorValue(value)) {
751
+ throw new ThemeLoadError(`Invalid color value for variable "${key}": ${value}`, name);
752
+ }
753
+ }
754
+ }
755
+ if (partial.colors) {
756
+ for (const [key, value] of Object.entries(partial.colors)) {
757
+ if (!COLOR_TOKENS.includes(key)) {
758
+ continue;
759
+ }
760
+ if (!isValidColorValue(value)) {
761
+ throw new ThemeLoadError(`Invalid color value for token "${key}": ${value}`, name);
762
+ }
763
+ }
764
+ }
765
+ return {
766
+ name,
767
+ vars: { ...darkTheme.vars, ...partial.vars },
768
+ colors: { ...darkTheme.colors, ...partial.colors }
769
+ };
770
+ }
771
+ function isValidColorValue(value) {
772
+ if (value === "") return true;
773
+ if (typeof value === "string") {
774
+ if (isHexColor(value)) return true;
775
+ if (value.length > 0) return true;
776
+ }
777
+ if (is256ColorIndex(value)) return true;
778
+ return false;
779
+ }
780
+ function resolveThemeColors(definition) {
781
+ const vars = definition.vars || {};
782
+ const colors = definition.colors;
783
+ const resolved = {};
784
+ for (const token of COLOR_TOKENS) {
785
+ const value = colors[token];
786
+ if (value === void 0) {
787
+ throw new ThemeLoadError(`Missing required color token: ${token}`, definition.name);
788
+ }
789
+ resolved[token] = resolveColorValue(value, vars, definition.name, /* @__PURE__ */ new Set());
790
+ }
791
+ return resolved;
792
+ }
793
+ function resolveColorValue(value, vars, themeName, visited) {
794
+ if (value === "") return "";
795
+ if (typeof value === "number") {
796
+ if (!is256ColorIndex(value)) {
797
+ throw new ThemeLoadError(`Invalid 256-color index: ${value}`, themeName);
798
+ }
799
+ return String(value);
800
+ }
801
+ if (isHexColor(value)) {
802
+ return value;
803
+ }
804
+ if (typeof value === "string") {
805
+ if (visited.has(value)) {
806
+ throw new ThemeLoadError(`Circular variable reference detected: ${value}`, themeName);
807
+ }
808
+ const varValue = vars[value];
809
+ if (varValue === void 0) {
810
+ throw new ThemeLoadError(`Unknown variable reference: ${value}`, themeName);
811
+ }
812
+ visited.add(value);
813
+ return resolveColorValue(varValue, vars, themeName, visited);
814
+ }
815
+ throw new ThemeLoadError(`Invalid color value: ${value}`, themeName);
816
+ }
817
+ function listAvailableThemes() {
818
+ const themes = new Set(Object.keys(builtInThemes));
819
+ if (existsSync(CUSTOM_THEMES_DIR)) {
820
+ try {
821
+ const files = readdirSync(CUSTOM_THEMES_DIR);
822
+ for (const file of files) {
823
+ if (file.endsWith(".json")) {
824
+ themes.add(file.slice(0, -5));
825
+ }
826
+ }
827
+ } catch {
828
+ }
829
+ }
830
+ return Array.from(themes).sort();
831
+ }
832
+ function themeExists(themeName) {
833
+ if (themeName in builtInThemes) return true;
834
+ const customPath = join(CUSTOM_THEMES_DIR, `${themeName}.json`);
835
+ return existsSync(customPath);
836
+ }
837
+ function detectTerminalBackground() {
838
+ const colorFgBg = process.env.COLORFGBG;
839
+ if (colorFgBg) {
840
+ const parts = colorFgBg.split(";");
841
+ if (parts.length >= 2) {
842
+ const bg = parseInt(parts[parts.length - 1], 10);
843
+ if (bg === 7 || bg === 15 || bg > 8) {
844
+ return "light";
845
+ }
846
+ }
847
+ }
848
+ if (process.env.TERMINAL_EMULATOR?.includes("JetBrains")) {
849
+ return "light";
850
+ }
851
+ return "dark";
852
+ }
853
+ function autoInitTheme(themeName) {
854
+ if (themeName) {
855
+ return initTheme(themeName);
856
+ }
857
+ const background = detectTerminalBackground();
858
+ return initTheme(background);
859
+ }
860
+
861
+ // src/ui/theme/ThemeContext.tsx
862
+ import { createContext, useContext, useMemo } from "react";
863
+ import { jsx } from "react/jsx-runtime";
864
+ var defaultContextValue = {
865
+ get theme() {
866
+ throw new Error("ThemeContext not initialized. Wrap your app with ThemeProvider.");
867
+ },
868
+ get colors() {
869
+ throw new Error("ThemeContext not initialized. Wrap your app with ThemeProvider.");
870
+ },
871
+ get name() {
872
+ throw new Error("ThemeContext not initialized. Wrap your app with ThemeProvider.");
873
+ },
874
+ getColor: () => {
875
+ throw new Error("ThemeContext not initialized. Wrap your app with ThemeProvider.");
876
+ }
877
+ };
878
+ var ThemeContext = createContext(defaultContextValue);
879
+ var ThemeProvider = ({ theme: providedTheme, themeName, children }) => {
880
+ const theme = useMemo(() => {
881
+ if (providedTheme) return providedTheme;
882
+ if (isThemeInitialized()) {
883
+ return getTheme();
884
+ }
885
+ if (themeName) {
886
+ return initTheme(themeName);
887
+ }
888
+ return initTheme();
889
+ }, [providedTheme, themeName]);
890
+ const value = useMemo(
891
+ () => ({
892
+ theme,
893
+ colors: theme.colors,
894
+ name: theme.name,
895
+ getColor: (token) => theme.getColor(token)
896
+ }),
897
+ [theme]
898
+ );
899
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
900
+ };
901
+ function useTheme() {
902
+ const context = useContext(ThemeContext);
903
+ return context;
904
+ }
905
+
906
+ // src/config.ts
907
+ import fs from "fs-extra";
908
+ import path from "path";
909
+ import YAML from "yaml";
910
+ var DEFAULT_CONFIG_PATH = AUTOHAND_FILES.configJson;
911
+ var YAML_CONFIG_PATH = AUTOHAND_FILES.configYaml;
912
+ var YML_CONFIG_PATH = AUTOHAND_FILES.configYml;
913
+ var DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
914
+ var DEFAULT_OLLAMA_URL = "http://localhost:11434";
915
+ var DEFAULT_LLAMACPP_URL = "http://localhost:8080";
916
+ var DEFAULT_OPENAI_URL = "https://api.openai.com/v1";
917
+ async function detectConfigPath(customPath) {
918
+ if (customPath) {
919
+ return path.resolve(customPath);
920
+ }
921
+ const envPath = process.env.AUTOHAND_CONFIG;
922
+ if (envPath) {
923
+ return path.resolve(envPath);
924
+ }
925
+ if (await fs.pathExists(YAML_CONFIG_PATH)) {
926
+ return YAML_CONFIG_PATH;
927
+ }
928
+ if (await fs.pathExists(YML_CONFIG_PATH)) {
929
+ return YML_CONFIG_PATH;
930
+ }
931
+ return DEFAULT_CONFIG_PATH;
932
+ }
933
+ function isYamlFile(filePath) {
934
+ const ext = path.extname(filePath).toLowerCase();
935
+ return ext === ".yaml" || ext === ".yml";
936
+ }
937
+ async function parseConfigFile(configPath) {
938
+ const content = await fs.readFile(configPath, "utf8");
939
+ if (isYamlFile(configPath)) {
940
+ return YAML.parse(content);
941
+ }
942
+ return JSON.parse(content);
943
+ }
944
+ async function loadConfig(customPath) {
945
+ const configPath = await detectConfigPath(customPath);
946
+ await fs.ensureDir(path.dirname(configPath));
947
+ let isNewConfig = false;
948
+ if (!await fs.pathExists(configPath)) {
949
+ const defaultConfig = {
950
+ provider: "openrouter",
951
+ openrouter: {
952
+ apiKey: "",
953
+ baseUrl: "https://openrouter.ai/api/v1",
954
+ model: "anthropic/claude-sonnet-4-20250514"
955
+ },
956
+ workspace: {
957
+ defaultRoot: process.cwd(),
958
+ allowDangerousOps: false
959
+ },
960
+ ui: {
961
+ theme: "dark",
962
+ autoConfirm: false
963
+ },
964
+ telemetry: {
965
+ enabled: true
966
+ }
967
+ };
968
+ await fs.writeJson(configPath, defaultConfig, { spaces: 2 });
969
+ isNewConfig = true;
970
+ }
971
+ let parsed;
972
+ try {
973
+ parsed = await parseConfigFile(configPath);
974
+ } catch (error) {
975
+ throw new Error(`Failed to parse config at ${configPath}: ${error.message}`);
976
+ }
977
+ const normalized = normalizeConfig(parsed);
978
+ const withEnv = mergeEnvVariables(normalized);
979
+ validateConfig(withEnv, configPath);
980
+ const themeName = withEnv.ui?.theme || "dark";
981
+ autoInitTheme(themeName);
982
+ return { ...withEnv, configPath, isNewConfig };
983
+ }
984
+ function mergeEnvVariables(config) {
985
+ return {
986
+ ...config,
987
+ api: {
988
+ baseUrl: process.env.AUTOHAND_API_URL || config.api?.baseUrl || "https://api.autohand.ai",
989
+ companySecret: process.env.AUTOHAND_SECRET || config.api?.companySecret || ""
990
+ }
991
+ };
992
+ }
993
+ function normalizeConfig(config) {
994
+ if (isModernConfig(config)) {
995
+ const provider = config.provider ?? "openrouter";
996
+ return { provider, ...config };
997
+ }
998
+ if (isLegacyConfig(config)) {
999
+ return {
1000
+ provider: "openrouter",
1001
+ openrouter: {
1002
+ apiKey: config.api_key ?? "replace-me",
1003
+ baseUrl: config.base_url ?? DEFAULT_BASE_URL,
1004
+ model: config.model ?? "anthropic/claude-3.5-sonnet"
1005
+ },
1006
+ workspace: {
1007
+ defaultRoot: process.cwd(),
1008
+ allowDangerousOps: false
1009
+ },
1010
+ ui: {
1011
+ autoConfirm: config.dry_run ?? false,
1012
+ theme: "dark"
1013
+ }
1014
+ };
1015
+ }
1016
+ return config;
1017
+ }
1018
+ function isModernConfig(config) {
1019
+ return typeof config.openrouter === "object" || typeof config.ollama === "object" || typeof config.llamacpp === "object" || typeof config.openai === "object";
1020
+ }
1021
+ function isLegacyConfig(config) {
1022
+ return typeof config.api_key === "string";
1023
+ }
1024
+ function validateConfig(config, configPath) {
1025
+ const provider = config.provider ?? "openrouter";
1026
+ const providerConfig = getProviderConfig(config, provider);
1027
+ if (config.workspace) {
1028
+ if (config.workspace.defaultRoot && typeof config.workspace.defaultRoot !== "string") {
1029
+ throw new Error(`workspace.defaultRoot must be a string in ${configPath}`);
1030
+ }
1031
+ if (config.workspace.allowDangerousOps !== void 0 && typeof config.workspace.allowDangerousOps !== "boolean") {
1032
+ throw new Error(`workspace.allowDangerousOps must be boolean in ${configPath}`);
1033
+ }
1034
+ }
1035
+ if (config.ui) {
1036
+ if (config.ui.theme && typeof config.ui.theme !== "string") {
1037
+ throw new Error(`ui.theme must be a string in ${configPath}`);
1038
+ }
1039
+ if (config.ui.theme && !themeExists(config.ui.theme)) {
1040
+ throw new Error(`ui.theme '${config.ui.theme}' not found. Use 'dark', 'light', or a custom theme in ~/.autohand/themes/`);
1041
+ }
1042
+ if (config.ui.autoConfirm !== void 0 && typeof config.ui.autoConfirm !== "boolean") {
1043
+ throw new Error(`ui.autoConfirm must be boolean in ${configPath}`);
1044
+ }
1045
+ }
1046
+ if (config.externalAgents) {
1047
+ if (config.externalAgents.enabled !== void 0 && typeof config.externalAgents.enabled !== "boolean") {
1048
+ throw new Error(`externalAgents.enabled must be boolean in ${configPath}`);
1049
+ }
1050
+ if (config.externalAgents.paths !== void 0) {
1051
+ if (!Array.isArray(config.externalAgents.paths)) {
1052
+ throw new Error(`externalAgents.paths must be an array in ${configPath}`);
1053
+ }
1054
+ for (const p of config.externalAgents.paths) {
1055
+ if (typeof p !== "string") {
1056
+ throw new Error(`externalAgents.paths must contain only strings in ${configPath}`);
1057
+ }
1058
+ }
1059
+ }
1060
+ }
1061
+ }
1062
+ function resolveWorkspaceRoot(config, requestedPath) {
1063
+ const candidate = requestedPath ?? process.cwd() ?? config.workspace?.defaultRoot;
1064
+ return path.resolve(candidate);
1065
+ }
1066
+ function getProviderConfig(config, provider) {
1067
+ const chosen = provider ?? config.provider ?? "openrouter";
1068
+ const configByProvider = {
1069
+ openrouter: config.openrouter,
1070
+ ollama: config.ollama,
1071
+ llamacpp: config.llamacpp,
1072
+ openai: config.openai
1073
+ };
1074
+ const entry = configByProvider[chosen];
1075
+ if (!entry) {
1076
+ return null;
1077
+ }
1078
+ if (chosen === "openrouter") {
1079
+ const { apiKey, model } = entry;
1080
+ if (!apiKey || apiKey === "replace-me" || !model) {
1081
+ return null;
1082
+ }
1083
+ } else {
1084
+ if (!entry.model) {
1085
+ return null;
1086
+ }
1087
+ }
1088
+ return {
1089
+ ...entry,
1090
+ baseUrl: entry.baseUrl ?? defaultBaseUrlFor(chosen, entry.port)
1091
+ };
1092
+ }
1093
+ function defaultBaseUrlFor(provider, port) {
1094
+ if (provider === "openrouter") return DEFAULT_BASE_URL;
1095
+ const p = port ? port.toString() : void 0;
1096
+ switch (provider) {
1097
+ case "ollama":
1098
+ return p ? `http://localhost:${p}` : DEFAULT_OLLAMA_URL;
1099
+ case "llamacpp":
1100
+ return p ? `http://localhost:${p}` : DEFAULT_LLAMACPP_URL;
1101
+ case "openai":
1102
+ return DEFAULT_OPENAI_URL;
1103
+ default:
1104
+ return void 0;
1105
+ }
1106
+ }
1107
+ async function saveConfig(config) {
1108
+ const { configPath, ...data } = config;
1109
+ if (isYamlFile(configPath)) {
1110
+ const yamlContent = YAML.stringify(data, { indent: 2 });
1111
+ await fs.writeFile(configPath, yamlContent, "utf8");
1112
+ } else {
1113
+ await fs.writeJson(configPath, data, { spaces: 2 });
1114
+ }
1115
+ }
1116
+
1117
+ export {
1118
+ hexToRgb,
1119
+ getTheme,
1120
+ isThemeInitialized,
1121
+ CUSTOM_THEMES_DIR,
1122
+ initTheme,
1123
+ listAvailableThemes,
1124
+ ThemeProvider,
1125
+ useTheme,
1126
+ loadConfig,
1127
+ resolveWorkspaceRoot,
1128
+ getProviderConfig,
1129
+ saveConfig
1130
+ };
1131
+ /**
1132
+ * @license
1133
+ * Copyright 2025 Autohand AI LLC
1134
+ * SPDX-License-Identifier: Apache-2.0
1135
+ */