flavor-code 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1081 -0
  3. package/dist/agent/loop.d.ts +31 -0
  4. package/dist/agent/message-queue.d.ts +19 -0
  5. package/dist/agent/planner.d.ts +28 -0
  6. package/dist/agent/subagents.d.ts +50 -0
  7. package/dist/agent/task-plan.d.ts +60 -0
  8. package/dist/agent/task-tools.d.ts +11 -0
  9. package/dist/agent/types.d.ts +117 -0
  10. package/dist/app-WC2XXGLM.js +3185 -0
  11. package/dist/app-WC2XXGLM.js.map +1 -0
  12. package/dist/auth/oauth-config.d.ts +4 -0
  13. package/dist/auth/oauth.d.ts +21 -0
  14. package/dist/auth/store.d.ts +15 -0
  15. package/dist/auth/types.d.ts +33 -0
  16. package/dist/chunk-BU64GNXE.js +15771 -0
  17. package/dist/chunk-BU64GNXE.js.map +1 -0
  18. package/dist/chunk-DM64TM4Z.js +124 -0
  19. package/dist/chunk-DM64TM4Z.js.map +1 -0
  20. package/dist/chunk-JZ322RCJ.js +40 -0
  21. package/dist/chunk-JZ322RCJ.js.map +1 -0
  22. package/dist/chunk-M744K7MK.js +246 -0
  23. package/dist/chunk-M744K7MK.js.map +1 -0
  24. package/dist/chunk-MVIMQEQT.js +692 -0
  25. package/dist/chunk-MVIMQEQT.js.map +1 -0
  26. package/dist/chunk-VOCZVFP7.js +153 -0
  27. package/dist/chunk-VOCZVFP7.js.map +1 -0
  28. package/dist/chunk-XS2JU6S3.js +35557 -0
  29. package/dist/chunk-XS2JU6S3.js.map +1 -0
  30. package/dist/claude-ink-ZL4UZMFY.js +27 -0
  31. package/dist/claude-ink-ZL4UZMFY.js.map +1 -0
  32. package/dist/cli.js +915 -0
  33. package/dist/cli.js.map +1 -0
  34. package/dist/codeisland/activate.mjs +74 -0
  35. package/dist/codeisland/bridge.mjs +97 -0
  36. package/dist/codeisland/flavor-plugin.json +27 -0
  37. package/dist/config/load.d.ts +19 -0
  38. package/dist/config/protected-file.d.ts +15 -0
  39. package/dist/config/schema.d.ts +176 -0
  40. package/dist/config/secret-envelope.d.ts +8 -0
  41. package/dist/context/compaction.d.ts +44 -0
  42. package/dist/context/manager.d.ts +65 -0
  43. package/dist/context/summarizer.d.ts +15 -0
  44. package/dist/desktop/main.js +17778 -0
  45. package/dist/desktop/main.js.map +1 -0
  46. package/dist/desktop/preload.cjs +90 -0
  47. package/dist/desktop/preload.cjs.map +1 -0
  48. package/dist/desktop-renderer/assets/index-C66pDvHs.js +144 -0
  49. package/dist/desktop-renderer/assets/index-C66pDvHs.js.map +1 -0
  50. package/dist/desktop-renderer/assets/index-Cm_ktmXm.css +1 -0
  51. package/dist/desktop-renderer/index.html +16 -0
  52. package/dist/devtools-SXJBSV2N.js +3 -0
  53. package/dist/devtools-SXJBSV2N.js.map +1 -0
  54. package/dist/execution/docker.d.ts +29 -0
  55. package/dist/execution/factory.d.ts +3 -0
  56. package/dist/execution/types.d.ts +18 -0
  57. package/dist/goal/classifier.d.ts +12 -0
  58. package/dist/goal/orchestrator.d.ts +34 -0
  59. package/dist/goal/planner.d.ts +9 -0
  60. package/dist/goal/store.d.ts +9 -0
  61. package/dist/goal/types.d.ts +190 -0
  62. package/dist/hallucination/confidence.d.ts +12 -0
  63. package/dist/hallucination/evidence-ledger.d.ts +29 -0
  64. package/dist/hallucination/guard.d.ts +27 -0
  65. package/dist/hallucination/messages.d.ts +10 -0
  66. package/dist/hallucination/retry-monitor.d.ts +19 -0
  67. package/dist/hallucination/sliding-window.d.ts +13 -0
  68. package/dist/hallucination/types.d.ts +49 -0
  69. package/dist/harness/local.d.ts +54 -0
  70. package/dist/hooks/bus.d.ts +9 -0
  71. package/dist/hooks/types.d.ts +105 -0
  72. package/dist/ide/client.d.ts +35 -0
  73. package/dist/incidents/reporter.d.ts +38 -0
  74. package/dist/init/project.d.ts +25 -0
  75. package/dist/load-WQGNTCHC.js +20 -0
  76. package/dist/load-WQGNTCHC.js.map +1 -0
  77. package/dist/loop/budget.d.ts +11 -0
  78. package/dist/loop/isolation.d.ts +21 -0
  79. package/dist/loop/orchestrator.d.ts +82 -0
  80. package/dist/loop/store.d.ts +13 -0
  81. package/dist/loop/types.d.ts +142 -0
  82. package/dist/loop/verifier.d.ts +13 -0
  83. package/dist/mcp/client.d.ts +66 -0
  84. package/dist/mcp/sdk.d.ts +27 -0
  85. package/dist/memory/coordinator.d.ts +32 -0
  86. package/dist/memory/extractor.d.ts +15 -0
  87. package/dist/memory/intent.d.ts +2 -0
  88. package/dist/memory/retrieval.d.ts +13 -0
  89. package/dist/memory/review.d.ts +36 -0
  90. package/dist/memory/similarity.d.ts +5 -0
  91. package/dist/memory/store.d.ts +58 -0
  92. package/dist/memory/types.d.ts +37 -0
  93. package/dist/models/anthropic.d.ts +24 -0
  94. package/dist/models/cache-profile.d.ts +23 -0
  95. package/dist/models/openai.d.ts +24 -0
  96. package/dist/models/registry.d.ts +16 -0
  97. package/dist/models/structured.d.ts +75 -0
  98. package/dist/models/types.d.ts +93 -0
  99. package/dist/permissions/classifier.d.ts +9 -0
  100. package/dist/permissions/engine.d.ts +28 -0
  101. package/dist/plugins/host.d.ts +15 -0
  102. package/dist/plugins/types.d.ts +136 -0
  103. package/dist/production.d.ts +59 -0
  104. package/dist/prompts/system.d.ts +28 -0
  105. package/dist/rpc/schema.d.ts +61 -0
  106. package/dist/rpc/server.d.ts +54 -0
  107. package/dist/sdk/index.d.ts +19 -0
  108. package/dist/sdk/index.js +53 -0
  109. package/dist/sdk/index.js.map +1 -0
  110. package/dist/session/checkpoint.d.ts +25 -0
  111. package/dist/session/store.d.ts +154 -0
  112. package/dist/session/tree.d.ts +31 -0
  113. package/dist/skills/builtin-loop.d.ts +12 -0
  114. package/dist/skills/registry.d.ts +47 -0
  115. package/dist/skills/tool.d.ts +9 -0
  116. package/dist/sleep/organizer.d.ts +37 -0
  117. package/dist/tools/ask-user-question.d.ts +49 -0
  118. package/dist/tools/file-diff.d.ts +9 -0
  119. package/dist/tools/files.d.ts +49 -0
  120. package/dist/tools/lsp.d.ts +77 -0
  121. package/dist/tools/managed.d.ts +59 -0
  122. package/dist/tools/runtime.d.ts +58 -0
  123. package/dist/tools/search.d.ts +76 -0
  124. package/dist/tools/shell.d.ts +37 -0
  125. package/dist/tools/task-output.d.ts +53 -0
  126. package/dist/tools/todo-write.d.ts +35 -0
  127. package/dist/tools/types.d.ts +56 -0
  128. package/dist/trace/recorder.d.ts +14 -0
  129. package/dist/trace/replay.d.ts +3 -0
  130. package/dist/trace/schema.d.ts +26 -0
  131. package/dist/ui/commands.d.ts +67 -0
  132. package/dist/ui/session.d.ts +126 -0
  133. package/dist/ui/transcript.d.ts +89 -0
  134. package/dist/utils/async.d.ts +5 -0
  135. package/dist/utils/envUtils.d.ts +1 -0
  136. package/dist/utils/error.d.ts +2 -0
  137. package/dist/utils/execFileNoThrow.d.ts +13 -0
  138. package/dist/utils/intl.d.ts +18 -0
  139. package/dist/utils/json.d.ts +44 -0
  140. package/dist/utils/log.d.ts +28 -0
  141. package/dist/utils/redact.d.ts +10 -0
  142. package/dist/utils/usage-summary.d.ts +34 -0
  143. package/dist/utils/version.d.ts +6 -0
  144. package/package.json +124 -0
  145. package//346/212/200/346/234/257/346/226/271/346/241/210/346/212/245/345/221/212.md +4797 -0
