@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/bin/Claude-Code-Clone +1 -1
- package/dist/index.js +4115 -12
- package/package.json +3 -3
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
|
-
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
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__ */
|
|
4118
|
+
element: /* @__PURE__ */ jsxDEV28(RootLayout, {}, undefined, false, undefined, this),
|
|
16
4119
|
children: [
|
|
17
|
-
{ index: true, element: /* @__PURE__ */
|
|
18
|
-
{ path: "sessions/new", element: /* @__PURE__ */
|
|
19
|
-
{ path: "sessions/:id", element: /* @__PURE__ */
|
|
20
|
-
{ path: "docs", element: /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
4136
|
+
createRoot(renderer).render(/* @__PURE__ */ jsxDEV28(App, {}, undefined, false, undefined, this));
|