@unikode/cli 1.0.7 → 1.0.9

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/dist/index.js CHANGED
@@ -3,26 +3,4129 @@
3
3
  import { createCliRenderer } from "@opentui/core";
4
4
  import { createRoot } from "@opentui/react";
5
5
  import { createMemoryRouter, RouterProvider } from "react-router";
6
- import { RootLayout } from "./layouts/root-layout";
7
- import { Home } from "./screens/home";
8
- import { Docs } from "./screens/docs";
9
- import { NewSession } from "./screens/new-session";
10
- import { Session } from "./screens/session";
6
+
7
+ // src/layouts/root-layout.tsx
8
+ import { Outlet } from "react-router";
9
+
10
+ // src/providers/toast/index.tsx
11
+ import {
12
+ createContext as createContext2,
13
+ useContext as useContext2,
14
+ useRef,
15
+ useState as useState2,
16
+ useCallback as useCallback2,
17
+ useMemo
18
+ } from "react";
19
+ import { useTerminalDimensions } from "@opentui/react";
20
+
21
+ // src/providers/toast/types.ts
22
+ var DEFAULT_DURATION = 3000;
23
+
24
+ // src/providers/theme/index.tsx
25
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
26
+ import { homedir } from "os";
27
+ import { join } from "path";
28
+ import { createContext, useContext, useState, useCallback } from "react";
29
+
30
+ // src/theme.ts
31
+ var THEMES = [
32
+ {
33
+ name: "Nightfox",
34
+ colors: {
35
+ primary: "#56D6C2",
36
+ planMode: "#CF8EF4",
37
+ selection: "#89B4FA",
38
+ thinking: "#CF8EF4",
39
+ success: "#82E0AA",
40
+ error: "#E74C5E",
41
+ info: "#56D6C2",
42
+ background: "#0D0D12",
43
+ surface: "#1A1A24",
44
+ dialogSurface: "#0A0A10",
45
+ thinkingBorder: "#34344A",
46
+ dimSeparator: "#4E4E66"
47
+ }
48
+ },
49
+ {
50
+ name: "Catppuccin Mocha",
51
+ colors: {
52
+ primary: "#E0AF68",
53
+ planMode: "#9D7CD8",
54
+ selection: "#B4A4E8",
55
+ thinking: "#9D7CD8",
56
+ success: "#73DACA",
57
+ error: "#F7768E",
58
+ info: "#7AA2F7",
59
+ background: "#11111B",
60
+ surface: "#1E1E2E",
61
+ dialogSurface: "#13131D",
62
+ thinkingBorder: "#45475A",
63
+ dimSeparator: "#585B70"
64
+ }
65
+ },
66
+ {
67
+ name: "Dracula",
68
+ colors: {
69
+ primary: "#BD93F9",
70
+ planMode: "#FF79C6",
71
+ selection: "#6272A4",
72
+ thinking: "#FF79C6",
73
+ success: "#50FA7B",
74
+ error: "#FF5555",
75
+ info: "#8BE9FD",
76
+ background: "#282A36",
77
+ surface: "#343746",
78
+ dialogSurface: "#21222C",
79
+ thinkingBorder: "#6272A4",
80
+ dimSeparator: "#44475A"
81
+ }
82
+ },
83
+ {
84
+ name: "Monokai Pro",
85
+ colors: {
86
+ primary: "#FFD866",
87
+ planMode: "#AB9DF2",
88
+ selection: "#AB9DF2",
89
+ thinking: "#AB9DF2",
90
+ success: "#A9DC76",
91
+ error: "#FF6188",
92
+ info: "#78DCE8",
93
+ background: "#2D2A2E",
94
+ surface: "#403E41",
95
+ dialogSurface: "#221F22",
96
+ thinkingBorder: "#5B595C",
97
+ dimSeparator: "#727072"
98
+ }
99
+ },
100
+ {
101
+ name: "Tokyo Night",
102
+ colors: {
103
+ primary: "#7AA2F7",
104
+ planMode: "#BB9AF7",
105
+ selection: "#7AA2F7",
106
+ thinking: "#BB9AF7",
107
+ success: "#9ECE6A",
108
+ error: "#F7768E",
109
+ info: "#7DCFFF",
110
+ background: "#1A1B26",
111
+ surface: "#24283B",
112
+ dialogSurface: "#16161E",
113
+ thinkingBorder: "#3B4261",
114
+ dimSeparator: "#565F89"
115
+ }
116
+ },
117
+ {
118
+ name: "Nord",
119
+ colors: {
120
+ primary: "#EBCB8B",
121
+ planMode: "#B48EAD",
122
+ selection: "#81A1C1",
123
+ thinking: "#B48EAD",
124
+ success: "#A3BE8C",
125
+ error: "#BF616A",
126
+ info: "#88C0D0",
127
+ background: "#2E3440",
128
+ surface: "#3B4252",
129
+ dialogSurface: "#272C36",
130
+ thinkingBorder: "#4C566A",
131
+ dimSeparator: "#616E88"
132
+ }
133
+ },
134
+ {
135
+ name: "Synthwave",
136
+ colors: {
137
+ primary: "#F472B6",
138
+ planMode: "#A855F7",
139
+ selection: "#E879F9",
140
+ thinking: "#A855F7",
141
+ success: "#4ADE80",
142
+ error: "#EF4444",
143
+ info: "#C084FC",
144
+ background: "#0A0A0A",
145
+ surface: "#171717",
146
+ dialogSurface: "#0D0D0D",
147
+ thinkingBorder: "#404040",
148
+ dimSeparator: "#525252"
149
+ }
150
+ },
151
+ {
152
+ name: "Midnight Sky",
153
+ colors: {
154
+ primary: "#6AAEF5",
155
+ planMode: "#B07AE8",
156
+ selection: "#8CC4F0",
157
+ thinking: "#B07AE8",
158
+ success: "#58CEA0",
159
+ error: "#E8555A",
160
+ info: "#7DCFFF",
161
+ background: "#0A0E14",
162
+ surface: "#141A22",
163
+ dialogSurface: "#0E1319",
164
+ thinkingBorder: "#4A5A6E",
165
+ dimSeparator: "#607080"
166
+ }
167
+ },
168
+ {
169
+ name: "Neon Nights",
170
+ colors: {
171
+ primary: "#E86ACA",
172
+ planMode: "#5ED4E8",
173
+ selection: "#D48EE0",
174
+ thinking: "#5ED4E8",
175
+ success: "#4ED89C",
176
+ error: "#F04858",
177
+ info: "#E86ACA",
178
+ background: "#0C0814",
179
+ surface: "#18122A",
180
+ dialogSurface: "#110C1E",
181
+ thinkingBorder: "#5C4878",
182
+ dimSeparator: "#745E90"
183
+ }
184
+ },
185
+ {
186
+ name: "Hacker Terminal",
187
+ colors: {
188
+ primary: "#00E5A0",
189
+ planMode: "#D946EF",
190
+ selection: "#2DD4BF",
191
+ thinking: "#D946EF",
192
+ success: "#4ADE80",
193
+ error: "#F43F5E",
194
+ info: "#06B6D4",
195
+ background: "#050505",
196
+ surface: "#131313",
197
+ dialogSurface: "#0A0A0A",
198
+ thinkingBorder: "#2E2E2E",
199
+ dimSeparator: "#454545"
200
+ }
201
+ },
202
+ {
203
+ name: "One Dark",
204
+ colors: {
205
+ primary: "#CBAACB",
206
+ planMode: "#55B6C2",
207
+ selection: "#98C379",
208
+ thinking: "#55B6C2",
209
+ success: "#98C379",
210
+ error: "#E06C75",
211
+ info: "#61AFEF",
212
+ background: "#1E2127",
213
+ surface: "#282C34",
214
+ dialogSurface: "#191C21",
215
+ thinkingBorder: "#3E4451",
216
+ dimSeparator: "#5C6370"
217
+ }
218
+ },
219
+ {
220
+ name: "Xcode Midnight",
221
+ colors: {
222
+ primary: "#FF7AB2",
223
+ planMode: "#6BDFFF",
224
+ selection: "#ACF2E4",
225
+ thinking: "#6BDFFF",
226
+ success: "#83C9BC",
227
+ error: "#FF6961",
228
+ info: "#B281EB",
229
+ background: "#1F1F24",
230
+ surface: "#2A2A30",
231
+ dialogSurface: "#18181D",
232
+ thinkingBorder: "#3E3E45",
233
+ dimSeparator: "#57575F"
234
+ }
235
+ },
236
+ {
237
+ name: "Catppuccin Frappe",
238
+ colors: {
239
+ primary: "#8CAAEE",
240
+ planMode: "#CA9EE6",
241
+ selection: "#A6D189",
242
+ thinking: "#CA9EE6",
243
+ success: "#A6D189",
244
+ error: "#E78284",
245
+ info: "#85C1DC",
246
+ background: "#232634",
247
+ surface: "#303446",
248
+ dialogSurface: "#1E2030",
249
+ thinkingBorder: "#51576D",
250
+ dimSeparator: "#626880"
251
+ }
252
+ },
253
+ {
254
+ name: "Vercel Dark",
255
+ colors: {
256
+ primary: "#8B5CF6",
257
+ planMode: "#EC4899",
258
+ selection: "#6366F1",
259
+ thinking: "#EC4899",
260
+ success: "#10B981",
261
+ error: "#EF4444",
262
+ info: "#3B82F6",
263
+ background: "#030712",
264
+ surface: "#111827",
265
+ dialogSurface: "#060C18",
266
+ thinkingBorder: "#1F2937",
267
+ dimSeparator: "#374151"
268
+ }
269
+ },
270
+ {
271
+ name: "Material Ocean",
272
+ colors: {
273
+ primary: "#82AAFF",
274
+ planMode: "#C792EA",
275
+ selection: "#717CB4",
276
+ thinking: "#C792EA",
277
+ success: "#C3E88D",
278
+ error: "#FF5370",
279
+ info: "#89DDFF",
280
+ background: "#0F111A",
281
+ surface: "#1A1C2A",
282
+ dialogSurface: "#090B16",
283
+ thinkingBorder: "#3B3F5C",
284
+ dimSeparator: "#4B5178"
285
+ }
286
+ },
287
+ {
288
+ name: "Dusk",
289
+ colors: {
290
+ primary: "#C9A0DC",
291
+ planMode: "#F2B866",
292
+ selection: "#E8889A",
293
+ thinking: "#F2B866",
294
+ success: "#7ED4A6",
295
+ error: "#E25A6E",
296
+ info: "#C9A0DC",
297
+ background: "#110D16",
298
+ surface: "#1E1726",
299
+ dialogSurface: "#15101C",
300
+ thinkingBorder: "#6B5880",
301
+ dimSeparator: "#7E6E94"
302
+ }
303
+ },
304
+ {
305
+ name: "Ocean",
306
+ colors: {
307
+ primary: "#3B9ECF",
308
+ planMode: "#E0A846",
309
+ selection: "#6CC9A1",
310
+ thinking: "#E0A846",
311
+ success: "#A8D45F",
312
+ error: "#D94F4F",
313
+ info: "#3B9ECF",
314
+ background: "#0B1218",
315
+ surface: "#152028",
316
+ dialogSurface: "#0F181F",
317
+ thinkingBorder: "#4A6A7A",
318
+ dimSeparator: "#5E7888"
319
+ }
320
+ },
321
+ {
322
+ name: "Soft Midnight",
323
+ colors: {
324
+ primary: "#60A5FA",
325
+ planMode: "#F9A8D4",
326
+ selection: "#93C5FD",
327
+ thinking: "#F9A8D4",
328
+ success: "#6EE7B7",
329
+ error: "#FCA5A5",
330
+ info: "#67E8F9",
331
+ background: "#0F172A",
332
+ surface: "#1E293B",
333
+ dialogSurface: "#0C1322",
334
+ thinkingBorder: "#334155",
335
+ dimSeparator: "#475569"
336
+ }
337
+ },
338
+ {
339
+ name: "Minimal Dark",
340
+ colors: {
341
+ primary: "#A78BFA",
342
+ planMode: "#38BDF8",
343
+ selection: "#818CF8",
344
+ thinking: "#38BDF8",
345
+ success: "#34D399",
346
+ error: "#FB7185",
347
+ info: "#22D3EE",
348
+ background: "#09090B",
349
+ surface: "#18181B",
350
+ dialogSurface: "#0C0C0F",
351
+ thinkingBorder: "#3F3F46",
352
+ dimSeparator: "#52525B"
353
+ }
354
+ },
355
+ {
356
+ name: "Unikrew Theme",
357
+ colors: {
358
+ primary: "#FDB415",
359
+ planMode: "#7C6CF5",
360
+ selection: "#3EC7E0",
361
+ thinking: "#8B7CFF",
362
+ success: "#4ADE80",
363
+ error: "#F87171",
364
+ info: "#38BDF8",
365
+ background: "#091B33",
366
+ surface: "#122640",
367
+ dialogSurface: "#18304D",
368
+ thinkingBorder: "#43617D",
369
+ dimSeparator: "#58738C"
370
+ }
371
+ },
372
+ {
373
+ name: "Gruvbox Dark",
374
+ colors: {
375
+ primary: "#FABD2F",
376
+ planMode: "#D3869B",
377
+ selection: "#FABD2F",
378
+ thinking: "#D3869B",
379
+ success: "#B8BB26",
380
+ error: "#FB4934",
381
+ info: "#83A598",
382
+ background: "#282828",
383
+ surface: "#3C3836",
384
+ dialogSurface: "#1D2021",
385
+ thinkingBorder: "#504945",
386
+ dimSeparator: "#665C54"
387
+ }
388
+ },
389
+ {
390
+ name: "Ros\xE9 Pine",
391
+ colors: {
392
+ primary: "#EBBCBA",
393
+ planMode: "#C4A7E7",
394
+ selection: "#C4A7E7",
395
+ thinking: "#C4A7E7",
396
+ success: "#31748F",
397
+ error: "#EB6F92",
398
+ info: "#9CCFD8",
399
+ background: "#191724",
400
+ surface: "#1F1D2E",
401
+ dialogSurface: "#16141F",
402
+ thinkingBorder: "#26233A",
403
+ dimSeparator: "#524F67"
404
+ }
405
+ },
406
+ {
407
+ name: "Ros\xE9 Pine Moon",
408
+ colors: {
409
+ primary: "#EA9A97",
410
+ planMode: "#C4A7E7",
411
+ selection: "#EA9A97",
412
+ thinking: "#C4A7E7",
413
+ success: "#3E8FB0",
414
+ error: "#EB6F92",
415
+ info: "#9CCFD8",
416
+ background: "#232136",
417
+ surface: "#2A273F",
418
+ dialogSurface: "#1E1C31",
419
+ thinkingBorder: "#393552",
420
+ dimSeparator: "#56526E"
421
+ }
422
+ },
423
+ {
424
+ name: "Kanagawa",
425
+ colors: {
426
+ primary: "#DCD7BA",
427
+ planMode: "#957FB8",
428
+ selection: "#7E9CD8",
429
+ thinking: "#957FB8",
430
+ success: "#76946A",
431
+ error: "#C34043",
432
+ info: "#7E9CD8",
433
+ background: "#1F1F28",
434
+ surface: "#2A2A37",
435
+ dialogSurface: "#16161D",
436
+ thinkingBorder: "#54546D",
437
+ dimSeparator: "#727169"
438
+ }
439
+ },
440
+ {
441
+ name: "Everforest Dark",
442
+ colors: {
443
+ primary: "#A7C080",
444
+ planMode: "#D699B6",
445
+ selection: "#A7C080",
446
+ thinking: "#D699B6",
447
+ success: "#83C092",
448
+ error: "#E67E80",
449
+ info: "#7FBBB3",
450
+ background: "#2D353B",
451
+ surface: "#343F44",
452
+ dialogSurface: "#272E33",
453
+ thinkingBorder: "#4F585E",
454
+ dimSeparator: "#859289"
455
+ }
456
+ },
457
+ {
458
+ name: "Ayu Dark",
459
+ colors: {
460
+ primary: "#E6B450",
461
+ planMode: "#D2A6FF",
462
+ selection: "#73B8FF",
463
+ thinking: "#D2A6FF",
464
+ success: "#7FD962",
465
+ error: "#D95757",
466
+ info: "#59C2FF",
467
+ background: "#0B0E14",
468
+ surface: "#11151C",
469
+ dialogSurface: "#080A0F",
470
+ thinkingBorder: "#2D3640",
471
+ dimSeparator: "#475266"
472
+ }
473
+ },
474
+ {
475
+ name: "GitHub Dark",
476
+ colors: {
477
+ primary: "#79C0FF",
478
+ planMode: "#D2A8FF",
479
+ selection: "#79C0FF",
480
+ thinking: "#D2A8FF",
481
+ success: "#56D364",
482
+ error: "#F85149",
483
+ info: "#58A6FF",
484
+ background: "#0D1117",
485
+ surface: "#161B22",
486
+ dialogSurface: "#090D13",
487
+ thinkingBorder: "#30363D",
488
+ dimSeparator: "#484F58"
489
+ }
490
+ },
491
+ {
492
+ name: "Palenight",
493
+ colors: {
494
+ primary: "#82AAFF",
495
+ planMode: "#C792EA",
496
+ selection: "#82AAFF",
497
+ thinking: "#C792EA",
498
+ success: "#C3E88D",
499
+ error: "#FF5370",
500
+ info: "#89DDFF",
501
+ background: "#292D3E",
502
+ surface: "#343850",
503
+ dialogSurface: "#232738",
504
+ thinkingBorder: "#4E5272",
505
+ dimSeparator: "#676E95"
506
+ }
507
+ },
508
+ {
509
+ name: "Vesper",
510
+ colors: {
511
+ primary: "#FFC799",
512
+ planMode: "#A78BFA",
513
+ selection: "#FFC799",
514
+ thinking: "#A78BFA",
515
+ success: "#6EE7B7",
516
+ error: "#EF4444",
517
+ info: "#FFC799",
518
+ background: "#101010",
519
+ surface: "#1C1C1C",
520
+ dialogSurface: "#0C0C0C",
521
+ thinkingBorder: "#333333",
522
+ dimSeparator: "#505050"
523
+ }
524
+ },
525
+ {
526
+ name: "Poimandres",
527
+ colors: {
528
+ primary: "#ADD7FF",
529
+ planMode: "#A6ACCD",
530
+ selection: "#ADD7FF",
531
+ thinking: "#A6ACCD",
532
+ success: "#5DE4C7",
533
+ error: "#D0679D",
534
+ info: "#89DDFF",
535
+ background: "#1B1E28",
536
+ surface: "#252B37",
537
+ dialogSurface: "#161922",
538
+ thinkingBorder: "#3B4058",
539
+ dimSeparator: "#506477"
540
+ }
541
+ },
542
+ {
543
+ name: "Moonlight",
544
+ colors: {
545
+ primary: "#82AAFF",
546
+ planMode: "#C099FF",
547
+ selection: "#C099FF",
548
+ thinking: "#C099FF",
549
+ success: "#C3E88D",
550
+ error: "#FF757F",
551
+ info: "#77E0C6",
552
+ background: "#1E2030",
553
+ surface: "#2B2F44",
554
+ dialogSurface: "#191B28",
555
+ thinkingBorder: "#3E4265",
556
+ dimSeparator: "#5B5E7A"
557
+ }
558
+ },
559
+ {
560
+ name: "Vitesse Dark",
561
+ colors: {
562
+ primary: "#4FC1FF",
563
+ planMode: "#C186E0",
564
+ selection: "#4FC1FF",
565
+ thinking: "#C186E0",
566
+ success: "#80C97F",
567
+ error: "#E45649",
568
+ info: "#4FC1FF",
569
+ background: "#121212",
570
+ surface: "#1E1E1E",
571
+ dialogSurface: "#0E0E0E",
572
+ thinkingBorder: "#333333",
573
+ dimSeparator: "#555555"
574
+ }
575
+ }
576
+ ];
577
+ var DEFAULT_THEME = THEMES.find((t) => t.name === "Unikrew Theme");
578
+
579
+ // src/providers/theme/index.tsx
11
580
  import { jsxDEV } from "@opentui/react/jsx-dev-runtime";