@@ -0,0 +1,3185 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module"; const require = createRequire(import.meta.url);
3
+ import {
4
+ statusPresentation
5
+ } from "./chunk-DM64TM4Z.js";
6
+ import {
7
+ COMMAND_DESCRIPTIONS,
8
+ MVP_COMMANDS,
9
+ createGlobTool,
10
+ createProductionRuntime,
11
+ createTranscriptState,
12
+ isDestructiveTool,
13
+ message,
14
+ modelContentTranscriptText,
15
+ redactErrorText,
16
+ transcriptReducer
17
+ } from "./chunk-BU64GNXE.js";
18
+ import "./chunk-MVIMQEQT.js";
19
+ import {
20
+ Box_default,
21
+ ClockContext,
22
+ OSC,
23
+ ScrollBox_default,
24
+ StdinContext_default,
25
+ TerminalSizeContext,
26
+ TerminalWriteContext,
27
+ Text,
28
+ instances_default,
29
+ osc,
30
+ shiftAnchor,
31
+ stripAnsi,
32
+ useStdout,
33
+ use_app_default,
34
+ use_input_default
35
+ } from "./chunk-XS2JU6S3.js";
36
+ import {
37
+ execFileNoThrow
38
+ } from "./chunk-VOCZVFP7.js";
39
+ import "./chunk-JZ322RCJ.js";
40
+
41
+ // src/ui/app.tsx
42
+ import { basename as basename3 } from "path";
43
+ import { useEffect as useEffect3, useReducer, useRef as useRef4, useState as useState2 } from "react";
44
+
45
+ // src/claude-ink/hooks/use-animation-frame.ts
46
+ import { useContext as useContext2, useEffect, useState } from "react";
47
+
48
+ // src/claude-ink/hooks/use-terminal-viewport.ts
49
+ import { useCallback, useContext, useLayoutEffect, useRef } from "react";
50
+ function useTerminalViewport() {
51
+ const terminalSize = useContext(TerminalSizeContext);
52
+ const elementRef = useRef(null);
53
+ const entryRef = useRef({ isVisible: true });
54
+ const setElement = useCallback((el) => {
55
+ elementRef.current = el;
56
+ }, []);
57
+ useLayoutEffect(() => {
58
+ const element = elementRef.current;
59
+ if (!element?.yogaNode || !terminalSize) {
60
+ return;
61
+ }
62
+ const height = element.yogaNode.getComputedHeight();
63
+ const rows = terminalSize.rows;
64
+ let absoluteTop = element.yogaNode.getComputedTop();
65
+ let parent = element.parentNode;
66
+ let root = element.yogaNode;
67
+ while (parent) {
68
+ if (parent.yogaNode) {
69
+ absoluteTop += parent.yogaNode.getComputedTop();
70
+ root = parent.yogaNode;
71
+ }
72
+ if (parent.scrollTop) absoluteTop -= parent.scrollTop;
73
+ parent = parent.parentNode;
74
+ }
75
+ const screenHeight = root.getComputedHeight();
76
+ const bottom = absoluteTop + height;
77
+ const cursorRestoreScroll = screenHeight > rows ? 1 : 0;
78
+ const viewportY = Math.max(0, screenHeight - rows) + cursorRestoreScroll;
79
+ const viewportBottom = viewportY + rows;
80
+ const visible = bottom > viewportY && absoluteTop < viewportBottom;
81
+ if (visible !== entryRef.current.isVisible) {
82
+ entryRef.current = { isVisible: visible };
83
+ }
84
+ });
85
+ return [setElement, entryRef.current];
86
+ }
87
+
88
+ // src/claude-ink/hooks/use-animation-frame.ts
89
+ function useAnimationFrame(intervalMs = 16) {
90
+ const clock = useContext2(ClockContext);
91
+ const [viewportRef, { isVisible }] = useTerminalViewport();
92
+ const [time, setTime] = useState(() => clock?.now() ?? 0);
93
+ const active = isVisible && intervalMs !== null;
94
+ useEffect(() => {
95
+ if (!clock || !active) return;
96
+ let lastUpdate = clock.now();
97
+ const onChange = () => {
98
+ const now = clock.now();
99
+ if (now - lastUpdate >= intervalMs) {
100
+ lastUpdate = now;
101
+ setTime(now);
102
+ }
103
+ };
104
+ return clock.subscribe(onChange, true);
105
+ }, [clock, intervalMs, active]);
106
+ return [viewportRef, time];
107
+ }
108
+
109
+ // src/claude-ink/hooks/use-selection.ts
110
+ import { useContext as useContext3, useMemo, useSyncExternalStore } from "react";
111
+ function useSelection() {
112
+ useContext3(StdinContext_default);
113
+ const ink = instances_default.get(process.stdout);
114
+ return useMemo(() => {
115
+ if (!ink) {
116
+ return {
117
+ copySelection: () => "",
118
+ copySelectionNoClear: () => "",
119
+ clearSelection: () => {
120
+ },
121
+ hasSelection: () => false,
122
+ getState: () => null,
123
+ subscribe: () => () => {
124
+ },
125
+ shiftAnchor: () => {
126
+ },
127
+ shiftSelection: () => {
128
+ },
129
+ moveFocus: () => {
130
+ },
131
+ captureScrolledRows: () => {
132
+ },
133
+ setSelectionBgColor: () => {
134
+ }
135
+ };
136
+ }
137
+ return {
138
+ copySelection: () => ink.copySelection(),
139
+ copySelectionNoClear: () => ink.copySelectionNoClear(),
140
+ clearSelection: () => ink.clearTextSelection(),
141
+ hasSelection: () => ink.hasTextSelection(),
142
+ getState: () => ink.selection,
143
+ subscribe: (cb) => ink.subscribeToSelectionChange(cb),
144
+ shiftAnchor: (dRow, minRow, maxRow) => shiftAnchor(ink.selection, dRow, minRow, maxRow),
145
+ shiftSelection: (dRow, minRow, maxRow) => ink.shiftSelectionForScroll(dRow, minRow, maxRow),
146
+ moveFocus: (move) => ink.moveSelectionFocus(move),
147
+ captureScrolledRows: (firstRow, lastRow, side) => ink.captureScrolledRows(firstRow, lastRow, side),
148
+ setSelectionBgColor: (color) => ink.setSelectionBgColor(color)
149
+ };
150
+ }, [ink]);
151
+ }
152
+
153
+ // src/claude-ink/hooks/use-terminal-title.ts
154
+ import { useContext as useContext4, useEffect as useEffect2 } from "react";
155
+ function useTerminalTitle(title) {
156
+ const writeRaw = useContext4(TerminalWriteContext);
157
+ useEffect2(() => {
158
+ if (title === null || !writeRaw) return;
159
+ const clean = stripAnsi(title);
160
+ writeRaw(osc(OSC.SET_TITLE_AND_ICON, clean));
161
+ }, [title, writeRaw]);
162
+ }
163
+
164
+ // src/ui/assistant-text.tsx
165
+ import React2 from "react";
166
+
167
+ // src/ui/markdown.tsx
168
+ import React, { useMemo as useMemo2, useRef as useRef2 } from "react";
169
+ import chalk2 from "chalk";
170
+ import { marked } from "marked";
171
+
172
+ // src/ui/highlight.ts
173
+ import chalk from "chalk";
174
+ chalk.level = Math.max(chalk.level, 3);
175
+ var KEYWORD = chalk.hex("#c586c0");
176
+ var STRING = chalk.hex("#ce9178");
177
+ var COMMENT = chalk.gray.italic;
178
+ var NUMBER = chalk.hex("#b5cea8");
179
+ var FUNCTION = chalk.hex("#dcdcaa");
180
+ var ATTRIBUTE = chalk.hex("#d7ba7d");
181
+ var KEYWORDS = {
182
+ javascript: /* @__PURE__ */ new Set([
183
+ "await",
184
+ "async",
185
+ "break",
186
+ "case",
187
+ "catch",
188
+ "class",
189
+ "const",
190
+ "continue",
191
+ "default",
192
+ "delete",
193
+ "do",
194
+ "else",
195
+ "export",
196
+ "extends",
197
+ "finally",
198
+ "for",
199
+ "from",
200
+ "function",
201
+ "if",
202
+ "import",
203
+ "in",
204
+ "instanceof",
205
+ "let",
206
+ "new",
207
+ "of",
208
+ "return",
209
+ "static",
210
+ "super",
211
+ "switch",
212
+ "this",
213
+ "throw",
214
+ "true",
215
+ "false",
216
+ "null",
217
+ "try",
218
+ "typeof",
219
+ "undefined",
220
+ "var",
221
+ "void",
222
+ "while",
223
+ "with",
224
+ "yield"
225
+ ]),
226
+ typescript: /* @__PURE__ */ new Set([
227
+ "abstract",
228
+ "any",
229
+ "as",
230
+ "async",
231
+ "await",
232
+ "boolean",
233
+ "break",
234
+ "case",
235
+ "catch",
236
+ "class",
237
+ "const",
238
+ "constructor",
239
+ "continue",
240
+ "declare",
241
+ "default",
242
+ "delete",
243
+ "do",
244
+ "else",
245
+ "enum",
246
+ "export",
247
+ "extends",
248
+ "false",
249
+ "finally",
250
+ "for",
251
+ "from",
252
+ "function",
253
+ "get",
254
+ "if",
255
+ "implements",
256
+ "import",
257
+ "in",
258
+ "instanceof",
259
+ "interface",
260
+ "is",
261
+ "keyof",
262
+ "let",
263
+ "namespace",
264
+ "never",
265
+ "new",
266
+ "null",
267
+ "number",
268
+ "object",
269
+ "of",
270
+ "private",
271
+ "protected",
272
+ "public",
273
+ "readonly",
274
+ "return",
275
+ "set",
276
+ "static",
277
+ "string",
278
+ "super",
279
+ "switch",
280
+ "this",
281
+ "throw",
282
+ "true",
283
+ "try",
284
+ "type",
285
+ "typeof",
286
+ "undefined",
287
+ "unique",
288
+ "unknown",
289
+ "var",
290
+ "void",
291
+ "while",
292
+ "with",
293
+ "yield"
294
+ ]),
295
+ python: /* @__PURE__ */ new Set([
296
+ "False",
297
+ "None",
298
+ "True",
299
+ "and",
300
+ "as",
301
+ "assert",
302
+ "async",
303
+ "await",
304
+ "break",
305
+ "class",
306
+ "continue",
307
+ "def",
308
+ "del",
309
+ "elif",
310
+ "else",
311
+ "except",
312
+ "finally",
313
+ "for",
314
+ "from",
315
+ "global",
316
+ "if",
317
+ "import",
318
+ "in",
319
+ "is",
320
+ "lambda",
321
+ "nonlocal",
322
+ "not",
323
+ "or",
324
+ "pass",
325
+ "raise",
326
+ "return",
327
+ "try",
328
+ "while",
329
+ "with",
330
+ "yield",
331
+ "self"
332
+ ]),
333
+ bash: /* @__PURE__ */ new Set([
334
+ "if",
335
+ "then",
336
+ "else",
337
+ "elif",
338
+ "fi",
339
+ "for",
340
+ "while",
341
+ "until",
342
+ "do",
343
+ "done",
344
+ "case",
345
+ "esac",
346
+ "in",
347
+ "function",
348
+ "return",
349
+ "export",
350
+ "local",
351
+ "echo",
352
+ "printf",
353
+ "test"
354
+ ]),
355
+ json: /* @__PURE__ */ new Set(["true", "false", "null"]),
356
+ rust: /* @__PURE__ */ new Set([
357
+ "as",
358
+ "async",
359
+ "await",
360
+ "break",
361
+ "const",
362
+ "continue",
363
+ "crate",
364
+ "dyn",
365
+ "else",
366
+ "enum",
367
+ "extern",
368
+ "false",
369
+ "fn",
370
+ "for",
371
+ "if",
372
+ "impl",
373
+ "in",
374
+ "let",
375
+ "loop",
376
+ "match",
377
+ "mod",
378
+ "move",
379
+ "mut",
380
+ "pub",
381
+ "ref",
382
+ "return",
383
+ "self",
384
+ "Self",
385
+ "static",
386
+ "struct",
387
+ "super",
388
+ "trait",
389
+ "true",
390
+ "type",
391
+ "unsafe",
392
+ "use",
393
+ "where",
394
+ "while"
395
+ ]),
396
+ go: /* @__PURE__ */ new Set([
397
+ "break",
398
+ "case",
399
+ "chan",
400
+ "const",
401
+ "continue",
402
+ "default",
403
+ "defer",
404
+ "else",
405
+ "fallthrough",
406
+ "for",
407
+ "func",
408
+ "go",
409
+ "goto",
410
+ "if",
411
+ "import",
412
+ "interface",
413
+ "map",
414
+ "package",
415
+ "range",
416
+ "return",
417
+ "select",
418
+ "struct",
419
+ "switch",
420
+ "type",
421
+ "var",
422
+ "true",
423
+ "false",
424
+ "nil"
425
+ ])
426
+ };
427
+ function normalizeLanguage(input) {
428
+ if (input === void 0) return void 0;
429
+ const lower = input.trim().toLowerCase();
430
+ switch (lower) {
431
+ case "js":
432
+ case "jsx":
433
+ case "javascript":
434
+ return "javascript";
435
+ case "ts":
436
+ case "tsx":
437
+ case "typescript":
438
+ return "typescript";
439
+ case "py":
440
+ case "python":
441
+ return "python";
442
+ case "sh":
443
+ case "shell":
444
+ case "bash":
445
+ case "zsh":
446
+ return "bash";
447
+ case "json":
448
+ return "json";
449
+ case "rs":
450
+ case "rust":
451
+ return "rust";
452
+ case "go":
453
+ case "golang":
454
+ return "go";
455
+ default:
456
+ return void 0;
457
+ }
458
+ }
459
+ function isIdentifierStart(ch) {
460
+ return /[A-Za-z_]/.test(ch) || ch === "@";
461
+ }
462
+ function isIdentifierPart(ch) {
463
+ return /[A-Za-z0-9_]/.test(ch);
464
+ }
465
+ function isDigit(ch) {
466
+ return ch >= "0" && ch <= "9";
467
+ }
468
+ function isNumberPrefix(line, i) {
469
+ const ch = line[i] ?? "";
470
+ if (isDigit(ch)) return true;
471
+ if (ch === "." && isDigit(line[i + 1] ?? "")) return true;
472
+ if (ch === "0" && (line[i + 1] === "x" || line[i + 1] === "X")) return true;
473
+ return false;
474
+ }
475
+ function readNumberToken(line, i) {
476
+ let j = i + 1;
477
+ const len = line.length;
478
+ while (j < len) {
479
+ const cj = line[j] ?? "";
480
+ if (isDigit(cj)) {
481
+ j += 1;
482
+ continue;
483
+ }
484
+ if (cj === "." && isDigit(line[j + 1] ?? "")) {
485
+ j += 1;
486
+ continue;
487
+ }
488
+ if (cj === "_") {
489
+ j += 1;
490
+ continue;
491
+ }
492
+ if ((cj === "e" || cj === "E") && /[+\-0-9]/.test(line[j + 1] ?? "")) {
493
+ j += 1;
494
+ if (line[j] === "+" || line[j] === "-") j += 1;
495
+ while (j < len && isDigit(line[j] ?? "")) j += 1;
496
+ continue;
497
+ }
498
+ break;
499
+ }
500
+ return j;
501
+ }
502
+ function readIdentifierToken(line, i) {
503
+ let j = i + 1;
504
+ while (j < line.length && isIdentifierPart(line[j] ?? "")) j += 1;
505
+ return j;
506
+ }
507
+ function readStringToken(line, i) {
508
+ const quote = line[i] ?? "";
509
+ let j = i + 1;
510
+ const len = line.length;
511
+ while (j < len) {
512
+ const cj = line[j] ?? "";
513
+ if (cj === "\\") {
514
+ j += 2;
515
+ continue;
516
+ }
517
+ if (cj === quote) {
518
+ j += 1;
519
+ return j;
520
+ }
521
+ j += 1;
522
+ }
523
+ return j;
524
+ }
525
+ function readBlockCommentToken(line, i) {
526
+ let j = i + 2;
527
+ const len = line.length;
528
+ while (j < len) {
529
+ if (line[j] === "*" && line[j + 1] === "/") return j + 2;
530
+ j += 1;
531
+ }
532
+ return len;
533
+ }
534
+ function highlightSingleLine(line, keywords) {
535
+ const tokens = [];
536
+ let i = 0;
537
+ const len = line.length;
538
+ const commentStart = line[0] === "#";
539
+ while (i < len) {
540
+ const ch = line[i] ?? "";
541
+ if (ch === "/" && line[i + 1] === "*") {
542
+ const end = readBlockCommentToken(line, i);
543
+ tokens.push({ text: line.slice(i, end), paint: COMMENT });
544
+ i = end;
545
+ continue;
546
+ }
547
+ if (commentStart && ch === "#") {
548
+ tokens.push({ text: line.slice(i), paint: COMMENT });
549
+ i = len;
550
+ continue;
551
+ }
552
+ if (ch === "/" && line[i + 1] === "/") {
553
+ tokens.push({ text: line.slice(i), paint: COMMENT });
554
+ i = len;
555
+ continue;
556
+ }
557
+ if (ch === "'" || ch === '"' || ch === "`") {
558
+ const end = readStringToken(line, i);
559
+ tokens.push({ text: line.slice(i, end), paint: STRING });
560
+ i = end;
561
+ continue;
562
+ }
563
+ if (isNumberPrefix(line, i)) {
564
+ const end = readNumberToken(line, i);
565
+ tokens.push({ text: line.slice(i, end), paint: NUMBER });
566
+ i = end;
567
+ continue;
568
+ }
569
+ if (isIdentifierStart(ch)) {
570
+ const end = readIdentifierToken(line, i);
571
+ const word = line.slice(i, end);
572
+ if (ch === "@") {
573
+ tokens.push({ text: word, paint: ATTRIBUTE });
574
+ } else if (keywords.has(word)) {
575
+ tokens.push({ text: word, paint: KEYWORD });
576
+ } else if (line[end] === "(") {
577
+ tokens.push({ text: word, paint: FUNCTION });
578
+ } else {
579
+ tokens.push({ text: word, paint: (s) => s });
580
+ }
581
+ i = end;
582
+ continue;
583
+ }
584
+ let j = i + 1;
585
+ while (j < len) {
586
+ const cj = line[j] ?? "";
587
+ if (isIdentifierStart(cj) || isDigit(cj) || cj === "'" || cj === '"' || cj === "`" || cj === "@" || cj === "/") break;
588
+ j += 1;
589
+ }
590
+ tokens.push({ text: line.slice(i, j), paint: (s) => s });
591
+ i = j;
592
+ }
593
+ let out = "";
594
+ for (const token of tokens) {
595
+ out += token.paint(token.text);
596
+ }
597
+ return out;
598
+ }
599
+ function highlightCode(source, language = void 0) {
600
+ if (source.length === 0) return "";
601
+ const lang = normalizeLanguage(language);
602
+ const keywords = lang !== void 0 ? KEYWORDS[lang] : KEYWORDS.javascript;
603
+ return source.split("\n").map((line) => highlightSingleLine(line, keywords)).join("\n");
604
+ }
605
+
606
+ // src/ui/char-width.ts
607
+ function charWidth(codePoint) {
608
+ if (codePoint === 8203 || // ZERO WIDTH SPACE
609
+ codePoint === 8204 || // ZERO WIDTH NON-JOINER
610
+ codePoint === 8205 || // ZERO WIDTH JOINER
611
+ codePoint === 65279 || // BOM / ZERO WIDTH NO-BREAK SPACE
612
+ codePoint >= 768 && codePoint <= 879 || // Combining Diacritical Marks
613
+ codePoint >= 1155 && codePoint <= 1161 || // Cyrillic combining marks
614
+ codePoint >= 1425 && codePoint <= 1469 || // Hebrew combining marks
615
+ codePoint >= 1552 && codePoint <= 1562 || // Arabic combining marks
616
+ codePoint >= 1611 && codePoint <= 1631 || // Arabic combining marks
617
+ codePoint >= 1648 && codePoint <= 1648 || // Arabic combining marks
618
+ codePoint >= 1750 && codePoint <= 1756 || // Arabic combining marks
619
+ codePoint >= 1759 && codePoint <= 1764 || // Arabic combining marks
620
+ codePoint >= 1767 && codePoint <= 1768 || // Arabic combining marks
621
+ codePoint >= 1770 && codePoint <= 1773 || // Arabic combining marks
622
+ codePoint >= 1809 && codePoint <= 1809 || // Syriac combining marks
623
+ codePoint >= 1840 && codePoint <= 1866 || // Syriac combining marks
624
+ codePoint >= 1958 && codePoint <= 1968 || // Thaana combining marks
625
+ codePoint >= 2304 && codePoint <= 2306 || // Devanagari combining marks
626
+ codePoint >= 2362 && codePoint <= 2362 || // Devanagari combining marks
627
+ codePoint >= 2364 && codePoint <= 2364 || // Devanagari combining marks
628
+ codePoint >= 2369 && codePoint <= 2376 || // Devanagari combining marks
629
+ codePoint >= 2381 && codePoint <= 2381 || // Devanagari combining marks
630
+ codePoint >= 2385 && codePoint <= 2391 || // Devanagari combining marks
631
+ codePoint >= 2402 && codePoint <= 2403 || // Devanagari combining marks
632
+ codePoint >= 2433 && codePoint <= 2433 || // Bengali combining marks
633
+ codePoint >= 2492 && codePoint <= 2492 || // Bengali combining marks
634
+ codePoint >= 2497 && codePoint <= 2500 || // Bengali combining marks
635
+ codePoint >= 2509 && codePoint <= 2509 || // Bengali combining marks
636
+ codePoint >= 2530 && codePoint <= 2531 || // Bengali combining marks
637
+ codePoint >= 2561 && codePoint <= 2562 || // Gurmukhi combining marks
638
+ codePoint >= 2620 && codePoint <= 2620 || // Gurmukhi combining marks
639
+ codePoint >= 2625 && codePoint <= 2626 || // Gurmukhi combining marks
640
+ codePoint >= 2631 && codePoint <= 2632 || // Gurmukhi combining marks
641
+ codePoint >= 2635 && codePoint <= 2637 || // Gurmukhi combining marks
642
+ codePoint >= 2672 && codePoint <= 2673 || // Gurmukhi combining marks
643
+ codePoint >= 2689 && codePoint <= 2690 || // Gujarati combining marks
644
+ codePoint >= 2748 && codePoint <= 2748 || // Gujarati combining marks
645
+ codePoint >= 2753 && codePoint <= 2757 || // Gujarati combining marks
646
+ codePoint >= 2759 && codePoint <= 2760 || // Gujarati combining marks
647
+ codePoint >= 2765 && codePoint <= 2765 || // Gujarati combining marks
648
+ codePoint >= 2786 && codePoint <= 2787 || // Gujarati combining marks
649
+ codePoint >= 2817 && codePoint <= 2817 || // Oriya combining marks
650
+ codePoint >= 2876 && codePoint <= 2876 || // Oriya combining marks
651
+ codePoint >= 2879 && codePoint <= 2879 || // Oriya combining marks
652
+ codePoint >= 2881 && codePoint <= 2883 || // Oriya combining marks
653
+ codePoint >= 2893 && codePoint <= 2893 || // Oriya combining marks
654
+ codePoint >= 2902 && codePoint <= 2902 || // Oriya combining marks
655
+ codePoint >= 2946 && codePoint <= 2946 || // Tamil combining marks
656
+ codePoint >= 3008 && codePoint <= 3008 || // Tamil combining marks
657
+ codePoint >= 3021 && codePoint <= 3021 || // Tamil combining marks
658
+ codePoint >= 3134 && codePoint <= 3136 || // Telugu combining marks
659
+ codePoint >= 3142 && codePoint <= 3144 || // Telugu combining marks
660
+ codePoint >= 3146 && codePoint <= 3149 || // Telugu combining marks
661
+ codePoint >= 3157 && codePoint <= 3158 || // Telugu combining marks
662
+ codePoint >= 3260 && codePoint <= 3260 || // Kannada combining marks
663
+ codePoint >= 3263 && codePoint <= 3263 || // Kannada combining marks
664
+ codePoint >= 3270 && codePoint <= 3270 || // Kannada combining marks
665
+ codePoint >= 3276 && codePoint <= 3277 || // Kannada combining marks
666
+ codePoint >= 3298 && codePoint <= 3299 || // Kannada combining marks
667
+ codePoint >= 3393 && codePoint <= 3395 || // Malayalam combining marks
668
+ codePoint >= 3405 && codePoint <= 3405 || // Malayalam combining marks
669
+ codePoint >= 3530 && codePoint <= 3530 || // Sinhala combining marks
670
+ codePoint >= 3538 && codePoint <= 3540 || // Sinhala combining marks
671
+ codePoint >= 3542 && codePoint <= 3542 || // Sinhala combining marks
672
+ codePoint >= 3633 && codePoint <= 3633 || // Thai combining marks
673
+ codePoint >= 3636 && codePoint <= 3642 || // Thai combining marks
674
+ codePoint >= 3655 && codePoint <= 3662 || // Thai combining marks
675
+ codePoint >= 3761 && codePoint <= 3761 || // Lao combining marks
676
+ codePoint >= 3764 && codePoint <= 3769 || // Lao combining marks
677
+ codePoint >= 3771 && codePoint <= 3772 || // Lao combining marks
678
+ codePoint >= 3784 && codePoint <= 3789 || // Lao combining marks
679
+ codePoint >= 3864 && codePoint <= 3865 || // Tibetan combining marks
680
+ codePoint >= 3893 && codePoint <= 3893 || // Tibetan combining marks
681
+ codePoint >= 3895 && codePoint <= 3895 || // Tibetan combining marks
682
+ codePoint >= 3897 && codePoint <= 3897 || // Tibetan combining marks
683
+ codePoint >= 3953 && codePoint <= 3966 || // Tibetan combining marks
684
+ codePoint >= 3968 && codePoint <= 3972 || // Tibetan combining marks
685
+ codePoint >= 3974 && codePoint <= 3975 || // Tibetan combining marks
686
+ codePoint >= 3984 && codePoint <= 3991 || // Tibetan combining marks
687
+ codePoint >= 3993 && codePoint <= 4028 || // Tibetan combining marks
688
+ codePoint >= 4038 && codePoint <= 4038 || // Tibetan combining marks
689
+ codePoint >= 4146 && codePoint <= 4151 || // Myanmar combining marks
690
+ codePoint >= 4153 && codePoint <= 4154 || // Myanmar combining marks
691
+ codePoint >= 4157 && codePoint <= 4158 || // Myanmar combining marks
692
+ codePoint >= 4184 && codePoint <= 4185 || // Myanmar combining marks
693
+ codePoint >= 4190 && codePoint <= 4192 || // Myanmar combining marks
694
+ codePoint >= 4209 && codePoint <= 4212 || // Myanmar combining marks
695
+ codePoint >= 4226 && codePoint <= 4226 || // Myanmar combining marks
696
+ codePoint >= 4229 && codePoint <= 4230 || // Myanmar combining marks
697
+ codePoint >= 4237 && codePoint <= 4237 || // Myanmar combining marks
698
+ codePoint >= 4957 && codePoint <= 4959 || // Ethiopic combining marks
699
+ codePoint >= 5906 && codePoint <= 5908 || // Tagalog combining marks
700
+ codePoint >= 5938 && codePoint <= 5940 || // Hanunoo combining marks
701
+ codePoint >= 5970 && codePoint <= 5971 || // Buhid combining marks
702
+ codePoint >= 6002 && codePoint <= 6003 || // Tagbanwa combining marks
703
+ codePoint >= 6068 && codePoint <= 6069 || // Khmer combining marks
704
+ codePoint >= 6071 && codePoint <= 6077 || // Khmer combining marks
705
+ codePoint >= 6086 && codePoint <= 6086 || // Khmer combining marks
706
+ codePoint >= 6089 && codePoint <= 6099 || // Khmer combining marks
707
+ codePoint >= 6109 && codePoint <= 6109 || // Khmer combining marks
708
+ codePoint >= 6155 && codePoint <= 6157 || // Mongolian combining marks
709
+ codePoint >= 6313 && codePoint <= 6313 || // Mongolian combining marks
710
+ codePoint >= 6432 && codePoint <= 6434 || // Limbu combining marks
711
+ codePoint >= 6439 && codePoint <= 6440 || // Limbu combining marks
712
+ codePoint >= 6450 && codePoint <= 6450 || // Limbu combining marks
713
+ codePoint >= 6457 && codePoint <= 6459 || // Limbu combining marks
714
+ codePoint >= 6679 && codePoint <= 6680 || // Buginese combining marks
715
+ codePoint >= 6912 && codePoint <= 6915 || // Balinese combining marks
716
+ codePoint >= 6964 && codePoint <= 6964 || // Balinese combining marks
717
+ codePoint >= 6966 && codePoint <= 6970 || // Balinese combining marks
718
+ codePoint >= 6972 && codePoint <= 6972 || // Balinese combining marks
719
+ codePoint >= 6978 && codePoint <= 6978 || // Balinese combining marks
720
+ codePoint >= 7019 && codePoint <= 7027 || // Balinese combining marks
721
+ codePoint >= 7616 && codePoint <= 7679 || // Combining Diacritical Marks Supplement
722
+ codePoint >= 8400 && codePoint <= 8432 || // Combining Diacritical Marks for Symbols
723
+ codePoint >= 11503 && codePoint <= 11505 || // Coptic combining marks
724
+ codePoint >= 11647 && codePoint <= 11647 || // Tifinagh combining marks
725
+ codePoint >= 11744 && codePoint <= 11775 || // Cyrillic Extended-A combining marks
726
+ codePoint >= 42607 && codePoint <= 42610 || // Cyrillic Extended-B combining marks
727
+ codePoint >= 42612 && codePoint <= 42621 || // Cyrillic Extended-B combining marks
728
+ codePoint >= 42654 && codePoint <= 42655 || // Cyrillic Extended-B combining marks
729
+ codePoint >= 42736 && codePoint <= 42737 || // Bamum combining marks
730
+ codePoint >= 43010 && codePoint <= 43010 || // Syloti Nagri combining marks
731
+ codePoint >= 43014 && codePoint <= 43014 || // Syloti Nagri combining marks
732
+ codePoint >= 43019 && codePoint <= 43019 || // Syloti Nagri combining marks
733
+ codePoint >= 43045 && codePoint <= 43046 || // Syloti Nagri combining marks
734
+ codePoint >= 43204 && codePoint <= 43204 || // Saurashtra combining marks
735
+ codePoint >= 43232 && codePoint <= 43249 || // Devanagari Extended combining marks
736
+ codePoint >= 43302 && codePoint <= 43309 || // Kayah Li combining marks
737
+ codePoint >= 43335 && codePoint <= 43345 || // Rejang combining marks
738
+ codePoint >= 43392 && codePoint <= 43394 || // Javanese combining marks
739
+ codePoint >= 43443 && codePoint <= 43443 || // Javanese combining marks
740
+ codePoint >= 43446 && codePoint <= 43449 || // Javanese combining marks
741
+ codePoint >= 43452 && codePoint <= 43452 || // Javanese combining marks
742
+ codePoint >= 43561 && codePoint <= 43566 || // Cham combining marks
743
+ codePoint >= 43569 && codePoint <= 43570 || // Cham combining marks
744
+ codePoint >= 43573 && codePoint <= 43574 || // Cham combining marks
745
+ codePoint >= 43587 && codePoint <= 43587 || // Cham combining marks
746
+ codePoint >= 43596 && codePoint <= 43596 || // Cham combining marks
747
+ codePoint >= 43696 && codePoint <= 43696 || // Tai Viet combining marks
748
+ codePoint >= 43698 && codePoint <= 43700 || // Tai Viet combining marks
749
+ codePoint >= 43703 && codePoint <= 43704 || // Tai Viet combining marks
750
+ codePoint >= 43710 && codePoint <= 43711 || // Tai Viet combining marks
751
+ codePoint >= 43713 && codePoint <= 43713 || // Tai Viet combining marks
752
+ codePoint >= 44005 && codePoint <= 44005 || // Meetei Mayek combining marks
753
+ codePoint >= 44008 && codePoint <= 44008 || // Meetei Mayek combining marks
754
+ codePoint >= 44013 && codePoint <= 44013 || // Meetei Mayek combining marks
755
+ codePoint >= 64286 && codePoint <= 64286 || // Hebrew combining marks
756
+ codePoint >= 65024 && codePoint <= 65039 || // Variation Selectors
757
+ codePoint >= 65056 && codePoint <= 65071 || // Combining Half Marks
758
+ codePoint >= 917760 && codePoint <= 917999) {
759
+ return 0;
760
+ }
761
+ if (codePoint >= 4352 && codePoint <= 4447 || // Hangul Jamo
762
+ codePoint >= 9001 && codePoint <= 9002 || // Left/Right-pointing angle bracket
763
+ codePoint >= 11904 && codePoint <= 12350 || // CJK Radicals Supplement, Kangxi Radicals, CJK Symbols
764
+ codePoint >= 12352 && codePoint <= 13247 || // Hiragana, Katakana, Bopomofo, Hangul Compatibility, Kanbun, CJK Strokes, Enclosed CJK
765
+ codePoint >= 13312 && codePoint <= 19903 || // CJK Unified Ideographs Extension A
766
+ codePoint >= 19968 && codePoint <= 42191 || // CJK Unified Ideographs, Yi Syllables, Yi Radicals
767
+ codePoint >= 43360 && codePoint <= 43388 || // Hangul Jamo Extended-A
768
+ codePoint >= 44032 && codePoint <= 55203 || // Hangul Syllables
769
+ codePoint >= 63744 && codePoint <= 64255 || // CJK Compatibility Ideographs
770
+ codePoint >= 65040 && codePoint <= 65049 || // Vertical forms
771
+ codePoint >= 65072 && codePoint <= 65135 || // CJK Compatibility Forms, Small Form Variants
772
+ codePoint >= 65281 && codePoint <= 65376 || // Fullwidth Forms
773
+ codePoint >= 65504 && codePoint <= 65510 || // Fullwidth Signs
774
+ codePoint >= 110592 && codePoint <= 111359 || // Kana Supplement, Kana Extended-A, Small Kana Extension
775
+ codePoint >= 126980 && codePoint <= 126980 || // Mahjong tile
776
+ codePoint >= 127183 && codePoint <= 127183 || // Playing card
777
+ codePoint >= 127232 && codePoint <= 129535 || // Enclosed Alphanumeric Supplement, Emoticons, Transport, etc.
778
+ codePoint >= 129536 && codePoint <= 129647 || // Chess Symbols
779
+ codePoint >= 129648 && codePoint <= 129791 || // Symbols and Pictographs Extended-A
780
+ codePoint >= 131072 && codePoint <= 196605 || // CJK Unified Ideographs Extension B–G, CJK Compatibility Supplement
781
+ codePoint >= 196608 && codePoint <= 262141) {
782
+ return 2;
783
+ }
784
+ return 1;
785
+ }
786
+
787
+ // src/ui/markdown.tsx
788
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
789
+ function MarkdownViewInner({ text }) {
790
+ const stablePrefixRef = useRef2("");
791
+ if (!text.startsWith(stablePrefixRef.current)) stablePrefixRef.current = "";
792
+ const boundary = stablePrefixRef.current.length;
793
+ const suffixTokens = useMemo2(() => {
794
+ try {
795
+ return marked.lexer(text.substring(boundary), { gfm: true, async: false });
796
+ } catch {
797
+ return [];
798
+ }
799
+ }, [text, boundary]);
800
+ let lastContentIndex = suffixTokens.length - 1;
801
+ while (lastContentIndex >= 0 && suffixTokens[lastContentIndex]?.type === "space") lastContentIndex--;
802
+ let advance = 0;
803
+ for (let index = 0; index < lastContentIndex; index++) advance += suffixTokens[index]?.raw.length ?? 0;
804
+ if (advance > 0) stablePrefixRef.current = text.substring(0, boundary + advance);
805
+ const stablePrefix = stablePrefixRef.current;
806
+ const unstableSuffix = text.substring(stablePrefix.length);
807
+ return /* @__PURE__ */ jsxs(Box_default, { flexDirection: "column", width: "100%", children: [
808
+ stablePrefix.length > 0 ? /* @__PURE__ */ jsx(MarkdownSegment, { text: stablePrefix }) : null,
809
+ unstableSuffix.length > 0 ? /* @__PURE__ */ jsx(MarkdownSegment, { text: unstableSuffix }) : null
810
+ ] });
811
+ }
812
+ var MarkdownSegment = React.memo(function MarkdownSegment2({ text }) {
813
+ const tokens = useMemo2(() => {
814
+ try {
815
+ return marked.lexer(text, { gfm: true, async: false });
816
+ } catch {
817
+ return [];
818
+ }
819
+ }, [text]);
820
+ return /* @__PURE__ */ jsx(Fragment, { children: tokens.map((token, index) => /* @__PURE__ */ jsx(BlockToken, { token }, index)) });
821
+ });
822
+ var MarkdownView = React.memo(MarkdownViewInner);
823
+ var BlockToken = React.memo(function BlockToken2({ token }) {
824
+ switch (token.type) {
825
+ case "space":
826
+ return /* @__PURE__ */ jsx(Box_default, { height: 1 });
827
+ case "heading":
828
+ return /* @__PURE__ */ jsx(HeadingView, { token });
829
+ case "paragraph":
830
+ return /* @__PURE__ */ jsx(Text, { children: /* @__PURE__ */ jsx(InlineTokens, { tokens: token.tokens ?? [] }) });
831
+ case "code":
832
+ return /* @__PURE__ */ jsx(CodeBlock, { token });
833
+ case "blockquote":
834
+ return /* @__PURE__ */ jsx(BlockquoteView, { token });
835
+ case "list":
836
+ return /* @__PURE__ */ jsx(ListView, { token });
837
+ case "hr":
838
+ return /* @__PURE__ */ jsx(HrView, {});
839
+ case "table":
840
+ return /* @__PURE__ */ jsx(TableView, { token });
841
+ case "html":
842
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[html]" });
843
+ default:
844
+ if ("text" in token && typeof token.text === "string") {
845
+ return /* @__PURE__ */ jsx(Text, { children: token.text });
846
+ }
847
+ return null;
848
+ }
849
+ });
850
+ function HeadingView({ token }) {
851
+ const styled = useMemo2(() => {
852
+ const inner = renderInlineText(token.tokens);
853
+ switch (token.depth) {
854
+ case 1:
855
+ return chalk2.bold.hex("#7dd3fc")(inner);
856
+ case 2:
857
+ return chalk2.bold.hex("#67e8f9")(inner);
858
+ case 3:
859
+ return chalk2.bold.hex("#93c5fd")(inner);
860
+ default:
861
+ return chalk2.italic.hex("#93c5fd")(inner);
862
+ }
863
+ }, [token.depth, token.tokens]);
864
+ return /* @__PURE__ */ jsx(Text, { children: styled });
865
+ }
866
+ function CodeBlock({ token }) {
867
+ const { stdout } = useStdout();
868
+ const columns = stdout?.columns ?? 80;
869
+ const innerWidth = Math.max(20, columns - 6);
870
+ const styledSource = useMemo2(() => highlightCode(token.text, token.lang), [token.text, token.lang]);
871
+ const lines = useMemo2(() => styledSource.split("\n"), [styledSource]);
872
+ const truncated = useMemo2(() => lines.map((line) => truncateToWidth(line, innerWidth - 2)), [lines, innerWidth]);
873
+ const langLabel = token.lang?.trim();
874
+ const label = langLabel && langLabel.length > 0 ? langLabel : "code";
875
+ const topBarWidth = Math.max(0, columns - 4 - label.length - 4);
876
+ return /* @__PURE__ */ jsxs(Box_default, { flexDirection: "column", paddingX: 1, children: [
877
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
878
+ "\u256D\u2500 ",
879
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: label }),
880
+ " ",
881
+ "\u2500".repeat(topBarWidth),
882
+ "\u256E"
883
+ ] }),
884
+ /* @__PURE__ */ jsx(Box_default, { flexDirection: "column", paddingX: 1, children: truncated.map((line, i) => /* @__PURE__ */ jsx(Text, { color: "gray", children: line }, i)) }),
885
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
886
+ "\u2570",
887
+ "\u2500".repeat(Math.max(0, columns - 6)),
888
+ "\u256F"
889
+ ] })
890
+ ] });
891
+ }
892
+ function BlockquoteView({ token }) {
893
+ return /* @__PURE__ */ jsx(Box_default, { flexDirection: "column", children: token.tokens.map((child, i) => {
894
+ const inline = "tokens" in child ? child.tokens ?? [] : [];
895
+ return /* @__PURE__ */ jsxs(Text, { children: [
896
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u2502 " }),
897
+ /* @__PURE__ */ jsx(InlineTokens, { tokens: inline })
898
+ ] }, i);
899
+ }) });
900
+ }
901
+ function HrView() {
902
+ const { stdout } = useStdout();
903
+ const width = Math.max(8, (stdout?.columns ?? 80) - 4);
904
+ return /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u2500".repeat(width) });
905
+ }
906
+ function ListView({ token }) {
907
+ const start = typeof token.start === "number" ? token.start : 1;
908
+ return /* @__PURE__ */ jsx(Box_default, { flexDirection: "column", children: token.items.map((item, i) => /* @__PURE__ */ jsx(ListItemView, { item, index: start + i, ordered: token.ordered }, i)) });
909
+ }
910
+ function ListItemView({
911
+ item,
912
+ index,
913
+ ordered
914
+ }) {
915
+ const bullet = ordered ? `${index}.` : "-";
916
+ const inlineTokens = [];
917
+ const blockTokens = [];
918
+ let firstParagraphConsumed = false;
919
+ for (const child of item.tokens) {
920
+ if (child.type === "paragraph") {
921
+ const paragraph = child;
922
+ if (!firstParagraphConsumed) {
923
+ inlineTokens.push(paragraph);
924
+ firstParagraphConsumed = true;
925
+ } else {
926
+ blockTokens.push(child);
927
+ }
928
+ } else if (child.type === "text") {
929
+ inlineTokens.push(child);
930
+ } else {
931
+ blockTokens.push(child);
932
+ }
933
+ }
934
+ return /* @__PURE__ */ jsxs(Box_default, { flexDirection: "column", children: [
935
+ /* @__PURE__ */ jsxs(Text, { children: [
936
+ /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
937
+ bullet,
938
+ " "
939
+ ] }),
940
+ inlineTokens.map((child, i) => {
941
+ if (child.type === "paragraph") {
942
+ return /* @__PURE__ */ jsx(InlineTokens, { tokens: child.tokens ?? [] }, i);
943
+ }
944
+ const tokens = child.tokens ?? [];
945
+ return tokens.length > 0 ? /* @__PURE__ */ jsx(InlineTokens, { tokens }, i) : /* @__PURE__ */ jsx(Text, { children: child.text }, i);
946
+ })
947
+ ] }),
948
+ blockTokens.map((child, i) => /* @__PURE__ */ jsx(Box_default, { marginLeft: 2, children: /* @__PURE__ */ jsx(BlockToken, { token: child }) }, i))
949
+ ] });
950
+ }
951
+ function TableView({ token }) {
952
+ const { stdout } = useStdout();
953
+ const maxWidth = stdout?.columns ?? 80;
954
+ const colCount = token.header.length;
955
+ if (colCount === 0) return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[empty table]" });
956
+ const colWidth = Math.max(8, Math.floor((maxWidth - colCount * 3) / colCount));
957
+ const pad = (value, w, align) => {
958
+ const stripped = stripAnsi2(value);
959
+ const width = Math.max(0, w);
960
+ if (stripped.length > width) return stripped.slice(0, width - 1) + "\u2026";
961
+ const diff = width - stripped.length;
962
+ if (align === "right") return " ".repeat(diff) + stripped;
963
+ if (align === "center") {
964
+ const left = Math.floor(diff / 2);
965
+ const right = diff - left;
966
+ return " ".repeat(left) + stripped + " ".repeat(right);
967
+ }
968
+ return stripped + " ".repeat(diff);
969
+ };
970
+ const separator = token.header.map((cell) => {
971
+ const align = cell.align;
972
+ if (align === "left") return ":" + "\u2500".repeat(colWidth - 2) + " ";
973
+ if (align === "right") return " " + "\u2500".repeat(colWidth - 2) + ":";
974
+ if (align === "center") return ":" + "\u2500".repeat(colWidth - 2) + ":";
975
+ return "\u2500".repeat(colWidth);
976
+ });
977
+ return /* @__PURE__ */ jsxs(Box_default, { flexDirection: "column", children: [
978
+ /* @__PURE__ */ jsx(Text, { children: token.header.map((cell, i) => pad(cell.text, colWidth, cell.align ?? "left")).join(" \u2502 ") }),
979
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: separator.join(" \u2502 ") }),
980
+ token.rows.map((row, ri) => /* @__PURE__ */ jsx(Text, { children: row.map((cell, i) => pad(cell.text, colWidth, token.header[i]?.align ?? "left")).join(" \u2502 ") }, ri))
981
+ ] });
982
+ }
983
+ function InlineTokens({ tokens }) {
984
+ return /* @__PURE__ */ jsx(Text, { children: tokens.map((token, i) => /* @__PURE__ */ jsx(InlineToken, { token }, i)) });
985
+ }
986
+ function InlineToken({ token }) {
987
+ switch (token.type) {
988
+ case "text":
989
+ return /* @__PURE__ */ jsx(Text, { children: token.raw ?? token.text });
990
+ case "strong":
991
+ return /* @__PURE__ */ jsx(Text, { bold: true, children: renderInlineText(token.tokens) });
992
+ case "em":
993
+ return /* @__PURE__ */ jsx(Text, { italic: true, children: renderInlineText(token.tokens) });
994
+ case "del":
995
+ return /* @__PURE__ */ jsx(Text, { strikethrough: true, children: renderInlineText(token.tokens) });
996
+ case "codespan":
997
+ return /* @__PURE__ */ jsx(Text, { color: "black", backgroundColor: "gray", children: token.text });
998
+ case "link": {
999
+ const link = token;
1000
+ const inner = renderInlineText(link.tokens);
1001
+ return /* @__PURE__ */ jsxs(Text, { children: [
1002
+ inner,
1003
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, color: "blue", children: [
1004
+ " (",
1005
+ link.href,
1006
+ ")"
1007
+ ] })
1008
+ ] });
1009
+ }
1010
+ case "image": {
1011
+ const img = token;
1012
+ return /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1013
+ "[image: ",
1014
+ img.text,
1015
+ "]"
1016
+ ] });
1017
+ }
1018
+ case "br":
1019
+ return /* @__PURE__ */ jsx(Text, { children: "\n" });
1020
+ case "escape":
1021
+ return /* @__PURE__ */ jsx(Text, { children: token.text });
1022
+ case "html":
1023
+ return null;
1024
+ default:
1025
+ if ("text" in token && typeof token.text === "string") {
1026
+ return /* @__PURE__ */ jsx(Text, { children: token.text });
1027
+ }
1028
+ return null;
1029
+ }
1030
+ }
1031
+ var ANSI_RE = /\x1B\[[0-?]*[ -/]*[@-~]/g;
1032
+ function stripAnsi2(value) {
1033
+ return value.replace(ANSI_RE, "");
1034
+ }
1035
+ function visibleWidth(value) {
1036
+ let width = 0;
1037
+ for (const ch of stripAnsi2(value)) {
1038
+ width += charWidth(ch.codePointAt(0) ?? 0);
1039
+ }
1040
+ return width;
1041
+ }
1042
+ function truncateToWidth(value, width) {
1043
+ if (width <= 0) return "";
1044
+ const stripped = stripAnsi2(value);
1045
+ if (visibleWidth(stripped) <= width) return value;
1046
+ const prefix = value.match(/^\x1B\[[0-?]*[ -/]*[@-~]*/)?.[0] ?? "";
1047
+ const suffix = "\x1B[0m";
1048
+ let out = "";
1049
+ let visual = 0;
1050
+ for (const ch of stripped) {
1051
+ const cw = charWidth(ch.codePointAt(0) ?? 0);
1052
+ if (visual + cw > width - 1) break;
1053
+ out += ch;
1054
+ visual += cw;
1055
+ }
1056
+ return prefix + out + "\u2026" + suffix;
1057
+ }
1058
+ function renderInlineText(tokens) {
1059
+ let out = "";
1060
+ for (const tok of tokens) {
1061
+ out += inlineTokenToString(tok);
1062
+ }
1063
+ return out;
1064
+ }
1065
+ function inlineTokenToString(token) {
1066
+ switch (token.type) {
1067
+ case "text":
1068
+ return token.raw ?? token.text;
1069
+ case "strong":
1070
+ return chalk2.bold(renderInlineText(token.tokens));
1071
+ case "em":
1072
+ return chalk2.italic(renderInlineText(token.tokens));
1073
+ case "del":
1074
+ return chalk2.strikethrough(renderInlineText(token.tokens));
1075
+ case "codespan":
1076
+ return chalk2.bgGray.black(` ${token.text} `);
1077
+ case "escape":
1078
+ return token.text;
1079
+ case "br":
1080
+ return "\n";
1081
+ case "link": {
1082
+ const link = token;
1083
+ const inner = renderInlineText(link.tokens);
1084
+ return `${inner} ${chalk2.blue.underline.dim(`(${link.href})`)}`;
1085
+ }
1086
+ default:
1087
+ if ("text" in token && typeof token.text === "string") return token.text;
1088
+ return "";
1089
+ }
1090
+ }
1091
+
1092
+ // src/ui/assistant-text.tsx
1093
+ import { jsx as jsx2 } from "react/jsx-runtime";
1094
+ function AssistantTextInner({ text }) {
1095
+ return /* @__PURE__ */ jsx2(MarkdownView, { text });
1096
+ }
1097
+ var AssistantText = React2.memo(AssistantTextInner);
1098
+
1099
+ // src/ui/signals.ts
1100
+ function createSessionInterruptHandler(getSession, shutdown) {
1101
+ return () => {
1102
+ if (getSession()?.interrupt() === "cancelled") return;
1103
+ void shutdown();
1104
+ };
1105
+ }
1106
+ function installSigintHandler(source, handler) {
1107
+ source.on("SIGINT", handler);
1108
+ return () => {
1109
+ source.off("SIGINT", handler);
1110
+ };
1111
+ }
1112
+
1113
+ // src/ui/wrap-prompt.ts
1114
+ function wrapPromptInput(input, codePointCursor, options) {
1115
+ const indent = Math.max(0, options.indent ?? 2);
1116
+ const columns = Math.max(1, options.columns);
1117
+ const usable = Math.max(1, columns - indent);
1118
+ const points = [...input];
1119
+ const len = points.length;
1120
+ const clamped = Math.max(0, Math.min(len, codePointCursor));
1121
+ const lines = [];
1122
+ let i = 0;
1123
+ while (i < len) {
1124
+ let lineVisualWidth = 0;
1125
+ const lineStart = i;
1126
+ while (i < len) {
1127
+ const cp = points[i].codePointAt(0) ?? 0;
1128
+ const cw = charWidth(cp);
1129
+ if (lineVisualWidth + cw > usable) break;
1130
+ lineVisualWidth += cw;
1131
+ i += 1;
1132
+ }
1133
+ if (i === lineStart) i += 1;
1134
+ lines.push(points.slice(lineStart, i).join(""));
1135
+ }
1136
+ if (lines.length === 0) lines.push("");
1137
+ let cursor = { line: 0, column: 0 };
1138
+ let consumed = 0;
1139
+ for (let line = 0; line < lines.length; line += 1) {
1140
+ const lineLength = [...lines[line] ?? ""].length;
1141
+ const next = consumed + lineLength;
1142
+ if (clamped <= next) {
1143
+ cursor = { line, column: clamped - consumed };
1144
+ break;
1145
+ }
1146
+ consumed = next;
1147
+ }
1148
+ return { lines, cursor, height: lines.length };
1149
+ }
1150
+
1151
+ // src/ui/task-progress.tsx
1152
+ import { useRef as useRef3 } from "react";
1153
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1154
+ function TaskStatusLine({ block, interactive }) {
1155
+ const running = block.state === "running";
1156
+ const foreground = interactive && (block.activity === "model" || block.task?.role === "main");
1157
+ const [ref, time] = useAnimationFrame(running && foreground ? 120 : null);
1158
+ const startedAt = useRef3(void 0);
1159
+ const previousState = useRef3(block.state);
1160
+ if (running && (startedAt.current === void 0 || previousState.current !== "running")) startedAt.current = time;
1161
+ if (!running) startedAt.current = void 0;
1162
+ previousState.current = block.state;
1163
+ const elapsed = block.startedAt === void 0 ? startedAt.current === void 0 ? 0 : Math.max(0, time - startedAt.current) : Math.max(0, Date.now() - block.startedAt);
1164
+ const presentation = statusPresentation(block, elapsed, foreground);
1165
+ const { statusLabel, statusColor, text } = presentation;
1166
+ let before = text;
1167
+ let after = "";
1168
+ if (statusLabel !== void 0) {
1169
+ const marker = ` \xB7 ${statusLabel}`;
1170
+ const idx = before.indexOf(marker);
1171
+ if (idx >= 0) {
1172
+ after = before.slice(idx + marker.length);
1173
+ before = before.slice(0, idx);
1174
+ }
1175
+ }
1176
+ return /* @__PURE__ */ jsxs2(Box_default, { ref, flexDirection: "row", children: [
1177
+ /* @__PURE__ */ jsxs2(Text, { ...presentation.color === void 0 ? {} : { color: presentation.color }, children: [
1178
+ presentation.glyph,
1179
+ " "
1180
+ ] }),
1181
+ presentation.badge ? /* @__PURE__ */ jsxs2(Text, { color: presentation.badgeColor, children: [
1182
+ presentation.badge,
1183
+ " "
1184
+ ] }) : null,
1185
+ /* @__PURE__ */ jsx3(Text, { ...presentation.color === void 0 ? {} : { color: presentation.color }, children: before }),
1186
+ statusLabel !== void 0 ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
1187
+ /* @__PURE__ */ jsx3(Text, { ...presentation.color === void 0 ? {} : { color: presentation.color }, children: " \xB7 " }),
1188
+ /* @__PURE__ */ jsx3(Text, { color: statusColor ?? presentation.color, children: statusLabel })
1189
+ ] }) : null,
1190
+ after ? /* @__PURE__ */ jsx3(Text, { ...presentation.color === void 0 ? {} : { color: presentation.color }, children: after }) : null
1191
+ ] });
1192
+ }
1193
+ function TaskProgressPanel({
1194
+ blocks,
1195
+ interactive,
1196
+ maxHeight = 8,
1197
+ scrollRef,
1198
+ onHoverChange
1199
+ }) {
1200
+ if (blocks.length === 0 || maxHeight <= 0) return null;
1201
+ return /* @__PURE__ */ jsxs2(
1202
+ Box_default,
1203
+ {
1204
+ flexDirection: "column",
1205
+ flexShrink: 0,
1206
+ maxHeight,
1207
+ onMouseEnter: () => onHoverChange?.(true),
1208
+ onMouseLeave: () => onHoverChange?.(false),
1209
+ children: [
1210
+ /* @__PURE__ */ jsx3(Text, { dimColor: true, children: "\u2500\u2500 task progress \u2500\u2500" }),
1211
+ maxHeight > 1 ? /* @__PURE__ */ jsx3(
1212
+ ScrollBox_default,
1213
+ {
1214
+ ...scrollRef === void 0 ? {} : { ref: scrollRef },
1215
+ flexDirection: "column",
1216
+ flexShrink: 1,
1217
+ maxHeight: maxHeight - 1,
1218
+ children: blocks.map((block) => /* @__PURE__ */ jsx3(TaskStatusLine, { block, interactive }, block.id))
1219
+ }
1220
+ ) : null
1221
+ ]
1222
+ }
1223
+ );
1224
+ }
1225
+
1226
+ // src/ui/file-diff-style.ts
1227
+ var CONTENT = "#f8f8f2";
1228
+ function fileDiffLineStyle(kind) {
1229
+ if (kind === "removed") {
1230
+ return { backgroundColor: "#3d0100", markerColor: "#ff5f56", contentColor: CONTENT };
1231
+ }
1232
+ if (kind === "added") {
1233
+ return { backgroundColor: "#022800", markerColor: "#50c878", contentColor: CONTENT };
1234
+ }
1235
+ return { markerColor: CONTENT, contentColor: CONTENT };
1236
+ }
1237
+
1238
+ // src/ui/slash-completion.ts
1239
+ function slashCandidatePresentation(selected) {
1240
+ return {
1241
+ marker: selected ? "\u203A " : " ",
1242
+ rowStyle: {},
1243
+ matchStyle: { color: "ansi:cyan", bold: true }
1244
+ };
1245
+ }
1246
+ function buildSlashCandidates(commands, plugins, skills) {
1247
+ const candidates = /* @__PURE__ */ new Map();
1248
+ for (const command of commands) {
1249
+ candidates.set(command.name, { name: command.name, kind: "command", description: command.description });
1250
+ }
1251
+ for (const name of plugins) {
1252
+ if (!candidates.has(name)) candidates.set(name, { name, kind: "plugin" });
1253
+ }
1254
+ for (const skill of skills) {
1255
+ if (!candidates.has(skill.name)) {
1256
+ candidates.set(skill.name, {
1257
+ name: skill.name,
1258
+ kind: "skill",
1259
+ description: skill.description,
1260
+ source: skill.source
1261
+ });
1262
+ }
1263
+ }
1264
+ return [...candidates.values()];
1265
+ }
1266
+ function completedSlashTokenLength(input, candidates, menuOpen) {
1267
+ if (menuOpen) return 0;
1268
+ const points = [...input];
1269
+ if (points[0] !== "/") return 0;
1270
+ const whitespace = points.findIndex((point) => /\s/u.test(point));
1271
+ let tokenEnd = whitespace < 0 ? points.length : whitespace;
1272
+ const name = points.slice(1, tokenEnd).join("");
1273
+ const matches = candidates.some((candidate) => candidate.name === name);
1274
+ return matches ? tokenEnd : 0;
1275
+ }
1276
+ function completedSlashTokenPresentation() {
1277
+ return { color: "rgb(120,155,255)", bold: true };
1278
+ }
1279
+ function deriveSlashCompletion(input, cursor, candidates, selectedIndex, visibleLimit = 6) {
1280
+ const points = [...input];
1281
+ const safeCursor = Math.max(0, Math.min(points.length, cursor));
1282
+ if (points[0] !== "/") return null;
1283
+ const whitespace = points.findIndex((point) => /\s/u.test(point));
1284
+ const tokenEnd = whitespace < 0 ? points.length : whitespace;
1285
+ if (safeCursor < 1 || safeCursor > tokenEnd) return null;
1286
+ const query = points.slice(1, tokenEnd).join("");
1287
+ const normalized = query.toLowerCase();
1288
+ const items = candidates.filter(({ name }) => name.toLowerCase().includes(normalized)).sort((left, right) => {
1289
+ const leftPrefix = left.name.toLowerCase().startsWith(normalized);
1290
+ const rightPrefix = right.name.toLowerCase().startsWith(normalized);
1291
+ if (leftPrefix !== rightPrefix) return leftPrefix ? -1 : 1;
1292
+ return left.name.localeCompare(right.name);
1293
+ });
1294
+ if (items.length === 0) return null;
1295
+ const selected = Math.max(0, Math.min(items.length - 1, selectedIndex));
1296
+ const limit = Math.max(1, visibleLimit);
1297
+ const windowStart = Math.max(0, Math.min(selected, items.length - limit));
1298
+ return { query, items, selectedIndex: selected, windowStart };
1299
+ }
1300
+ function moveSlashSelection(index, delta, count) {
1301
+ if (count <= 0) return 0;
1302
+ return (index + delta + count) % count;
1303
+ }
1304
+ function completeSlashSelection(input, cursor, name) {
1305
+ const points = [...input];
1306
+ const safeCursor = Math.max(0, Math.min(points.length, cursor));
1307
+ const tokenEndOffset = points.slice(safeCursor).findIndex((point) => /\s/u.test(point));
1308
+ const tokenEnd = tokenEndOffset < 0 ? points.length : safeCursor + tokenEndOffset;
1309
+ const suffix = points.slice(tokenEnd).join("").replace(/^\s*/u, "");
1310
+ const prefix = `/${name} `;
1311
+ return { text: prefix + suffix, cursor: [...prefix].length };
1312
+ }
1313
+ function matchRanges(value, query) {
1314
+ if (query.length === 0) return [];
1315
+ const normalizedValue = value.toLowerCase();
1316
+ const normalizedQuery = query.toLowerCase();
1317
+ const ranges = [];
1318
+ let start = 0;
1319
+ while (start < normalizedValue.length) {
1320
+ const index = normalizedValue.indexOf(normalizedQuery, start);
1321
+ if (index < 0) break;
1322
+ ranges.push([index, index + normalizedQuery.length]);
1323
+ start = index + normalizedQuery.length;
1324
+ }
1325
+ return ranges;
1326
+ }
1327
+
1328
+ // src/ui/compact-progress.ts
1329
+ var COMPACT_PROGRESS_CELLS = 10;
1330
+ var COMPACT_PROGRESS_COMPLETE = "#5b8cff";
1331
+ var COMPACT_PROGRESS_REMAINING = "#525761";
1332
+ function compactProgressPresentation(progress) {
1333
+ const normalized = Math.max(0, Math.min(100, Math.floor(progress / 10) * 10));
1334
+ const completed = normalized / 10;
1335
+ return {
1336
+ progress: normalized,
1337
+ cells: Array.from({ length: COMPACT_PROGRESS_CELLS }, (_, index) => ({
1338
+ completed: index < completed,
1339
+ color: index < completed ? COMPACT_PROGRESS_COMPLETE : COMPACT_PROGRESS_REMAINING
1340
+ }))
1341
+ };
1342
+ }
1343
+
1344
+ // src/ui/mention-completion.ts
1345
+ function basename(path) {
1346
+ const normalized = path.replaceAll("\\", "/");
1347
+ const index = normalized.lastIndexOf("/");
1348
+ return index < 0 ? path : normalized.slice(index + 1);
1349
+ }
1350
+ function mentionCandidatePresentation(selected) {
1351
+ if (selected) {
1352
+ const selectedText = { color: "rgb(120,155,255)", bold: true };
1353
+ return {
1354
+ marker: "\u203A ",
1355
+ textStyle: selectedText,
1356
+ highlightMatches: false,
1357
+ matchStyle: { color: "ansi:cyan", bold: true }
1358
+ };
1359
+ }
1360
+ return {
1361
+ marker: " ",
1362
+ textStyle: {},
1363
+ highlightMatches: true,
1364
+ matchStyle: { color: "ansi:cyan", bold: true }
1365
+ };
1366
+ }
1367
+ function buildMentionCandidates(paths) {
1368
+ return [...new Set(paths.map((path) => path.replaceAll("\\", "/")))].sort(comparePaths);
1369
+ }
1370
+ function deriveMentionCompletion(input, cursor, candidates, selectedIndex, visibleLimit = 6) {
1371
+ const token = mentionTokenAtCursor(input, cursor);
1372
+ if (token === null) return null;
1373
+ const normalizedQuery = token.query.toLowerCase();
1374
+ const items = candidates.filter((path) => path.toLowerCase().includes(normalizedQuery)).sort((left, right) => {
1375
+ const rankDelta = mentionRank(left, normalizedQuery) - mentionRank(right, normalizedQuery);
1376
+ return rankDelta || comparePaths(left, right);
1377
+ });
1378
+ if (items.length === 0) return null;
1379
+ const selected = Math.max(0, Math.min(items.length - 1, selectedIndex));
1380
+ const limit = Math.max(1, visibleLimit);
1381
+ const windowStart = Math.max(0, Math.min(selected, items.length - limit));
1382
+ return { query: token.query, items, selectedIndex: selected, windowStart };
1383
+ }
1384
+ function moveMentionSelection(index, delta, count) {
1385
+ if (count <= 0) return 0;
1386
+ return (index + delta + count) % count;
1387
+ }
1388
+ function completeMentionSelection(input, cursor, path) {
1389
+ const points = [...input];
1390
+ const token = mentionTokenAtCursor(input, cursor);
1391
+ const safeCursor = Math.max(0, Math.min(points.length, cursor));
1392
+ if (token === null) return { text: input, cursor: safeCursor };
1393
+ const escapedPath = path.replaceAll("\\", "/").replaceAll(" ", "\\ ");
1394
+ const inserted = `@${escapedPath} `;
1395
+ const prefix = points.slice(0, token.start).join("");
1396
+ const suffix = points.slice(token.end).join("").replace(/^\s*/u, "");
1397
+ return {
1398
+ text: prefix + inserted + suffix,
1399
+ cursor: [...prefix + inserted].length,
1400
+ span: { start: [...prefix].length, end: [...prefix + inserted].length }
1401
+ };
1402
+ }
1403
+ function mentionTokenAtCursor(input, cursor) {
1404
+ const points = [...input];
1405
+ const safeCursor = Math.max(0, Math.min(points.length, cursor));
1406
+ let start = safeCursor;
1407
+ while (start > 0 && !isTokenBoundary(points, start - 1)) start -= 1;
1408
+ if (points[start] !== "@" || safeCursor <= start) return null;
1409
+ let end = safeCursor;
1410
+ while (end < points.length && !isTokenBoundary(points, end)) end += 1;
1411
+ return {
1412
+ start,
1413
+ end,
1414
+ query: unescapeQuery(points.slice(start + 1, end).join(""))
1415
+ };
1416
+ }
1417
+ function isTokenBoundary(points, index) {
1418
+ if (!/\s/u.test(points[index] ?? "")) return false;
1419
+ let slashCount = 0;
1420
+ for (let cursor = index - 1; cursor >= 0 && points[cursor] === "\\"; cursor -= 1) slashCount += 1;
1421
+ return slashCount % 2 === 0;
1422
+ }
1423
+ function unescapeQuery(value) {
1424
+ return value.replace(/\\(\s)/gu, "$1");
1425
+ }
1426
+ function mentionRank(path, query) {
1427
+ const normalizedPath = path.toLowerCase();
1428
+ const filename = basename(normalizedPath);
1429
+ if (filename.startsWith(query)) return 0;
1430
+ if (normalizedPath.startsWith(query)) return 1;
1431
+ return 2;
1432
+ }
1433
+ function comparePaths(left, right) {
1434
+ return left.localeCompare(right, "en");
1435
+ }
1436
+
1437
+ // src/ui/welcome.tsx
1438
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1439
+ var WIDE_WELCOME_COLUMNS = 72;
1440
+ var FLAVOR_ACCENT = "#67D4FF";
1441
+ var FLAVOR_WORDMARK = [
1442
+ "\u250C\u2500\u2510\u252C \u250C\u2500\u2510\u252C \u252C\u250C\u2500\u2510\u252C\u2500\u2510",
1443
+ "\u251C\u2524 \u2502 \u251C\u2500\u2524\u2514\u2510\u250C\u2518\u2502 \u2502\u251C\u252C\u2518",
1444
+ "\u2514 \u2534\u2500\u2518\u2534 \u2534 \u2514\u2518 \u2514\u2500\u2518\u2534\u2514\u2500"
1445
+ ].join("\n");
1446
+ function WelcomeCard({ model, serviceName, workspaceName, columns }) {
1447
+ const wide = Math.max(1, Math.floor(columns)) >= WIDE_WELCOME_COLUMNS;
1448
+ return /* @__PURE__ */ jsx4(Box_default, { width: "100%", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: wide ? /* @__PURE__ */ jsxs3(Box_default, { width: "100%", flexDirection: "row", children: [
1449
+ /* @__PURE__ */ jsxs3(
1450
+ Box_default,
1451
+ {
1452
+ width: "36%",
1453
+ flexDirection: "column",
1454
+ alignItems: "center",
1455
+ borderStyle: "single",
1456
+ borderTop: false,
1457
+ borderBottom: false,
1458
+ borderLeft: false,
1459
+ borderColor: "yellow",
1460
+ paddingRight: 1,
1461
+ children: [
1462
+ /* @__PURE__ */ jsx4(Text, { bold: true, color: "yellowBright", children: "Welcome back!" }),
1463
+ /* @__PURE__ */ jsx4(Text, { color: FLAVOR_ACCENT, children: FLAVOR_WORDMARK }),
1464
+ serviceName === void 0 ? null : /* @__PURE__ */ jsx4(Text, { color: "cyan", wrap: "truncate-end", children: serviceName }),
1465
+ /* @__PURE__ */ jsx4(Text, { dimColor: true, wrap: "truncate-end", children: model }),
1466
+ /* @__PURE__ */ jsx4(Text, { dimColor: true, wrap: "truncate-end", children: workspaceName })
1467
+ ]
1468
+ }
1469
+ ),
1470
+ /* @__PURE__ */ jsxs3(Box_default, { flexGrow: 1, flexDirection: "column", paddingLeft: 1, children: [
1471
+ /* @__PURE__ */ jsx4(Text, { bold: true, color: "yellowBright", children: "Tips for getting started" }),
1472
+ /* @__PURE__ */ jsxs3(Text, { children: [
1473
+ "Run ",
1474
+ /* @__PURE__ */ jsx4(Text, { color: "cyan", children: "/init" }),
1475
+ " to create or refresh FLAVOR.md"
1476
+ ] }),
1477
+ /* @__PURE__ */ jsxs3(Text, { children: [
1478
+ "Type ",
1479
+ /* @__PURE__ */ jsx4(Text, { color: "cyan", children: "@" }),
1480
+ " to attach a project file"
1481
+ ] }),
1482
+ /* @__PURE__ */ jsx4(Box_default, { height: 1 }),
1483
+ /* @__PURE__ */ jsx4(Text, { bold: true, color: "yellowBright", children: "Quick commands" }),
1484
+ /* @__PURE__ */ jsxs3(Text, { children: [
1485
+ /* @__PURE__ */ jsx4(Text, { color: "cyan", children: "/help" }),
1486
+ " \xB7 ",
1487
+ /* @__PURE__ */ jsx4(Text, { color: "cyan", children: "/config" }),
1488
+ " \xB7 ",
1489
+ /* @__PURE__ */ jsx4(Text, { color: "cyan", children: "/tasks" })
1490
+ ] })
1491
+ ] })
1492
+ ] }) : /* @__PURE__ */ jsxs3(Box_default, { width: "100%", flexDirection: "column", children: [
1493
+ /* @__PURE__ */ jsx4(Text, { bold: true, color: FLAVOR_ACCENT, children: "\u25C6 Flavor Code" }),
1494
+ /* @__PURE__ */ jsx4(Text, { children: "Welcome back!" }),
1495
+ /* @__PURE__ */ jsxs3(Text, { dimColor: true, wrap: "truncate-end", children: [
1496
+ model,
1497
+ " \xB7 ",
1498
+ workspaceName
1499
+ ] }),
1500
+ /* @__PURE__ */ jsxs3(Text, { children: [
1501
+ /* @__PURE__ */ jsx4(Text, { color: "cyan", children: "/init" }),
1502
+ " setup \xB7 ",
1503
+ /* @__PURE__ */ jsx4(Text, { color: "cyan", children: "/help" }),
1504
+ " commands"
1505
+ ] })
1506
+ ] }) });
1507
+ }
1508
+
1509
+ // src/session/assets.ts
1510
+ import { createHash, randomUUID } from "crypto";
1511
+ import { constants } from "fs";
1512
+ import { mkdir, open, rename, rm } from "fs/promises";
1513
+ import { basename as basename2, join, resolve } from "path";
1514
+ var DEFAULT_MAX_IMAGES = 5;
1515
+ var DEFAULT_MAX_IMAGE_BYTES = 5 * 1024 * 1024;
1516
+ var DEFAULT_MAX_TOTAL_IMAGE_BYTES = 20 * 1024 * 1024;
1517
+ var SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
1518
+ var BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
1519
+ var SessionAssetStore = class {
1520
+ #root;
1521
+ #maxImages;
1522
+ #maxImageBytes;
1523
+ #maxTotalBytes;
1524
+ constructor(options) {
1525
+ this.#root = join(resolve(options.workspace), ".flavor", "session-assets");
1526
+ this.#maxImages = positiveInteger(options.maxImages ?? DEFAULT_MAX_IMAGES, "maxImages");
1527
+ this.#maxImageBytes = positiveInteger(options.maxImageBytes ?? DEFAULT_MAX_IMAGE_BYTES, "maxImageBytes");
1528
+ this.#maxTotalBytes = positiveInteger(options.maxTotalBytes ?? DEFAULT_MAX_TOTAL_IMAGE_BYTES, "maxTotalBytes");
1529
+ }
1530
+ async store(sessionId, inputs) {
1531
+ if (!SESSION_ID.test(sessionId)) throw new Error("Invalid session id");
1532
+ if (inputs.length > this.#maxImages) {
1533
+ throw new Error(`A prompt can contain at most ${this.#maxImages} images`);
1534
+ }
1535
+ const decoded = inputs.map((input) => decodeAttachment(input, this.#maxImageBytes));
1536
+ const total = decoded.reduce((sum, item) => sum + item.data.byteLength, 0);
1537
+ if (total > this.#maxTotalBytes) {
1538
+ throw new Error(`Image attachments exceed the maximum total size of ${this.#maxTotalBytes} bytes`);
1539
+ }
1540
+ if (decoded.length === 0) return [];
1541
+ const directory = join(this.#root, sessionId);
1542
+ await mkdir(directory, { recursive: true, mode: 448 });
1543
+ const output = [];
1544
+ for (const item of decoded) {
1545
+ const sha256 = createHash("sha256").update(item.data).digest("hex");
1546
+ const target = join(directory, `${sha256}${extension(item.mediaType)}`);
1547
+ await writeOnce(target, item.data);
1548
+ output.push({
1549
+ type: "image",
1550
+ source: { type: "file", path: target },
1551
+ mediaType: item.mediaType,
1552
+ sha256,
1553
+ bytes: item.data.byteLength,
1554
+ name: safeName(item.name)
1555
+ });
1556
+ }
1557
+ return output;
1558
+ }
1559
+ };
1560
+ function decodeAttachment(input, maxBytes) {
1561
+ if (!input.name || input.name.length > 255) throw new Error("Image name must be between 1 and 255 characters");
1562
+ if (!["image/png", "image/jpeg", "image/webp"].includes(input.mediaType)) {
1563
+ throw new Error(`Unsupported image media type: ${String(input.mediaType)}`);
1564
+ }
1565
+ if (!input.dataBase64 || !BASE64.test(input.dataBase64)) throw new Error("Image data is not valid base64");
1566
+ const data = Buffer.from(input.dataBase64, "base64");
1567
+ if (data.toString("base64") !== input.dataBase64) throw new Error("Image data is not canonical base64");
1568
+ if (data.byteLength > maxBytes) throw new Error(`Image exceeds the maximum size of ${maxBytes} bytes`);
1569
+ const detected = detectMediaType(data);
1570
+ if (detected !== input.mediaType) {
1571
+ throw new Error(`Image media type ${input.mediaType} does not match file bytes`);
1572
+ }
1573
+ return { ...input, data };
1574
+ }
1575
+ function detectMediaType(data) {
1576
+ if (data.length >= 8 && data.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
1577
+ return "image/png";
1578
+ }
1579
+ if (data.length >= 3 && data[0] === 255 && data[1] === 216 && data[2] === 255) return "image/jpeg";
1580
+ if (data.length >= 12 && data.subarray(0, 4).toString("ascii") === "RIFF" && data.subarray(8, 12).toString("ascii") === "WEBP") return "image/webp";
1581
+ return void 0;
1582
+ }
1583
+ function extension(mediaType) {
1584
+ if (mediaType === "image/png") return ".png";
1585
+ if (mediaType === "image/jpeg") return ".jpg";
1586
+ return ".webp";
1587
+ }
1588
+ function safeName(name) {
1589
+ return basename2(name).replace(/[\u0000-\u001f\u007f]/g, "_").slice(0, 255) || "image";
1590
+ }
1591
+ async function writeOnce(target, data) {
1592
+ const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
1593
+ let handle;
1594
+ try {
1595
+ handle = await open(temporary, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
1596
+ await handle.writeFile(data);
1597
+ await handle.sync();
1598
+ await handle.close();
1599
+ handle = void 0;
1600
+ try {
1601
+ await rename(temporary, target);
1602
+ } catch (error) {
1603
+ if (!isCode(error, "EEXIST") && !isCode(error, "EPERM")) throw error;
1604
+ }
1605
+ } finally {
1606
+ await handle?.close().catch(() => void 0);
1607
+ await rm(temporary, { force: true }).catch(() => void 0);
1608
+ }
1609
+ }
1610
+ function positiveInteger(value, name) {
1611
+ if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${name} must be a positive integer`);
1612
+ return value;
1613
+ }
1614
+ function isCode(error, code) {
1615
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
1616
+ }
1617
+
1618
+ // src/ui/clipboard-image.ts
1619
+ var CLIPBOARD_TIMEOUT_MS = 1e4;
1620
+ var NO_IMAGE_EXIT_CODE = 3;
1621
+ var READ_WINDOWS_CLIPBOARD_IMAGE = String.raw`
1622
+ Add-Type -AssemblyName System.Windows.Forms
1623
+ Add-Type -AssemblyName System.Drawing
1624
+ $image = [Windows.Forms.Clipboard]::GetImage()
1625
+ if ($null -eq $image) { exit 3 }
1626
+ $stream = New-Object System.IO.MemoryStream
1627
+ try {
1628
+ $image.Save($stream, [System.Drawing.Imaging.ImageFormat]::Png)
1629
+ [Console]::Out.Write([Convert]::ToBase64String($stream.ToArray()))
1630
+ } finally {
1631
+ $stream.Dispose()
1632
+ $image.Dispose()
1633
+ }
1634
+ `.trim();
1635
+ var READ_MAC_CLIPBOARD_IMAGE = String.raw`
1636
+ ObjC.import("AppKit");
1637
+ ObjC.import("Foundation");
1638
+ ObjC.import("stdlib");
1639
+
1640
+ const pasteboard = $.NSPasteboard.generalPasteboard;
1641
+ let data = pasteboard.dataForType($.NSPasteboardTypePNG);
1642
+ if (!data) {
1643
+ const tiff = pasteboard.dataForType($.NSPasteboardTypeTIFF);
1644
+ if (tiff) {
1645
+ const representation = $.NSBitmapImageRep.imageRepWithData(tiff);
1646
+ if (representation) {
1647
+ data = representation.representationUsingTypeProperties(
1648
+ $.NSBitmapImageFileTypePNG,
1649
+ $({})
1650
+ );
1651
+ }
1652
+ }
1653
+ }
1654
+ if (!data) {
1655
+ $.exit(3);
1656
+ }
1657
+ const encoded = data.base64EncodedStringWithOptions(0);
1658
+ console.log(ObjC.unwrap(encoded));
1659
+ `.trim();
1660
+ function shouldReadClipboardImage(input, key, isPasted) {
1661
+ return (key.ctrl || key.meta === true) && input.toLowerCase() === "v" || isPasted && input.length === 0;
1662
+ }
1663
+ async function readClipboardImage(options = {}) {
1664
+ const platform = options.platform ?? process.platform;
1665
+ if (platform === "win32") return readWindowsClipboardImage({ ...options, platform });
1666
+ if (platform === "darwin") return readMacClipboardImage({ ...options, platform });
1667
+ throw new Error("Pasting clipboard images is currently supported on Windows and macOS only");
1668
+ }
1669
+ async function readWindowsClipboardImage(options = {}) {
1670
+ const platform = options.platform ?? process.platform;
1671
+ if (platform !== "win32") {
1672
+ throw new Error("Pasting clipboard images is currently supported on Windows only");
1673
+ }
1674
+ const result = await (options.run ?? execFileNoThrow)(
1675
+ "powershell.exe",
1676
+ ["-NoProfile", "-NonInteractive", "-STA", "-Command", READ_WINDOWS_CLIPBOARD_IMAGE],
1677
+ { timeout: CLIPBOARD_TIMEOUT_MS, useCwd: false }
1678
+ );
1679
+ return attachmentFromResult(result, options.now?.() ?? /* @__PURE__ */ new Date());
1680
+ }
1681
+ async function readMacClipboardImage(options = {}) {
1682
+ const platform = options.platform ?? process.platform;
1683
+ if (platform !== "darwin") {
1684
+ throw new Error("Pasting clipboard images through AppKit is supported on macOS only");
1685
+ }
1686
+ const result = await (options.run ?? execFileNoThrow)(
1687
+ "osascript",
1688
+ ["-l", "JavaScript", "-e", READ_MAC_CLIPBOARD_IMAGE],
1689
+ { timeout: CLIPBOARD_TIMEOUT_MS, useCwd: false }
1690
+ );
1691
+ return attachmentFromResult(result, options.now?.() ?? /* @__PURE__ */ new Date());
1692
+ }
1693
+ function attachmentFromResult(result, now) {
1694
+ if (result.code === NO_IMAGE_EXIT_CODE) return void 0;
1695
+ if (result.code !== 0) {
1696
+ const detail = result.stderr.trim() || result.error?.trim();
1697
+ throw new Error(detail ? `Could not read the clipboard image: ${detail}` : "Could not read the clipboard image");
1698
+ }
1699
+ const dataBase64 = result.stdout.trim();
1700
+ if (dataBase64.length === 0) return void 0;
1701
+ return {
1702
+ name: `clipboard-${timestamp(now)}.png`,
1703
+ mediaType: "image/png",
1704
+ dataBase64
1705
+ };
1706
+ }
1707
+ function timestamp(value) {
1708
+ const year = value.getUTCFullYear();
1709
+ const month = String(value.getUTCMonth() + 1).padStart(2, "0");
1710
+ const day = String(value.getUTCDate()).padStart(2, "0");
1711
+ const hour = String(value.getUTCHours()).padStart(2, "0");
1712
+ const minute = String(value.getUTCMinutes()).padStart(2, "0");
1713
+ const second = String(value.getUTCSeconds()).padStart(2, "0");
1714
+ return `${year}${month}${day}-${hour}${minute}${second}`;
1715
+ }
1716
+
1717
+ // src/ui/app.tsx
1718
+ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1719
+ var HISTORY_CAP = 200;
1720
+ var BUILTIN_SLASH_CANDIDATES = MVP_COMMANDS.map((name) => ({ name, description: COMMAND_DESCRIPTIONS[name] }));
1721
+ var PROMPT_HORIZONTAL_PADDING = 1;
1722
+ function removeLastCliImageOnBackspace(input, cursor, images) {
1723
+ if (input.length === 0 && cursor === 0 && images.length > 0) {
1724
+ return { handled: true, images: images.slice(0, -1) };
1725
+ }
1726
+ return { handled: false, images: [...images] };
1727
+ }
1728
+ function prepareCliSubmission(input, images, activeSession) {
1729
+ const entered = input.trim();
1730
+ if (entered.length === 0 && images.length === 0) return { kind: "empty" };
1731
+ if (images.length > 0 && entered.startsWith("/")) {
1732
+ return { kind: "error", message: "Image attachments cannot be used with slash commands." };
1733
+ }
1734
+ if (images.length > 0 && activeSession) {
1735
+ return { kind: "error", message: "Images can only be attached to a new prompt." };
1736
+ }
1737
+ const text = entered || "Analyze the attached image(s).";
1738
+ if (images.length === 0) return { kind: "ready", text, displayText: text };
1739
+ const content = [{ type: "text", text }, ...images];
1740
+ return {
1741
+ kind: "ready",
1742
+ text,
1743
+ displayText: modelContentTranscriptText(content),
1744
+ content
1745
+ };
1746
+ }
1747
+ function promptDelivery(activeSession, key) {
1748
+ if (!activeSession) return "prompt";
1749
+ return "followUp";
1750
+ }
1751
+ function resolvePromptDelivery(activeSession, key, input) {
1752
+ if (activeSession) {
1753
+ const followUp = input.match(/^\/follow-?up\s+([\s\S]+)$/iu);
1754
+ if (followUp !== null) return { delivery: "followUp", prompt: followUp[1].trim() };
1755
+ const steer = input.match(/^\/steer\s+([\s\S]+)$/iu);
1756
+ if (steer !== null) return { delivery: "steer", prompt: steer[1].trim() };
1757
+ }
1758
+ return { delivery: promptDelivery(activeSession, key), prompt: input };
1759
+ }
1760
+ var SinglePendingPrompt = class {
1761
+ #value;
1762
+ get value() {
1763
+ return this.#value;
1764
+ }
1765
+ queue(prompt) {
1766
+ const value = prompt.trim();
1767
+ if (value.length === 0 || this.#value !== void 0) return false;
1768
+ this.#value = value;
1769
+ return true;
1770
+ }
1771
+ cancel() {
1772
+ const value = this.#value;
1773
+ this.#value = void 0;
1774
+ return value;
1775
+ }
1776
+ take() {
1777
+ return this.cancel();
1778
+ }
1779
+ };
1780
+ async function runTerminalSubmissionChain(options) {
1781
+ let prompt = options.initialPrompt;
1782
+ let initial = true;
1783
+ while (prompt !== void 0) {
1784
+ options.onStart(initial ? options.initialDisplayPrompt ?? prompt : prompt);
1785
+ if (initial && options.initialSubmit !== void 0) {
1786
+ try {
1787
+ await options.initialSubmit(prompt);
1788
+ } catch (error) {
1789
+ safeReport(options.report, safeUiError(error));
1790
+ }
1791
+ } else {
1792
+ await submitSafely(options.session, prompt, options.report);
1793
+ }
1794
+ options.onFinish();
1795
+ if (options.shouldContinue?.() === false) return;
1796
+ prompt = options.pending.take();
1797
+ if (prompt !== void 0) options.onPendingConsumed();
1798
+ initial = false;
1799
+ }
1800
+ }
1801
+ function classifyTerminalInput(key) {
1802
+ if (key.wheelUp) return { type: "scroll", rows: -3 };
1803
+ if (key.wheelDown) return { type: "scroll", rows: 3 };
1804
+ if (key.pageUp) return { type: "page", fraction: -0.5 };
1805
+ if (key.pageDown) return { type: "page", fraction: 0.5 };
1806
+ if (key.upArrow) return { type: "history", direction: "up" };
1807
+ if (key.downArrow) return { type: "history", direction: "down" };
1808
+ return null;
1809
+ }
1810
+ function completionKeyAction(key, menuOpen) {
1811
+ if (!menuOpen) return null;
1812
+ if (key.upArrow) return { type: "select", delta: -1 };
1813
+ if (key.downArrow) return { type: "select", delta: 1 };
1814
+ if (key.tab) return { type: "complete" };
1815
+ if (key.escape) return { type: "dismiss" };
1816
+ return null;
1817
+ }
1818
+ function slashKeyAction(key, completion) {
1819
+ return completionKeyAction(key, completion !== null);
1820
+ }
1821
+ function App({ workspace, home, resumeSession }) {
1822
+ const { exit } = use_app_default();
1823
+ const { stdout } = useStdout();
1824
+ const [runtime, setRuntime] = useState2();
1825
+ const [input, setInput] = useState2("");
1826
+ const [pastedBlocks, setPastedBlocks] = useState2([]);
1827
+ const [imageAttachments, setImageAttachments] = useState2([]);
1828
+ const [clipboardNotice, setClipboardNotice] = useState2();
1829
+ const [history, setHistory] = useState2([]);
1830
+ const [historyCursor, setHistoryCursor] = useState2(0);
1831
+ const [promptCursor, setPromptCursor] = useState2(0);
1832
+ const [slashCandidates, setSlashCandidates] = useState2(
1833
+ () => buildSlashCandidates(BUILTIN_SLASH_CANDIDATES, [], [])
1834
+ );
1835
+ const [slashSelection, setSlashSelection] = useState2(0);
1836
+ const [dismissedSlashInput, setDismissedSlashInput] = useState2();
1837
+ const [mentionCandidates, setMentionCandidates] = useState2([]);
1838
+ const [mentionSelection, setMentionSelection] = useState2(0);
1839
+ const [dismissedMentionInput, setDismissedMentionInput] = useState2();
1840
+ const [pendingPrompt, setPendingPrompt] = useState2();
1841
+ const [revision, setRevision] = useState2(0);
1842
+ const [ideContext, setIdeContext] = useState2();
1843
+ const [questionIndex, setQuestionIndex] = useState2(0);
1844
+ const [questionAnswers, setQuestionAnswers] = useState2({});
1845
+ const [customQuestionActive, setCustomQuestionActive] = useState2(false);
1846
+ const [columns, setColumns] = useState2(stdout?.columns ?? 80);
1847
+ const [rows, setRows] = useState2(stdout?.rows ?? 24);
1848
+ const [transcript, dispatch] = useReducer(transcriptReducer, void 0, createTranscriptState);
1849
+ const scrollRef = useRef4(null);
1850
+ const taskScrollRef = useRef4(null);
1851
+ const taskPanelHovered = useRef4(false);
1852
+ const runtimeRef = useRef4(void 0);
1853
+ const pendingPromptRef = useRef4(void 0);
1854
+ pendingPromptRef.current ??= new SinglePendingPrompt();
1855
+ const closing = useRef4(false);
1856
+ const clipboardBusy = useRef4(false);
1857
+ const textBuf = useRef4({ pending: "", timer: null });
1858
+ useTerminalTitle("Flavor Code");
1859
+ const flushText = () => {
1860
+ const t = textBuf.current;
1861
+ if (t.timer !== null) {
1862
+ clearTimeout(t.timer);
1863
+ t.timer = null;
1864
+ }
1865
+ if (t.pending.length > 0) {
1866
+ dispatch({ type: "session", event: { type: "text", text: t.pending } });
1867
+ t.pending = "";
1868
+ }
1869
+ };
1870
+ const shutdown = async (active) => {
1871
+ if (closing.current) return;
1872
+ closing.current = true;
1873
+ await shutdownRuntime(active, exit, (error) => {
1874
+ dispatch({ type: "submit-error", message: error });
1875
+ });
1876
+ };
1877
+ const shutdownRef = useRef4(shutdown);
1878
+ shutdownRef.current = shutdown;
1879
+ const interruptRef = useRef4(void 0);
1880
+ interruptRef.current ??= createSessionInterruptHandler(
1881
+ () => runtimeRef.current?.session,
1882
+ () => shutdownRef.current(runtimeRef.current)
1883
+ );
1884
+ const interrupt = interruptRef.current;
1885
+ useEffect3(() => {
1886
+ let disposed = false;
1887
+ const FLUSH_MS = 100;
1888
+ const receive = (event) => {
1889
+ if (event.type === "exit") {
1890
+ flushText();
1891
+ void shutdownRef.current(runtimeRef.current);
1892
+ return;
1893
+ }
1894
+ if (event.type === "clear") {
1895
+ flushText();
1896
+ dispatch({ type: "clear" });
1897
+ return;
1898
+ }
1899
+ if (event.type === "text") {
1900
+ const t = textBuf.current;
1901
+ t.pending += event.text;
1902
+ if (t.timer === null) t.timer = setTimeout(flushText, FLUSH_MS);
1903
+ return;
1904
+ }
1905
+ flushText();
1906
+ dispatch({ type: "session", event });
1907
+ };
1908
+ void createProductionRuntime({
1909
+ workspace,
1910
+ ...home === void 0 ? {} : { home },
1911
+ ...resumeSession === void 0 ? {} : { resumeSession },
1912
+ output: receive,
1913
+ onApprovalChange: () => setRevision((value) => value + 1)
1914
+ }).then(async (created) => {
1915
+ if (disposed) {
1916
+ await created.dispose();
1917
+ return;
1918
+ }
1919
+ dispatch({ type: "restore", state: created.restoredTranscript });
1920
+ runtimeRef.current = created;
1921
+ setSlashCandidates(buildSlashCandidates(BUILTIN_SLASH_CANDIDATES, created.services.pluginCommands(), []));
1922
+ setRuntime(created);
1923
+ await created.session.start();
1924
+ try {
1925
+ const skills = await created.services.skills();
1926
+ if (!disposed) {
1927
+ setSlashCandidates(buildSlashCandidates(BUILTIN_SLASH_CANDIDATES, created.services.pluginCommands(), skills));
1928
+ }
1929
+ } catch {
1930
+ }
1931
+ }).catch((error) => {
1932
+ dispatch({ type: "submit", prompt: "startup" });
1933
+ dispatch({ type: "submit-error", message: safeUiError(error) });
1934
+ });
1935
+ return () => {
1936
+ disposed = true;
1937
+ flushText();
1938
+ void closeAndDisposeRuntime(runtimeRef.current, (error) => process.stderr.write(`flavor cleanup: ${error}
1939
+ `));
1940
+ };
1941
+ }, [workspace, home, resumeSession]);
1942
+ useEffect3(() => installSigintHandler(process, interrupt), [interrupt]);
1943
+ useEffect3(() => {
1944
+ if (runtime?.services.ideContext === void 0) {
1945
+ setIdeContext(void 0);
1946
+ return;
1947
+ }
1948
+ let disposed = false;
1949
+ let refreshing = false;
1950
+ let previous = "";
1951
+ const refresh = async () => {
1952
+ if (refreshing) return;
1953
+ refreshing = true;
1954
+ try {
1955
+ const next = await runtime.services.ideContext().catch(() => void 0);
1956
+ if (disposed) return;
1957
+ const serialized = JSON.stringify(next);
1958
+ if (serialized !== previous) {
1959
+ previous = serialized;
1960
+ setIdeContext(next);
1961
+ }
1962
+ } finally {
1963
+ refreshing = false;
1964
+ }
1965
+ };
1966
+ void refresh();
1967
+ const timer = setInterval(() => void refresh(), 400);
1968
+ return () => {
1969
+ disposed = true;
1970
+ clearInterval(timer);
1971
+ };
1972
+ }, [runtime]);
1973
+ useEffect3(() => {
1974
+ const controller = new AbortController();
1975
+ let disposed = false;
1976
+ void createGlobTool(workspace, { defaultLimit: 1e4 }).execute({ pattern: "**", limit: 1e4 }, controller.signal).then((result) => {
1977
+ if (!disposed) {
1978
+ setMentionCandidates(buildMentionCandidates(result.matches));
1979
+ }
1980
+ }).catch(() => {
1981
+ });
1982
+ return () => {
1983
+ disposed = true;
1984
+ controller.abort();
1985
+ };
1986
+ }, [workspace]);
1987
+ useEffect3(() => {
1988
+ if (!stdout || typeof stdout.on !== "function") return;
1989
+ const onResize = () => {
1990
+ setColumns(stdout.columns ?? 80);
1991
+ setRows(stdout.rows ?? 24);
1992
+ };
1993
+ stdout.on("resize", onResize);
1994
+ onResize();
1995
+ return () => {
1996
+ stdout.off("resize", onResize);
1997
+ };
1998
+ }, [stdout]);
1999
+ const approval = runtime?.approvals.pending;
2000
+ const questions = runtime?.services.questions.pending;
2001
+ const memoryReviews = runtime?.memoryReviews.pending ?? [];
2002
+ const memoryAutoDismissSeconds = runtime?.memoryReviews.autoDismissSeconds ?? 0;
2003
+ useEffect3(() => {
2004
+ setQuestionIndex(0);
2005
+ setQuestionAnswers({});
2006
+ setCustomQuestionActive(false);
2007
+ }, [questions]);
2008
+ const derivedSlashCompletion = deriveSlashCompletion(input, promptCursor, slashCandidates, slashSelection);
2009
+ const slashCompletion = dismissedSlashInput === input || transcript.active !== void 0 || approval !== void 0 || questions !== void 0 ? null : derivedSlashCompletion;
2010
+ const derivedMentionCompletion = slashCompletion === null ? deriveMentionCompletion(input, promptCursor, mentionCandidates, mentionSelection) : null;
2011
+ const mentionCompletion = dismissedMentionInput === input || transcript.active !== void 0 || approval !== void 0 || questions !== void 0 ? null : derivedMentionCompletion;
2012
+ const completedTokenLength = completedSlashTokenLength(input, slashCandidates, slashCompletion !== null);
2013
+ const selection = useSelection();
2014
+ const selectMention = (path) => {
2015
+ const next = completeMentionSelection(input, promptCursor, path);
2016
+ setInput(next.text);
2017
+ setPromptCursor(next.cursor);
2018
+ setMentionSelection(0);
2019
+ setDismissedMentionInput(next.text);
2020
+ };
2021
+ const pasteClipboardImage = async () => {
2022
+ const active = runtimeRef.current;
2023
+ if (active === void 0) {
2024
+ setClipboardNotice("Flavor is still starting; try pasting the image again.");
2025
+ return;
2026
+ }
2027
+ if (transcript.active !== void 0) {
2028
+ setClipboardNotice("Images can only be attached to a new prompt.");
2029
+ return;
2030
+ }
2031
+ if (imageAttachments.length >= DEFAULT_MAX_IMAGES) {
2032
+ setClipboardNotice(`A prompt can contain at most ${DEFAULT_MAX_IMAGES} images.`);
2033
+ return;
2034
+ }
2035
+ if (clipboardBusy.current) return;
2036
+ clipboardBusy.current = true;
2037
+ setClipboardNotice("Reading image from clipboard...");
2038
+ try {
2039
+ const attachment = await readClipboardImage();
2040
+ if (attachment === void 0) {
2041
+ setClipboardNotice("Clipboard does not contain an image.");
2042
+ return;
2043
+ }
2044
+ const incomingBytes = Buffer.byteLength(attachment.dataBase64, "base64");
2045
+ const currentBytes = imageAttachments.reduce((sum, image) => sum + image.bytes, 0);
2046
+ if (currentBytes + incomingBytes > DEFAULT_MAX_TOTAL_IMAGE_BYTES) {
2047
+ throw new Error(`Image attachments exceed the maximum total size of ${DEFAULT_MAX_TOTAL_IMAGE_BYTES} bytes`);
2048
+ }
2049
+ const [stored] = await new SessionAssetStore({ workspace }).store(active.sessionId, [attachment]);
2050
+ if (stored === void 0) throw new Error("Clipboard image could not be stored");
2051
+ setImageAttachments((current) => [...current, stored]);
2052
+ setClipboardNotice(`Added [Image #${imageAttachments.length + 1}] ${stored.name ?? basename3(stored.source.path)}`);
2053
+ } catch (error) {
2054
+ setClipboardNotice(safeUiError(error));
2055
+ } finally {
2056
+ clipboardBusy.current = false;
2057
+ }
2058
+ };
2059
+ use_input_default((character, key, event) => {
2060
+ const terminalAction = classifyTerminalInput(key);
2061
+ if (terminalAction?.type === "scroll") {
2062
+ const scroll = selectWheelScrollTarget(scrollRef.current, taskScrollRef.current, taskPanelHovered.current);
2063
+ if (scroll !== null) {
2064
+ if (terminalAction.rows < 0) scrollUp(scroll, -terminalAction.rows);
2065
+ else scrollDown(scroll, terminalAction.rows);
2066
+ }
2067
+ return;
2068
+ }
2069
+ if (terminalAction?.type === "page") {
2070
+ const scroll = scrollRef.current;
2071
+ if (scroll !== null) jumpScroll(scroll, Math.floor(scroll.getViewportHeight() * terminalAction.fraction));
2072
+ return;
2073
+ }
2074
+ const active = runtimeRef.current;
2075
+ if (key.ctrl && character === "c") {
2076
+ if (selection.hasSelection()) {
2077
+ selection.copySelection();
2078
+ } else {
2079
+ interrupt();
2080
+ }
2081
+ return;
2082
+ }
2083
+ if (shouldReadClipboardImage(character, key, event.keypress.isPasted)) {
2084
+ void pasteClipboardImage();
2085
+ return;
2086
+ }
2087
+ if (active?.approvals.pending !== void 0) {
2088
+ if (character.toLowerCase() === "y") active.approvals.resolve("once");
2089
+ if (character.toLowerCase() === "n" || key.escape) active.approvals.resolve("deny");
2090
+ if (character.toLowerCase() === "a") {
2091
+ if (isDestructiveTool(active.approvals.pending.tool)) {
2092
+ active.approvals.resolve("once");
2093
+ } else {
2094
+ active.approvals.resolve("always");
2095
+ }
2096
+ }
2097
+ return;
2098
+ }
2099
+ const pendingMemory = active?.memoryReviews.pending[0];
2100
+ if (pendingMemory !== void 0 && key.ctrl && (character.toLowerCase() === "y" || character.toLowerCase() === "n")) {
2101
+ if (character.toLowerCase() === "y") {
2102
+ void active.memoryReviews.accept(pendingMemory.id).catch((error) => {
2103
+ dispatch({ type: "submit-error", message: safeUiError(error) });
2104
+ });
2105
+ } else active.memoryReviews.dismiss(pendingMemory.id);
2106
+ return;
2107
+ }
2108
+ const qs = active?.services.questions.pending;
2109
+ if (qs !== void 0 && qs.length > 0) {
2110
+ const questionsService = active.services.questions;
2111
+ const currentIndex = Math.min(questionIndex, qs.length - 1);
2112
+ const question = qs[currentIndex];
2113
+ const commitQuestionAnswer = (answer) => {
2114
+ const next = { ...questionAnswers, [currentIndex]: answer };
2115
+ setInput("");
2116
+ setPromptCursor(0);
2117
+ setCustomQuestionActive(false);
2118
+ if (currentIndex === qs.length - 1) {
2119
+ setQuestionAnswers({});
2120
+ setQuestionIndex(0);
2121
+ questionsService.answer(next);
2122
+ } else {
2123
+ setQuestionAnswers(next);
2124
+ setQuestionIndex(currentIndex + 1);
2125
+ }
2126
+ };
2127
+ if (key.escape) {
2128
+ if (customQuestionActive) {
2129
+ setCustomQuestionActive(false);
2130
+ setInput("");
2131
+ setPromptCursor(0);
2132
+ } else questionsService.cancel();
2133
+ return;
2134
+ }
2135
+ if (customQuestionActive) {
2136
+ if (key.return) {
2137
+ const answer = input.trim();
2138
+ if (answer) commitQuestionAnswer(answer);
2139
+ return;
2140
+ }
2141
+ } else {
2142
+ const digit = parseInt(character, 10);
2143
+ if (digit >= 1 && digit <= question.options.length) {
2144
+ commitQuestionAnswer(question.options[digit - 1].label);
2145
+ return;
2146
+ }
2147
+ if (digit === question.options.length + 1) {
2148
+ setCustomQuestionActive(true);
2149
+ setInput("");
2150
+ setPromptCursor(0);
2151
+ return;
2152
+ }
2153
+ if (!key.ctrl && !key.meta && character) setCustomQuestionActive(true);
2154
+ else return;
2155
+ }
2156
+ }
2157
+ if (key.escape && transcript.active !== void 0) {
2158
+ const restored = pendingPromptRef.current.cancel();
2159
+ if (restored !== void 0) {
2160
+ setPendingPrompt(void 0);
2161
+ setInput(restored);
2162
+ setPastedBlocks([]);
2163
+ setPromptCursor([...restored].length);
2164
+ setSlashSelection(0);
2165
+ setDismissedSlashInput(void 0);
2166
+ setMentionSelection(0);
2167
+ setDismissedMentionInput(void 0);
2168
+ return;
2169
+ }
2170
+ }
2171
+ const menuAction = slashKeyAction(key, slashCompletion);
2172
+ if (menuAction?.type === "select" && slashCompletion !== null) {
2173
+ setSlashSelection((value) => moveSlashSelection(value, menuAction.delta, slashCompletion.items.length));
2174
+ return;
2175
+ }
2176
+ if (menuAction?.type === "complete" && slashCompletion !== null) {
2177
+ const selected = slashCompletion.items[slashCompletion.selectedIndex];
2178
+ if (selected !== void 0) {
2179
+ const next = completeSlashSelection(input, promptCursor, selected.name);
2180
+ setInput(next.text);
2181
+ setPromptCursor(next.cursor);
2182
+ setDismissedSlashInput(next.text);
2183
+ }
2184
+ return;
2185
+ }
2186
+ if (menuAction?.type === "dismiss") {
2187
+ setDismissedSlashInput(input);
2188
+ return;
2189
+ }
2190
+ const mentionAction = completionKeyAction(key, mentionCompletion !== null);
2191
+ if (mentionAction?.type === "select" && mentionCompletion !== null) {
2192
+ setMentionSelection((value) => moveMentionSelection(value, mentionAction.delta, mentionCompletion.items.length));
2193
+ return;
2194
+ }
2195
+ if (mentionAction?.type === "complete" && mentionCompletion !== null) {
2196
+ const selected = mentionCompletion.items[mentionCompletion.selectedIndex];
2197
+ if (selected !== void 0) selectMention(selected);
2198
+ return;
2199
+ }
2200
+ if (mentionAction?.type === "dismiss") {
2201
+ setDismissedMentionInput(input);
2202
+ return;
2203
+ }
2204
+ if (key.return) {
2205
+ const images = [...imageAttachments];
2206
+ const prepared = prepareCliSubmission(input, images, transcript.active !== void 0);
2207
+ if (prepared.kind === "empty" || active === void 0) return;
2208
+ if (prepared.kind === "error") {
2209
+ setClipboardNotice(prepared.message);
2210
+ return;
2211
+ }
2212
+ const submittedText = prepared.text;
2213
+ const { delivery, prompt } = resolvePromptDelivery(transcript.active !== void 0, key, submittedText);
2214
+ if (!prompt) return;
2215
+ if (delivery === "followUp" && transcript.active !== void 0) {
2216
+ if (!pendingPromptRef.current.queue(prompt)) return;
2217
+ setPendingPrompt(prompt);
2218
+ }
2219
+ scrollRef.current?.scrollToBottom();
2220
+ setHistory((current) => [...current, submittedText].slice(-HISTORY_CAP));
2221
+ setHistoryCursor(history.length + 1);
2222
+ setInput("");
2223
+ setPastedBlocks([]);
2224
+ setImageAttachments([]);
2225
+ setClipboardNotice(void 0);
2226
+ setPromptCursor(0);
2227
+ setSlashSelection(0);
2228
+ setDismissedSlashInput(void 0);
2229
+ setMentionSelection(0);
2230
+ setDismissedMentionInput(void 0);
2231
+ if (delivery === "prompt") {
2232
+ void runTerminalSubmissionChain({
2233
+ session: active.session,
2234
+ initialPrompt: prompt,
2235
+ ...prepared.content === void 0 ? {} : {
2236
+ initialDisplayPrompt: prepared.displayText,
2237
+ initialSubmit: (text) => active.session.submit({
2238
+ text,
2239
+ content: prepared.content
2240
+ })
2241
+ },
2242
+ pending: pendingPromptRef.current,
2243
+ onStart: (next) => dispatch({ type: "submit", prompt: next }),
2244
+ onFinish: () => dispatch({ type: "finish" }),
2245
+ onPendingConsumed: () => setPendingPrompt(void 0),
2246
+ report: (error) => dispatch({ type: "submit-error", message: error }),
2247
+ shouldContinue: () => !closing.current
2248
+ });
2249
+ } else if (delivery === "steer") {
2250
+ try {
2251
+ active.session.steer(prompt);
2252
+ } catch (error) {
2253
+ dispatch({ type: "submit-error", message: safeUiError(error) });
2254
+ }
2255
+ }
2256
+ return;
2257
+ }
2258
+ if (key.backspace) {
2259
+ const imageEdit = removeLastCliImageOnBackspace(input, promptCursor, imageAttachments);
2260
+ if (imageEdit.handled) {
2261
+ setImageAttachments(imageEdit.images);
2262
+ setClipboardNotice(imageEdit.images.length === 0 ? "Removed image attachment." : `Removed image attachment; ${imageEdit.images.length} remaining.`);
2263
+ return;
2264
+ }
2265
+ const next = editPromptWithPastedBlocks(
2266
+ { text: input, cursor: promptCursor },
2267
+ { type: "backspace" },
2268
+ pastedBlocks
2269
+ );
2270
+ setInput(next.text);
2271
+ setPromptCursor(next.cursor);
2272
+ setPastedBlocks(next.pastedBlocks);
2273
+ setSlashSelection(0);
2274
+ setDismissedSlashInput(void 0);
2275
+ setMentionSelection(0);
2276
+ setDismissedMentionInput(void 0);
2277
+ } else if (key.delete) {
2278
+ updatePrompt({ type: "delete" }, input, promptCursor, setInput, setPromptCursor);
2279
+ setSlashSelection(0);
2280
+ setDismissedSlashInput(void 0);
2281
+ setMentionSelection(0);
2282
+ setDismissedMentionInput(void 0);
2283
+ } else if (key.leftArrow) setPromptCursor((value) => Math.max(0, value - 1));
2284
+ else if (key.rightArrow) setPromptCursor((value) => Math.min([...input].length, value + 1));
2285
+ else if (terminalAction?.type === "history" && terminalAction.direction === "up" && history.length) {
2286
+ const next = navigateHistory({ history, cursor: historyCursor }, "up");
2287
+ setHistoryCursor(next.cursor);
2288
+ setInput(next.input);
2289
+ setPromptCursor(next.promptCursor);
2290
+ setSlashSelection(0);
2291
+ setDismissedSlashInput(void 0);
2292
+ setMentionSelection(0);
2293
+ setDismissedMentionInput(void 0);
2294
+ } else if (terminalAction?.type === "history" && terminalAction.direction === "down" && history.length) {
2295
+ const next = navigateHistory({ history, cursor: historyCursor }, "down");
2296
+ setHistoryCursor(next.cursor);
2297
+ setInput(next.input);
2298
+ setPromptCursor(next.promptCursor);
2299
+ setSlashSelection(0);
2300
+ setDismissedSlashInput(void 0);
2301
+ setMentionSelection(0);
2302
+ setDismissedMentionInput(void 0);
2303
+ } else if (!key.ctrl && !key.meta && character) {
2304
+ updatePrompt({ type: "insert", value: character }, input, promptCursor, setInput, setPromptCursor);
2305
+ if (/[\r\n]/u.test(character)) {
2306
+ setPastedBlocks((current) => [...current, { id: current.length + 1, text: character }]);
2307
+ }
2308
+ setSlashSelection(0);
2309
+ setDismissedSlashInput(void 0);
2310
+ setMentionSelection(0);
2311
+ setDismissedMentionInput(void 0);
2312
+ }
2313
+ });
2314
+ void revision;
2315
+ if (runtime === void 0) return /* @__PURE__ */ jsx5(
2316
+ StartingLayout,
2317
+ {
2318
+ workspaceName: basename3(workspace),
2319
+ completed: transcript.completed,
2320
+ ...transcript.active === void 0 ? {} : { active: transcript.active },
2321
+ columns,
2322
+ rows
2323
+ }
2324
+ );
2325
+ const llmServiceName = runtime.services.llmServiceName?.();
2326
+ return /* @__PURE__ */ jsx5(
2327
+ TerminalLayout,
2328
+ {
2329
+ model: runtime.services.mainModel(),
2330
+ ...llmServiceName === void 0 ? {} : { serviceName: llmServiceName },
2331
+ workspaceName: basename3(workspace),
2332
+ completed: transcript.completed,
2333
+ ...transcript.active === void 0 ? {} : { active: transcript.active },
2334
+ input,
2335
+ pastedBlocks,
2336
+ imageAttachments,
2337
+ ...clipboardNotice === void 0 ? {} : { clipboardNotice },
2338
+ promptCursor,
2339
+ onPromptCursorChange: setPromptCursor,
2340
+ columns,
2341
+ rows,
2342
+ activeSession: transcript.active !== void 0,
2343
+ ...ideContext === void 0 ? {} : { ideContext },
2344
+ completedSlashTokenLength: completedTokenLength,
2345
+ scrollRef,
2346
+ taskScrollRef,
2347
+ onTaskPanelHoverChange: (hovered) => {
2348
+ taskPanelHovered.current = hovered;
2349
+ },
2350
+ ...slashCompletion === null ? {} : { completion: slashCompletion },
2351
+ ...mentionCompletion === null ? {} : { mentionCompletion, onMentionSelect: selectMention },
2352
+ ...approval === void 0 ? {} : { approval },
2353
+ ...questions === void 0 ? {} : { questions },
2354
+ memoryReviews,
2355
+ memoryAutoDismissSeconds,
2356
+ ...pendingPrompt === void 0 ? {} : { pendingPrompt },
2357
+ questionIndex,
2358
+ questionAnswers,
2359
+ customQuestionActive
2360
+ }
2361
+ );
2362
+ }
2363
+ function StartingLayout({
2364
+ workspaceName,
2365
+ completed,
2366
+ active,
2367
+ columns,
2368
+ rows
2369
+ }) {
2370
+ return /* @__PURE__ */ jsx5(
2371
+ TerminalLayout,
2372
+ {
2373
+ model: "starting",
2374
+ workspaceName,
2375
+ completed,
2376
+ ...active === void 0 ? {} : { active },
2377
+ input: "",
2378
+ promptCursor: 0,
2379
+ columns,
2380
+ rows,
2381
+ activeSession: false
2382
+ }
2383
+ );
2384
+ }
2385
+ function TerminalLayout({
2386
+ model,
2387
+ serviceName,
2388
+ workspaceName,
2389
+ completed,
2390
+ active,
2391
+ input,
2392
+ pastedBlocks = [],
2393
+ imageAttachments = [],
2394
+ clipboardNotice,
2395
+ promptCursor,
2396
+ columns,
2397
+ rows = 24,
2398
+ activeSession,
2399
+ pendingPrompt,
2400
+ approval,
2401
+ questions,
2402
+ memoryReviews = [],
2403
+ memoryAutoDismissSeconds = 0,
2404
+ questionIndex = 0,
2405
+ questionAnswers = {},
2406
+ customQuestionActive = false,
2407
+ completion,
2408
+ mentionCompletion,
2409
+ onMentionSelect,
2410
+ completedSlashTokenLength: tokenLength = 0,
2411
+ scrollRef,
2412
+ taskScrollRef,
2413
+ onTaskPanelHoverChange,
2414
+ onPromptCursorChange,
2415
+ ideContext
2416
+ }) {
2417
+ const dividerWidth = Math.max(1, columns - 1);
2418
+ const showWelcome = completed.length === 0 && active === void 0;
2419
+ const activeCompletion = completion ?? mentionCompletion;
2420
+ const menuRows = activeCompletion === void 0 ? 0 : Math.min(6, activeCompletion.items.length - activeCompletion.windowStart);
2421
+ const activeTaskBlocks = active?.blocks.filter(
2422
+ (block) => block.kind === "status" && block.task !== void 0
2423
+ ) ?? [];
2424
+ const activeWithoutTasks = active === void 0 ? void 0 : {
2425
+ ...active,
2426
+ blocks: active.blocks.filter(
2427
+ (block) => !(block.kind === "status" && block.task !== void 0)
2428
+ )
2429
+ };
2430
+ const questionRows = questions === void 0 ? 0 : 4 + (questions[questionIndex]?.options.length ?? 0) + questions.length * 2;
2431
+ const memoryReviewRows = memoryReviews.length === 0 ? 0 : 5;
2432
+ const fixedBottomRows = (approval === void 0 ? 0 : 3) + questionRows + memoryReviewRows + menuRows + (pendingPrompt === void 0 ? 0 : 1) + imageAttachments.length + (clipboardNotice === void 0 ? 0 : 1) + 2;
2433
+ const taskPanelRows = taskPanelViewportRows(rows, fixedBottomRows, activeTaskBlocks.length > 0);
2434
+ const availableBottomRows = Math.max(1, rows - taskPanelRows - 1);
2435
+ const bottomMaxRows = Math.min(availableBottomRows, Math.max(Math.floor(rows / 2), fixedBottomRows + 1));
2436
+ const promptMaxLines = Math.max(1, bottomMaxRows - fixedBottomRows);
2437
+ return /* @__PURE__ */ jsxs4(Box_default, { height: rows, width: "100%", flexDirection: "column", overflow: "hidden", children: [
2438
+ /* @__PURE__ */ jsxs4(ScrollBox_default, { ...scrollRef === void 0 ? {} : { ref: scrollRef }, flexGrow: 1, flexDirection: "column", stickyScroll: true, children: [
2439
+ showWelcome ? /* @__PURE__ */ jsx5(WelcomeCard, { model, ...serviceName === void 0 ? {} : { serviceName }, workspaceName, columns }) : /* @__PURE__ */ jsxs4(Text, { dimColor: true, children: [
2440
+ "flavor \xB7 ",
2441
+ model,
2442
+ " \xB7 ",
2443
+ workspaceName
2444
+ ] }),
2445
+ /* @__PURE__ */ jsx5(Box_default, { height: 1 }),
2446
+ completed.map((turn, index) => /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", children: [
2447
+ index > 0 ? /* @__PURE__ */ jsx5(TurnSeparator, { width: columns }) : null,
2448
+ /* @__PURE__ */ jsx5(TurnView, { turn, interactive: false })
2449
+ ] }, turn.id)),
2450
+ activeWithoutTasks === void 0 ? null : /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", children: [
2451
+ completed.length > 0 ? /* @__PURE__ */ jsx5(TurnSeparator, { width: columns }) : null,
2452
+ /* @__PURE__ */ jsx5(TurnView, { turn: activeWithoutTasks, interactive: activeSession })
2453
+ ] })
2454
+ ] }),
2455
+ /* @__PURE__ */ jsx5(
2456
+ TaskProgressPanel,
2457
+ {
2458
+ blocks: activeTaskBlocks,
2459
+ interactive: activeSession,
2460
+ maxHeight: taskPanelRows,
2461
+ ...taskScrollRef === void 0 ? {} : { scrollRef: taskScrollRef },
2462
+ ...onTaskPanelHoverChange === void 0 ? {} : { onHoverChange: onTaskPanelHoverChange }
2463
+ }
2464
+ ),
2465
+ /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", flexShrink: 0, maxHeight: bottomMaxRows, width: "100%", overflowY: "hidden", children: [
2466
+ approval === void 0 ? null : /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", marginBottom: 1, children: [
2467
+ /* @__PURE__ */ jsxs4(Text, { color: "magenta", children: [
2468
+ "\u250C\u2500 approval \xB7 ",
2469
+ approval.tool
2470
+ ] }),
2471
+ /* @__PURE__ */ jsxs4(Text, { wrap: "truncate-end", color: "magentaBright", children: [
2472
+ "\u2502 ",
2473
+ approval.reason ?? "This action needs permission."
2474
+ ] }),
2475
+ isDestructiveTool(approval.tool) ? /* @__PURE__ */ jsxs4(Text, { bold: true, color: "magenta", children: [
2476
+ "\u2514\u2500 Allow? ",
2477
+ /* @__PURE__ */ jsx5(Text, { color: "green", children: "y" }),
2478
+ "=once / ",
2479
+ /* @__PURE__ */ jsx5(Text, { color: "red", children: "n" }),
2480
+ "=deny"
2481
+ ] }) : /* @__PURE__ */ jsxs4(Text, { bold: true, color: "magenta", children: [
2482
+ "\u2514\u2500 Allow? ",
2483
+ /* @__PURE__ */ jsx5(Text, { color: "green", children: "y" }),
2484
+ "=once / ",
2485
+ /* @__PURE__ */ jsx5(Text, { color: "yellow", children: "a" }),
2486
+ "=same-type / ",
2487
+ /* @__PURE__ */ jsx5(Text, { color: "red", children: "n" }),
2488
+ "=deny"
2489
+ ] })
2490
+ ] }),
2491
+ !questions || questions.length === 0 ? null : /* @__PURE__ */ jsx5(QuestionCards, { questions, activeIndex: questionIndex, answers: questionAnswers, customActive: customQuestionActive }),
2492
+ memoryReviews.length === 0 ? null : /* @__PURE__ */ jsx5(MemoryReviewCards, { reviews: memoryReviews, autoDismissSeconds: memoryAutoDismissSeconds }),
2493
+ completion === void 0 ? null : /* @__PURE__ */ jsx5(SlashMenu, { completion }),
2494
+ mentionCompletion === void 0 ? null : /* @__PURE__ */ jsx5(MentionMenu, { completion: mentionCompletion, ...onMentionSelect === void 0 ? {} : { onSelect: onMentionSelect } }),
2495
+ pendingPrompt === void 0 ? null : /* @__PURE__ */ jsxs4(Text, { color: "yellow", wrap: "truncate-end", children: [
2496
+ "Pending \xB7 ",
2497
+ pendingPrompt,
2498
+ " \xB7 Esc edit"
2499
+ ] }),
2500
+ /* @__PURE__ */ jsx5(Text, { dimColor: true, children: "\u2500".repeat(dividerWidth) }),
2501
+ imageAttachments.map((image, index) => /* @__PURE__ */ jsxs4(Text, { color: "cyan", wrap: "truncate-end", children: [
2502
+ "[Image #",
2503
+ index + 1,
2504
+ "] ",
2505
+ image.name ?? basename3(image.source.path)
2506
+ ] }, `${image.sha256}:${index}`)),
2507
+ clipboardNotice === void 0 ? null : /* @__PURE__ */ jsx5(Text, { color: "yellow", wrap: "truncate-end", children: clipboardNotice }),
2508
+ /* @__PURE__ */ jsx5(
2509
+ PromptLine,
2510
+ {
2511
+ input,
2512
+ pastedBlocks,
2513
+ cursor: promptCursor,
2514
+ columns,
2515
+ maxVisibleLines: promptMaxLines,
2516
+ completedSlashTokenLength: tokenLength,
2517
+ ...onPromptCursorChange === void 0 ? {} : { onCursorChange: onPromptCursorChange }
2518
+ }
2519
+ ),
2520
+ /* @__PURE__ */ jsx5(
2521
+ FooterStatus,
2522
+ {
2523
+ hint: activeSession ? "Ctrl+C cancel \xB7 Enter queue \xB7 Esc edit pending \xB7 /steer sends now" : completion !== void 0 ? "\u2191/\u2193 select \xB7 Tab complete \xB7 Esc close" : mentionCompletion !== void 0 ? "\u2191/\u2193 select \xB7 Tab complete \xB7 click choose \xB7 Esc close" : "Enter send \xB7 Ctrl/Cmd+V image \xB7 \u2191\u2193 history \xB7 Ctrl+C exit",
2524
+ ...ideContext === void 0 ? {} : { ideContext }
2525
+ }
2526
+ )
2527
+ ] })
2528
+ ] });
2529
+ }
2530
+ function FooterStatus({
2531
+ hint,
2532
+ ideContext
2533
+ }) {
2534
+ const ide = ideFooterPresentation(ideContext);
2535
+ return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "row", width: "100%", justifyContent: "space-between", children: [
2536
+ /* @__PURE__ */ jsx5(Box_default, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsx5(Text, { dimColor: true, wrap: "truncate-end", children: hint }) }),
2537
+ ide === void 0 ? null : /* @__PURE__ */ jsx5(Box_default, { flexShrink: 0, marginLeft: 1, children: /* @__PURE__ */ jsxs4(Text, { color: "cyan", wrap: "truncate-end", children: [
2538
+ "\u29C9 ",
2539
+ ide
2540
+ ] }) })
2541
+ ] });
2542
+ }
2543
+ function ideFooterPresentation(context) {
2544
+ if (context?.filePath === void 0) return void 0;
2545
+ if (context.selection.isEmpty || !context.selectedText) return `In ${basename3(context.filePath)}`;
2546
+ const endLine = context.selection.end.character === 0 && context.selection.end.line > context.selection.start.line ? context.selection.end.line - 1 : context.selection.end.line;
2547
+ const count = Math.max(1, endLine - context.selection.start.line + 1);
2548
+ return `${count} ${count === 1 ? "line" : "lines"} selected`;
2549
+ }
2550
+ function QuestionCards({ questions, activeIndex, answers, customActive }) {
2551
+ return /* @__PURE__ */ jsx5(Box_default, { flexDirection: "column", marginBottom: 1, children: questions.map((q, qi) => /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", marginBottom: qi < questions.length - 1 ? 1 : 0, children: [
2552
+ /* @__PURE__ */ jsxs4(Text, { bold: true, color: "cyan", children: [
2553
+ "\u250C\u2500 ",
2554
+ q.header
2555
+ ] }),
2556
+ /* @__PURE__ */ jsxs4(Text, { wrap: "truncate-end", color: "cyanBright", children: [
2557
+ "\u2502 ",
2558
+ q.question
2559
+ ] }),
2560
+ qi < activeIndex ? /* @__PURE__ */ jsxs4(Text, { color: "green", children: [
2561
+ "\u2502 \u2713 ",
2562
+ answers[qi]
2563
+ ] }) : qi > activeIndex ? null : q.options.map((opt, oi) => /* @__PURE__ */ jsxs4(Text, { color: "cyan", children: [
2564
+ "\u2502 ",
2565
+ /* @__PURE__ */ jsx5(Text, { bold: true, color: "green", children: oi + 1 }),
2566
+ ". ",
2567
+ opt.label,
2568
+ /* @__PURE__ */ jsxs4(Text, { dimColor: true, children: [
2569
+ " ",
2570
+ opt.description
2571
+ ] })
2572
+ ] }, oi)),
2573
+ qi === activeIndex ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
2574
+ /* @__PURE__ */ jsxs4(Text, { color: "cyan", children: [
2575
+ "\u2502 ",
2576
+ /* @__PURE__ */ jsx5(Text, { bold: true, color: "green", children: q.options.length + 1 }),
2577
+ ". Custom input",
2578
+ /* @__PURE__ */ jsx5(Text, { dimColor: true, children: " Type your own answer" })
2579
+ ] }),
2580
+ /* @__PURE__ */ jsxs4(Text, { dimColor: true, color: "cyan", children: [
2581
+ "\u2514\u2500 ",
2582
+ customActive ? "Type below and press Enter; Esc returns to choices" : `Press 1-${q.options.length + 1} to choose; Esc dismisses`
2583
+ ] })
2584
+ ] }) : null
2585
+ ] }, qi)) });
2586
+ }
2587
+ function MemoryReviewCards({ reviews, autoDismissSeconds }) {
2588
+ const review = reviews[0];
2589
+ const [remaining, setRemaining] = useState2(autoDismissSeconds);
2590
+ useEffect3(() => {
2591
+ if (autoDismissSeconds <= 0) return;
2592
+ setRemaining(autoDismissSeconds);
2593
+ const timer = setInterval(() => setRemaining((current) => Math.max(0, current - 1)), 1e3);
2594
+ return () => clearInterval(timer);
2595
+ }, [review.id, autoDismissSeconds]);
2596
+ return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", marginBottom: 1, children: [
2597
+ /* @__PURE__ */ jsxs4(Text, { bold: true, color: "yellow", children: [
2598
+ "\u250C\u2500 Long-term memory requires confirmation (",
2599
+ reviews.length,
2600
+ ")"
2601
+ ] }),
2602
+ /* @__PURE__ */ jsxs4(Text, { color: "yellowBright", wrap: "truncate-end", children: [
2603
+ "\u2502 [",
2604
+ review.type,
2605
+ "] ",
2606
+ review.content
2607
+ ] }),
2608
+ /* @__PURE__ */ jsx5(Text, { dimColor: true, children: "\u2502 Model-generated content is not stored until you approve it." }),
2609
+ /* @__PURE__ */ jsxs4(Text, { color: "yellow", children: [
2610
+ "\u2514\u2500 ",
2611
+ /* @__PURE__ */ jsx5(Text, { bold: true, children: "Ctrl+Y" }),
2612
+ " save / ",
2613
+ /* @__PURE__ */ jsx5(Text, { bold: true, children: "Ctrl+N" }),
2614
+ " ignore",
2615
+ autoDismissSeconds > 0 ? /* @__PURE__ */ jsx5(Text, { bold: true, color: "cyanBright", children: ` (auto-dismiss in ${remaining}s)` }) : " (conversation remains available)"
2616
+ ] })
2617
+ ] });
2618
+ }
2619
+ function SlashMenu({ completion }) {
2620
+ const visible = completion.items.slice(completion.windowStart, completion.windowStart + 6);
2621
+ return /* @__PURE__ */ jsx5(Box_default, { flexDirection: "column", width: "100%", children: visible.map((candidate, visibleIndex) => {
2622
+ const index = completion.windowStart + visibleIndex;
2623
+ const presentation = slashCandidatePresentation(index === completion.selectedIndex);
2624
+ return /* @__PURE__ */ jsxs4(Text, { ...presentation.rowStyle, wrap: "truncate-end", children: [
2625
+ presentation.marker,
2626
+ /* @__PURE__ */ jsx5(HighlightedName, { name: candidate.name, query: completion.query, matchStyle: presentation.matchStyle }),
2627
+ candidate.description === void 0 ? null : /* @__PURE__ */ jsx5(Text, { dimColor: true, children: ` ${candidate.description}` })
2628
+ ] }, `${candidate.kind}:${candidate.name}`);
2629
+ }) });
2630
+ }
2631
+ function MentionMenu({
2632
+ completion,
2633
+ onSelect
2634
+ }) {
2635
+ const visible = completion.items.slice(completion.windowStart, completion.windowStart + 6);
2636
+ return /* @__PURE__ */ jsx5(Box_default, { flexDirection: "column", width: "100%", children: visible.map((path, visibleIndex) => {
2637
+ const index = completion.windowStart + visibleIndex;
2638
+ const presentation = mentionCandidatePresentation(index === completion.selectedIndex);
2639
+ return /* @__PURE__ */ jsx5(
2640
+ Box_default,
2641
+ {
2642
+ width: "100%",
2643
+ onClick: (event) => {
2644
+ if (!event.cellIsBlank) onSelect?.(path);
2645
+ },
2646
+ children: /* @__PURE__ */ jsxs4(Text, { ...presentation.textStyle, wrap: "truncate-end", children: [
2647
+ presentation.marker,
2648
+ presentation.highlightMatches ? /* @__PURE__ */ jsx5(HighlightedName, { name: path, query: completion.query, matchStyle: presentation.matchStyle }) : path
2649
+ ] })
2650
+ },
2651
+ path
2652
+ );
2653
+ }) });
2654
+ }
2655
+ function HighlightedName({
2656
+ name,
2657
+ query,
2658
+ matchStyle
2659
+ }) {
2660
+ const ranges = matchRanges(name, query);
2661
+ if (ranges.length === 0) return /* @__PURE__ */ jsx5(Text, { children: name });
2662
+ const parts = [];
2663
+ let cursor = 0;
2664
+ for (const [start, end] of ranges) {
2665
+ if (start > cursor) parts.push(name.slice(cursor, start));
2666
+ parts.push(/* @__PURE__ */ jsx5(Text, { ...matchStyle, children: name.slice(start, end) }, `${start}:${end}`));
2667
+ cursor = end;
2668
+ }
2669
+ if (cursor < name.length) parts.push(name.slice(cursor));
2670
+ return /* @__PURE__ */ jsx5(Text, { children: parts });
2671
+ }
2672
+ function jumpScroll(scroll, delta) {
2673
+ const maximum = Math.max(0, scroll.getScrollHeight() - scroll.getViewportHeight());
2674
+ const target = scroll.getScrollTop() + scroll.getPendingDelta() + delta;
2675
+ if (target >= maximum) {
2676
+ scroll.scrollTo(maximum);
2677
+ scroll.scrollToBottom();
2678
+ } else {
2679
+ scroll.scrollTo(Math.max(0, target));
2680
+ }
2681
+ }
2682
+ function selectWheelScrollTarget(transcript, tasks, taskPanelHovered) {
2683
+ return taskPanelHovered && tasks !== null ? tasks : transcript;
2684
+ }
2685
+ function taskPanelViewportRows(rows, reservedBottomRows, hasTasks) {
2686
+ if (!hasTasks) return 0;
2687
+ const terminalRows = Math.max(0, Math.floor(rows));
2688
+ const reserved = Math.max(0, Math.floor(reservedBottomRows));
2689
+ const available = Math.max(0, terminalRows - reserved - 1);
2690
+ return Math.min(8, Math.max(1, Math.floor(terminalRows / 3)), available);
2691
+ }
2692
+ function scrollDown(scroll, amount) {
2693
+ const maximum = Math.max(0, scroll.getScrollHeight() - scroll.getViewportHeight());
2694
+ if (scroll.getScrollTop() + scroll.getPendingDelta() + amount >= maximum) scroll.scrollToBottom();
2695
+ else scroll.scrollBy(amount);
2696
+ }
2697
+ function scrollUp(scroll, amount) {
2698
+ if (scroll.getScrollTop() + scroll.getPendingDelta() - amount <= 0) scroll.scrollTo(0);
2699
+ else scroll.scrollBy(-amount);
2700
+ }
2701
+ function TurnSeparator({ width }) {
2702
+ return /* @__PURE__ */ jsx5(Text, { dimColor: true, children: "\u2500".repeat(Math.max(1, width - 1)) });
2703
+ }
2704
+ function TurnView({ turn, interactive }) {
2705
+ if (turn.kind === "compaction") {
2706
+ return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", marginBottom: 1, borderStyle: "round", borderColor: "ansi:yellowBright", paddingX: 1, children: [
2707
+ /* @__PURE__ */ jsx5(Text, { color: "ansi:yellowBright", bold: true, children: turn.prompt }),
2708
+ turn.blocks.map((block, index) => block.kind === "status" ? /* @__PURE__ */ jsx5(StatusBlockView, { block, interactive }, block.id) : /* @__PURE__ */ jsx5(Box_default, { children: /* @__PURE__ */ jsx5(AssistantText, { text: block.text }) }, `${turn.id}-text-${index}`))
2709
+ ] });
2710
+ }
2711
+ return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", marginBottom: 1, children: [
2712
+ /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "row", backgroundColor: "#3a3a3a", paddingX: 1, paddingY: 0, children: [
2713
+ /* @__PURE__ */ jsx5(Text, { color: "ansi:whiteBright", bold: true, backgroundColor: "#3a3a3a", children: "\u276F" }),
2714
+ /* @__PURE__ */ jsxs4(Text, { color: "ansi:whiteBright", bold: true, backgroundColor: "#3a3a3a", children: [
2715
+ " ",
2716
+ turn.prompt
2717
+ ] })
2718
+ ] }),
2719
+ /* @__PURE__ */ jsx5(Box_default, { flexDirection: "column", paddingLeft: 2, marginTop: 1, children: turn.blocks.map((block, index) => block.kind === "status" ? /* @__PURE__ */ jsx5(StatusBlockView, { block, interactive }, block.id) : /* @__PURE__ */ jsx5(Box_default, { marginBottom: 1, children: /* @__PURE__ */ jsx5(AssistantText, { text: block.text }) }, `${turn.id}-text-${index}`)) })
2720
+ ] });
2721
+ }
2722
+ var DIFF_CONTENT = fileDiffLineStyle("context").contentColor;
2723
+ var DIFF_REMOVED_MARKER = fileDiffLineStyle("removed").markerColor;
2724
+ var DIFF_ADDED_MARKER = fileDiffLineStyle("added").markerColor;
2725
+ function FileDiffView({ presentation }) {
2726
+ const operation = presentation.operation === "create" ? "Create" : presentation.operation === "delete" ? "Delete" : "Update";
2727
+ if (presentation.operation === "delete") {
2728
+ return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "row", children: [
2729
+ /* @__PURE__ */ jsx5(Text, { color: DIFF_REMOVED_MARKER, children: "\u25CF" }),
2730
+ /* @__PURE__ */ jsxs4(Text, { color: DIFF_CONTENT, children: [
2731
+ " ",
2732
+ /* @__PURE__ */ jsx5(Text, { bold: true, children: operation }),
2733
+ "(",
2734
+ basename3(presentation.path),
2735
+ ")"
2736
+ ] })
2737
+ ] });
2738
+ }
2739
+ const lineWidth = Math.max(1, ...presentation.lines.map((line) => Math.max(line.oldLine ?? 0, line.newLine ?? 0))).toString().length;
2740
+ return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", width: "100%", children: [
2741
+ /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "row", children: [
2742
+ /* @__PURE__ */ jsx5(Text, { color: DIFF_ADDED_MARKER, children: "\u25CF" }),
2743
+ /* @__PURE__ */ jsxs4(Text, { color: DIFF_CONTENT, children: [
2744
+ " ",
2745
+ /* @__PURE__ */ jsx5(Text, { bold: true, children: operation }),
2746
+ "(",
2747
+ basename3(presentation.path),
2748
+ ")"
2749
+ ] })
2750
+ ] }),
2751
+ /* @__PURE__ */ jsxs4(Text, { color: DIFF_CONTENT, children: [
2752
+ " \u2514 Added ",
2753
+ lineCount(presentation.added),
2754
+ ", removed ",
2755
+ lineCount(presentation.removed)
2756
+ ] }),
2757
+ presentation.lines.map((line, index) => /* @__PURE__ */ jsx5(FileDiffRow, { line, lineWidth }, `${line.kind}:${line.oldLine ?? ""}:${line.newLine ?? ""}:${index}`))
2758
+ ] });
2759
+ }
2760
+ function FileDiffRow({ line, lineWidth }) {
2761
+ if (line.kind === "omitted") {
2762
+ return /* @__PURE__ */ jsxs4(Text, { color: DIFF_CONTENT, dimColor: true, children: [
2763
+ " ".repeat(lineWidth + 4),
2764
+ line.text
2765
+ ] });
2766
+ }
2767
+ const number = line.newLine ?? line.oldLine;
2768
+ const marker = line.kind === "removed" ? "-" : line.kind === "added" ? "+" : " ";
2769
+ const { backgroundColor, markerColor, contentColor } = fileDiffLineStyle(line.kind);
2770
+ const prefix = `${String(number ?? "").padStart(lineWidth)} ${marker}| `;
2771
+ return /* @__PURE__ */ jsxs4(
2772
+ Box_default,
2773
+ {
2774
+ flexDirection: "row",
2775
+ width: "100%",
2776
+ ...backgroundColor === void 0 ? {} : { backgroundColor },
2777
+ children: [
2778
+ /* @__PURE__ */ jsx5(
2779
+ Text,
2780
+ {
2781
+ color: markerColor,
2782
+ ...line.kind === "context" ? { dimColor: true } : {},
2783
+ ...backgroundColor === void 0 ? {} : { backgroundColor },
2784
+ children: prefix
2785
+ }
2786
+ ),
2787
+ /* @__PURE__ */ jsx5(
2788
+ Text,
2789
+ {
2790
+ color: contentColor,
2791
+ ...backgroundColor === void 0 ? {} : { backgroundColor },
2792
+ children: line.text
2793
+ }
2794
+ )
2795
+ ]
2796
+ }
2797
+ );
2798
+ }
2799
+ function lineCount(count) {
2800
+ return `${count} ${count === 1 ? "line" : "lines"}`;
2801
+ }
2802
+ function StatusLine({ block, interactive }) {
2803
+ if (block.progress !== void 0) return /* @__PURE__ */ jsx5(CompactProgressLine, { progress: block.progress });
2804
+ const running = block.state === "running";
2805
+ const [ref, time] = useAnimationFrame(running && interactive ? 120 : null);
2806
+ const color = statusLineColor(block);
2807
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
2808
+ const spinner = running && interactive ? frames[Math.floor(Math.max(0, time) / 120) % frames.length] + " " : "";
2809
+ return /* @__PURE__ */ jsxs4(Box_default, { ref, flexDirection: "row", children: [
2810
+ /* @__PURE__ */ jsxs4(Text, { ...color === void 0 ? { dimColor: true } : { color }, children: [
2811
+ spinner,
2812
+ block.text
2813
+ ] }),
2814
+ block.hint === void 0 ? null : /* @__PURE__ */ jsxs4(Text, { dimColor: true, wrap: "truncate-end", children: [
2815
+ " (",
2816
+ block.hint,
2817
+ ")"
2818
+ ] })
2819
+ ] });
2820
+ }
2821
+ function statusLineColor(block) {
2822
+ if (block.tone === "retry" || block.tone === "warning") return "ansi:yellowBright";
2823
+ if (block.state === "running") return "#d77757";
2824
+ if (block.state === "completed") return "ansi:green";
2825
+ if (block.state === "failed" || block.state === "cancelled") return "#e06c50";
2826
+ return void 0;
2827
+ }
2828
+ function CompactProgressLine({ progress }) {
2829
+ const presentation = compactProgressPresentation(progress);
2830
+ return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "row", children: [
2831
+ /* @__PURE__ */ jsx5(Text, { children: "Compacting context " }),
2832
+ presentation.cells.map((cell, index) => /* @__PURE__ */ jsxs4(Text, { color: cell.color, children: [
2833
+ "\u25A0",
2834
+ index === presentation.cells.length - 1 ? "" : " "
2835
+ ] }, index)),
2836
+ /* @__PURE__ */ jsxs4(Text, { dimColor: true, children: [
2837
+ " ",
2838
+ presentation.progress,
2839
+ "%"
2840
+ ] })
2841
+ ] });
2842
+ }
2843
+ function PromptLine({
2844
+ input,
2845
+ pastedBlocks,
2846
+ cursor,
2847
+ columns,
2848
+ maxVisibleLines,
2849
+ completedSlashTokenLength: tokenLength = 0,
2850
+ onCursorChange
2851
+ }) {
2852
+ const innerColumns = Math.max(1, columns - PROMPT_HORIZONTAL_PADDING * 2);
2853
+ const presentation = pastedDraftPresentation(input, cursor, pastedBlocks);
2854
+ const wrap = wrapPromptInput(presentation.text, presentation.cursor, { columns: innerColumns, indent: 2 });
2855
+ const visibleCount = Math.max(1, maxVisibleLines ?? wrap.lines.length);
2856
+ const windowStart = Math.min(
2857
+ Math.max(0, wrap.cursor.line - visibleCount + 1),
2858
+ Math.max(0, wrap.lines.length - visibleCount)
2859
+ );
2860
+ const visibleLines = wrap.lines.slice(windowStart, windowStart + visibleCount);
2861
+ const lineStarts = [];
2862
+ let nextLineStart = 0;
2863
+ for (const line of wrap.lines) {
2864
+ lineStarts.push(nextLineStart);
2865
+ nextLineStart += [...line].length;
2866
+ }
2867
+ return /* @__PURE__ */ jsx5(Box_default, { width: "100%", flexDirection: "column", paddingX: PROMPT_HORIZONTAL_PADDING, children: visibleLines.map((line, visibleIndex) => {
2868
+ const lineIndex = windowStart + visibleIndex;
2869
+ const isCursorLine = lineIndex === wrap.cursor.line;
2870
+ const points = [...line];
2871
+ const cursorCol = wrap.cursor.column;
2872
+ const styledCount = Math.max(0, Math.min(points.length, tokenLength - (lineStarts[lineIndex] ?? 0)));
2873
+ return /* @__PURE__ */ jsxs4(
2874
+ Box_default,
2875
+ {
2876
+ ...onCursorChange === void 0 ? {} : {
2877
+ onClick: (event) => {
2878
+ onCursorChange(promptCursorFromClick(input, pastedBlocks, {
2879
+ columns: innerColumns,
2880
+ lineIndex,
2881
+ localColumn: event.localCol
2882
+ }));
2883
+ event.stopImmediatePropagation();
2884
+ }
2885
+ },
2886
+ children: [
2887
+ /* @__PURE__ */ jsx5(Text, { color: "yellow", bold: true, children: lineIndex === 0 ? "\u276F " : " " }),
2888
+ /* @__PURE__ */ jsx5(PromptLineContent, { points, cursor: cursorCol, cursorVisible: isCursorLine, styledCount })
2889
+ ]
2890
+ },
2891
+ lineIndex
2892
+ );
2893
+ }) });
2894
+ }
2895
+ function StatusBlockView({
2896
+ block,
2897
+ interactive
2898
+ }) {
2899
+ const visibleBlock = cliToolTitle(block);
2900
+ const outcome = cliToolOutcome(block);
2901
+ const primary = visibleBlock.activity === "model" || visibleBlock.task !== void 0 ? /* @__PURE__ */ jsx5(TaskStatusLine, { block: visibleBlock, interactive }) : visibleBlock.state === "completed" && visibleBlock.presentation !== void 0 ? /* @__PURE__ */ jsx5(FileDiffView, { presentation: visibleBlock.presentation }) : /* @__PURE__ */ jsx5(StatusLine, { block: visibleBlock, interactive });
2902
+ return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", children: [
2903
+ primary,
2904
+ outcome === void 0 ? null : /* @__PURE__ */ jsx5(Box_default, { paddingLeft: 2, children: /* @__PURE__ */ jsxs4(Text, { dimColor: true, children: [
2905
+ "\u2514 ",
2906
+ outcome
2907
+ ] }) }),
2908
+ block.details === void 0 ? null : /* @__PURE__ */ jsx5(Box_default, { paddingLeft: 2, children: /* @__PURE__ */ jsx5(AssistantText, { text: block.details }) })
2909
+ ] });
2910
+ }
2911
+ function cliToolTitle(block) {
2912
+ if (block.tool === void 0 || block.presentation !== void 0) return block;
2913
+ const input = record(block.tool.input);
2914
+ const path = typeof input?.path === "string" ? basename3(input.path) : void 0;
2915
+ if (path === void 0 || block.text.includes(path)) return block;
2916
+ return { ...block, text: `${block.text} ${path}` };
2917
+ }
2918
+ function cliToolOutcome(block) {
2919
+ const result = block.tool?.result;
2920
+ if (result === void 0 || block.presentation !== void 0) return void 0;
2921
+ if (!result.ok) return truncateInline(result.error?.message ?? "Tool call failed");
2922
+ const output = record(result.output);
2923
+ if (output === void 0) return void 0;
2924
+ if (typeof output.exitCode === "number" || output.exitCode === null) {
2925
+ const exit = output.exitCode === null ? "no exit code" : `exit ${output.exitCode}`;
2926
+ return output.truncated === true ? `${exit} \xB7 output truncated` : exit;
2927
+ }
2928
+ if (typeof output.replacements === "number") return `${output.replacements} replacement${output.replacements === 1 ? "" : "s"}`;
2929
+ if (typeof output.bytes === "number") return `${formatBytes(output.bytes)} written`;
2930
+ if (Array.isArray(output.matches)) return `${output.matches.length} match${output.matches.length === 1 ? "" : "es"}${output.truncated === true ? " \xB7 truncated" : ""}`;
2931
+ if (Array.isArray(output.paths)) return `${output.paths.length} file${output.paths.length === 1 ? "" : "s"}${output.truncated === true ? " \xB7 truncated" : ""}`;
2932
+ if (Array.isArray(output.files)) return `${output.files.length} file${output.files.length === 1 ? "" : "s"} changed`;
2933
+ return void 0;
2934
+ }
2935
+ function record(value) {
2936
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
2937
+ }
2938
+ function truncateInline(value, maxChars = 180) {
2939
+ const text = value.replace(/\s+/gu, " ").trim();
2940
+ return text.length <= maxChars ? text : `${text.slice(0, maxChars - 1)}\u2026`;
2941
+ }
2942
+ function formatBytes(bytes) {
2943
+ if (bytes < 1024) return `${bytes} B`;
2944
+ if (bytes < 1048576) return `${(bytes / 1024).toFixed(bytes < 10240 ? 1 : 0)} KB`;
2945
+ return `${(bytes / 1048576).toFixed(1)} MB`;
2946
+ }
2947
+ function pastedDraftModel(input, cursor, blocks) {
2948
+ const sourceMatches = [];
2949
+ for (const block of blocks) {
2950
+ const extraLines = block.text.split(/\r\n|\r|\n/u).length - 1;
2951
+ if (extraLines === 0 || block.text.length === 0) continue;
2952
+ let start = input.indexOf(block.text);
2953
+ while (start >= 0) {
2954
+ const end = start + block.text.length;
2955
+ const overlaps = sourceMatches.some((match) => start < match.end && end > match.start);
2956
+ if (!overlaps) {
2957
+ sourceMatches.push({
2958
+ start,
2959
+ end,
2960
+ startPoint: [...input.slice(0, start)].length,
2961
+ endPoint: [...input.slice(0, end)].length,
2962
+ label: `[Pasted text #${block.id} +${extraLines} lines]`
2963
+ });
2964
+ break;
2965
+ }
2966
+ start = input.indexOf(block.text, start + 1);
2967
+ }
2968
+ }
2969
+ if (sourceMatches.length === 0) return { text: input, cursor, matches: [] };
2970
+ sourceMatches.sort((left, right) => left.start - right.start);
2971
+ let text = "";
2972
+ let sourceOffset = 0;
2973
+ const matches = [];
2974
+ for (const match of sourceMatches) {
2975
+ text += input.slice(sourceOffset, match.start);
2976
+ const displayStartPoint = [...text].length;
2977
+ text += match.label;
2978
+ const displayEndPoint = [...text].length;
2979
+ matches.push({
2980
+ sourceStartPoint: match.startPoint,
2981
+ sourceEndPoint: match.endPoint,
2982
+ displayStartPoint,
2983
+ displayEndPoint,
2984
+ label: match.label
2985
+ });
2986
+ sourceOffset = match.end;
2987
+ }
2988
+ text += input.slice(sourceOffset);
2989
+ let pointDelta = 0;
2990
+ let mappedCursor = cursor;
2991
+ for (const match of matches) {
2992
+ const labelLength = match.displayEndPoint - match.displayStartPoint;
2993
+ if (cursor <= match.sourceStartPoint) break;
2994
+ if (cursor < match.sourceEndPoint) {
2995
+ mappedCursor = match.displayEndPoint;
2996
+ break;
2997
+ }
2998
+ pointDelta += labelLength - (match.sourceEndPoint - match.sourceStartPoint);
2999
+ mappedCursor = cursor + pointDelta;
3000
+ }
3001
+ return { text, cursor: mappedCursor, matches };
3002
+ }
3003
+ function pastedDraftPresentation(input, cursor, blocks) {
3004
+ const model = pastedDraftModel(input, cursor, blocks);
3005
+ return { text: model.text, cursor: model.cursor };
3006
+ }
3007
+ function codePointIndexAtVisualColumn(line, targetColumn) {
3008
+ const points = [...line];
3009
+ const target = Math.max(0, targetColumn);
3010
+ let visualColumn = 0;
3011
+ for (let index = 0; index < points.length; index += 1) {
3012
+ const width = charWidth(points[index].codePointAt(0) ?? 0);
3013
+ if (target <= visualColumn) return index;
3014
+ if (target < visualColumn + width) {
3015
+ return target - visualColumn < width / 2 ? index : index + 1;
3016
+ }
3017
+ visualColumn += width;
3018
+ }
3019
+ return points.length;
3020
+ }
3021
+ function sourceCursorFromDisplayed(model, displayedCursor) {
3022
+ let sourceOffset = 0;
3023
+ let displayOffset = 0;
3024
+ for (const match of model.matches) {
3025
+ if (displayedCursor < match.displayStartPoint) {
3026
+ return sourceOffset + displayedCursor - displayOffset;
3027
+ }
3028
+ if (displayedCursor <= match.displayEndPoint) return match.sourceEndPoint;
3029
+ sourceOffset = match.sourceEndPoint;
3030
+ displayOffset = match.displayEndPoint;
3031
+ }
3032
+ return sourceOffset + displayedCursor - displayOffset;
3033
+ }
3034
+ function promptCursorFromClick(input, blocks, position) {
3035
+ const model = pastedDraftModel(input, 0, blocks);
3036
+ const wrap = wrapPromptInput(model.text, 0, { columns: position.columns, indent: 2 });
3037
+ const lineIndex = Math.max(0, Math.min(wrap.lines.length - 1, position.lineIndex));
3038
+ const beforeLine = wrap.lines.slice(0, lineIndex).reduce((total, line) => total + [...line].length, 0);
3039
+ const withinLine = codePointIndexAtVisualColumn(
3040
+ wrap.lines[lineIndex] ?? "",
3041
+ position.localColumn - 2
3042
+ );
3043
+ const sourceCursor = sourceCursorFromDisplayed(model, beforeLine + withinLine);
3044
+ return Math.max(0, Math.min([...input].length, sourceCursor));
3045
+ }
3046
+ function PromptLineContent({
3047
+ points,
3048
+ cursor,
3049
+ cursorVisible,
3050
+ styledCount
3051
+ }) {
3052
+ const tokenStyle = completedSlashTokenPresentation();
3053
+ return /* @__PURE__ */ jsxs4(Text, { children: [
3054
+ points.map((point, index) => {
3055
+ const styled = index < styledCount;
3056
+ const caret = cursorVisible && index === cursor;
3057
+ return /* @__PURE__ */ jsx5(
3058
+ Text,
3059
+ {
3060
+ ...styled ? tokenStyle : {},
3061
+ ...caret ? { inverse: true } : {},
3062
+ children: point
3063
+ },
3064
+ index
3065
+ );
3066
+ }),
3067
+ cursorVisible && cursor >= points.length ? /* @__PURE__ */ jsx5(Text, { inverse: true, children: " " }) : null
3068
+ ] });
3069
+ }
3070
+ function editPrompt(state, edit) {
3071
+ const points = [...state.text];
3072
+ const cursor = Math.max(0, Math.min(points.length, state.cursor));
3073
+ if (edit.type === "insert") {
3074
+ const inserted = [...edit.value];
3075
+ points.splice(cursor, 0, ...inserted);
3076
+ return { text: points.join(""), cursor: cursor + inserted.length };
3077
+ }
3078
+ if (edit.type === "left") return { text: state.text, cursor: Math.max(0, cursor - 1) };
3079
+ if (edit.type === "right") return { text: state.text, cursor: Math.min(points.length, cursor + 1) };
3080
+ if (edit.type === "backspace") {
3081
+ if (cursor === 0) return { text: state.text, cursor };
3082
+ points.splice(cursor - 1, 1);
3083
+ return { text: points.join(""), cursor: cursor - 1 };
3084
+ }
3085
+ if (edit.type === "delete") {
3086
+ points.splice(cursor, 1);
3087
+ return { text: points.join(""), cursor };
3088
+ }
3089
+ return { text: state.text, cursor };
3090
+ }
3091
+ function editPromptWithPastedBlocks(state, edit, pastedBlocks) {
3092
+ const points = [...state.text];
3093
+ const cursor = Math.max(0, Math.min(points.length, state.cursor));
3094
+ const latest = pastedBlocks.at(-1);
3095
+ if (edit.type === "backspace" && latest !== void 0) {
3096
+ const pastedPoints = [...latest.text];
3097
+ const start = cursor - pastedPoints.length;
3098
+ if (start >= 0 && points.slice(start, cursor).join("") === latest.text) {
3099
+ points.splice(start, pastedPoints.length);
3100
+ return {
3101
+ text: points.join(""),
3102
+ cursor: start,
3103
+ pastedBlocks: pastedBlocks.slice(0, -1)
3104
+ };
3105
+ }
3106
+ }
3107
+ return { ...editPrompt({ text: state.text, cursor }, edit), pastedBlocks: [...pastedBlocks] };
3108
+ }
3109
+ function navigateHistory(state, direction) {
3110
+ const cursor = direction === "up" ? Math.max(0, state.cursor - 1) : Math.min(state.history.length, state.cursor + 1);
3111
+ const input = state.history[cursor] ?? "";
3112
+ return { cursor, input, promptCursor: [...input].length };
3113
+ }
3114
+ function updatePrompt(edit, text, cursor, setText, setCursor) {
3115
+ const next = editPrompt({ text, cursor }, edit);
3116
+ setText(next.text);
3117
+ setCursor(next.cursor);
3118
+ }
3119
+ async function submitSafely(session, prompt, report) {
3120
+ try {
3121
+ await session.submit(prompt);
3122
+ } catch (error) {
3123
+ safeReport(report, safeUiError(error));
3124
+ }
3125
+ }
3126
+ async function shutdownRuntime(runtime, exit, report) {
3127
+ try {
3128
+ await closeAndDisposeRuntime(runtime, report);
3129
+ } finally {
3130
+ exit();
3131
+ }
3132
+ }
3133
+ async function closeAndDisposeRuntime(runtime, report) {
3134
+ if (runtime === void 0) return;
3135
+ try {
3136
+ await runtime.session.close();
3137
+ } catch (error) {
3138
+ safeReport(report, safeUiError(error));
3139
+ } finally {
3140
+ try {
3141
+ await runtime.dispose();
3142
+ } catch (error) {
3143
+ safeReport(report, safeUiError(error));
3144
+ }
3145
+ }
3146
+ }
3147
+ function safeUiError(error) {
3148
+ return redactErrorText(message(error)).slice(0, 2e3);
3149
+ }
3150
+ function safeReport(report, value) {
3151
+ try {
3152
+ report(value);
3153
+ } catch {
3154
+ }
3155
+ }
3156
+ export {
3157
+ App,
3158
+ FooterStatus,
3159
+ HISTORY_CAP,
3160
+ MentionMenu,
3161
+ PromptLine,
3162
+ SinglePendingPrompt,
3163
+ TerminalLayout,
3164
+ classifyTerminalInput,
3165
+ closeAndDisposeRuntime,
3166
+ completionKeyAction,
3167
+ editPrompt,
3168
+ editPromptWithPastedBlocks,
3169
+ ideFooterPresentation,
3170
+ navigateHistory,
3171
+ pastedDraftPresentation,
3172
+ prepareCliSubmission,
3173
+ promptCursorFromClick,
3174
+ promptDelivery,
3175
+ removeLastCliImageOnBackspace,
3176
+ resolvePromptDelivery,
3177
+ runTerminalSubmissionChain,
3178
+ selectWheelScrollTarget,
3179
+ shutdownRuntime,
3180
+ slashKeyAction,
3181
+ statusLineColor,
3182
+ submitSafely,
3183
+ taskPanelViewportRows
3184
+ };
3185
+ //# sourceMappingURL=app-WC2XXGLM.js.map