581
+ var CONFIG_DIR = join(homedir(), ".Claude-Code-Clone");
582
+ var THEME_PREFERENCES_PATH = join(CONFIG_DIR, "preferences.json");
583
+ function getInitialTheme() {
584
+ try {
585
+ const preferences = JSON.parse(readFileSync(THEME_PREFERENCES_PATH, "utf8"));
586
+ const savedTheme = THEMES.find((theme) => theme.name === preferences.themeName);
587
+ return savedTheme ?? DEFAULT_THEME;
588
+ } catch {
589
+ return DEFAULT_THEME;
590
+ }
591
+ }
592
+ function persistTheme(theme) {
593
+ try {
594
+ mkdirSync(CONFIG_DIR, { recursive: true });
595
+ writeFileSync(THEME_PREFERENCES_PATH, JSON.stringify({ themeName: theme.name }, null, 2), "utf8");
596
+ } catch {}
597
+ }
598
+ var ThemeContext = createContext(null);
599
+ function useTheme() {
600
+ const value = useContext(ThemeContext);
601
+ if (!value) {
602
+ throw new Error("useTheme must be used within a ThemeProvider");
603
+ }
604
+ return value;
605
+ }
606
+ function ThemeProvider({ children }) {
607
+ const [currentTheme, setCurrentTheme] = useState(getInitialTheme);
608
+ const setTheme = useCallback((theme) => {
609
+ setCurrentTheme(theme);
610
+ persistTheme(theme);
611
+ }, []);
612
+ return /* @__PURE__ */ jsxDEV(ThemeContext.Provider, {
613
+ value: { colors: currentTheme.colors, currentTheme, setTheme },
614
+ children
615
+ }, undefined, false, undefined, this);
616
+ }
617
+
618
+ // src/providers/toast/index.tsx
619
+ import { jsxDEV as jsxDEV2 } from "@opentui/react/jsx-dev-runtime";
620
+ var ToastContext = createContext2(null);
621
+ function useToast() {
622
+ const value = useContext2(ToastContext);
623
+ if (!value) {
624
+ throw new Error("useToast must be used within a ToastProvider");
625
+ }
626
+ return value;
627
+ }
628
+ function ToastProvider({ children }) {
629
+ const [currentToast, setCurrentToast] = useState2(null);
630
+ const timeoutHandleRef = useRef(null);
631
+ const clearCurrentTimeout = useCallback2(() => {
632
+ if (timeoutHandleRef.current) {
633
+ clearTimeout(timeoutHandleRef.current);
634
+ timeoutHandleRef.current = null;
635
+ }
636
+ }, []);
637
+ const show = useCallback2((options) => {
638
+ const duration = options.duration ?? DEFAULT_DURATION;
639
+ clearCurrentTimeout();
640
+ setCurrentToast({
641
+ variant: options.variant ?? "info",
642
+ ...options,
643
+ duration
644
+ });
645
+ timeoutHandleRef.current = setTimeout(() => {
646
+ setCurrentToast(null);
647
+ }, duration).unref();
648
+ }, [clearCurrentTimeout]);
649
+ const value = useMemo(() => ({ show }), [show]);
650
+ return /* @__PURE__ */ jsxDEV2(ToastContext.Provider, {
651
+ value,
652
+ children: [
653
+ children,
654
+ /* @__PURE__ */ jsxDEV2(Toast, {
655
+ currentToast
656
+ }, undefined, false, undefined, this)
657
+ ]
658
+ }, undefined, true, undefined, this);
659
+ }
660
+ function Toast({ currentToast }) {
661
+ const { width } = useTerminalDimensions();
662
+ const { colors } = useTheme();
663
+ if (!currentToast) {
664
+ return null;
665
+ }
666
+ const variantColors = {
667
+ success: colors.success,
668
+ error: colors.error,
669
+ info: colors.info
670
+ };
671
+ const borderColor = currentToast.variant ? variantColors[currentToast.variant] : variantColors.info;
672
+ return /* @__PURE__ */ jsxDEV2("box", {
673
+ position: "absolute",
674
+ justifyContent: "center",
675
+ alignItems: "flex-start",
676
+ top: 2,
677
+ right: 2,
678
+ zIndex: 100,
679
+ width: Math.max(1, Math.min(60, width - 6)),
680
+ paddingLeft: 2,
681
+ paddingRight: 2,
682
+ paddingTop: 1,
683
+ paddingBottom: 1,
684
+ backgroundColor: colors.surface,
685
+ borderColor,
686
+ border: ["left", "right"],
687
+ children: /* @__PURE__ */ jsxDEV2("box", {
688
+ flexDirection: "column",
689
+ gap: 1,
690
+ width: "100%",
691
+ children: /* @__PURE__ */ jsxDEV2("text", {
692
+ fg: "#E1E1E1",
693
+ wrapMode: "word",
694
+ width: "100%",
695
+ children: currentToast.message
696
+ }, undefined, false, undefined, this)
697
+ }, undefined, false, undefined, this)
698
+ }, undefined, false, undefined, this);
699
+ }
700
+
701
+ // src/providers/dialog/index.tsx
702
+ import { createContext as createContext4, useContext as useContext4, useState as useState4, useCallback as useCallback4 } from "react";
703
+ import { TextAttributes, RGBA } from "@opentui/core";
704
+ import { useKeyboard as useKeyboard2, useTerminalDimensions as useTerminalDimensions2 } from "@opentui/react";
705
+
706
+ // src/providers/keyboard-layer/index.tsx
707
+ import {
708
+ createContext as createContext3,
709
+ useContext as useContext3,
710
+ useState as useState3,
711
+ useCallback as useCallback3,
712
+ useRef as useRef2
713
+ } from "react";
714
+ import { useKeyboard, useRenderer } from "@opentui/react";
715
+ import { jsxDEV as jsxDEV3 } from "@opentui/react/jsx-dev-runtime";
716
+ var KeyboardLayerContext = createContext3(null);
717
+ function KeyboardLayerProvider({ children }) {
718
+ const [stack, setStack] = useState3(["base"]);
719
+ const stackRef = useRef2(stack);
720
+ stackRef.current = stack;
721
+ const responders = useRef2(new Map);
722
+ const renderer = useRenderer();
723
+ const push = useCallback3((id, responder) => {
724
+ if (responder) {
725
+ responders.current.set(id, responder);
726
+ }
727
+ setStack((prev) => {
728
+ if (prev.includes(id)) {
729
+ return prev;
730
+ }
731
+ return [...prev, id];
732
+ });
733
+ }, []);
734
+ const pop = useCallback3((id) => {
735
+ responders.current.delete(id);
736
+ setStack((prev) => prev.filter((layer) => layer !== id));
737
+ }, []);
738
+ const isTopLayer = useCallback3((id) => {
739
+ return stack.length === 0 || stack[stack.length - 1] === id;
740
+ }, [stack]);
741
+ const setResponder = useCallback3((id, responder) => {
742
+ if (responder) {
743
+ responders.current.set(id, responder);
744
+ } else {
745
+ responders.current.delete(id);
746
+ }
747
+ }, []);
748
+ useKeyboard((key) => {
749
+ if (!key.ctrl || key.name !== "c")
750
+ return;
751
+ const currentStack = stackRef.current;
752
+ for (let i = currentStack.length - 1;i >= 0; i--) {
753
+ const layerId = currentStack[i];
754
+ const responder = responders.current.get(layerId);
755
+ if (responder && responder()) {
756
+ return;
757
+ }
758
+ }
759
+ renderer.destroy();
760
+ });
761
+ return /* @__PURE__ */ jsxDEV3(KeyboardLayerContext.Provider, {
762
+ value: { push, pop, isTopLayer, setResponder },
763
+ children
764
+ }, undefined, false, undefined, this);
765
+ }
766
+ function useKeyboardLayer() {
767
+ const context = useContext3(KeyboardLayerContext);
768
+ if (!context) {
769
+ throw new Error("useKeyboardLayer must be used within a KeyboardLayerProvider");
770
+ }
771
+ return context;
772
+ }
773
+
774
+ // src/providers/dialog/index.tsx
775
+ import { jsxDEV as jsxDEV4 } from "@opentui/react/jsx-dev-runtime";
776
+ var DialogContext = createContext4(null);
777
+ function useDialog() {
778
+ const value = useContext4(DialogContext);
779
+ if (!value) {
780
+ throw new Error("use dialog must be used within a dialog provider");
781
+ }
782
+ return value;
783
+ }
784
+ function DialogProvider({ children }) {
785
+ const [currentDialog, setCurrentDialog] = useState4(null);
786
+ const { push, pop } = useKeyboardLayer();
787
+ const close = useCallback4(() => {
788
+ setCurrentDialog(null);
789
+ pop("dialog");
790
+ }, [pop]);
791
+ const open = useCallback4((config) => {
792
+ setCurrentDialog(config);
793
+ push("dialog", () => {
794
+ close();
795
+ return true;
796
+ });
797
+ }, [push, close]);
798
+ const value = {
799
+ open,
800
+ close
801
+ };
802
+ return /* @__PURE__ */ jsxDEV4(DialogContext.Provider, {
803
+ value,
804
+ children: [
805
+ children,
806
+ /* @__PURE__ */ jsxDEV4(Dialog, {
807
+ currentDialog,
808
+ close
809
+ }, undefined, false, undefined, this)
810
+ ]
811
+ }, undefined, true, undefined, this);
812
+ }
813
+ function Dialog({ currentDialog, close }) {
814
+ const { isTopLayer } = useKeyboardLayer();
815
+ const dimensions = useTerminalDimensions2();
816
+ const { colors } = useTheme();
817
+ useKeyboard2((key) => {
818
+ if (!currentDialog || !isTopLayer("dialog"))
819
+ return;
820
+ if (key.name === "escape") {
821
+ close();
822
+ }
823
+ });
824
+ if (!currentDialog) {
825
+ return null;
826
+ }
827
+ const { title, children } = currentDialog;
828
+ return /* @__PURE__ */ jsxDEV4("box", {
829
+ position: "absolute",
830
+ left: 0,
831
+ top: 0,
832
+ width: dimensions.width,
833
+ height: dimensions.height,
834
+ justifyContent: "center",
835
+ alignItems: "center",
836
+ backgroundColor: RGBA.fromInts(0, 0, 0, 150),
837
+ zIndex: 100,
838
+ onMouseDown: () => close(),
839
+ children: /* @__PURE__ */ jsxDEV4("box", {
840
+ width: Math.min(60, dimensions.width - 4),
841
+ height: "auto",
842
+ backgroundColor: colors.dialogSurface,
843
+ paddingX: 4,
844
+ paddingY: 1,
845
+ flexDirection: "column",
846
+ gap: 1,
847
+ onMouseDown: (e) => e.stopPropagation(),
848
+ children: [
849
+ /* @__PURE__ */ jsxDEV4("box", {
850
+ paddingBottom: 1,
851
+ flexDirection: "row",
852
+ alignItems: "center",
853
+ justifyContent: "space-between",
854
+ children: [
855
+ /* @__PURE__ */ jsxDEV4("text", {
856
+ attributes: TextAttributes.BOLD,
857
+ children: title
858
+ }, undefined, false, undefined, this),
859
+ /* @__PURE__ */ jsxDEV4("text", {
860
+ attributes: TextAttributes.DIM,
861
+ onMouseDown: () => close(),
862
+ children: "esc"
863
+ }, undefined, false, undefined, this)
864
+ ]
865
+ }, undefined, true, undefined, this),
866
+ /* @__PURE__ */ jsxDEV4("box", {
867
+ flexGrow: 1,
868
+ children
869
+ }, undefined, false, undefined, this)
870
+ ]
871
+ }, undefined, true, undefined, this)
872
+ }, undefined, false, undefined, this);
873
+ }
874
+
875
+ // src/layouts/themed-root.tsx
876
+ import { jsxDEV as jsxDEV5 } from "@opentui/react/jsx-dev-runtime";
877
+ function ThemedRoot({ children }) {
878
+ const { colors } = useTheme();
879
+ return /* @__PURE__ */ jsxDEV5("box", {
880
+ backgroundColor: colors.background,
881
+ width: "100%",
882
+ height: "100%",
883
+ flexGrow: 1,
884
+ children
885
+ }, undefined, false, undefined, this);
886
+ }
887
+
888
+ // src/providers/prompt-config/index.tsx
889
+ import { createContext as createContext5, useContext as useContext5, useState as useState5, useCallback as useCallback5 } from "react";
890
+
891
+ // ../shared/src/models.ts
892
+ var SUPPORTED_CHAT_MODELS = [
893
+ {
894
+ id: "claude-sonnet-4-6",
895
+ provider: "anthropic",
896
+ pricing: {
897
+ inputUsdPerMillionTokens: 3,
898
+ outputUsdPerMillionTokens: 15
899
+ }
900
+ },
901
+ {
902
+ id: "claude-haiku-4-5",
903
+ provider: "anthropic",
904
+ pricing: {
905
+ inputUsdPerMillionTokens: 1,
906
+ outputUsdPerMillionTokens: 5
907
+ }
908
+ },
909
+ {
910
+ id: "claude-opus-4-6",
911
+ provider: "anthropic",
912
+ pricing: {
913
+ inputUsdPerMillionTokens: 5,
914
+ outputUsdPerMillionTokens: 25
915
+ }
916
+ },
917
+ {
918
+ id: "gpt-5.4",
919
+ provider: "openai",
920
+ pricing: {
921
+ inputUsdPerMillionTokens: 2.5,
922
+ outputUsdPerMillionTokens: 15
923
+ }
924
+ },
925
+ {
926
+ id: "gpt-5.4-mini",
927
+ provider: "openai",
928
+ pricing: {
929
+ inputUsdPerMillionTokens: 0.75,
930
+ outputUsdPerMillionTokens: 4.5
931
+ }
932
+ },
933
+ {
934
+ id: "gpt-5.4-nano",
935
+ provider: "openai",
936
+ pricing: {
937
+ inputUsdPerMillionTokens: 0.2,
938
+ outputUsdPerMillionTokens: 1.25
939
+ }
940
+ },
941
+ {
942
+ id: "gemini-2.5-flash",
943
+ provider: "google",
944
+ pricing: {
945
+ inputUsdPerMillionTokens: 0.3,
946
+ outputUsdPerMillionTokens: 1
947
+ }
948
+ },
949
+ {
950
+ id: "gemini-3.1-flash-lite",
951
+ provider: "google",
952
+ pricing: {
953
+ inputUsdPerMillionTokens: 100.25,
954
+ outputUsdPerMillionTokens: 110.5
955
+ }
956
+ },
957
+ {
958
+ id: "ai/smollm2",
959
+ provider: "local-docker",
960
+ pricing: {
961
+ inputUsdPerMillionTokens: 2,
962
+ outputUsdPerMillionTokens: 2
963
+ }
964
+ },
965
+ {
966
+ id: "gpt-oss:20b-cloud",
967
+ provider: "local-ollama",
968
+ pricing: {
969
+ inputUsdPerMillionTokens: 30,
970
+ outputUsdPerMillionTokens: 30
971
+ }
972
+ }
973
+ ];
974
+ function findSupportedChatModel(modelId) {
975
+ return SUPPORTED_CHAT_MODELS.find((model) => model.id === modelId);
976
+ }
977
+ var DEFAULT_CHAT_MODEL_ID = "gemini-2.5-flash";
978
+ // ../shared/src/schemas.ts
979
+ import { z } from "zod";
980
+ import { tool } from "ai";
981
+ var Mode = {
982
+ BUILD: "BUILD",
983
+ PLAN: "PLAN",
984
+ DOCUMENTATION: "DOCUMENTATION"
985
+ };
986
+ var allModes = Object.values(Mode);
987
+ var modeSchema = z.enum(Object.values(Mode));
988
+ var toolInputSchemas = {
989
+ readFile: z.object({
990
+ path: z.string().describe("Relative path to the file to read")
991
+ }),
992
+ listDirectory: z.object({
993
+ path: z.string().default(".").describe("Relative directory path to list")
994
+ }),
995
+ glob: z.object({
996
+ pattern: z.string().describe("Glob pattern to match files"),
997
+ path: z.string().default(".").describe("Directory to search from")
998
+ }),
999
+ grep: z.object({
1000
+ pattern: z.string().describe("Regex pattern to search for"),
1001
+ path: z.string().default(".").describe("Directory to search from"),
1002
+ include: z.string().optional().describe("Optional glob for files to include")
1003
+ }),
1004
+ writeFile: z.object({
1005
+ path: z.string().describe("Relative path to write"),
1006
+ content: z.string().describe("File contents")
1007
+ }),
1008
+ editFile: z.object({
1009
+ path: z.string().describe("Relative path to edit"),
1010
+ oldString: z.string().describe("Exact text to replace; must be unique"),
1011
+ newString: z.string().describe("Replacement text")
1012
+ }),
1013
+ bash: z.object({
1014
+ command: z.string().describe("Shell command to run"),
1015
+ description: z.string().optional().describe("Short description of the command"),
1016
+ timeout: z.number().optional().describe("Timeout in milliseconds")
1017
+ }),
1018
+ webSearchTool: z.object({
1019
+ query: z.string().describe("The search query. Keep it short and specific (3-8 words works best)."),
1020
+ searchDepth: z.enum(["basic", "advanced"]).optional().default("basic").describe("'basic' is faster and cheaper, good for simple factual queries. " + "'advanced' does deeper analysis, better for research-heavy queries."),
1021
+ maxResults: z.number().int().min(1).max(10).optional().default(5).describe("Number of search results to return (1-10)."),
1022
+ includeAnswer: z.boolean().optional().default(true).describe("Whether to include Tavily's own synthesized short answer alongside raw results.")
1023
+ }),
1024
+ fetchUrlTool: z.object({
1025
+ url: z.string().url().describe("The exact URL of the webpage or documentation to download and read")
1026
+ }),
1027
+ undoLastEditSchema: z.object({
1028
+ path: z.string().describe("Relative path to the file you want to revert to its previous state")
1029
+ }),
1030
+ viewOutline: z.object({
1031
+ path: z.string().describe("Relative path to the code file (e.g., .ts, .py, .go, .java) to extract the structure from")
1032
+ }),
1033
+ proposePlanTool: z.object({
1034
+ title: z.string().describe("Short descriptive title of the architectural change"),
1035
+ currentState: z.string().describe("Brief description of how the code/system currently works, relevant to this change"),
1036
+ desiredState: z.string().describe("Brief description of how the code/system should work after this change"),
1037
+ steps: z.array(z.string()).min(1).describe("Step-by-step logical roadmap of what will be modified, in execution order"),
1038
+ affectedFiles: z.array(z.string()).describe("List of relative file paths that will be edited or created. Use an empty array if no files are known yet (e.g. still investigating)."),
1039
+ risks: z.array(z.string()).optional().describe("Optional: notable risks, breaking changes, or things the user should be aware of before approving (e.g. 'will require a database migration').")
1040
+ }),
1041
+ askQuestionTool: z.object({
1042
+ title: z.string().optional().describe("Optional short title for the clarification request"),
1043
+ context: z.string().optional().describe("Optional short note explaining why clarification is needed"),
1044
+ questions: z.array(z.string()).min(1).describe("One or more clarification questions for the user")
1045
+ }),
1046
+ getFileMetadata: z.object({
1047
+ path: z.string().describe("Relative path to the target file")
1048
+ }),
1049
+ writeMarkdownFile: z.object({
1050
+ path: z.string().describe("Relative path to the markdown file. MUST end in .md or .mdx"),
1051
+ content: z.string().describe("The full markdown content to write to the file. This will overwrite the file if it already exists.")
1052
+ })
1053
+ };
1054
+ var documentationToolContracts = {
1055
+ writeMarkdownFile: tool({
1056
+ description: "Create or overwrite a Markdown (.md or .mdx) file with specified content. Use this tool exclusively for writing documentation, guides, or notes. This tool cannot modify code or configuration files, and will reject any path that does not end in a markdown extension. It will automatically create any missing parent directories.",
1057
+ inputSchema: toolInputSchemas.writeMarkdownFile
1058
+ }),
1059
+ getFileMetadata: tool({
1060
+ description: "Retrieve metadata for a file (size, line count, extension) to determine if it is safe to read entirely.",
1061
+ inputSchema: toolInputSchemas.getFileMetadata
1062
+ }),
1063
+ listDirectory: tool({
1064
+ description: "List entries in a directory under the current project directory.",
1065
+ inputSchema: toolInputSchemas.listDirectory
1066
+ }),
1067
+ glob: tool({
1068
+ description: "Find files matching a glob pattern under the current project directory.",
1069
+ inputSchema: toolInputSchemas.glob
1070
+ }),
1071
+ grep: tool({
1072
+ description: "Search file contents with a regular expression under the current project directory.",
1073
+ inputSchema: toolInputSchemas.grep
1074
+ }),
1075
+ viewOutline: tool({
1076
+ description: "Extracts classes, function signatures, interfaces, and methods from a file without loading its full text content, saving context tokens.",
1077
+ inputSchema: toolInputSchemas.viewOutline
1078
+ }),
1079
+ readFile: tool({
1080
+ description: "Read a file from the current project directory.",
1081
+ inputSchema: toolInputSchemas.readFile
1082
+ })
1083
+ };
1084
+ var readOnlyToolContracts = {
1085
+ readFile: tool({
1086
+ description: "Read a file from the current project directory.",
1087
+ inputSchema: toolInputSchemas.readFile
1088
+ }),
1089
+ listDirectory: tool({
1090
+ description: "List entries in a directory under the current project directory.",
1091
+ inputSchema: toolInputSchemas.listDirectory
1092
+ }),
1093
+ glob: tool({
1094
+ description: "Find files matching a glob pattern under the current project directory.",
1095
+ inputSchema: toolInputSchemas.glob
1096
+ }),
1097
+ grep: tool({
1098
+ description: "Search file contents with a regular expression under the current project directory.",
1099
+ inputSchema: toolInputSchemas.grep
1100
+ }),
1101
+ webSearchTool: tool({
1102
+ description: "Searches the live web to find up-to-date information, news, or factual data. Use this tool whenever the user asks about current events, real-time data, or topics requiring external verification. The input should be a concise, targeted search query string.",
1103
+ inputSchema: toolInputSchemas.webSearchTool
1104
+ }),
1105
+ fetchUrlTool: tool({
1106
+ description: "Fetch and read the full content of a specific webpage URL, returned as clean markdown. " + "Use this after a web search points you to a specific documentation page, GitHub issue, " + "blog post, or article, and you need the actual content \u2014 not just the search snippet. " + "Do not use this for search; use webSearch to find URLs first, then this tool to read one.",
1107
+ inputSchema: toolInputSchemas.fetchUrlTool
1108
+ }),
1109
+ viewOutline: tool({
1110
+ description: "Extracts classes, function signatures, interfaces, and methods from a file without loading its full text content, saving context tokens.",
1111
+ inputSchema: toolInputSchemas.viewOutline
1112
+ }),
1113
+ proposePlanTool: tool({
1114
+ description: "Use this tool BEFORE making any file changes when the user's request involves " + "architectural changes, multi-file edits, new features, or anything non-trivial. " + "It presents a structured plan (current state vs. desired state, step-by-step roadmap, " + "and affected files) for the user to review and approve in the UI. " + "Do NOT call editFile, undoLastEdit, or any other file-modifying tool in the same turn " + "as this one \u2014 wait for explicit user approval first. " + "Skip this tool only for trivial, single-line, unambiguous fixes the user has already " + "described in full detail.",
1115
+ inputSchema: toolInputSchemas.proposePlanTool
1116
+ }),
1117
+ askQuestionTool: tool({
1118
+ description: "Use this tool when you need to ask the user one or more clarification questions before continuing. " + "Provide a short title when helpful, then ask concise questions that the UI can display in a dedicated clarification message.",
1119
+ inputSchema: toolInputSchemas.askQuestionTool
1120
+ }),
1121
+ getFileMetadata: tool({
1122
+ description: "Retrieve metadata for a file (size, line count, extension) to determine if it is safe to read entirely.",
1123
+ inputSchema: toolInputSchemas.getFileMetadata
1124
+ })
1125
+ };
1126
+ var buildToolContracts = {
1127
+ ...readOnlyToolContracts,
1128
+ writeFile: tool({
1129
+ description: "Create or overwrite a file under the current project directory.",
1130
+ inputSchema: toolInputSchemas.writeFile
1131
+ }),
1132
+ editFile: tool({
1133
+ description: "Replace exact text in a file under the current project directory.",
1134
+ inputSchema: toolInputSchemas.editFile
1135
+ }),
1136
+ bash: tool({
1137
+ description: "Run a shell command in the current project directory.",
1138
+ inputSchema: toolInputSchemas.bash
1139
+ }),
1140
+ undoLastEditSchema: tool({
1141
+ description: "Reverts the specified file to its exact state before the most recent modification. Use this immediately if your last edit introduced syntax errors, broke tests, or failed to solve the issue.",
1142
+ inputSchema: toolInputSchemas.undoLastEditSchema
1143
+ })
1144
+ };
1145
+ // src/providers/prompt-config/index.tsx
1146
+ import { jsxDEV as jsxDEV6 } from "@opentui/react/jsx-dev-runtime";
1147
+ var PromptConfigContext = createContext5(null);
1148
+ function usePromptConfig() {
1149
+ const value = useContext5(PromptConfigContext);
1150
+ if (!value) {
1151
+ throw new Error("usePromptConfig must be used within a PromptConfigProvider");
1152
+ }
1153
+ return value;
1154
+ }
1155
+ function PromptConfigProvider({ children }) {
1156
+ const [mode, setMode] = useState5(Mode.BUILD);
1157
+ const [model, setModel] = useState5(DEFAULT_CHAT_MODEL_ID);
1158
+ const toggleMode = useCallback5(() => {
1159
+ setMode((m) => m === Mode.BUILD ? Mode.PLAN : Mode.BUILD);
1160
+ }, []);
1161
+ return /* @__PURE__ */ jsxDEV6(PromptConfigContext.Provider, {
1162
+ value: {
1163
+ mode,
1164
+ toggleMode,
1165
+ setMode,
1166
+ model,
1167
+ setModel
1168
+ },
1169
+ children
1170
+ }, undefined, false, undefined, this);
1171
+ }
1172
+
1173
+ // src/layouts/root-layout.tsx
1174
+ import { jsxDEV as jsxDEV7 } from "@opentui/react/jsx-dev-runtime";
1175
+ function RootLayout() {
1176
+ return /* @__PURE__ */ jsxDEV7(ThemeProvider, {
1177
+ children: /* @__PURE__ */ jsxDEV7(ToastProvider, {
1178
+ children: /* @__PURE__ */ jsxDEV7(KeyboardLayerProvider, {
1179
+ children: /* @__PURE__ */ jsxDEV7(DialogProvider, {
1180
+ children: /* @__PURE__ */ jsxDEV7(PromptConfigProvider, {
1181
+ children: /* @__PURE__ */ jsxDEV7(ThemedRoot, {
1182
+ children: /* @__PURE__ */ jsxDEV7(Outlet, {}, undefined, false, undefined, this)
1183
+ }, undefined, false, undefined, this)
1184
+ }, undefined, false, undefined, this)
1185
+ }, undefined, false, undefined, this)
1186
+ }, undefined, false, undefined, this)
1187
+ }, undefined, false, undefined, this)
1188
+ }, undefined, false, undefined, this);
1189
+ }
1190
+
1191
+ // src/components/header.tsx
1192
+ import { jsxDEV as jsxDEV8 } from "@opentui/react/jsx-dev-runtime";
1193
+ function Header() {
1194
+ return /* @__PURE__ */ jsxDEV8("box", {
1195
+ flexDirection: "column",
1196
+ alignItems: "center",
1197
+ marginBottom: 2,
1198
+ children: [
1199
+ /* @__PURE__ */ jsxDEV8("text", {
1200
+ fg: "#FDB515",
1201
+ children: "\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"
1202
+ }, undefined, false, undefined, this),
1203
+ /* @__PURE__ */ jsxDEV8("box", {
1204
+ children: /* @__PURE__ */ jsxDEV8("ascii-font", {
1205
+ font: "block",
1206
+ text: "UniKode"
1207
+ }, undefined, false, undefined, this)
1208
+ }, undefined, false, undefined, this),
1209
+ /* @__PURE__ */ jsxDEV8("text", {
1210
+ fg: "#B0B0B0",
1211
+ children: "Build \u2022 Debug \u2022 Explain \u2022 Ship"
1212
+ }, undefined, false, undefined, this),
1213
+ /* @__PURE__ */ jsxDEV8("text", {
1214
+ fg: "#FDB515",
1215
+ children: "\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"
1216
+ }, undefined, false, undefined, this),
1217
+ /* @__PURE__ */ jsxDEV8("text", {
1218
+ children: [
1219
+ /* @__PURE__ */ jsxDEV8("span", {
1220
+ fg: "#FDB515",
1221
+ children: ">"
1222
+ }, undefined, false, undefined, this),
1223
+ /* @__PURE__ */ jsxDEV8("span", {
1224
+ fg: "#FFFFFF",
1225
+ children: [
1226
+ " ",
1227
+ "Ready for your next command..."
1228
+ ]
1229
+ }, undefined, true, undefined, this)
1230
+ ]
1231
+ }, undefined, true, undefined, this)
1232
+ ]
1233
+ }, undefined, true, undefined, this);
1234
+ }
1235
+
1236
+ // src/components/input-bar.tsx
1237
+ import { readdir } from "fs/promises";
1238
+ import { isAbsolute, relative, resolve } from "path";
1239
+ import { TextAttributes as TextAttributes6 } from "@opentui/core";
1240
+ import { useRef as useRef6, useCallback as useCallback11, useEffect as useEffect3, useState as useState9 } from "react";
1241
+ import { useRenderer as useRenderer2, useKeyboard as useKeyboard5 } from "@opentui/react";
1242
+
1243
+ // src/components/command-menu/index.tsx
1244
+ import { TextAttributes as TextAttributes4 } from "@opentui/core";
1245
+
1246
+ // src/components/dialogs/models-dialog.tsx
1247
+ import { useCallback as useCallback7 } from "react";
1248
+
1249
+ // src/components/dialog-search-list.tsx
1250
+ import { useCallback as useCallback6, useRef as useRef3, useState as useState6 } from "react";
1251
+ import { TextAttributes as TextAttributes2 } from "@opentui/core";
1252
+ import { useKeyboard as useKeyboard3 } from "@opentui/react";
1253
+ import { jsxDEV as jsxDEV9 } from "@opentui/react/jsx-dev-runtime";
1254
+ var MAX_VISIBLE_ITEMS = 6;
1255
+ function DialogSearchList({
1256
+ items,
1257
+ onSelect,
1258
+ onHighlight,
1259
+ filterFn,
1260
+ renderItem,
1261
+ getKey,
1262
+ placeholder = "Search",
1263
+ emptyText = "No results"
1264
+ }) {
1265
+ const [selectedIndex, setSelectedIndex] = useState6(0);
1266
+ const [searchValue, setSearchValue] = useState6("");
1267
+ const inputRef = useRef3(null);
1268
+ const scrollRef = useRef3(null);
1269
+ const { isTopLayer } = useKeyboardLayer();
1270
+ const { colors } = useTheme();
1271
+ const handleContentChange = useCallback6(() => {
1272
+ const text = inputRef.current?.value ?? "";
1273
+ setSearchValue(text);
1274
+ setSelectedIndex(0);
1275
+ const scrollbox = scrollRef.current;
1276
+ if (scrollbox) {
1277
+ scrollbox.scrollTo(0);
1278
+ }
1279
+ }, []);
1280
+ const filtered = searchValue ? items.filter((item) => filterFn(item, searchValue)) : items;
1281
+ const visibleHeight = Math.min(filtered.length, MAX_VISIBLE_ITEMS);
1282
+ useKeyboard3((key) => {
1283
+ if (!isTopLayer("dialog"))
1284
+ return;
1285
+ if (key.name === "return" || key.name === "enter") {
1286
+ const item = filtered[selectedIndex];
1287
+ if (item) {
1288
+ onSelect(item);
1289
+ }
1290
+ } else if (key.name === "up") {
1291
+ setSelectedIndex((i) => {
1292
+ const newIndex = Math.max(0, i - 1);
1293
+ const sb = scrollRef.current;
1294
+ if (sb && newIndex < sb.scrollTop) {
1295
+ sb.scrollTo(newIndex);
1296
+ }
1297
+ const item = filtered[newIndex];
1298
+ if (item && onHighlight)
1299
+ onHighlight(item);
1300
+ return newIndex;
1301
+ });
1302
+ } else if (key.name === "down") {
1303
+ setSelectedIndex((i) => {
1304
+ const newIndex = Math.min(filtered.length - 1, i + 1);
1305
+ const sb = scrollRef.current;
1306
+ if (sb) {
1307
+ const viewportHeight = sb.viewport.height;
1308
+ const visibleEnd = sb.scrollTop + viewportHeight - 1;
1309
+ if (newIndex > visibleEnd) {
1310
+ sb.scrollTo(newIndex - viewportHeight + 1);
1311
+ }
1312
+ }
1313
+ const item = filtered[newIndex];
1314
+ if (item && onHighlight)
1315
+ onHighlight(item);
1316
+ return newIndex;
1317
+ });
1318
+ }
1319
+ });
1320
+ return /* @__PURE__ */ jsxDEV9("box", {
1321
+ flexDirection: "column",
1322
+ gap: 1,
1323
+ children: [
1324
+ /* @__PURE__ */ jsxDEV9("input", {
1325
+ ref: inputRef,
1326
+ placeholder,
1327
+ focused: true,
1328
+ onContentChange: handleContentChange
1329
+ }, undefined, false, undefined, this),
1330
+ filtered.length === 0 ? /* @__PURE__ */ jsxDEV9("text", {
1331
+ attributes: TextAttributes2.DIM,
1332
+ children: emptyText
1333
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV9("scrollbox", {
1334
+ ref: scrollRef,
1335
+ height: visibleHeight,
1336
+ children: filtered.map((item, i) => {
1337
+ const isSelected = i === selectedIndex;
1338
+ return /* @__PURE__ */ jsxDEV9("box", {
1339
+ flexDirection: "row",
1340
+ height: 1,
1341
+ overflow: "hidden",
1342
+ backgroundColor: isSelected ? colors.selection : undefined,
1343
+ onMouseMove: () => {
1344
+ setSelectedIndex(i);
1345
+ if (onHighlight)
1346
+ onHighlight(item);
1347
+ },
1348
+ onMouseDown: () => onSelect(item),
1349
+ children: renderItem(item, isSelected)
1350
+ }, getKey(item), false, undefined, this);
1351
+ })
1352
+ }, undefined, false, undefined, this)
1353
+ ]
1354
+ }, undefined, true, undefined, this);
1355
+ }
1356
+
1357
+ // src/components/dialogs/models-dialog.tsx
1358
+ import { jsxDEV as jsxDEV10 } from "@opentui/react/jsx-dev-runtime";
1359
+ var ModelsDialogContent = ({ models, onSelectModel }) => {
1360
+ const dialog = useDialog();
1361
+ const handleSelect = useCallback7((modelId) => {
1362
+ onSelectModel(modelId);
1363
+ dialog.close();
1364
+ }, [dialog, onSelectModel]);
1365
+ const getDisplayName = useCallback7((modelId) => {
1366
+ const model = findSupportedChatModel(modelId);
1367
+ const isLocal = model ? model.provider === "local-docker" || model.provider === "local-ollama" : false;
1368
+ return isLocal ? `${modelId} (L)` : modelId;
1369
+ }, []);
1370
+ return /* @__PURE__ */ jsxDEV10(DialogSearchList, {
1371
+ items: models,
1372
+ onSelect: handleSelect,
1373
+ filterFn: (modelId, query) => getDisplayName(modelId).toLowerCase().includes(query.toLowerCase()),
1374
+ renderItem: (modelId, isSelected) => /* @__PURE__ */ jsxDEV10("text", {
1375
+ selectable: false,
1376
+ fg: isSelected ? "black" : "white",
1377
+ children: getDisplayName(modelId)
1378
+ }, undefined, false, undefined, this),
1379
+ getKey: (modelId) => modelId,
1380
+ placeholder: "Search models",
1381
+ emptyText: "No matching models"
1382
+ }, undefined, false, undefined, this);
1383
+ };
1384
+ // src/components/dialogs/sessions-dialog.tsx
1385
+ import { useCallback as useCallback8, useEffect, useState as useState7 } from "react";
1386
+ import { TextAttributes as TextAttributes3 } from "@opentui/core";
1387
+ import { format } from "date-fns";
1388
+ import { useNavigate } from "react-router";
1389
+
1390
+ // src/lib/api-client.ts
1391
+ import { hc } from "hono/client";
1392
+
1393
+ // src/lib/auth.ts
1394
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync } from "fs";
1395
+ import { homedir as homedir2 } from "os";
1396
+ import { join as join2 } from "path";
1397
+ var AUTH_DIR = join2(homedir2(), ".Claude-Code-Clone");
1398
+ var AUTH_FILE = join2(AUTH_DIR, "auth.json");
1399
+ function getAuth() {
1400
+ try {
1401
+ const data = readFileSync2(AUTH_FILE, "utf-8");
1402
+ const parsed = JSON.parse(data);
1403
+ return typeof parsed.token === "string" ? { token: parsed.token } : null;
1404
+ } catch {
1405
+ return null;
1406
+ }
1407
+ }
1408
+ function saveAuth(data) {
1409
+ if (!existsSync(AUTH_DIR)) {
1410
+ mkdirSync2(AUTH_DIR, { mode: 448 });
1411
+ }
1412
+ writeFileSync2(AUTH_FILE, JSON.stringify(data), { mode: 384 });
1413
+ }
1414
+ function clearAuth() {
1415
+ try {
1416
+ unlinkSync(AUTH_FILE);
1417
+ } catch {}
1418
+ }
1419
+
1420
+ // src/lib/api-client.ts
1421
+ var apiClient = hc(process.env.API_URL ?? "http://localhost:3000", {
1422
+ fetch: async (input, init) => {
1423
+ const headers = new Headers(init?.headers);
1424
+ const auth = getAuth();
1425
+ if (auth) {
1426
+ headers.set("Authorization", `Bearer ${auth.token}`);
1427
+ }
1428
+ const response = await fetch(input, { ...init, headers });
1429
+ if (response.status === 401) {
1430
+ clearAuth();
1431
+ }
1432
+ return response;
1433
+ }
1434
+ });
1435
+
1436
+ // src/lib/http-errors.ts
1437
+ async function getErrorMessage(response) {
1438
+ try {
1439
+ const data = await response.json();
1440
+ if (typeof data.error === "string" && data.error.length > 0) {
1441
+ return data.error;
1442
+ }
1443
+ } catch {}
1444
+ return response.statusText || `request failed with status ${response.status}`;
1445
+ }
1446
+
1447
+ // src/components/dialogs/sessions-dialog.tsx
1448
+ import { jsxDEV as jsxDEV11, Fragment } from "@opentui/react/jsx-dev-runtime";
1449
+ var SessionsDialogContent = () => {
1450
+ const [sessions, setSessions] = useState7([]);
1451
+ const [loading, setLoading] = useState7(true);
1452
+ const { close } = useDialog();
1453
+ const navigate = useNavigate();
1454
+ const { show } = useToast();
1455
+ useEffect(() => {
1456
+ let ignore = false;
1457
+ const fetchSessions = async () => {
1458
+ try {
1459
+ const res = await apiClient.sessions.$get();
1460
+ if (!res.ok) {
1461
+ throw new Error(await getErrorMessage(res));
1462
+ }
1463
+ const data = await res.json();
1464
+ if (!ignore) {
1465
+ setSessions(data);
1466
+ setLoading(false);
1467
+ }
1468
+ } catch (error) {
1469
+ if (!ignore) {
1470
+ show({
1471
+ variant: "error",
1472
+ message: error instanceof Error ? error.message : "Failed to fetch sessions"
1473
+ });
1474
+ close();
1475
+ }
1476
+ }
1477
+ };
1478
+ fetchSessions();
1479
+ return () => {
1480
+ ignore = true;
1481
+ };
1482
+ }, [close, show]);
1483
+ const handleSelect = useCallback8((session) => {
1484
+ close();
1485
+ navigate(`/sessions/${session.id}`);
1486
+ }, [close, navigate]);
1487
+ if (loading) {
1488
+ return /* @__PURE__ */ jsxDEV11("box", {
1489
+ flexDirection: "column",
1490
+ children: /* @__PURE__ */ jsxDEV11("text", {
1491
+ attributes: TextAttributes3.DIM,
1492
+ children: "Loading sessions..."
1493
+ }, undefined, false, undefined, this)
1494
+ }, undefined, false, undefined, this);
1495
+ }
1496
+ return /* @__PURE__ */ jsxDEV11(DialogSearchList, {
1497
+ items: sessions,
1498
+ onSelect: handleSelect,
1499
+ filterFn: (s, query) => s.title.toLowerCase().includes(query.toLowerCase()),
1500
+ renderItem: (session, isSelected) => /* @__PURE__ */ jsxDEV11(Fragment, {
1501
+ children: [
1502
+ /* @__PURE__ */ jsxDEV11("text", {
1503
+ selectable: false,
1504
+ fg: isSelected ? "black" : "white",
1505
+ children: session.title
1506
+ }, undefined, false, undefined, this),
1507
+ /* @__PURE__ */ jsxDEV11("box", {
1508
+ flexGrow: 1
1509
+ }, undefined, false, undefined, this),
1510
+ /* @__PURE__ */ jsxDEV11("text", {
1511
+ selectable: false,
1512
+ fg: isSelected ? "black" : undefined,
1513
+ attributes: TextAttributes3.DIM,
1514
+ children: format(new Date(session.createdAt), "hh:mm a")
1515
+ }, undefined, false, undefined, this)
1516
+ ]
1517
+ }, undefined, true, undefined, this),
1518
+ getKey: (s) => s.id,
1519
+ placeholder: "Search sessions",
1520
+ emptyText: "No matching sessions"
1521
+ }, undefined, false, undefined, this);
1522
+ };
1523
+ // src/components/dialogs/agents-dialog.tsx
1524
+ import { useCallback as useCallback9 } from "react";
1525
+ import { jsxDEV as jsxDEV12 } from "@opentui/react/jsx-dev-runtime";
1526
+ var AVAILABLE_MODES = [...Object.values(Mode)];
1527
+ function getModeLabel(mode) {
1528
+ return mode.charAt(0).toUpperCase() + mode.slice(1).toLowerCase();
1529
+ }
1530
+ var AgentsDialogContent = ({ currentMode, onSelectMode }) => {
1531
+ const dialog = useDialog();
1532
+ const handleSelect = useCallback9((nextMode) => {
1533
+ onSelectMode(nextMode);
1534
+ dialog.close();
1535
+ }, [onSelectMode, dialog]);
1536
+ return /* @__PURE__ */ jsxDEV12(DialogSearchList, {
1537
+ items: AVAILABLE_MODES,
1538
+ onSelect: handleSelect,
1539
+ filterFn: (item, query) => getModeLabel(item).toLowerCase().includes(query.toLowerCase()),
1540
+ renderItem: (item, isSelected) => /* @__PURE__ */ jsxDEV12("text", {
1541
+ selectable: false,
1542
+ fg: isSelected ? "black" : "white",
1543
+ children: [
1544
+ item === currentMode ? " \u2022 " : " ",
1545
+ getModeLabel(item)
1546
+ ]
1547
+ }, undefined, true, undefined, this),
1548
+ getKey: (item) => item,
1549
+ placeholder: "Search agents",
1550
+ emptyText: "No matching agents"
1551
+ }, undefined, false, undefined, this);
1552
+ };
1553
+ // src/components/dialogs/theme-dialog.tsx
1554
+ import { useCallback as useCallback10, useEffect as useEffect2, useRef as useRef4 } from "react";
1555
+ import { jsxDEV as jsxDEV13 } from "@opentui/react/jsx-dev-runtime";
1556
+ var ThemeDialogContent = () => {
1557
+ const dialog = useDialog();
1558
+ const { setTheme, currentTheme } = useTheme();
1559
+ const originalThemeRef = useRef4(currentTheme);
1560
+ const confirmedRef = useRef4(false);
1561
+ useEffect2(() => {
1562
+ return () => {
1563
+ if (!confirmedRef.current) {
1564
+ setTheme(originalThemeRef.current);
1565
+ }
1566
+ };
1567
+ }, [setTheme]);
1568
+ const handleSelect = useCallback10((theme) => {
1569
+ confirmedRef.current = true;
1570
+ setTheme(theme);
1571
+ dialog.close();
1572
+ }, [setTheme, dialog]);
1573
+ const handleHighlight = useCallback10((theme) => {
1574
+ setTheme(theme);
1575
+ }, [setTheme]);
1576
+ return /* @__PURE__ */ jsxDEV13(DialogSearchList, {
1577
+ items: THEMES,
1578
+ onSelect: handleSelect,
1579
+ onHighlight: handleHighlight,
1580
+ filterFn: (t, query) => t.name.toLowerCase().includes(query.toLowerCase()),
1581
+ renderItem: (theme, isSelected) => /* @__PURE__ */ jsxDEV13("text", {
1582
+ selectable: false,
1583
+ fg: isSelected ? "black" : "white",
1584
+ children: [
1585
+ theme.name === originalThemeRef.current.name ? " \u2022 " : " ",
1586
+ theme.name
1587
+ ]
1588
+ }, undefined, true, undefined, this),
1589
+ getKey: (t) => t.name,
1590
+ placeholder: "Search themes",
1591
+ emptyText: "No matching themes"
1592
+ }, undefined, false, undefined, this);
1593
+ };
1594
+ // src/lib/oauth.ts
1595
+ import open from "open";
1596
+ var LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
1597
+ function toBase64Url(input) {
1598
+ return Buffer.from(input).toString("base64url");
1599
+ }
1600
+ async function createPkceChallenge(verifier) {
1601
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
1602
+ return toBase64Url(new Uint8Array(digest));
1603
+ }
1604
+ function encodeState(state) {
1605
+ return toBase64Url(JSON.stringify(state));
1606
+ }
1607
+ function decodeState(state) {
1608
+ const [encoded] = state.split(".");
1609
+ if (!encoded) {
1610
+ throw new Error("Invalid state");
1611
+ }
1612
+ return JSON.parse(Buffer.from(encoded, "base64url").toString());
1613
+ }
1614
+ function getErrorMessage2(error) {
1615
+ return error instanceof Error ? error.message : String(error);
1616
+ }
1617
+ async function performLogin() {
1618
+ const clerkFrontendApi = process.env.CLERK_FRONTEND_API;
1619
+ const clientId = process.env.CLERK_OAUTH_CLIENT_ID;
1620
+ const apiUrl = process.env.API_URL ?? "http://localhost:3000";
1621
+ if (!clerkFrontendApi)
1622
+ throw new Error("CLERK_FRONTEND_API not set");
1623
+ if (!clientId)
1624
+ throw new Error("CLERK_OAUTH_CLIENT_ID not set");
1625
+ const nonce = crypto.randomUUID();
1626
+ const codeVerifier = toBase64Url(crypto.getRandomValues(new Uint8Array(32)));
1627
+ const codeChallenge = await createPkceChallenge(codeVerifier);
1628
+ let settled = false;
1629
+ return new Promise((resolve, reject) => {
1630
+ const server = Bun.serve({
1631
+ port: 0,
1632
+ async fetch(req) {
1633
+ const url = new URL(req.url);
1634
+ if (url.pathname !== "/callback") {
1635
+ return new Response("Not found", { status: 404 });
1636
+ }
1637
+ const error = url.searchParams.get("error");
1638
+ if (error) {
1639
+ const msg = url.searchParams.get("error_description") ?? error;
1640
+ settled = true;
1641
+ reject(new Error(msg));
1642
+ setTimeout(() => server.stop(), 500);
1643
+ return new Response(`Authentication failed: ${msg}`, { status: 400 });
1644
+ }
1645
+ const code = url.searchParams.get("code");
1646
+ const state2 = url.searchParams.get("state");
1647
+ if (!code || !state2) {
1648
+ settled = true;
1649
+ reject(new Error("Missing code or state"));
1650
+ setTimeout(() => server.stop(), 500);
1651
+ return new Response("Bad request", { status: 400 });
1652
+ }
1653
+ try {
1654
+ const payload = decodeState(state2);
1655
+ if (payload.nonce !== nonce)
1656
+ throw new Error("State mismatch");
1657
+ } catch (err) {
1658
+ settled = true;
1659
+ reject(err);
1660
+ setTimeout(() => server.stop(), 500);
1661
+ return new Response("Invalid state", { status: 400 });
1662
+ }
1663
+ try {
1664
+ const redirectUri2 = `${apiUrl}/auth/callback`;
1665
+ const tokenRes = await fetch(`${clerkFrontendApi}/oauth/token`, {
1666
+ method: "POST",
1667
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1668
+ body: new URLSearchParams({
1669
+ grant_type: "authorization_code",
1670
+ code,
1671
+ redirect_uri: redirectUri2,
1672
+ client_id: clientId,
1673
+ code_verifier: codeVerifier
1674
+ })
1675
+ });
1676
+ if (!tokenRes.ok) {
1677
+ const details = await tokenRes.text();
1678
+ throw new Error(details || "Failed to exchange authorization code");
1679
+ }
1680
+ const tokenData = await tokenRes.json();
1681
+ settled = true;
1682
+ saveAuth({ token: tokenData.access_token });
1683
+ resolve({ token: tokenData.access_token });
1684
+ setTimeout(() => server.stop(), 500);
1685
+ return new Response("Authenticated! You can close this tab.");
1686
+ } catch (err) {
1687
+ settled = true;
1688
+ reject(err);
1689
+ const message = getErrorMessage2(err);
1690
+ setTimeout(() => server.stop(), 500);
1691
+ return new Response(`Authentication failed: ${message}`, { status: 400 });
1692
+ }
1693
+ }
1694
+ });
1695
+ const port = server.port;
1696
+ if (typeof port !== "number") {
1697
+ server.stop();
1698
+ reject(new Error("Failed to start callback server"));
1699
+ return;
1700
+ }
1701
+ const state = encodeState({ port, nonce });
1702
+ const redirectUri = `${apiUrl}/auth/callback`;
1703
+ const authorizeUrl = new URL(`${clerkFrontendApi}/oauth/authorize`);
1704
+ authorizeUrl.searchParams.set("response_type", "code");
1705
+ authorizeUrl.searchParams.set("client_id", clientId);
1706
+ authorizeUrl.searchParams.set("redirect_uri", redirectUri);
1707
+ authorizeUrl.searchParams.set("scope", "openid email profile");
1708
+ authorizeUrl.searchParams.set("state", state);
1709
+ authorizeUrl.searchParams.set("prompt", "login");
1710
+ authorizeUrl.searchParams.set("code_challenge", codeChallenge);
1711
+ authorizeUrl.searchParams.set("code_challenge_method", "S256");
1712
+ open(authorizeUrl.toString());
1713
+ setTimeout(() => {
1714
+ if (!settled) {
1715
+ settled = true;
1716
+ server.stop();
1717
+ reject(new Error("Login timed out"));
1718
+ }
1719
+ }, LOGIN_TIMEOUT_MS);
1720
+ });
1721
+ }
1722
+
1723
+ // src/lib/upgrage.ts
1724
+ import open2 from "open";
1725
+ async function openUpgradeCheckout() {
1726
+ const response = await apiClient.billing.checkout.$post();
1727
+ if (response.ok) {
1728
+ const data = await response.json();
1729
+ await open2(data.url);
1730
+ return;
1731
+ }
1732
+ throw new Error(await getErrorMessage(response));
1733
+ }
1734
+ async function openBillingPortal() {
1735
+ const response = await apiClient.billing.portal.$post();
1736
+ if (response.ok) {
1737
+ const data = await response.json();
1738
+ await open2(data.url);
1739
+ return;
1740
+ }
1741
+ throw new Error(await getErrorMessage(response));
1742
+ }
1743
+
1744
+ // src/components/command-menu/commands.tsx
1745
+ import { jsxDEV as jsxDEV14 } from "@opentui/react/jsx-dev-runtime";
1746
+ var COMMANDS = [
1747
+ {
1748
+ name: "new",
1749
+ description: "start a new conversation",
1750
+ value: "/new",
1751
+ action: (ctx) => {
1752
+ ctx.navigate("/");
1753
+ }
1754
+ },
1755
+ {
1756
+ name: "agents",
1757
+ description: "switch agents",
1758
+ value: "/agents",
1759
+ action: (ctx) => {
1760
+ ctx.dialog.open({
1761
+ title: "Agent Selection",
1762
+ children: /* @__PURE__ */ jsxDEV14(AgentsDialogContent, {
1763
+ currentMode: ctx.mode,
1764
+ onSelectMode: ctx.setMode
1765
+ }, undefined, false, undefined, this)
1766
+ });
1767
+ }
1768
+ },
1769
+ {
1770
+ name: "models",
1771
+ description: "select an AI model for generaftion",
1772
+ value: "/models",
1773
+ action: (ctx) => {
1774
+ ctx.dialog.open({
1775
+ title: "Agent Selection",
1776
+ children: /* @__PURE__ */ jsxDEV14(ModelsDialogContent, {
1777
+ models: SUPPORTED_CHAT_MODELS.map((model) => model.id),
1778
+ onSelectModel: ctx.setModel
1779
+ }, undefined, false, undefined, this)
1780
+ });
1781
+ }
1782
+ },
1783
+ {
1784
+ name: "sessions",
1785
+ description: "browse past sessions",
1786
+ value: "/sessions",
1787
+ action: (ctx) => {
1788
+ ctx.dialog.open({
1789
+ title: "Sessions Selection",
1790
+ children: /* @__PURE__ */ jsxDEV14(SessionsDialogContent, {}, undefined, false, undefined, this)
1791
+ });
1792
+ }
1793
+ },
1794
+ {
1795
+ name: "themes",
1796
+ description: "change color theme",
1797
+ value: "/themes",
1798
+ action: (ctx) => {
1799
+ ctx.dialog.open({
1800
+ title: "Theme Selection",
1801
+ children: /* @__PURE__ */ jsxDEV14(ThemeDialogContent, {}, undefined, false, undefined, this)
1802
+ });
1803
+ }
1804
+ },
1805
+ {
1806
+ name: "login",
1807
+ description: "sign in to your account",
1808
+ value: "/login",
1809
+ action: async (ctx) => {
1810
+ ctx.toast.show({ message: "logging in " });
1811
+ try {
1812
+ await performLogin();
1813
+ ctx.toast.show({ variant: "success", message: "login successful" });
1814
+ } catch (error) {
1815
+ const message = error instanceof Error ? error.message : "login unsuccessful";
1816
+ ctx.toast.show({ variant: "error", message });
1817
+ }
1818
+ }
1819
+ },
1820
+ {
1821
+ name: "logout",
1822
+ description: "sign out of your account",
1823
+ value: "/logout",
1824
+ action: (ctx) => {
1825
+ clearAuth();
1826
+ ctx.toast.show({ variant: "success", message: "logging out " });
1827
+ }
1828
+ },
1829
+ {
1830
+ name: "upgrade",
1831
+ description: "buy more credits",
1832
+ value: "/upgrade",
1833
+ action: async (ctx) => {
1834
+ ctx.toast.show({ message: "opening credit checkout " });
1835
+ try {
1836
+ await openUpgradeCheckout();
1837
+ ctx.toast.show({
1838
+ variant: "success",
1839
+ message: "Checkout opened in browser"
1840
+ });
1841
+ } catch (error) {
1842
+ const message = error instanceof Error ? error.message : "Failed to open checkout";
1843
+ ctx.toast.show({ variant: "error", message });
1844
+ }
1845
+ }
1846
+ },
1847
+ {
1848
+ name: "usage",
1849
+ description: "open billing porta; in your browser",
1850
+ value: "/usage",
1851
+ action: async (ctx) => {
1852
+ ctx.toast.show({ message: "opening billing portal " });
1853
+ try {
1854
+ await openBillingPortal();
1855
+ ctx.toast.show({
1856
+ variant: "success",
1857
+ message: "Billing portal opened in browser"
1858
+ });
1859
+ } catch (error) {
1860
+ const message = error instanceof Error ? error.message : "Failed to open billing portal";
1861
+ ctx.toast.show({ variant: "error", message });
1862
+ }
1863
+ }
1864
+ },
1865
+ {
1866
+ name: "docs",
1867
+ description: "open the docs page",
1868
+ value: "/docs",
1869
+ action: (ctx) => {
1870
+ ctx.navigate("/docs");
1871
+ }
1872
+ },
1873
+ {
1874
+ name: "exit",
1875
+ description: "exit the program",
1876
+ value: "/exit",
1877
+ action: (ctx) => {
1878
+ ctx.exit();
1879
+ }
1880
+ }
1881
+ ];
1882
+
1883
+ // src/components/command-menu/filter-command.ts
1884
+ function getFilteredCommands(query) {
1885
+ if (query.length === 0)
1886
+ return COMMANDS;
1887
+ return COMMANDS.filter((cmd) => cmd.name.toLowerCase().startsWith(query.toLowerCase()));
1888
+ }
1889
+
1890
+ // src/components/command-menu/index.tsx
1891
+ import { jsxDEV as jsxDEV15 } from "@opentui/react/jsx-dev-runtime";
1892
+ var MAX_VISIBLE_ITEMS2 = 8;
1893
+ var COMMAND_COL_WIDTH = Math.max(...COMMANDS.map((cmd) => cmd.name.length)) + 4;
1894
+ function CommandMenu({
1895
+ query,
1896
+ selectedIndex,
1897
+ scrollRef,
1898
+ onSelect,
1899
+ onExecute
1900
+ }) {
1901
+ const { colors } = useTheme();
1902
+ const filtered = getFilteredCommands(query);
1903
+ const visibleHeight = Math.min(filtered.length, MAX_VISIBLE_ITEMS2);
1904
+ if (filtered.length === 0) {
1905
+ return /* @__PURE__ */ jsxDEV15("box", {
1906
+ paddingX: 1,
1907
+ children: /* @__PURE__ */ jsxDEV15("text", {
1908
+ attributes: TextAttributes4.DIM,
1909
+ children: "No mathing commands"
1910
+ }, undefined, false, undefined, this)
1911
+ }, undefined, false, undefined, this);
1912
+ }
1913
+ return /* @__PURE__ */ jsxDEV15("scrollbox", {
1914
+ ref: scrollRef,
1915
+ height: visibleHeight,
1916
+ children: filtered.map((cmd, i) => {
1917
+ const isSelected = i === selectedIndex;
1918
+ return /* @__PURE__ */ jsxDEV15("box", {
1919
+ flexDirection: "row",
1920
+ paddingX: 1,
1921
+ height: 1,
1922
+ overflow: "hidden",
1923
+ backgroundColor: isSelected ? colors.selection : undefined,
1924
+ onMouseMove: () => onSelect(i),
1925
+ onMouseDown: () => onExecute(i),
1926
+ children: [
1927
+ /* @__PURE__ */ jsxDEV15("box", {
1928
+ width: COMMAND_COL_WIDTH,
1929
+ flexShrink: 0,
1930
+ children: /* @__PURE__ */ jsxDEV15("text", {
1931
+ selectable: false,
1932
+ fg: isSelected ? "black" : "white",
1933
+ children: [
1934
+ "/",
1935
+ cmd.name
1936
+ ]
1937
+ }, undefined, true, undefined, this)
1938
+ }, undefined, false, undefined, this),
1939
+ /* @__PURE__ */ jsxDEV15("box", {
1940
+ flexGrow: 1,
1941
+ flexShrink: 1,
1942
+ overflow: "hidden",
1943
+ children: /* @__PURE__ */ jsxDEV15("text", {
1944
+ selectable: false,
1945
+ fg: isSelected ? "black" : "gray",
1946
+ children: [
1947
+ "/",
1948
+ cmd.description
1949
+ ]
1950
+ }, undefined, true, undefined, this)
1951
+ }, undefined, false, undefined, this)
1952
+ ]
1953
+ }, cmd.value, true, undefined, this);
1954
+ })
1955
+ }, undefined, false, undefined, this);
1956
+ }
1957
+
1958
+ // src/components/status-bar.tsx
1959
+ import { TextAttributes as TextAttributes5 } from "@opentui/core";
1960
+ import { jsxDEV as jsxDEV16 } from "@opentui/react/jsx-dev-runtime";
1961
+ function StatusBar() {
1962
+ const { colors } = useTheme();
1963
+ const { mode, model } = usePromptConfig();
1964
+ return /* @__PURE__ */ jsxDEV16("box", {
1965
+ flexDirection: "row",
1966
+ gap: 1,
1967
+ children: [
1968
+ /* @__PURE__ */ jsxDEV16("text", {
1969
+ fg: mode === Mode.PLAN ? colors.planMode : colors.primary,
1970
+ children: mode
1971
+ }, undefined, false, undefined, this),
1972
+ /* @__PURE__ */ jsxDEV16("text", {
1973
+ attributes: TextAttributes5.DIM,
1974
+ fg: colors.dimSeparator,
1975
+ children: [
1976
+ "`",
1977
+ ">",
1978
+ "`"
1979
+ ]
1980
+ }, undefined, true, undefined, this),
1981
+ /* @__PURE__ */ jsxDEV16("text", {
1982
+ children: model
1983
+ }, undefined, false, undefined, this)
1984
+ ]
1985
+ }, undefined, true, undefined, this);
1986
+ }
1987
+
1988
+ // src/components/command-menu/use-command-menu.ts
1989
+ import { useRef as useRef5, useState as useState8, useMemo as useMemo2 } from "react";
1990
+ import { useKeyboard as useKeyboard4 } from "@opentui/react";
1991
+ function useCommandMenu() {
1992
+ const [textValue, setTextValue] = useState8("");
1993
+ const [selectedIndex, setSelectedIndex] = useState8(0);
1994
+ const [showCommandMenu, setShowCommandMenu] = useState8(false);
1995
+ const scrollRef = useRef5(null);
1996
+ const { push, pop, isTopLayer } = useKeyboardLayer();
1997
+ const commandQuery = showCommandMenu && textValue.startsWith("/") ? textValue.slice(1) : "";
1998
+ const filteredCommands = useMemo2(() => getFilteredCommands(commandQuery), [commandQuery]);
1999
+ const close = () => {
2000
+ setShowCommandMenu(false);
2001
+ pop("command");
2002
+ };
2003
+ const handleContentChange = (text) => {
2004
+ setTextValue(text);
2005
+ setSelectedIndex(0);
2006
+ const scrollbox = scrollRef.current;
2007
+ if (scrollbox) {
2008
+ scrollbox.scrollTo(0);
2009
+ }
2010
+ const prefix = text.startsWith("/") ? text.slice(1) : null;
2011
+ if (prefix !== null && !prefix.includes(" ")) {
2012
+ setShowCommandMenu(true);
2013
+ push("command", () => {
2014
+ close();
2015
+ return true;
2016
+ });
2017
+ } else {
2018
+ close();
2019
+ }
2020
+ };
2021
+ const resolveCommand = (index) => {
2022
+ const command = filteredCommands[index];
2023
+ if (command) {
2024
+ close();
2025
+ }
2026
+ return command;
2027
+ };
2028
+ useKeyboard4((key) => {
2029
+ if (!showCommandMenu || !isTopLayer("command"))
2030
+ return;
2031
+ if (key.name === "escape") {
2032
+ key.preventDefault();
2033
+ close();
2034
+ } else if (key.name === "up") {
2035
+ key.preventDefault();
2036
+ setSelectedIndex((i) => {
2037
+ const newIndex = Math.max(0, i - 1);
2038
+ const sb = scrollRef.current;
2039
+ if (sb && newIndex < sb.scrollTop) {
2040
+ sb.scrollTo(newIndex);
2041
+ }
2042
+ return newIndex;
2043
+ });
2044
+ } else if (key.name === "down") {
2045
+ key.preventDefault();
2046
+ setSelectedIndex((i) => {
2047
+ if (filteredCommands.length === 0) {
2048
+ return 0;
2049
+ }
2050
+ const newIndex = Math.min(filteredCommands.length - 1, i + 1);
2051
+ const sb = scrollRef.current;
2052
+ if (sb) {
2053
+ const viewPortHeight = sb.viewport.height;
2054
+ const visibleEnd = sb.scrollTop + viewPortHeight - 1;
2055
+ if (newIndex > visibleEnd) {
2056
+ sb.scrollTo(newIndex - viewPortHeight + 1);
2057
+ }
2058
+ }
2059
+ return newIndex;
2060
+ });
2061
+ }
2062
+ });
2063
+ return {
2064
+ showCommandMenu,
2065
+ commandQuery,
2066
+ selectedIndex,
2067
+ scrollRef,
2068
+ handleContentChange,
2069
+ resolveCommand,
2070
+ setSelectedIndex
2071
+ };
2072
+ }
2073
+
2074
+ // src/components/input-bar.tsx
2075
+ import { useNavigate as useNavigate2 } from "react-router";
2076
+ import { jsxDEV as jsxDEV17 } from "@opentui/react/jsx-dev-runtime";
2077
+ var MAX_VISIBLE_MENTIONS = 8;
2078
+ var CURRENT_DIRECTORY = process.cwd();
2079
+ var MAX_FALLBACK_MENTION_CANDIDATES = 32;
2080
+ var MENTION_QUERY_CHARACTER = /[A-Za-z0-9._/-]/;
2081
+ var RECURSIVE_MENTION_IGNORED_DIRECTORIES = new Set(["node_modules"]);
2082
+ function isWithinCurrentDirectory(targetPath) {
2083
+ const relativePath = relative(CURRENT_DIRECTORY, targetPath);
2084
+ return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute(relativePath);
2085
+ }
2086
+ function isMentionQueryCharacter(character) {
2087
+ return MENTION_QUERY_CHARACTER.test(character);
2088
+ }
2089
+ function findActiveMention(text, cursorOffset) {
2090
+ const safeOffset = Math.max(0, Math.min(cursorOffset, text.length));
2091
+ let start = safeOffset;
2092
+ while (start > 0 && !/\s/.test(text[start - 1])) {
2093
+ start -= 1;
2094
+ }
2095
+ let end = safeOffset;
2096
+ while (end < text.length && !/\s/.test(text[end])) {
2097
+ end += 1;
2098
+ }
2099
+ const token = text.slice(start, end);
2100
+ const relativeCursor = safeOffset - start;
2101
+ const mentionStart = token.lastIndexOf("@", relativeCursor);
2102
+ if (mentionStart === -1) {
2103
+ return null;
2104
+ }
2105
+ const previousCharacter = token[mentionStart - 1];
2106
+ if (previousCharacter && isMentionQueryCharacter(previousCharacter)) {
2107
+ return null;
2108
+ }
2109
+ let mentionEnd = mentionStart + 1;
2110
+ while (mentionEnd < token.length && isMentionQueryCharacter(token[mentionEnd])) {
2111
+ mentionEnd += 1;
2112
+ }
2113
+ if (relativeCursor < mentionStart || relativeCursor > mentionEnd) {
2114
+ return null;
2115
+ }
2116
+ return {
2117
+ start: start + mentionStart,
2118
+ end: start + mentionEnd,
2119
+ query: token.slice(mentionStart + 1, mentionEnd)
2120
+ };
2121
+ }
2122
+ async function getMentionCandidates(query) {
2123
+ const normalizedQuery = query.startsWith("./") ? query.slice(2) : query;
2124
+ if (normalizedQuery.startsWith("/")) {
2125
+ return [];
2126
+ }
2127
+ const hasTrailingSlash = normalizedQuery.endsWith("/");
2128
+ const lastSlashIndex = hasTrailingSlash ? normalizedQuery.length - 1 : normalizedQuery.lastIndexOf("/");
2129
+ const directoryPart = hasTrailingSlash ? normalizedQuery.slice(0, -1) : lastSlashIndex === -1 ? "" : normalizedQuery.slice(0, lastSlashIndex);
2130
+ const namePrefix = hasTrailingSlash ? "" : lastSlashIndex === -1 ? normalizedQuery : normalizedQuery.slice(lastSlashIndex + 1);
2131
+ const absoluteDirectory = resolve(CURRENT_DIRECTORY, directoryPart || ".");
2132
+ if (!isWithinCurrentDirectory(absoluteDirectory)) {
2133
+ return [];
2134
+ }
2135
+ try {
2136
+ const entries = await readdir(absoluteDirectory, { withFileTypes: true });
2137
+ const lowercasePrefix = namePrefix.toLowerCase();
2138
+ const showHiddenEntries = namePrefix.startsWith(".");
2139
+ const directMatches = entries.filter((entry) => showHiddenEntries || !entry.name.startsWith(".")).filter((entry) => {
2140
+ return lowercasePrefix === "" || entry.name.toLowerCase().startsWith(lowercasePrefix);
2141
+ }).sort((left, right) => {
2142
+ if (left.isDirectory() !== right.isDirectory()) {
2143
+ return left.isDirectory() ? -1 : 1;
2144
+ }
2145
+ return left.name.localeCompare(right.name);
2146
+ }).map((entry) => {
2147
+ const path = directoryPart ? `${directoryPart}/${entry.name}` : entry.name;
2148
+ const kind = entry.isDirectory() ? "directory" : "file";
2149
+ return {
2150
+ path: kind === "directory" ? `${path}/` : path,
2151
+ kind
2152
+ };
2153
+ });
2154
+ if (directMatches.length > 0 || directoryPart !== "" || namePrefix === "") {
2155
+ return directMatches;
2156
+ }
2157
+ const fallbackMatches = [];
2158
+ const visit = async (absoluteDirectory2, directoryPart2) => {
2159
+ const entries2 = await readdir(absoluteDirectory2, { withFileTypes: true });
2160
+ for (const entry of entries2) {
2161
+ if (!showHiddenEntries && entry.name.startsWith(".")) {
2162
+ continue;
2163
+ }
2164
+ if (entry.isDirectory() && RECURSIVE_MENTION_IGNORED_DIRECTORIES.has(entry.name)) {
2165
+ continue;
2166
+ }
2167
+ const path = directoryPart2 ? `${directoryPart2}/${entry.name}` : entry.name;
2168
+ const kind = entry.isDirectory() ? "directory" : "file";
2169
+ if (entry.name.toLowerCase().startsWith(lowercasePrefix)) {
2170
+ fallbackMatches.push({
2171
+ path: kind === "directory" ? `${path}/` : path,
2172
+ kind
2173
+ });
2174
+ if (fallbackMatches.length >= MAX_FALLBACK_MENTION_CANDIDATES) {
2175
+ return;
2176
+ }
2177
+ }
2178
+ if (entry.isDirectory()) {
2179
+ await visit(resolve(absoluteDirectory2, entry.name), path);
2180
+ if (fallbackMatches.length >= MAX_FALLBACK_MENTION_CANDIDATES) {
2181
+ return;
2182
+ }
2183
+ }
2184
+ }
2185
+ };
2186
+ await visit(CURRENT_DIRECTORY, "");
2187
+ return fallbackMatches.sort((left, right) => left.path.localeCompare(right.path));
2188
+ } catch {
2189
+ return [];
2190
+ }
2191
+ }
2192
+ function FileMentionMenu({
2193
+ candidates,
2194
+ selectedIndex,
2195
+ scrollRef,
2196
+ onSelect,
2197
+ onExecute
2198
+ }) {
2199
+ const { colors } = useTheme();
2200
+ const visibleHeight = Math.min(candidates.length, MAX_VISIBLE_MENTIONS);
2201
+ if (candidates.length === 0) {
2202
+ return /* @__PURE__ */ jsxDEV17("box", {
2203
+ paddingX: 1,
2204
+ children: /* @__PURE__ */ jsxDEV17("text", {
2205
+ attributes: TextAttributes6.DIM,
2206
+ children: "No matching files or folders"
2207
+ }, undefined, false, undefined, this)
2208
+ }, undefined, false, undefined, this);
2209
+ }
2210
+ return /* @__PURE__ */ jsxDEV17("scrollbox", {
2211
+ ref: scrollRef,
2212
+ height: visibleHeight,
2213
+ children: candidates.map((candidate, index) => {
2214
+ const isSelected = index === selectedIndex;
2215
+ return /* @__PURE__ */ jsxDEV17("box", {
2216
+ flexDirection: "row",
2217
+ paddingX: 1,
2218
+ height: 1,
2219
+ overflow: "hidden",
2220
+ backgroundColor: isSelected ? colors.selection : undefined,
2221
+ onMouseMove: () => onSelect(index),
2222
+ onMouseDown: () => onExecute(index),
2223
+ children: [
2224
+ /* @__PURE__ */ jsxDEV17("box", {
2225
+ flexGrow: 1,
2226
+ flexShrink: 1,
2227
+ overflow: "hidden",
2228
+ children: /* @__PURE__ */ jsxDEV17("text", {
2229
+ selectable: false,
2230
+ fg: isSelected ? "black" : "white",
2231
+ children: candidate.path
2232
+ }, undefined, false, undefined, this)
2233
+ }, undefined, false, undefined, this),
2234
+ /* @__PURE__ */ jsxDEV17("box", {
2235
+ width: 8,
2236
+ alignItems: "flex-end",
2237
+ flexShrink: 0,
2238
+ children: /* @__PURE__ */ jsxDEV17("text", {
2239
+ selectable: false,
2240
+ fg: isSelected ? "black" : "gray",
2241
+ children: candidate.kind === "directory" ? "Folder" : "File"
2242
+ }, undefined, false, undefined, this)
2243
+ }, undefined, false, undefined, this)
2244
+ ]
2245
+ }, candidate.path, true, undefined, this);
2246
+ })
2247
+ }, undefined, false, undefined, this);
2248
+ }
2249
+ var TEXTAREA_KEY_BINDINGS = [
2250
+ { name: "return", ctrl: true, action: "newline" },
2251
+ { name: "enter", ctrl: true, action: "newline" },
2252
+ { name: "return", action: "submit" },
2253
+ { name: "enter", action: "submit" }
2254
+ ];
2255
+ function InputBar({ onSubmit, disabled = false }) {
2256
+ const textareaRef = useRef6(null);
2257
+ const onSubmitRef = useRef6(() => {});
2258
+ const renderer = useRenderer2();
2259
+ const toast = useToast();
2260
+ const dialog = useDialog();
2261
+ const { isTopLayer, setResponder, push, pop } = useKeyboardLayer();
2262
+ const { colors } = useTheme();
2263
+ const navigate = useNavigate2();
2264
+ const activeMentionRef = useRef6(null);
2265
+ const mentionScrollRef = useRef6(null);
2266
+ const [activeMention, setActiveMention] = useState9(null);
2267
+ const [mentionCandidates, setMentionCandidates] = useState9([]);
2268
+ const [mentionSelectedIndex, setMentionSelectedIndex] = useState9(0);
2269
+ const { mode, setMode, setModel, toggleMode } = usePromptConfig();
2270
+ const {
2271
+ showCommandMenu,
2272
+ commandQuery,
2273
+ selectedIndex,
2274
+ scrollRef,
2275
+ handleContentChange,
2276
+ resolveCommand,
2277
+ setSelectedIndex
2278
+ } = useCommandMenu();
2279
+ const showMentionMenu = activeMention !== null;
2280
+ const closeMentionMenu = useCallback11(() => {
2281
+ activeMentionRef.current = null;
2282
+ setActiveMention(null);
2283
+ setMentionCandidates([]);
2284
+ pop("mention");
2285
+ }, [pop]);
2286
+ const syncMentionMenu = useCallback11((text, cursorOffset) => {
2287
+ const nextMention = findActiveMention(text, cursorOffset);
2288
+ const previousMention = activeMentionRef.current;
2289
+ const mentionChanged = previousMention?.start !== nextMention?.start || previousMention?.end !== nextMention?.end || previousMention?.query !== nextMention?.query;
2290
+ if (!nextMention) {
2291
+ if (previousMention) {
2292
+ closeMentionMenu();
2293
+ }
2294
+ return;
2295
+ }
2296
+ activeMentionRef.current = nextMention;
2297
+ setActiveMention(nextMention);
2298
+ push("mention", () => {
2299
+ closeMentionMenu();
2300
+ return true;
2301
+ });
2302
+ if (mentionChanged) {
2303
+ setMentionSelectedIndex(0);
2304
+ mentionScrollRef.current?.scrollTo(0);
2305
+ }
2306
+ }, [closeMentionMenu, push]);
2307
+ const handleTextareaContentChange = useCallback11(() => {
2308
+ const textarea = textareaRef.current;
2309
+ if (!textarea)
2310
+ return;
2311
+ const text = textarea.plainText;
2312
+ handleContentChange(textarea.plainText);
2313
+ syncMentionMenu(text, textarea.cursorOffset);
2314
+ }, [handleContentChange, syncMentionMenu]);
2315
+ const handleSubmit = useCallback11(() => {
2316
+ if (disabled)
2317
+ return;
2318
+ const textarea = textareaRef.current;
2319
+ if (!textarea)
2320
+ return;
2321
+ const text = textarea.plainText.trim();
2322
+ if (text.length === 0)
2323
+ return;
2324
+ onSubmit(text);
2325
+ textarea.setText("");
2326
+ }, [disabled, onSubmit]);
2327
+ const handleMentionExecute = useCallback11((index) => {
2328
+ const textarea = textareaRef.current;
2329
+ const mention = activeMentionRef.current;
2330
+ const candidate = mentionCandidates[index];
2331
+ if (!textarea || !mention || !candidate)
2332
+ return;
2333
+ const insertion = candidate.kind === "directory" ? candidate.path : `${candidate.path} `;
2334
+ const nextText = `${textarea.plainText.slice(0, mention.start)}@${insertion}${textarea.plainText.slice(mention.end)}`;
2335
+ textarea.replaceText(nextText);
2336
+ textarea.cursorOffset = mention.start + insertion.length + 1;
2337
+ syncMentionMenu(nextText, textarea.cursorOffset);
2338
+ }, [mentionCandidates, syncMentionMenu]);
2339
+ const handleTextareaCursorChange = useCallback11(() => {
2340
+ const textarea = textareaRef.current;
2341
+ if (!textarea)
2342
+ return;
2343
+ syncMentionMenu(textarea.plainText, textarea.cursorOffset);
2344
+ }, [syncMentionMenu]);
2345
+ const handleCommand = useCallback11((command) => {
2346
+ const textarea = textareaRef.current;
2347
+ if (!textarea || !command)
2348
+ return;
2349
+ textarea.setText("");
2350
+ if (command.action) {
2351
+ command.action({
2352
+ exit: () => renderer.destroy(),
2353
+ toast,
2354
+ dialog,
2355
+ navigate,
2356
+ mode,
2357
+ setMode,
2358
+ setModel
2359
+ });
2360
+ } else {
2361
+ textarea.insertText(command.value + " ");
2362
+ }
2363
+ }, [renderer, toast, dialog, navigate, Mode, setMode, setModel]);
2364
+ const handleCommandExecute = useCallback11((index) => {
2365
+ const command = resolveCommand(index);
2366
+ handleCommand(command);
2367
+ }, [resolveCommand, handleCommand]);
2368
+ useEffect3(() => {
2369
+ if (!activeMention) {
2370
+ setMentionCandidates([]);
2371
+ return;
2372
+ }
2373
+ let ignore = false;
2374
+ const loadCandidates = async () => {
2375
+ const nextCandidates = await getMentionCandidates(activeMention.query);
2376
+ if (ignore)
2377
+ return;
2378
+ setMentionCandidates(nextCandidates);
2379
+ setMentionSelectedIndex((currentIndex) => {
2380
+ if (nextCandidates.length === 0) {
2381
+ return 0;
2382
+ }
2383
+ return Math.min(currentIndex, nextCandidates.length - 1);
2384
+ });
2385
+ };
2386
+ loadCandidates();
2387
+ return () => {
2388
+ ignore = true;
2389
+ };
2390
+ }, [activeMention]);
2391
+ useEffect3(() => {
2392
+ const textarea = textareaRef.current;
2393
+ if (!textarea)
2394
+ return;
2395
+ textarea.onSubmit = () => {
2396
+ onSubmitRef.current();
2397
+ };
2398
+ }, []);
2399
+ onSubmitRef.current = () => {
2400
+ if (disabled)
2401
+ return;
2402
+ if (showCommandMenu) {
2403
+ const command = resolveCommand(selectedIndex);
2404
+ handleCommand(command);
2405
+ return;
2406
+ }
2407
+ if (showMentionMenu) {
2408
+ const candidate = mentionCandidates[mentionSelectedIndex];
2409
+ if (candidate) {
2410
+ handleMentionExecute(mentionSelectedIndex);
2411
+ return;
2412
+ }
2413
+ }
2414
+ handleSubmit();
2415
+ };
2416
+ useKeyboard5((key) => {
2417
+ if (disabled) {
2418
+ return;
2419
+ }
2420
+ if (!isTopLayer("base")) {
2421
+ return;
2422
+ }
2423
+ if (key.name === "tab") {
2424
+ key.preventDefault();
2425
+ toggleMode();
2426
+ }
2427
+ });
2428
+ useEffect3(() => {
2429
+ setResponder("base", () => {
2430
+ if (disabled)
2431
+ return false;
2432
+ const textarea = textareaRef.current;
2433
+ if (textarea && textarea.plainText.length > 0) {
2434
+ textarea.setText("");
2435
+ return true;
2436
+ }
2437
+ return false;
2438
+ });
2439
+ return () => setResponder("base", null);
2440
+ }, [disabled, setResponder]);
2441
+ useKeyboard5((key) => {
2442
+ if (disabled)
2443
+ return;
2444
+ if (!showMentionMenu || !isTopLayer("mention"))
2445
+ return;
2446
+ if (key.name === "escape") {
2447
+ key.preventDefault();
2448
+ closeMentionMenu();
2449
+ } else if (key.name === "up") {
2450
+ key.preventDefault();
2451
+ setMentionSelectedIndex((currentIndex) => {
2452
+ const nextIndex = Math.max(0, currentIndex - 1);
2453
+ const scrollbox = mentionScrollRef.current;
2454
+ if (scrollbox && nextIndex < scrollbox.scrollTop) {
2455
+ scrollbox.scrollTo(nextIndex);
2456
+ }
2457
+ return nextIndex;
2458
+ });
2459
+ } else if (key.name === "down") {
2460
+ key.preventDefault();
2461
+ setMentionSelectedIndex((currentIndex) => {
2462
+ if (mentionCandidates.length === 0) {
2463
+ return 0;
2464
+ }
2465
+ const nextIndex = Math.min(mentionCandidates.length - 1, currentIndex + 1);
2466
+ const scrollbox = mentionScrollRef.current;
2467
+ if (scrollbox) {
2468
+ const viewportHeight = scrollbox.viewport.height;
2469
+ const visibleEnd = scrollbox.scrollTop + viewportHeight - 1;
2470
+ if (nextIndex > visibleEnd) {
2471
+ scrollbox.scrollTo(nextIndex - viewportHeight + 1);
2472
+ }
2473
+ }
2474
+ return nextIndex;
2475
+ });
2476
+ }
2477
+ });
2478
+ return /* @__PURE__ */ jsxDEV17("box", {
2479
+ width: "100%",
2480
+ alignItems: "stretch",
2481
+ children: /* @__PURE__ */ jsxDEV17("box", {
2482
+ width: "100%",
2483
+ border: ["left"],
2484
+ borderColor: mode === Mode.BUILD ? colors.primary : colors.planMode,
2485
+ children: /* @__PURE__ */ jsxDEV17("box", {
2486
+ position: "relative",
2487
+ justifyContent: "center",
2488
+ width: "100%",
2489
+ paddingX: 2,
2490
+ paddingY: 1,
2491
+ backgroundColor: colors.surface,
2492
+ gap: 1,
2493
+ children: [
2494
+ showCommandMenu && /* @__PURE__ */ jsxDEV17("box", {
2495
+ position: "absolute",
2496
+ bottom: "100%",
2497
+ left: 0,
2498
+ width: "100%",
2499
+ backgroundColor: colors.surface,
2500
+ zIndex: 10,
2501
+ children: /* @__PURE__ */ jsxDEV17(CommandMenu, {
2502
+ query: commandQuery,
2503
+ selectedIndex,
2504
+ scrollRef,
2505
+ onSelect: setSelectedIndex,
2506
+ onExecute: handleCommandExecute
2507
+ }, undefined, false, undefined, this)
2508
+ }, undefined, false, undefined, this),
2509
+ !showCommandMenu && showMentionMenu && /* @__PURE__ */ jsxDEV17("box", {
2510
+ position: "absolute",
2511
+ bottom: "100%",
2512
+ left: 0,
2513
+ width: "100%",
2514
+ backgroundColor: colors.surface,
2515
+ zIndex: 10,
2516
+ children: /* @__PURE__ */ jsxDEV17(FileMentionMenu, {
2517
+ candidates: mentionCandidates,
2518
+ selectedIndex: mentionSelectedIndex,
2519
+ scrollRef: mentionScrollRef,
2520
+ onSelect: setMentionSelectedIndex,
2521
+ onExecute: handleMentionExecute
2522
+ }, undefined, false, undefined, this)
2523
+ }, undefined, false, undefined, this),
2524
+ /* @__PURE__ */ jsxDEV17("textarea", {
2525
+ ref: textareaRef,
2526
+ focused: !disabled && (isTopLayer("base") || isTopLayer("command") || isTopLayer("mention")),
2527
+ keyBindings: TEXTAREA_KEY_BINDINGS,
2528
+ onContentChange: handleTextareaContentChange,
2529
+ placeholder: `Ask anything ... "Fix this bug for me!!"`
2530
+ }, undefined, false, undefined, this),
2531
+ /* @__PURE__ */ jsxDEV17(StatusBar, {}, undefined, false, undefined, this)
2532
+ ]
2533
+ }, undefined, true, undefined, this)
2534
+ }, undefined, false, undefined, this)
2535
+ }, undefined, false, undefined, this);
2536
+ }
2537
+
2538
+ // src/screens/home.tsx
2539
+ import { useCallback as useCallback12 } from "react";
2540
+ import { useNavigate as useNavigate3 } from "react-router";
2541
+ import { TextAttributes as TextAttributes7 } from "@opentui/core";
2542
+ import { jsxDEV as jsxDEV18 } from "@opentui/react/jsx-dev-runtime";
2543
+ function Home() {
2544
+ const navigate = useNavigate3();
2545
+ const { mode, model } = usePromptConfig();
2546
+ const handleSubmit = useCallback12((text) => {
2547
+ navigate("/sessions/new", { state: { message: text, mode, model } });
2548
+ }, [navigate, mode, model]);
2549
+ return /* @__PURE__ */ jsxDEV18("box", {
2550
+ alignItems: "center",
2551
+ justifyContent: "center",
2552
+ flexGrow: 1,
2553
+ gap: 2,
2554
+ position: "relative",
2555
+ width: "100%",
2556
+ height: "100%",
2557
+ children: [
2558
+ /* @__PURE__ */ jsxDEV18(Header, {}, undefined, false, undefined, this),
2559
+ /* @__PURE__ */ jsxDEV18("box", {
2560
+ width: "100%",
2561
+ maxWidth: 78,
2562
+ paddingX: 2,
2563
+ flexDirection: "column",
2564
+ gap: 1,
2565
+ children: [
2566
+ /* @__PURE__ */ jsxDEV18(InputBar, {
2567
+ onSubmit: handleSubmit
2568
+ }, undefined, false, undefined, this),
2569
+ /* @__PURE__ */ jsxDEV18("box", {
2570
+ flexDirection: "row",
2571
+ gap: 1,
2572
+ flexShrink: 0,
2573
+ marginLeft: "auto",
2574
+ children: [
2575
+ /* @__PURE__ */ jsxDEV18("text", {
2576
+ children: "tab"
2577
+ }, undefined, false, undefined, this),
2578
+ /* @__PURE__ */ jsxDEV18("text", {
2579
+ attributes: TextAttributes7.DIM,
2580
+ children: "agents"
2581
+ }, undefined, false, undefined, this)
2582
+ ]
2583
+ }, undefined, true, undefined, this)
2584
+ ]
2585
+ }, undefined, true, undefined, this)
2586
+ ]
2587
+ }, undefined, true, undefined, this);
2588
+ }
2589
+
2590
+ // src/screens/docs.tsx
2591
+ import { useNavigate as useNavigate4 } from "react-router";
2592
+ import { jsxDEV as jsxDEV19 } from "@opentui/react/jsx-dev-runtime";
2593
+ function Docs() {
2594
+ const navigate = useNavigate4();
2595
+ return /* @__PURE__ */ jsxDEV19("box", {
2596
+ flexDirection: "column",
2597
+ alignItems: "center",
2598
+ width: "100%",
2599
+ height: "100%",
2600
+ children: /* @__PURE__ */ jsxDEV19("box", {
2601
+ width: "100%",
2602
+ flexDirection: "column",
2603
+ position: "relative",
2604
+ children: [
2605
+ /* @__PURE__ */ jsxDEV19("scrollbox", {
2606
+ children: [
2607
+ /* @__PURE__ */ jsxDEV19("box", {
2608
+ paddingTop: 4,
2609
+ children: /* @__PURE__ */ jsxDEV19(Header, {}, undefined, false, undefined, this)
2610
+ }, undefined, false, undefined, this),
2611
+ /* @__PURE__ */ jsxDEV19("box", {
2612
+ paddingX: 2,
2613
+ marginBottom: 2,
2614
+ marginTop: 2,
2615
+ alignItems: "center",
2616
+ justifyContent: "center",
2617
+ flexDirection: "column",
2618
+ children: /* @__PURE__ */ jsxDEV19("text", {
2619
+ minWidth: 64,
2620
+ children: [
2621
+ /* @__PURE__ */ jsxDEV19("b", {
2622
+ children: [
2623
+ "Project Overview",
2624
+ `
2625
+
2626
+ `
2627
+ ]
2628
+ }, undefined, true, undefined, this),
2629
+ /* @__PURE__ */ jsxDEV19("span", {
2630
+ fg: "cyan",
2631
+ children: "UniKode "
2632
+ }, undefined, false, undefined, this),
2633
+ /* @__PURE__ */ jsxDEV19("span", {
2634
+ fg: "gray",
2635
+ children: [
2636
+ "is a terminal-first AI coding assistant",
2637
+ `
2638
+ `
2639
+ ]
2640
+ }, undefined, true, undefined, this),
2641
+ /* @__PURE__ */ jsxDEV19("span", {
2642
+ fg: "gray",
2643
+ children: [
2644
+ "built with Bun, TypeScript, OpenTUI and React.",
2645
+ `
2646
+
2647
+ `
2648
+ ]
2649
+ }, undefined, true, undefined, this),
2650
+ /* @__PURE__ */ jsxDEV19("span", {
2651
+ fg: "gray",
2652
+ children: [
2653
+ "The project includes four packages:",
2654
+ `
2655
+ `
2656
+ ]
2657
+ }, undefined, true, undefined, this),
2658
+ /* @__PURE__ */ jsxDEV19("span", {
2659
+ fg: "gray",
2660
+ children: "\u2022 "
2661
+ }, undefined, false, undefined, this),
2662
+ /* @__PURE__ */ jsxDEV19("span", {
2663
+ fg: "cyan",
2664
+ children: "cli "
2665
+ }, undefined, false, undefined, this),
2666
+ /* @__PURE__ */ jsxDEV19("span", {
2667
+ fg: "gray",
2668
+ children: [
2669
+ "- Terminal UI client",
2670
+ `
2671
+ `
2672
+ ]
2673
+ }, undefined, true, undefined, this),
2674
+ /* @__PURE__ */ jsxDEV19("span", {
2675
+ fg: "gray",
2676
+ children: "\u2022 "
2677
+ }, undefined, false, undefined, this),
2678
+ /* @__PURE__ */ jsxDEV19("span", {
2679
+ fg: "cyan",
2680
+ children: "sdk "
2681
+ }, undefined, false, undefined, this),
2682
+ /* @__PURE__ */ jsxDEV19("span", {
2683
+ fg: "gray",
2684
+ children: [
2685
+ "- Core logic and API integration",
2686
+ `
2687
+ `
2688
+ ]
2689
+ }, undefined, true, undefined, this),
2690
+ /* @__PURE__ */ jsxDEV19("span", {
2691
+ fg: "gray",
2692
+ children: "\u2022 "
2693
+ }, undefined, false, undefined, this),
2694
+ /* @__PURE__ */ jsxDEV19("span", {
2695
+ fg: "cyan",
2696
+ children: "server "
2697
+ }, undefined, false, undefined, this),
2698
+ /* @__PURE__ */ jsxDEV19("span", {
2699
+ fg: "gray",
2700
+ children: [
2701
+ "- Relay server for API requests",
2702
+ `
2703
+ `
2704
+ ]
2705
+ }, undefined, true, undefined, this),
2706
+ /* @__PURE__ */ jsxDEV19("span", {
2707
+ fg: "gray",
2708
+ children: "\u2022 "
2709
+ }, undefined, false, undefined, this),
2710
+ /* @__PURE__ */ jsxDEV19("span", {
2711
+ fg: "cyan",
2712
+ children: "shared "
2713
+ }, undefined, false, undefined, this),
2714
+ /* @__PURE__ */ jsxDEV19("span", {
2715
+ fg: "gray",
2716
+ children: [
2717
+ "- Shared types and utilities",
2718
+ `
2719
+
2720
+ `
2721
+ ]
2722
+ }, undefined, true, undefined, this),
2723
+ /* @__PURE__ */ jsxDEV19("span", {
2724
+ fg: "gray",
2725
+ children: "It provides access to 20+ AI models through multiple providers."
2726
+ }, undefined, false, undefined, this)
2727
+ ]
2728
+ }, undefined, true, undefined, this)
2729
+ }, undefined, false, undefined, this),
2730
+ /* @__PURE__ */ jsxDEV19("box", {
2731
+ paddingX: 2,
2732
+ marginBottom: 2,
2733
+ alignItems: "center",
2734
+ justifyContent: "center",
2735
+ flexDirection: "column",
2736
+ children: /* @__PURE__ */ jsxDEV19("text", {
2737
+ minWidth: 64,
2738
+ children: [
2739
+ /* @__PURE__ */ jsxDEV19("b", {
2740
+ children: [
2741
+ "Important Information",
2742
+ `
2743
+
2744
+ `
2745
+ ]
2746
+ }, undefined, true, undefined, this),
2747
+ /* @__PURE__ */ jsxDEV19("b", {
2748
+ children: [
2749
+ "Session Persistence",
2750
+ `
2751
+ `
2752
+ ]
2753
+ }, undefined, true, undefined, this),
2754
+ /* @__PURE__ */ jsxDEV19("span", {
2755
+ fg: "gray",
2756
+ children: [
2757
+ "\u2022 All chat sessions are automatically saved to PostgreSQL",
2758
+ `
2759
+ `
2760
+ ]
2761
+ }, undefined, true, undefined, this),
2762
+ /* @__PURE__ */ jsxDEV19("span", {
2763
+ fg: "gray",
2764
+ children: [
2765
+ "\u2022 Previous sessions can be resumed instantly",
2766
+ `
2767
+
2768
+ `
2769
+ ]
2770
+ }, undefined, true, undefined, this),
2771
+ /* @__PURE__ */ jsxDEV19("span", {
2772
+ fg: "gray",
2773
+ children: [
2774
+ "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
2775
+ `
2776
+
2777
+ `
2778
+ ]
2779
+ }, undefined, true, undefined, this),
2780
+ /* @__PURE__ */ jsxDEV19("b", {
2781
+ children: [
2782
+ "Keyboard Shortcuts",
2783
+ `
2784
+
2785
+ `
2786
+ ]
2787
+ }, undefined, true, undefined, this),
2788
+ /* @__PURE__ */ jsxDEV19("span", {
2789
+ fg: "cyan",
2790
+ children: "Ctrl + C "
2791
+ }, undefined, false, undefined, this),
2792
+ /* @__PURE__ */ jsxDEV19("span", {
2793
+ fg: "gray",
2794
+ children: "\u2022 Cancel current request"
2795
+ }, undefined, false, undefined, this)
2796
+ ]
2797
+ }, undefined, true, undefined, this)
2798
+ }, undefined, false, undefined, this),
2799
+ /* @__PURE__ */ jsxDEV19("box", {
2800
+ paddingX: 2,
2801
+ marginBottom: 2,
2802
+ alignItems: "center",
2803
+ justifyContent: "center",
2804
+ flexDirection: "column",
2805
+ children: /* @__PURE__ */ jsxDEV19("text", {
2806
+ minWidth: 64,
2807
+ children: [
2808
+ /* @__PURE__ */ jsxDEV19("b", {
2809
+ children: [
2810
+ "Available Commands",
2811
+ `
2812
+
2813
+ `
2814
+ ]
2815
+ }, undefined, true, undefined, this),
2816
+ /* @__PURE__ */ jsxDEV19("span", {
2817
+ fg: "cyan",
2818
+ children: "/new "
2819
+ }, undefined, false, undefined, this),
2820
+ /* @__PURE__ */ jsxDEV19("span", {
2821
+ fg: "gray",
2822
+ children: [
2823
+ "- start a new conversation",
2824
+ `
2825
+ `
2826
+ ]
2827
+ }, undefined, true, undefined, this),
2828
+ /* @__PURE__ */ jsxDEV19("span", {
2829
+ fg: "cyan",
2830
+ children: "/history "
2831
+ }, undefined, false, undefined, this),
2832
+ /* @__PURE__ */ jsxDEV19("span", {
2833
+ fg: "gray",
2834
+ children: [
2835
+ "- view conversation history",
2836
+ `
2837
+ `
2838
+ ]
2839
+ }, undefined, true, undefined, this),
2840
+ /* @__PURE__ */ jsxDEV19("span", {
2841
+ fg: "cyan",
2842
+ children: "/model "
2843
+ }, undefined, false, undefined, this),
2844
+ /* @__PURE__ */ jsxDEV19("span", {
2845
+ fg: "gray",
2846
+ children: [
2847
+ "- switch AI model",
2848
+ `
2849
+ `
2850
+ ]
2851
+ }, undefined, true, undefined, this),
2852
+ /* @__PURE__ */ jsxDEV19("span", {
2853
+ fg: "cyan",
2854
+ children: "/clear "
2855
+ }, undefined, false, undefined, this),
2856
+ /* @__PURE__ */ jsxDEV19("span", {
2857
+ fg: "gray",
2858
+ children: [
2859
+ "- clear current screen",
2860
+ `
2861
+ `
2862
+ ]
2863
+ }, undefined, true, undefined, this),
2864
+ /* @__PURE__ */ jsxDEV19("span", {
2865
+ fg: "cyan",
2866
+ children: "/help "
2867
+ }, undefined, false, undefined, this),
2868
+ /* @__PURE__ */ jsxDEV19("span", {
2869
+ fg: "gray",
2870
+ children: [
2871
+ "- show this help message",
2872
+ `
2873
+ `
2874
+ ]
2875
+ }, undefined, true, undefined, this),
2876
+ /* @__PURE__ */ jsxDEV19("span", {
2877
+ fg: "cyan",
2878
+ children: "/exit "
2879
+ }, undefined, false, undefined, this),
2880
+ /* @__PURE__ */ jsxDEV19("span", {
2881
+ fg: "gray",
2882
+ children: "- exit the application"
2883
+ }, undefined, false, undefined, this)
2884
+ ]
2885
+ }, undefined, true, undefined, this)
2886
+ }, undefined, false, undefined, this)
2887
+ ]
2888
+ }, undefined, true, undefined, this),
2889
+ /* @__PURE__ */ jsxDEV19("box", {
2890
+ width: "100%",
2891
+ paddingX: 2,
2892
+ flexDirection: "column",
2893
+ gap: 1,
2894
+ flexShrink: 0,
2895
+ marginBottom: 1,
2896
+ alignItems: "center",
2897
+ justifyContent: "center",
2898
+ paddingBottom: 2,
2899
+ children: /* @__PURE__ */ jsxDEV19("box", {
2900
+ alignItems: "center",
2901
+ justifyContent: "center",
2902
+ flexDirection: "column",
2903
+ flexGrow: 1,
2904
+ children: /* @__PURE__ */ jsxDEV19("text", {
2905
+ fg: "yellow",
2906
+ onMouseDown: () => navigate("/"),
2907
+ children: /* @__PURE__ */ jsxDEV19("strong", {
2908
+ children: "\u2190 Back"
2909
+ }, undefined, false, undefined, this)
2910
+ }, undefined, false, undefined, this)
2911
+ }, undefined, false, undefined, this)
2912
+ }, undefined, false, undefined, this)
2913
+ ]
2914
+ }, undefined, true, undefined, this)
2915
+ }, undefined, false, undefined, this);
2916
+ }
2917
+
2918
+ // src/screens/new-session.tsx
2919
+ import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef8 } from "react";
2920
+ import { z as z2 } from "zod";
2921
+ import { useNavigate as useNavigate5, useLocation } from "react-router";
2922
+
2923
+ // src/components/session-shell.tsx
2924
+ import { TextAttributes as TextAttributes8 } from "@opentui/core";
2925
+
2926
+ // src/components/spinner.tsx
2927
+ import"opentui-spinner/react";
2928
+ import { jsxDEV as jsxDEV20 } from "@opentui/react/jsx-dev-runtime";
2929
+ function Spinner({ mode = Mode.BUILD }) {
2930
+ const { colors } = useTheme();
2931
+ const activeColor = mode === Mode.PLAN ? colors.planMode : colors.primary;
2932
+ return /* @__PURE__ */ jsxDEV20("spinner", {
2933
+ name: "aesthetic",
2934
+ color: activeColor
2935
+ }, undefined, false, undefined, this);
2936
+ }
2937
+
2938
+ // src/components/session-shell.tsx
2939
+ import { jsxDEV as jsxDEV21, Fragment as Fragment2 } from "@opentui/react/jsx-dev-runtime";
2940
+ function SessionShell({
2941
+ children,
2942
+ onSubmit,
2943
+ inputDisabled = false,
2944
+ loading = false,
2945
+ interruptible = false
2946
+ }) {
2947
+ const { mode } = usePromptConfig();
2948
+ return /* @__PURE__ */ jsxDEV21("box", {
2949
+ flexDirection: "column",
2950
+ flexGrow: 1,
2951
+ width: "100%",
2952
+ height: "100%",
2953
+ paddingY: 1,
2954
+ paddingX: 2,
2955
+ gap: 1,
2956
+ children: [
2957
+ /* @__PURE__ */ jsxDEV21("scrollbox", {
2958
+ flexGrow: 1,
2959
+ width: "100%",
2960
+ stickyScroll: true,
2961
+ stickyStart: "bottom",
2962
+ children: /* @__PURE__ */ jsxDEV21("box", {
2963
+ children
2964
+ }, undefined, false, undefined, this)
2965
+ }, undefined, false, undefined, this),
2966
+ /* @__PURE__ */ jsxDEV21("box", {
2967
+ flexShrink: 0,
2968
+ children: /* @__PURE__ */ jsxDEV21(InputBar, {
2969
+ onSubmit,
2970
+ disabled: inputDisabled
2971
+ }, undefined, false, undefined, this)
2972
+ }, undefined, false, undefined, this),
2973
+ /* @__PURE__ */ jsxDEV21("box", {
2974
+ flexShrink: 0,
2975
+ flexDirection: "row",
2976
+ justifyContent: "space-between",
2977
+ width: "100%",
2978
+ height: 1,
2979
+ gap: 2,
2980
+ paddingLeft: 1,
2981
+ children: [
2982
+ /* @__PURE__ */ jsxDEV21("box", {
2983
+ flexDirection: "row",
2984
+ alignItems: "center",
2985
+ gap: 2,
2986
+ children: loading ? /* @__PURE__ */ jsxDEV21(Fragment2, {
2987
+ children: [
2988
+ /* @__PURE__ */ jsxDEV21(Spinner, {
2989
+ mode
2990
+ }, undefined, false, undefined, this),
2991
+ interruptible ? /* @__PURE__ */ jsxDEV21("text", {
2992
+ children: "esc to interrupt"
2993
+ }, undefined, false, undefined, this) : null
2994
+ ]
2995
+ }, undefined, true, undefined, this) : null
2996
+ }, undefined, false, undefined, this),
2997
+ /* @__PURE__ */ jsxDEV21("box", {
2998
+ flexDirection: "row",
2999
+ gap: 1,
3000
+ flexShrink: 0,
3001
+ marginLeft: "auto",
3002
+ children: [
3003
+ /* @__PURE__ */ jsxDEV21("text", {
3004
+ children: "tab"
3005
+ }, undefined, false, undefined, this),
3006
+ /* @__PURE__ */ jsxDEV21("text", {
3007
+ attributes: TextAttributes8.DIM,
3008
+ children: "agents"
3009
+ }, undefined, false, undefined, this)
3010
+ ]
3011
+ }, undefined, true, undefined, this)
3012
+ ]
3013
+ }, undefined, true, undefined, this)
3014
+ ]
3015
+ }, undefined, true, undefined, this);
3016
+ }
3017
+
3018
+ // src/components/messages/user-message.tsx
3019
+ import { jsxDEV as jsxDEV22 } from "@opentui/react/jsx-dev-runtime";
3020
+ function UserMessage({ message, mode }) {
3021
+ const { colors } = useTheme();
3022
+ return /* @__PURE__ */ jsxDEV22("box", {
3023
+ width: "100%",
3024
+ alignItems: "center",
3025
+ children: /* @__PURE__ */ jsxDEV22("box", {
3026
+ border: ["left"],
3027
+ borderColor: mode === Mode.PLAN ? colors.planMode : colors.primary,
3028
+ width: "100%",
3029
+ children: /* @__PURE__ */ jsxDEV22("box", {
3030
+ justifyContent: "center",
3031
+ paddingX: 2,
3032
+ paddingY: 1,
3033
+ backgroundColor: colors.surface,
3034
+ width: "100%",
3035
+ children: /* @__PURE__ */ jsxDEV22("text", {
3036
+ children: message
3037
+ }, undefined, false, undefined, this)
3038
+ }, undefined, false, undefined, this)
3039
+ }, undefined, false, undefined, this)
3040
+ }, undefined, false, undefined, this);
3041
+ }
3042
+ // src/components/messages/bot-message.tsx
3043
+ import prettyMs from "pretty-ms";
3044
+ import { TextAttributes as TextAttributes10 } from "@opentui/core";
3045
+
3046
+ // src/components/messages/question-message.tsx
3047
+ import { useCallback as useCallback13, useRef as useRef7, useState as useState10 } from "react";
3048
+ import { TextAttributes as TextAttributes9 } from "@opentui/core";
3049
+ import { jsxDEV as jsxDEV23 } from "@opentui/react/jsx-dev-runtime";
3050
+ function QuestionMessage({ prompt, onSubmit }) {
3051
+ const { colors } = useTheme();
3052
+ const inputRef = useRef7(null);
3053
+ const [answerText, setAnswerText] = useState10("");
3054
+ const handleSubmit = useCallback13(() => {
3055
+ if (!prompt.isActive) {
3056
+ return;
3057
+ }
3058
+ onSubmit(prompt.id, inputRef.current?.value ?? answerText);
3059
+ }, [answerText, onSubmit, prompt.id, prompt.isActive]);
3060
+ return /* @__PURE__ */ jsxDEV23("box", {
3061
+ width: "100%",
3062
+ alignItems: "center",
3063
+ children: /* @__PURE__ */ jsxDEV23("box", {
3064
+ border: ["left"],
3065
+ borderColor: colors.info,
3066
+ width: "100%",
3067
+ children: /* @__PURE__ */ jsxDEV23("box", {
3068
+ flexDirection: "column",
3069
+ gap: 1,
3070
+ paddingX: 2,
3071
+ paddingY: 1,
3072
+ backgroundColor: colors.surface,
3073
+ width: "100%",
3074
+ children: [
3075
+ /* @__PURE__ */ jsxDEV23("box", {
3076
+ flexDirection: "column",
3077
+ gap: 0,
3078
+ children: [
3079
+ /* @__PURE__ */ jsxDEV23("text", {
3080
+ attributes: TextAttributes9.BOLD,
3081
+ fg: colors.info,
3082
+ children: prompt.title ?? "Clarification request"
3083
+ }, undefined, false, undefined, this),
3084
+ prompt.context ? /* @__PURE__ */ jsxDEV23("text", {
3085
+ attributes: TextAttributes9.DIM,
3086
+ children: prompt.context
3087
+ }, undefined, false, undefined, this) : null
3088
+ ]
3089
+ }, undefined, true, undefined, this),
3090
+ /* @__PURE__ */ jsxDEV23("box", {
3091
+ flexDirection: "column",
3092
+ gap: 0,
3093
+ children: prompt.questions.map((question, index) => /* @__PURE__ */ jsxDEV23("text", {
3094
+ children: [
3095
+ index + 1,
3096
+ ". ",
3097
+ question
3098
+ ]
3099
+ }, `${prompt.id}-${index}`, true, undefined, this))
3100
+ }, undefined, false, undefined, this),
3101
+ /* @__PURE__ */ jsxDEV23("box", {
3102
+ border: ["left"],
3103
+ borderColor: prompt.isActive ? colors.primary : colors.dimSeparator,
3104
+ width: "100%",
3105
+ paddingX: 1,
3106
+ children: prompt.isActive ? /* @__PURE__ */ jsxDEV23("input", {
3107
+ ref: inputRef,
3108
+ focused: true,
3109
+ placeholder: "Type your answer and submit",
3110
+ onContentChange: () => {
3111
+ setAnswerText(inputRef.current?.value ?? "");
3112
+ }
3113
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV23("text", {
3114
+ attributes: TextAttributes9.DIM,
3115
+ children: "This prompt is no longer active"
3116
+ }, undefined, false, undefined, this)
3117
+ }, undefined, false, undefined, this),
3118
+ /* @__PURE__ */ jsxDEV23("box", {
3119
+ width: "100%",
3120
+ justifyContent: "flex-end",
3121
+ onMouseDown: handleSubmit,
3122
+ children: /* @__PURE__ */ jsxDEV23("text", {
3123
+ fg: prompt.isActive ? colors.success : colors.dimSeparator,
3124
+ children: "submit"
3125
+ }, undefined, false, undefined, this)
3126
+ }, undefined, false, undefined, this)
3127
+ ]
3128
+ }, undefined, true, undefined, this)
3129
+ }, undefined, false, undefined, this)
3130
+ }, undefined, false, undefined, this);
3131
+ }
3132
+
3133
+ // src/components/messages/bot-message.tsx
3134
+ import { jsxDEV as jsxDEV24, Fragment as Fragment3 } from "@opentui/react/jsx-dev-runtime";
3135
+ function formatToolName(name) {
3136
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
3137
+ }
3138
+ function isToolPart(part) {
3139
+ return part.type === "dynamic-tool" || part.type.startsWith("tool-");
3140
+ }
3141
+ function formatToolArgs(tc) {
3142
+ if (!("input" in tc) || tc.input == null)
3143
+ return "";
3144
+ if (typeof tc.input !== "object")
3145
+ return String(tc.input);
3146
+ return Object.values(tc.input).map(String).join(" ");
3147
+ }
3148
+ function groupConsecutiveParts(parts) {
3149
+ const groups = [];
3150
+ for (let i = 0;i < parts.length; i++) {
3151
+ const part = parts[i];
3152
+ const lastGroup = groups[groups.length - 1];
3153
+ if (lastGroup && lastGroup.type === part.type) {
3154
+ lastGroup.parts.push(part);
3155
+ } else {
3156
+ const key = isToolPart(part) ? `group-tc-${part.toolCallId}` : `group-${part.type}-${i}`;
3157
+ groups.push({ type: part.type, parts: [part], key });
3158
+ }
3159
+ }
3160
+ return groups;
3161
+ }
3162
+ function BotMessage({
3163
+ parts,
3164
+ model,
3165
+ mode,
3166
+ durationMs,
3167
+ streaming = false,
3168
+ questionPrompts = [],
3169
+ onSubmitQuestionAnswer
3170
+ }) {
3171
+ const { colors } = useTheme();
3172
+ return /* @__PURE__ */ jsxDEV24("box", {
3173
+ width: "100%",
3174
+ alignItems: "center",
3175
+ children: [
3176
+ groupConsecutiveParts(parts).map((group, i) => /* @__PURE__ */ jsxDEV24("box", {
3177
+ width: "100%",
3178
+ paddingTop: i === 0 ? 0 : 1,
3179
+ children: group.parts.map((part, j) => {
3180
+ if (part.type === "reasoning") {
3181
+ return /* @__PURE__ */ jsxDEV24("box", {
3182
+ border: ["left"],
3183
+ borderColor: colors.thinkingBorder,
3184
+ width: "100%",
3185
+ paddingX: 2,
3186
+ children: /* @__PURE__ */ jsxDEV24("text", {
3187
+ attributes: TextAttributes10.DIM,
3188
+ children: [
3189
+ /* @__PURE__ */ jsxDEV24("em", {
3190
+ fg: colors.thinking,
3191
+ children: "Thinking:"
3192
+ }, undefined, false, undefined, this),
3193
+ " ",
3194
+ part.text
3195
+ ]
3196
+ }, undefined, true, undefined, this)
3197
+ }, `reasoning-${j}`, false, undefined, this);
3198
+ }
3199
+ if (isToolPart(part)) {
3200
+ const toolName = part.type === "dynamic-tool" ? part.toolName : part.type.slice("tool-".length);
3201
+ const questionPrompt = toolName === "askQuestionTool" ? questionPrompts.find((prompt) => prompt.toolCallId === part.toolCallId) : undefined;
3202
+ if (questionPrompt) {
3203
+ return /* @__PURE__ */ jsxDEV24("box", {
3204
+ width: "100%",
3205
+ children: /* @__PURE__ */ jsxDEV24(QuestionMessage, {
3206
+ prompt: questionPrompt,
3207
+ onSubmit: onSubmitQuestionAnswer ?? (() => {})
3208
+ }, undefined, false, undefined, this)
3209
+ }, part.toolCallId, false, undefined, this);
3210
+ }
3211
+ return /* @__PURE__ */ jsxDEV24("box", {
3212
+ border: ["left"],
3213
+ borderColor: colors.thinkingBorder,
3214
+ width: "100%",
3215
+ paddingX: 2,
3216
+ children: /* @__PURE__ */ jsxDEV24("text", {
3217
+ attributes: TextAttributes10.DIM,
3218
+ children: [
3219
+ /* @__PURE__ */ jsxDEV24("em", {
3220
+ fg: colors.info,
3221
+ children: [
3222
+ formatToolName(toolName),
3223
+ ":"
3224
+ ]
3225
+ }, undefined, true, undefined, this),
3226
+ " ",
3227
+ formatToolArgs(part),
3228
+ part.state !== "output-available" && part.state !== "output-error" ? " \u2026" : "",
3229
+ part.state === "output-error" ? ` ${part.errorText}` : ""
3230
+ ]
3231
+ }, undefined, true, undefined, this)
3232
+ }, part.toolCallId, false, undefined, this);
3233
+ }
3234
+ if (part.type === "text") {
3235
+ return /* @__PURE__ */ jsxDEV24("box", {
3236
+ paddingX: 3,
3237
+ width: "100%",
3238
+ children: /* @__PURE__ */ jsxDEV24("text", {
3239
+ children: part.text
3240
+ }, undefined, false, undefined, this)
3241
+ }, `text-${j}`, false, undefined, this);
3242
+ }
3243
+ return null;
3244
+ })
3245
+ }, group.key, false, undefined, this)),
3246
+ /* @__PURE__ */ jsxDEV24("box", {
3247
+ paddingX: 3,
3248
+ paddingY: 1,
3249
+ gap: 1,
3250
+ width: "100%",
3251
+ children: /* @__PURE__ */ jsxDEV24("box", {
3252
+ flexDirection: "row",
3253
+ gap: 2,
3254
+ children: [
3255
+ /* @__PURE__ */ jsxDEV24("text", {
3256
+ fg: mode === Mode.PLAN ? colors.planMode : colors.primary,
3257
+ children: "\u25C9"
3258
+ }, undefined, false, undefined, this),
3259
+ /* @__PURE__ */ jsxDEV24("box", {
3260
+ flexDirection: "row",
3261
+ gap: 1,
3262
+ children: [
3263
+ /* @__PURE__ */ jsxDEV24("text", {
3264
+ children: mode
3265
+ }, undefined, false, undefined, this),
3266
+ /* @__PURE__ */ jsxDEV24("text", {
3267
+ attributes: TextAttributes10.DIM,
3268
+ fg: colors.dimSeparator,
3269
+ children: "\u203A"
3270
+ }, undefined, false, undefined, this),
3271
+ /* @__PURE__ */ jsxDEV24("text", {
3272
+ attributes: TextAttributes10.DIM,
3273
+ children: model
3274
+ }, undefined, false, undefined, this),
3275
+ durationMs != null ? /* @__PURE__ */ jsxDEV24(Fragment3, {
3276
+ children: [
3277
+ /* @__PURE__ */ jsxDEV24("text", {
3278
+ attributes: TextAttributes10.DIM,
3279
+ fg: colors.dimSeparator,
3280
+ children: "\u203A"
3281
+ }, undefined, false, undefined, this),
3282
+ /* @__PURE__ */ jsxDEV24("text", {
3283
+ attributes: TextAttributes10.DIM,
3284
+ children: prettyMs(durationMs)
3285
+ }, undefined, false, undefined, this)
3286
+ ]
3287
+ }, undefined, true, undefined, this) : null
3288
+ ]
3289
+ }, undefined, true, undefined, this)
3290
+ ]
3291
+ }, undefined, true, undefined, this)
3292
+ }, undefined, false, undefined, this)
3293
+ ]
3294
+ }, undefined, true, undefined, this);
3295
+ }
3296
+ // src/components/messages/error-message.tsx
3297
+ import { TextAttributes as TextAttributes11 } from "@opentui/core";
3298
+ import { jsxDEV as jsxDEV25 } from "@opentui/react/jsx-dev-runtime";
3299
+ function ErrorMessage({ message }) {
3300
+ const { colors } = useTheme();
3301
+ return /* @__PURE__ */ jsxDEV25("box", {
3302
+ width: "100%",
3303
+ alignItems: "center",
3304
+ children: /* @__PURE__ */ jsxDEV25("box", {
3305
+ border: ["left"],
3306
+ borderColor: colors.error,
3307
+ width: "100%",
3308
+ children: /* @__PURE__ */ jsxDEV25("box", {
3309
+ justifyContent: "center",
3310
+ paddingX: 2,
3311
+ paddingY: 1,
3312
+ backgroundColor: colors.surface,
3313
+ width: "100%",
3314
+ children: /* @__PURE__ */ jsxDEV25("text", {
3315
+ attributes: TextAttributes11.DIM,
3316
+ children: message
3317
+ }, undefined, false, undefined, this)
3318
+ }, undefined, false, undefined, this)
3319
+ }, undefined, false, undefined, this)
3320
+ }, undefined, false, undefined, this);
3321
+ }
3322
+ // src/screens/new-session.tsx
3323
+ import { jsxDEV as jsxDEV26 } from "@opentui/react/jsx-dev-runtime";
3324
+ var newSessionStateSchema = z2.object({
3325
+ message: z2.string(),
3326
+ mode: modeSchema,
3327
+ model: z2.string()
3328
+ });
3329
+ function NewSession() {
3330
+ const navigate = useNavigate5();
3331
+ const location = useLocation();
3332
+ const toast = useToast();
3333
+ const hasStartedRef = useRef8(false);
3334
+ const state = useMemo3(() => {
3335
+ const parsed = newSessionStateSchema.safeParse(location.state);
3336
+ return parsed.success ? parsed.data : null;
3337
+ }, [location.state]);
3338
+ useEffect4(() => {
3339
+ if (!state) {
3340
+ navigate("/", { replace: true });
3341
+ }
3342
+ }, [state, navigate]);
3343
+ useEffect4(() => {
3344
+ if (!state || hasStartedRef.current)
3345
+ return;
3346
+ hasStartedRef.current = true;
3347
+ let ignore = false;
3348
+ const createSession = async () => {
3349
+ try {
3350
+ const res = await apiClient.sessions.$post({
3351
+ json: {
3352
+ title: state.message.slice(0, 100)
3353
+ }
3354
+ });
3355
+ if (ignore)
3356
+ return;
3357
+ if (!res.ok) {
3358
+ throw new Error(await getErrorMessage(res));
3359
+ }
3360
+ const session = await res.json();
3361
+ navigate(`/sessions/${session.id}`, { replace: true, state: { session, initialPrompt: state } });
3362
+ } catch (error) {
3363
+ if (ignore)
3364
+ return;
3365
+ toast.show({
3366
+ variant: "error",
3367
+ message: error instanceof Error ? error.message : "Failed to create session"
3368
+ });
3369
+ navigate("/", { replace: true });
3370
+ }
3371
+ };
3372
+ createSession();
3373
+ return () => {
3374
+ ignore = true;
3375
+ };
3376
+ }, [state, navigate, toast]);
3377
+ if (!state)
3378
+ return null;
3379
+ return /* @__PURE__ */ jsxDEV26(SessionShell, {
3380
+ onSubmit: () => {},
3381
+ inputDisabled: true,
3382
+ loading: true,
3383
+ children: /* @__PURE__ */ jsxDEV26(UserMessage, {
3384
+ message: state.message,
3385
+ mode: state.mode
3386
+ }, undefined, false, undefined, this)
3387
+ }, undefined, false, undefined, this);
3388
+ }
3389
+
3390
+ // src/screens/session.tsx
3391
+ import { useState as useState12, useEffect as useEffect5, useMemo as useMemo5, useRef as useRef9 } from "react";
3392
+ import { useParams, useLocation as useLocation2, useNavigate as useNavigate6 } from "react-router";
3393
+ import { z as z3 } from "zod";
3394
+
3395
+ // src/hooks/use-chat.ts
3396
+ import { useCallback as useCallback14, useMemo as useMemo4, useState as useState11 } from "react";
3397
+ import { useChat as useAiChat } from "@ai-sdk/react";
3398
+ import {
3399
+ DefaultChatTransport,
3400
+ lastAssistantMessageIsCompleteWithToolCalls
3401
+ } from "ai";
3402
+
3403
+ // src/lib/local-tools.ts
3404
+ import { mkdir, readFile, readdir as readdir2, stat, writeFile } from "fs/promises";
3405
+ import { dirname, isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve2 } from "path";
3406
+ import { JSDOM } from "jsdom";
3407
+ import { Readability } from "@mozilla/readability";
3408
+ import TurndownService from "turndown";
3409
+ import fs from "fs/promises";
3410
+ import path from "path";
3411
+ import { createReadStream } from "fs";
3412
+ import readline from "readline";
3413
+ var MAX_FILE_SIZE = 1e4;
3414
+ var MAX_RESULTS = 200;
3415
+ var MAX_MATCHES = 50;
3416
+ var MAX_OUTPUT = 20000;
3417
+ var DEFAULT_TIMEOUT = 30000;
3418
+ var MAX_CONTENT_LENGTH = 15000;
3419
+ var FETCH_TIMEOUT_MS = 1e4;
3420
+ var turndownService = new TurndownService({
3421
+ headingStyle: "atx",
3422
+ codeBlockStyle: "fenced"
3423
+ });
3424
+ var editHistory = new Map;
3425
+ function resolveInsideCwd(path2) {
3426
+ const cwd = process.cwd();
3427
+ const resolved = resolve2(cwd, path2);
3428
+ const rel = relative2(cwd, resolved);
3429
+ if (rel.startsWith("..") || isAbsolute2(rel)) {
3430
+ throw new Error("Path is outside the project directory");
3431
+ }
3432
+ return { cwd, resolved };
3433
+ }
3434
+ function recordEdit(resolvedPath, oldString, newString) {
3435
+ const stack = editHistory.get(resolvedPath) ?? [];
3436
+ stack.push({ oldString, newString });
3437
+ editHistory.set(resolvedPath, stack);
3438
+ }
3439
+ function truncate(value, limit) {
3440
+ return value.length > limit ? `${value.slice(0, limit)}
3441
+ ... (truncated, ${value.length} total chars)` : value;
3442
+ }
3443
+ async function countLines(filePath) {
3444
+ return new Promise((resolve3, reject) => {
3445
+ let lines = 0;
3446
+ const stream = createReadStream(filePath);
3447
+ const rl = readline.createInterface({
3448
+ input: stream,
3449
+ crlfDelay: Infinity
3450
+ });
3451
+ rl.on("line", () => {
3452
+ lines++;
3453
+ });
3454
+ rl.on("close", () => {
3455
+ resolve3(lines);
3456
+ });
3457
+ rl.on("error", (err) => {
3458
+ reject(err);
3459
+ });
3460
+ });
3461
+ }
3462
+ async function executeLocalTool(toolName, input, mode) {
3463
+ if (mode === Mode.PLAN && !["readFile", "listDirectory", "glob", "grep", "webSearchTool", "fetchUrlTool", "viewOutline", "proposePlanTool", "getFileMetadata"].includes(toolName)) {
3464
+ throw new Error(`Tool ${toolName} is not available in PLAN mode`);
3465
+ } else if (mode === Mode.DOCUMENTATION && !["readFile", "listDirectory", "viewOutline", "getFileMetadata", "writeMarkdownFile", "glob", "grep"].includes(toolName)) {
3466
+ throw new Error(`Tool ${toolName} is not available in DOCUMENTATION mode`);
3467
+ }
3468
+ switch (toolName) {
3469
+ case "readFile": {
3470
+ const { path: path2 } = toolInputSchemas.readFile.parse(input);
3471
+ const { resolved } = resolveInsideCwd(path2);
3472
+ const content = await readFile(resolved, "utf-8");
3473
+ return content.length > MAX_FILE_SIZE ? { content: content.slice(0, MAX_FILE_SIZE), truncated: true, totalLength: content.length } : { content };
3474
+ }
3475
+ case "listDirectory": {
3476
+ const { path: path2 } = toolInputSchemas.listDirectory.parse(input);
3477
+ const { cwd, resolved } = resolveInsideCwd(path2);
3478
+ const entries = await readdir2(resolved);
3479
+ const results = [];
3480
+ for (const entry of entries) {
3481
+ if (entry.startsWith(".") || entry === "node_modules")
3482
+ continue;
3483
+ const info = await stat(join3(resolved, entry));
3484
+ results.push({ name: entry, type: info.isDirectory() ? "directory" : "file" });
3485
+ }
3486
+ results.sort((a, b) => a.type !== b.type ? a.type === "directory" ? -1 : 1 : a.name.localeCompare(b.name));
3487
+ return { path: relative2(cwd, resolved) || ".", entries: results };
3488
+ }
3489
+ case "glob": {
3490
+ const { pattern, path: path2 } = toolInputSchemas.glob.parse(input);
3491
+ const { cwd, resolved } = resolveInsideCwd(path2);
3492
+ const glob = new Bun.Glob(pattern);
3493
+ const files = [];
3494
+ let truncated = false;
3495
+ for await (const match of glob.scan({ cwd: resolved, dot: false, onlyFiles: true })) {
3496
+ if (match.includes("node_modules"))
3497
+ continue;
3498
+ if (files.length >= MAX_RESULTS) {
3499
+ truncated = true;
3500
+ break;
3501
+ }
3502
+ files.push(relative2(cwd, resolve2(resolved, match)));
3503
+ }
3504
+ files.sort();
3505
+ return { files, ...truncated ? { truncated: true } : {} };
3506
+ }
3507
+ case "grep": {
3508
+ const { pattern, path: path2, include } = toolInputSchemas.grep.parse(input);
3509
+ const { cwd, resolved } = resolveInsideCwd(path2);
3510
+ const args = [
3511
+ "-rn",
3512
+ "--color=never",
3513
+ "--exclude-dir=node_modules",
3514
+ "--exclude-dir=.git",
3515
+ "-E"
3516
+ ];
3517
+ if (include)
3518
+ args.push(`--include=${include}`);
3519
+ args.push(pattern, resolved);
3520
+ const proc = Bun.spawn(["grep", ...args], { cwd, stdout: "pipe", stderr: "pipe" });
3521
+ const [stdout, stderr] = await Promise.all([
3522
+ new Response(proc.stdout).text(),
3523
+ new Response(proc.stderr).text()
3524
+ ]);
3525
+ const exitCode = await proc.exited;
3526
+ if (exitCode !== 0 && exitCode !== 1)
3527
+ throw new Error(`grep failed: ${stderr.trim()}`);
3528
+ if (!stdout.trim())
3529
+ return { matches: [], message: "No matches found" };
3530
+ const lines = stdout.trim().split(`
3531
+ `);
3532
+ const matches = [];
3533
+ let truncated = false;
3534
+ for (const line of lines) {
3535
+ if (matches.length >= MAX_MATCHES) {
3536
+ truncated = true;
3537
+ break;
3538
+ }
3539
+ const match = line.match(/^(.+?):(\d+):(.*)$/);
3540
+ if (match) {
3541
+ matches.push({
3542
+ file: relative2(cwd, match[1]),
3543
+ line: Number(match[2]),
3544
+ content: match[3]
3545
+ });
3546
+ }
3547
+ }
3548
+ return { matches, ...truncated ? { truncated: true, totalMatches: lines.length } : {} };
3549
+ }
3550
+ case "writeFile": {
3551
+ const { path: path2, content } = toolInputSchemas.writeFile.parse(input);
3552
+ const { cwd, resolved } = resolveInsideCwd(path2);
3553
+ await mkdir(dirname(resolved), { recursive: true });
3554
+ await writeFile(resolved, content, "utf-8");
3555
+ return {
3556
+ success: true,
3557
+ path: relative2(cwd, resolved),
3558
+ bytesWritten: Buffer.byteLength(content, "utf-8")
3559
+ };
3560
+ }
3561
+ case "editFile": {
3562
+ const { path: path2, oldString, newString } = toolInputSchemas.editFile.parse(input);
3563
+ const { cwd, resolved } = resolveInsideCwd(path2);
3564
+ const content = await readFile(resolved, "utf-8");
3565
+ const occurrences = content.split(oldString).length - 1;
3566
+ if (occurrences === 0)
3567
+ throw new Error("oldString not found in file");
3568
+ if (occurrences > 1)
3569
+ throw new Error(`oldString is ambiguous; found ${occurrences} matches`);
3570
+ await writeFile(resolved, content.replace(oldString, newString), "utf-8");
3571
+ recordEdit(resolved, oldString, newString);
3572
+ return { success: true, path: relative2(cwd, resolved) };
3573
+ }
3574
+ case "bash": {
3575
+ const { command, timeout = DEFAULT_TIMEOUT } = toolInputSchemas.bash.parse(input);
3576
+ const proc = Bun.spawn(["bash", "-c", command], {
3577
+ cwd: resolveInsideCwd(".").resolved,
3578
+ stdout: "pipe",
3579
+ stderr: "pipe",
3580
+ env: { ...process.env, TERM: "dumb" }
3581
+ });
3582
+ const timer = setTimeout(() => proc.kill(), timeout);
3583
+ const [stdout, stderr] = await Promise.all([
3584
+ new Response(proc.stdout).text(),
3585
+ new Response(proc.stderr).text()
3586
+ ]);
3587
+ const exitCode = await proc.exited;
3588
+ clearTimeout(timer);
3589
+ return {
3590
+ stdout: truncate(stdout, MAX_OUTPUT),
3591
+ stderr: truncate(stderr, MAX_OUTPUT),
3592
+ exitCode
3593
+ };
3594
+ }
3595
+ case "webSearchTool": {
3596
+ const { query, searchDepth, maxResults, includeAnswer } = toolInputSchemas.webSearchTool.parse(input);
3597
+ const apiKey = process.env.TAVILY_API_KEY;
3598
+ const tavilyapiurl = process.env.TAVILY_API_URL;
3599
+ if (!apiKey) {
3600
+ return {
3601
+ error: true,
3602
+ message: "TAVILY_API_KEY is not set in the environment. Add it to your .env file to enable web search."
3603
+ };
3604
+ }
3605
+ try {
3606
+ const response = await fetch(`${tavilyapiurl}`, {
3607
+ method: "POST",
3608
+ headers: {
3609
+ "Content-Type": "application/json"
3610
+ },
3611
+ body: JSON.stringify({
3612
+ api_key: apiKey,
3613
+ query,
3614
+ search_depth: searchDepth,
3615
+ max_results: maxResults,
3616
+ include_answer: includeAnswer
3617
+ })
3618
+ });
3619
+ if (!response.ok) {
3620
+ const errorText = await response.text();
3621
+ return {
3622
+ error: true,
3623
+ message: `Tavily API request failed (${response.status}): ${errorText}`
3624
+ };
3625
+ }
3626
+ const data = await response.json();
3627
+ return {
3628
+ error: false,
3629
+ query,
3630
+ answer: data.answer ?? null,
3631
+ results: (data.results ?? []).map((r) => ({
3632
+ title: r.title,
3633
+ url: r.url,
3634
+ content: r.content,
3635
+ score: r.score,
3636
+ publishedDate: r.published_date ?? null
3637
+ }))
3638
+ };
3639
+ } catch (err) {
3640
+ return {
3641
+ error: true,
3642
+ message: `Web search failed: ${err instanceof Error ? err.message : String(err)}`
3643
+ };
3644
+ }
3645
+ }
3646
+ case "fetchUrlTool": {
3647
+ const { url } = toolInputSchemas.fetchUrlTool.parse(input);
3648
+ let parsed;
3649
+ try {
3650
+ parsed = new URL(url);
3651
+ } catch {
3652
+ return { error: true, message: `Invalid URL: ${url}` };
3653
+ }
3654
+ if (!["http:", "https:"].includes(parsed.protocol)) {
3655
+ return { error: true, message: "Only http:// and https:// URLs are supported." };
3656
+ }
3657
+ const blockedHosts = ["localhost", "127.0.0.1", "0.0.0.0", "::1"];
3658
+ if (blockedHosts.includes(parsed.hostname) || parsed.hostname.startsWith("192.168") || parsed.hostname.startsWith("10.") || parsed.hostname.startsWith("169.254.")) {
3659
+ return { error: true, message: "Fetching local/private network addresses is not allowed." };
3660
+ }
3661
+ const controller = new AbortController;
3662
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
3663
+ try {
3664
+ const response = await fetch(parsed.toString(), {
3665
+ signal: controller.signal,
3666
+ headers: {
3667
+ "User-Agent": "Mozilla/5.0 (compatible; AgentBot/1.0; +tool-fetch)",
3668
+ Accept: "text/html,application/xhtml+xml"
3669
+ },
3670
+ redirect: "follow"
3671
+ });
3672
+ clearTimeout(timeout);
3673
+ if (!response.ok) {
3674
+ return { error: true, message: `Failed to fetch URL: HTTP ${response.status} ${response.statusText}` };
3675
+ }
3676
+ const contentType = response.headers.get("constent-type") ?? "";
3677
+ const explicitlyNonParsable = /^(application\/(pdf|zip|octet-stream|vnd\.|x-)|image\/|video\/|audio\/|font\/)/i.test(contentType);
3678
+ if (explicitlyNonParsable) {
3679
+ if (contentType.includes("application/json") || contentType.startsWith("text/")) {
3680
+ const raw = await response.text();
3681
+ return {
3682
+ error: false,
3683
+ url: parsed.toString(),
3684
+ title: null,
3685
+ content: raw.slice(0, MAX_CONTENT_LENGTH),
3686
+ truncated: raw.length > MAX_CONTENT_LENGTH,
3687
+ note: `Content-Type was "${contentType}", returned as raw text (not HTML, no markdown conversion applied).`
3688
+ };
3689
+ }
3690
+ return {
3691
+ error: true,
3692
+ message: `URL returned non-text content (Content-Type: ${contentType}); cannot extract readable content. Consider a dedicated PDF/file tool for this URL instead.`
3693
+ };
3694
+ }
3695
+ const html = await response.text();
3696
+ const dom = new JSDOM(html, { url: parsed.toString() });
3697
+ const reader = new Readability(dom.window.document);
3698
+ const article = reader.parse();
3699
+ if (!article || !article.content) {
3700
+ const bodyHtml = dom.window.document.body?.innerHTML ?? "";
3701
+ const fallbackMarkdown = turndownService.turndown(bodyHtml);
3702
+ return {
3703
+ error: false,
3704
+ url: parsed.toString(),
3705
+ title: dom.window.document.title || null,
3706
+ content: fallbackMarkdown.slice(0, MAX_CONTENT_LENGTH),
3707
+ truncated: fallbackMarkdown.length > MAX_CONTENT_LENGTH,
3708
+ note: "Readability could not isolate a main article; returned full-page content as a fallback (may include navigation/boilerplate)."
3709
+ };
3710
+ }
3711
+ const markdown = turndownService.turndown(article.content);
3712
+ const truncated = markdown.length > MAX_CONTENT_LENGTH;
3713
+ return {
3714
+ error: false,
3715
+ url: parsed.toString(),
3716
+ title: article.title ?? null,
3717
+ byline: article.byline ?? null,
3718
+ excerpt: article.excerpt ?? null,
3719
+ content: truncated ? markdown.slice(0, MAX_CONTENT_LENGTH) : markdown,
3720
+ truncated
3721
+ };
3722
+ } catch (err) {
3723
+ clearTimeout(timeout);
3724
+ if (err instanceof Error && err.name === "AbortError") {
3725
+ return { error: true, message: `Request timed out after ${FETCH_TIMEOUT_MS}ms.` };
3726
+ }
3727
+ return {
3728
+ error: true,
3729
+ message: `Failed to fetch URL: ${err instanceof Error ? err.message : String(err)}`
3730
+ };
3731
+ }
3732
+ }
3733
+ case "undoLastEditSchema": {
3734
+ const { path: path2 } = toolInputSchemas.undoLastEditSchema.parse(input);
3735
+ const { cwd, resolved } = resolveInsideCwd(path2);
3736
+ const stack = editHistory.get(resolved);
3737
+ if (!stack || stack.length === 0) {
3738
+ throw new Error(`No recorded edits to undo for "${path2}"`);
3739
+ }
3740
+ const lastEdit = stack[stack.length - 1];
3741
+ if (!lastEdit) {
3742
+ throw new Error(`No recorded edits to undo for "${path2}"`);
3743
+ }
3744
+ const content = await readFile(resolved, "utf-8");
3745
+ const occurrences = content.split(lastEdit.newString).length - 1;
3746
+ if (occurrences === 0) {
3747
+ throw new Error(`Cannot undo: the text this tool previously inserted into "${path2}" is no longer present ` + `(the file may have changed since then).`);
3748
+ }
3749
+ if (occurrences > 1) {
3750
+ throw new Error(`Cannot undo: found ${occurrences} matches for the last inserted text in "${path2}"; ` + `undo would be ambiguous.`);
3751
+ }
3752
+ await writeFile(resolved, content.replace(lastEdit.newString, lastEdit.oldString), "utf-8");
3753
+ stack.pop();
3754
+ if (stack.length === 0) {
3755
+ editHistory.delete(resolved);
3756
+ }
3757
+ return { success: true, path: relative2(cwd, resolved), remainingHistory: stack.length };
3758
+ }
3759
+ case "viewOutline": {
3760
+ const { path: path2 } = toolInputSchemas.viewOutline.parse(input);
3761
+ const { cwd, resolved } = resolveInsideCwd(path2);
3762
+ const content = await readFile(resolved, "utf-8");
3763
+ const lines = content.split(`
3764
+ `);
3765
+ const outline = [];
3766
+ const structurePattern = /^(?:\s*)(?:export\s+|async\s+|private\s+|public\s+)*(class\s+|function\s+|interface\s+|type\s+|def\s+|const\s+\w+\s*=\s*(?:async\s*)*\([^)]*\)\s*=>)/;
3767
+ lines.forEach((line, index) => {
3768
+ if (structurePattern.test(line)) {
3769
+ outline.push(`Line ${index + 1}: ${line.trimEnd()}`);
3770
+ }
3771
+ });
3772
+ if (outline.length === 0) {
3773
+ return `The file "${resolved}" was read successfully, but no major structural elements (classes, functions, interfaces, or methods) were detected.`;
3774
+ }
3775
+ return `### Code Structure Outline for: ${resolved}
3776
+
3777
+ ${outline.join(`
3778
+ `)}`;
3779
+ }
3780
+ case "proposePlanTool": {
3781
+ const { title, currentState, desiredState, steps, affectedFiles, risks } = toolInputSchemas.proposePlanTool.parse(input);
3782
+ return {
3783
+ status: "pending_approval",
3784
+ plan: {
3785
+ title,
3786
+ currentState,
3787
+ desiredState,
3788
+ steps,
3789
+ affectedFiles,
3790
+ risks: risks ?? []
3791
+ },
3792
+ instructions: "This plan is now awaiting user approval in the UI. Do not proceed with any file " + "edits until you receive a follow-up message confirming approval."
3793
+ };
3794
+ }
3795
+ case "getFileMetadata": {
3796
+ const { path: targetPath } = toolInputSchemas.getFileMetadata.parse(input);
3797
+ const { cwd, resolved } = resolveInsideCwd(targetPath);
3798
+ try {
3799
+ const absolutePath = resolved;
3800
+ const stats = await fs.stat(absolutePath);
3801
+ if (!stats.isFile()) {
3802
+ return { error: `Path exists but is not a regular file: ${targetPath}` };
3803
+ }
3804
+ const extension = path.extname(absolutePath);
3805
+ const sizeBytes = stats.size;
3806
+ const lastModified = stats.mtime.toISOString();
3807
+ let lineCount = 0;
3808
+ const MASSIVE_FILE_THRESHOLD = 50 * 1024 * 1024;
3809
+ if (sizeBytes < MASSIVE_FILE_THRESHOLD) {
3810
+ lineCount = await countLines(absolutePath);
3811
+ } else {
3812
+ lineCount = Math.floor(sizeBytes / 50);
3813
+ }
3814
+ const SAFE_READ_LIMIT_BYTES = 100 * 1024;
3815
+ return {
3816
+ success: true,
3817
+ path: targetPath,
3818
+ extension: extension || "(no extension)",
3819
+ sizeBytes,
3820
+ sizeKB: Math.round(sizeBytes / 1024 * 100) / 100,
3821
+ lineCount,
3822
+ lastModified,
3823
+ isSafeToReadEntirely: sizeBytes <= SAFE_READ_LIMIT_BYTES
3824
+ };
3825
+ } catch (error) {
3826
+ if (error.code === "ENOENT") {
3827
+ return { error: `File not found at path: ${targetPath}` };
3828
+ }
3829
+ return {
3830
+ error: `Failed to retrieve metadata: ${error instanceof Error ? error.message : String(error)}`
3831
+ };
3832
+ }
3833
+ }
3834
+ case "writeMarkdownFile": {
3835
+ const { path: targetPath, content } = toolInputSchemas.writeMarkdownFile.parse(input);
3836
+ const { cwd, resolved } = resolveInsideCwd(targetPath);
3837
+ const ext = path.extname(resolved).toLowerCase();
3838
+ if (ext !== ".md" && ext !== ".mdx") {
3839
+ return {
3840
+ error: `Permission denied: Documentation agent is only permitted to write .md or .mdx files. Attempted to write a '${ext || "(none)"}' file.`
3841
+ };
3842
+ }
3843
+ try {
3844
+ const absolutePath = resolved;
3845
+ const targetDir = path.dirname(absolutePath);
3846
+ await fs.mkdir(targetDir, { recursive: true });
3847
+ await fs.writeFile(absolutePath, content, "utf-8");
3848
+ const bytesWritten = Buffer.byteLength(content, "utf-8");
3849
+ return {
3850
+ success: true,
3851
+ message: `Successfully wrote markdown content to ${targetPath}`,
3852
+ path: targetPath,
3853
+ sizeKB: Math.round(bytesWritten / 1024 * 100) / 100
3854
+ };
3855
+ } catch (error) {
3856
+ return {
3857
+ error: `Failed to write markdown file: ${error instanceof Error ? error.message : String(error)}`
3858
+ };
3859
+ }
3860
+ }
3861
+ default:
3862
+ throw new Error(`Unknown tool: ${toolName}`);
3863
+ }
3864
+ }
3865
+
3866
+ // src/hooks/use-chat.ts
3867
+ function useChat(sessionId, initialMessages) {
3868
+ const [questionPrompts, setQuestionPrompts] = useState11([]);
3869
+ const transport = useMemo4(() => {
3870
+ return new DefaultChatTransport({
3871
+ api: apiClient.chat.$url().toString(),
3872
+ headers() {
3873
+ const auth = getAuth();
3874
+ return auth ? { Authorization: `Bearer ${auth.token}` } : new Headers;
3875
+ },
3876
+ prepareSendMessagesRequest({ messages }) {
3877
+ const message = messages[messages.length - 1];
3878
+ if (!message)
3879
+ throw new Error("No message to send");
3880
+ const metadata = messages.findLast((m) => m.metadata?.mode && m.metadata?.model)?.metadata;
3881
+ const previousMessage = messages[messages.length - 2];
3882
+ const requestMessages = message.role === "assistant" && previousMessage?.role === "user" ? [previousMessage, message] : [message];
3883
+ return {
3884
+ body: {
3885
+ id: sessionId,
3886
+ messages: requestMessages,
3887
+ mode: message.metadata?.mode ?? metadata?.mode,
3888
+ model: message.metadata?.model ?? metadata?.model
3889
+ }
3890
+ };
3891
+ }
3892
+ });
3893
+ }, [sessionId]);
3894
+ const deactivateQuestionPrompts = useCallback14(() => {
3895
+ setQuestionPrompts((prompts) => prompts.map((prompt) => ({ ...prompt, isActive: false })));
3896
+ }, []);
3897
+ const submitQuestionAnswer = useCallback14((promptId, answerText) => {
3898
+ const prompt = questionPrompts.find((entry) => entry.id === promptId);
3899
+ if (!prompt) {
3900
+ return;
3901
+ }
3902
+ setQuestionPrompts((prompts) => prompts.filter((entry) => entry.id !== promptId));
3903
+ chat.addToolOutput({
3904
+ tool: prompt.toolName,
3905
+ toolCallId: prompt.toolCallId,
3906
+ output: {
3907
+ answer: answerText
3908
+ }
3909
+ });
3910
+ }, [questionPrompts]);
3911
+ const chat = useAiChat({
3912
+ id: sessionId,
3913
+ messages: initialMessages,
3914
+ transport,
3915
+ onToolCall({ toolCall }) {
3916
+ if (toolCall.toolName === "askQuestionTool") {
3917
+ const parsed = toolInputSchemas.askQuestionTool.parse(toolCall.input);
3918
+ const promptId = globalThis.crypto?.randomUUID?.() ?? `${toolCall.toolCallId}-${Date.now()}`;
3919
+ setQuestionPrompts((prompts) => [
3920
+ ...prompts.map((prompt) => ({ ...prompt, isActive: false })),
3921
+ {
3922
+ id: promptId,
3923
+ toolCallId: toolCall.toolCallId,
3924
+ toolName: toolCall.toolName,
3925
+ title: parsed.title,
3926
+ context: parsed.context,
3927
+ questions: parsed.questions,
3928
+ isActive: true
3929
+ }
3930
+ ]);
3931
+ return;
3932
+ }
3933
+ const mode = chat.messages.at(-1)?.metadata?.mode ?? "BUILD";
3934
+ executeLocalTool(toolCall.toolName, toolCall.input, mode).then((output) => chat.addToolOutput({
3935
+ tool: toolCall.toolName,
3936
+ toolCallId: toolCall.toolCallId,
3937
+ output
3938
+ })).catch((error) => chat.addToolOutput({
3939
+ tool: toolCall.toolName,
3940
+ toolCallId: toolCall.toolCallId,
3941
+ state: "output-error",
3942
+ errorText: error instanceof Error ? error.message : String(error)
3943
+ }));
3944
+ },
3945
+ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls
3946
+ });
3947
+ return {
3948
+ messages: chat.messages,
3949
+ status: chat.status,
3950
+ error: chat.error,
3951
+ questionPrompts,
3952
+ deactivateQuestionPrompts,
3953
+ submitQuestionAnswer,
3954
+ submit: (params) => {
3955
+ return chat.sendMessage({
3956
+ text: params.userText,
3957
+ metadata: {
3958
+ mode: params.mode,
3959
+ model: params.model
3960
+ }
3961
+ });
3962
+ },
3963
+ abort: chat.stop,
3964
+ interrupt: chat.stop
3965
+ };
3966
+ }
3967
+
3968
+ // src/screens/session.tsx
3969
+ import { useKeyboard as useKeyboard6 } from "@opentui/react";
3970
+ import { jsxDEV as jsxDEV27 } from "@opentui/react/jsx-dev-runtime";
3971
+ var sessionLocationSchema = z3.object({
3972
+ session: z3.custom((val) => val != null && typeof val === "object" && ("id" in val)),
3973
+ initialPrompt: z3.object({
3974
+ message: z3.string(),
3975
+ mode: z3.custom(),
3976
+ model: z3.custom()
3977
+ }).optional()
3978
+ });
3979
+ function ChatMessage({
3980
+ msg,
3981
+ questionPrompts,
3982
+ onSubmitQuestionAnswer
3983
+ }) {
3984
+ if (msg.role === "user") {
3985
+ const text = msg.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
3986
+ return /* @__PURE__ */ jsxDEV27(UserMessage, {
3987
+ message: text,
3988
+ mode: msg.metadata?.mode ?? "BUILD"
3989
+ }, undefined, false, undefined, this);
3990
+ }
3991
+ return /* @__PURE__ */ jsxDEV27(BotMessage, {
3992
+ parts: msg.parts,
3993
+ model: msg.metadata?.model ?? "unknown",
3994
+ mode: msg.metadata?.mode ?? "BUILD",
3995
+ durationMs: msg.metadata?.durationMs,
3996
+ streaming: false,
3997
+ questionPrompts,
3998
+ onSubmitQuestionAnswer
3999
+ }, undefined, false, undefined, this);
4000
+ }
4001
+ function SessionChat({ session, initialPrompt }) {
4002
+ const [initialMessages] = useState12(() => session.messages);
4003
+ const {
4004
+ messages,
4005
+ status,
4006
+ submit,
4007
+ abort,
4008
+ interrupt,
4009
+ error,
4010
+ questionPrompts,
4011
+ submitQuestionAnswer,
4012
+ deactivateQuestionPrompts
4013
+ } = useChat(session.id, initialMessages);
4014
+ const hasSubmittedInitialPromptRef = useRef9(false);
4015
+ const { mode, model } = usePromptConfig();
4016
+ const { isTopLayer } = useKeyboardLayer();
4017
+ useEffect5(() => {
4018
+ return () => {
4019
+ abort();
4020
+ };
4021
+ }, [abort]);
4022
+ useKeyboard6((key) => {
4023
+ if (key.name === "escape" && isTopLayer("base") && status === "streaming") {
4024
+ key.preventDefault();
4025
+ interrupt();
4026
+ }
4027
+ });
4028
+ useEffect5(() => {
4029
+ if (!initialPrompt || hasSubmittedInitialPromptRef.current)
4030
+ return;
4031
+ hasSubmittedInitialPromptRef.current = true;
4032
+ submit({
4033
+ userText: initialPrompt.message,
4034
+ mode: initialPrompt.mode,
4035
+ model: initialPrompt.model
4036
+ });
4037
+ }, [initialPrompt, submit]);
4038
+ return /* @__PURE__ */ jsxDEV27(SessionShell, {
4039
+ onSubmit: (text) => {
4040
+ deactivateQuestionPrompts();
4041
+ submit({ userText: text, mode, model });
4042
+ },
4043
+ loading: status === "submitted" || status === "streaming",
4044
+ interruptible: status === "submitted" || status === "streaming",
4045
+ children: [
4046
+ messages.map((msg) => /* @__PURE__ */ jsxDEV27(ChatMessage, {
4047
+ msg,
4048
+ questionPrompts,
4049
+ onSubmitQuestionAnswer: submitQuestionAnswer
4050
+ }, msg.id, false, undefined, this)),
4051
+ error && /* @__PURE__ */ jsxDEV27(ErrorMessage, {
4052
+ message: error.message
4053
+ }, undefined, false, undefined, this)
4054
+ ]
4055
+ }, undefined, true, undefined, this);
4056
+ }
4057
+ function Session() {
4058
+ const { id } = useParams();
4059
+ const location = useLocation2();
4060
+ const navigate = useNavigate6();
4061
+ const toast = useToast();
4062
+ const prefetched = useMemo5(() => {
4063
+ const parsed = sessionLocationSchema.safeParse(location.state);
4064
+ return parsed.success ? parsed.data : null;
4065
+ }, [location.state]);
4066
+ const [session, setSession] = useState12(prefetched?.session ?? null);
4067
+ useEffect5(() => {
4068
+ if (prefetched?.session)
4069
+ return;
4070
+ setSession(null);
4071
+ if (!id)
4072
+ return;
4073
+ let ignore = false;
4074
+ const fetchSession = async () => {
4075
+ try {
4076
+ const res = await apiClient.sessions[":id"].$get({
4077
+ param: { id }
4078
+ });
4079
+ if (ignore)
4080
+ return;
4081
+ if (!res.ok)
4082
+ throw new Error(await getErrorMessage(res));
4083
+ const resolved = await res.json();
4084
+ setSession(resolved);
4085
+ } catch (err) {
4086
+ if (ignore)
4087
+ return;
4088
+ toast.show({
4089
+ variant: "error",
4090
+ message: err instanceof Error ? err.message : "Failed to load session"
4091
+ });
4092
+ navigate("/", { replace: true });
4093
+ }
4094
+ };
4095
+ fetchSession();
4096
+ return () => {
4097
+ ignore = true;
4098
+ };
4099
+ }, [id, prefetched, toast, navigate]);
4100
+ if (!session) {
4101
+ return /* @__PURE__ */ jsxDEV27(SessionShell, {
4102
+ onSubmit: () => {},
4103
+ inputDisabled: true,
4104
+ loading: true
4105
+ }, undefined, false, undefined, this);
4106
+ }
4107
+ return /* @__PURE__ */ jsxDEV27(SessionChat, {
4108
+ session,
4109
+ initialPrompt: prefetched?.initialPrompt
4110
+ }, session.id, false, undefined, this);
4111
+ }
4112
+
4113
+ // src/index.tsx
4114
+ import { jsxDEV as jsxDEV28 } from "@opentui/react/jsx-dev-runtime";
12
4115
  var router = createMemoryRouter([
13
4116
  {
14
4117
  path: "/",
15
- element: /* @__PURE__ */ jsxDEV(RootLayout, {}, undefined, false, undefined, this),
4118
+ element: /* @__PURE__ */ jsxDEV28(RootLayout, {}, undefined, false, undefined, this),
16
4119
  children: [
17
- { index: true, element: /* @__PURE__ */ jsxDEV(Home, {}, undefined, false, undefined, this) },
18
- { path: "sessions/new", element: /* @__PURE__ */ jsxDEV(NewSession, {}, undefined, false, undefined, this) },
19
- { path: "sessions/:id", element: /* @__PURE__ */ jsxDEV(Session, {}, undefined, false, undefined, this) },
20
- { path: "docs", element: /* @__PURE__ */ jsxDEV(Docs, {}, undefined, false, undefined, this) }
4120
+ { index: true, element: /* @__PURE__ */ jsxDEV28(Home, {}, undefined, false, undefined, this) },
4121
+ { path: "sessions/new", element: /* @__PURE__ */ jsxDEV28(NewSession, {}, undefined, false, undefined, this) },
4122
+ { path: "sessions/:id", element: /* @__PURE__ */ jsxDEV28(Session, {}, undefined, false, undefined, this) },
4123
+ { path: "docs", element: /* @__PURE__ */ jsxDEV28(Docs, {}, undefined, false, undefined, this) }
21
4124
  ]
22
4125
  }
23
4126
  ]);
24
4127
  function App() {
25
- return /* @__PURE__ */ jsxDEV(RouterProvider, {
4128
+ return /* @__PURE__ */ jsxDEV28(RouterProvider, {
26
4129
  router
27
4130
  }, undefined, false, undefined, this);
28
4131
  }
@@ -30,4 +4133,4 @@ var renderer = await createCliRenderer({
30
4133
  targetFps: 60,
31
4134
  exitOnCtrlC: false
32
4135
  });
33
- createRoot(renderer).render(/* @__PURE__ */ jsxDEV(App, {}, undefined, false, undefined, this));
4136
+ createRoot(renderer).render(/* @__PURE__ */ jsxDEV28(App, {}, undefined, false, undefined, this));