omnius 1.0.861 → 1.0.863

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 (38) hide show
  1. package/dist/index.js +2 -2
  2. package/dist/library.js +120 -96
  3. package/dist/tui/native/worker.js +3031 -97
  4. package/docs/DISCOVERY.json +1156 -112
  5. package/docs/DISCOVERY.md +32 -2
  6. package/docs/work-orders/evidence/instrument-100x30.png +0 -0
  7. package/docs/work-orders/evidence/instrument-120x36.png +0 -0
  8. package/docs/work-orders/evidence/instrument-160x48.png +0 -0
  9. package/docs/work-orders/evidence/instrument-60x20.png +0 -0
  10. package/docs/work-orders/evidence/instrument-80x24.png +0 -0
  11. package/docs/work-orders/evidence/instrument-collapse-120x36.png +0 -0
  12. package/docs/work-orders/evidence/instrument-help-scroll-120x36.png +0 -0
  13. package/docs/work-orders/evidence/instrument-package-artifact-20261016.json +18 -0
  14. package/docs/work-orders/evidence/instrument-package-install-20261016.json +69 -0
  15. package/docs/work-orders/evidence/instrument-popup-api-120x36.png +0 -0
  16. package/docs/work-orders/evidence/instrument-popup-api-input-120x36.png +0 -0
  17. package/docs/work-orders/evidence/instrument-popup-config-120x36.png +0 -0
  18. package/docs/work-orders/evidence/instrument-popup-dark-120x36.png +0 -0
  19. package/docs/work-orders/evidence/instrument-popup-dark-60x20.png +0 -0
  20. package/docs/work-orders/evidence/instrument-popup-light-120x36.png +0 -0
  21. package/docs/work-orders/evidence/instrument-popup-model-120x36.png +0 -0
  22. package/docs/work-orders/evidence/instrument-popup-network-120x36.png +0 -0
  23. package/docs/work-orders/evidence/instrument-production-terminal-live.json +6228 -0
  24. package/docs/work-orders/evidence/instrument-semantic-components-20261016.json +76 -0
  25. package/docs/work-orders/evidence/instrument-targeted-verification-20261016.json +408 -0
  26. package/docs/work-orders/evidence/instrument-terminal-light-120x36.png +0 -0
  27. package/docs/work-orders/evidence/native-tui-committed-scope-20261015.json +67 -0
  28. package/docs/work-orders/evidence/native-tui-local-inference-repair-20261015.json +144 -0
  29. package/docs/work-orders/evidence/native-tui-package-artifact-20261015.json +24 -0
  30. package/docs/work-orders/evidence/native-tui-package-install-20261015.json +95 -0
  31. package/docs/work-orders/evidence/native-tui-production-terminal-20261015.json +1255 -0
  32. package/docs/work-orders/evidence/native-tui-semantic-components-20261015.json +72 -0
  33. package/docs/work-orders/evidence/native-tui-targeted-verification-20261015.json +95 -0
  34. package/docs/work-orders/instrument-command-surface-audit.md +237 -0
  35. package/docs/work-orders/instrument-tui.md +108 -0
  36. package/docs/work-orders/native-tui-renderer.md +112 -90
  37. package/npm-shrinkwrap.json +6 -6
  38. package/package.json +1 -1
@@ -2,142 +2,3069 @@
2
2
  import { createWriteStream } from "node:fs";
3
3
  import { createInterface } from "node:readline";
4
4
  import { PassThrough } from "node:stream";
5
+ import { CliRenderEvents as CliRenderEvents4, NativeSession, RGBA as RGBA3, createCliRenderer } from "@opentui/core";
6
+
7
+ // packages/cli/src/tui/native/components/frontend.ts
5
8
  import {
6
- createCliRenderer,
7
- NativeSession,
8
- RGBA,
9
- StyledText,
9
+ BoxRenderable as BoxRenderable4,
10
+ CliRenderEvents as CliRenderEvents2,
11
+ EmbeddedTerminalRenderable as EmbeddedTerminalRenderable2,
12
+ ScrollBoxRenderable as ScrollBoxRenderable4,
13
+ SyntaxStyle as SyntaxStyle2,
14
+ TabSelectRenderable,
15
+ TextareaRenderable as TextareaRenderable2,
16
+ TextRenderable as TextRenderable4,
17
+ TextAttributes as TextAttributes6,
18
+ createClipboard,
19
+ createHostClipboard,
20
+ createRendererClipboardAdapter
21
+ } from "@opentui/core";
22
+
23
+ // packages/cli/src/tui/native/components/blocks.ts
24
+ import {
25
+ BoxRenderable,
26
+ CodeRenderable,
27
+ EmbeddedTerminalRenderable,
28
+ ImageRenderable,
29
+ MarkdownRenderable,
30
+ RenderableEvents,
31
+ ScrollBoxRenderable,
10
32
  TextRenderable
11
33
  } from "@opentui/core";
12
- var replies = createWriteStream("", { fd: 3, autoClose: false });
13
- var reply = (message) => replies.write(JSON.stringify(message) + "\n");
14
- var terminalInput = new PassThrough();
15
- var rows = [];
16
- var renderer;
17
- var session;
18
- var stopping = false;
19
- var suspended = false;
20
- var lastCursor = { row: 0, col: 0, visible: false };
21
- var colors = /* @__PURE__ */ new Map();
22
- function color(value, background) {
23
- if (value === void 0)
24
- return background ? RGBA.defaultBackground() : RGBA.defaultForeground();
25
- const cached = colors.get(value);
26
- if (cached) return cached;
27
- const parsed = value.startsWith("index:") ? RGBA.fromIndex(Number(value.slice(6))) : RGBA.fromHex(value);
28
- if (colors.size >= 4096) colors.delete(colors.keys().next().value);
29
- colors.set(value, parsed);
30
- return parsed;
31
- }
32
- function applyFrame(frame) {
33
- const ui = renderer;
34
- if (ui.terminalWidth !== frame.columns || ui.terminalHeight !== frame.rows)
35
- ui.resize(frame.columns, frame.rows);
36
- while (rows.length > frame.rows) rows.pop().destroy();
37
- while (rows.length < frame.rows) {
38
- const row = rows.length;
39
- const text = new TextRenderable(ui, {
40
- id: `omnius-row-${row}`,
34
+
35
+ // packages/cli/src/tui/native/components/text.ts
36
+ import { CliRenderEvents, RGBA, StyledText, TextAttributes } from "@opentui/core";
37
+
38
+ // packages/cli/src/tui/native/text.ts
39
+ function stripTextControls(value) {
40
+ return value.replace(/\x1b\][\s\S]*?(?:\x07|\x1b\\)/g, "").replace(/\x1b[P_^X][\s\S]*?\x1b\\/g, "").replace(/\x1b\[[-0-?]*[ -/]*[@-~]/g, "").replace(/\x1b[\s\S]/g, "").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "");
41
+ }
42
+ function nativeTextFromAnsi(value) {
43
+ const result = [];
44
+ let style = { attributes: 0 };
45
+ let previous = 0;
46
+ const pattern = /\x1b(?:\[[-0-?]*[ -/]*[@-~]|\][\s\S]*?(?:\x07|\x1b\\)|[P_^X][\s\S]*?\x1b\\|[\s\S])/g;
47
+ const append = (text) => {
48
+ const clean = stripTextControls(text);
49
+ if (!clean) return;
50
+ const last = result.at(-1);
51
+ if (last && last.fg === style.fg && last.bg === style.bg && last.attributes === style.attributes && last.link === style.link)
52
+ last.text += clean;
53
+ else result.push({ ...style, text: clean });
54
+ };
55
+ for (const match of value.matchAll(pattern)) {
56
+ append(value.slice(previous, match.index));
57
+ previous = match.index + match[0].length;
58
+ const seq = match[0];
59
+ if (seq.startsWith("\x1B]8;")) {
60
+ const url = seq.replace(/^\x1b\]8;[^;]*;/, "").replace(/(?:\x07|\x1b\\)$/, "");
61
+ if (/^(?:https?:|file:)/.test(url)) style.link = url;
62
+ else delete style.link;
63
+ continue;
64
+ }
65
+ if (!/^\x1b\[[-\d;:]*m$/.test(seq)) continue;
66
+ const codes = seq.slice(2, -1).replace(/:/g, ";").split(";").map(Number);
67
+ for (let i = 0; i < codes.length; i++) {
68
+ const code = codes[i];
69
+ const attr = style.attributes ?? 0;
70
+ if (code === 0) style = { attributes: 0 };
71
+ else if (code >= 1 && code <= 9) {
72
+ const bit = {
73
+ 1: 1,
74
+ 2: 2,
75
+ 3: 4,
76
+ 4: 8,
77
+ 5: 16,
78
+ 6: 16,
79
+ 7: 32,
80
+ 8: 64,
81
+ 9: 128
82
+ }[code];
83
+ style.attributes = attr | bit;
84
+ } else if (code >= 22 && code <= 29) {
85
+ const bit = { 22: 3, 23: 4, 24: 8, 25: 16, 27: 32, 28: 64, 29: 128 }[code] ?? 0;
86
+ style.attributes = attr & ~bit;
87
+ } else if (code === 39) delete style.fg;
88
+ else if (code === 49) delete style.bg;
89
+ else if (code >= 30 && code <= 37) style.fg = `index:${code - 30}`;
90
+ else if (code >= 90 && code <= 97) style.fg = `index:${code - 90 + 8}`;
91
+ else if (code >= 40 && code <= 47) style.bg = `index:${code - 40}`;
92
+ else if (code >= 100 && code <= 107) style.bg = `index:${code - 100 + 8}`;
93
+ else if ((code === 38 || code === 48) && codes[i + 1] === 5) {
94
+ const role = code === 38 ? "fg" : "bg";
95
+ const index = codes[i + 2] ?? 0;
96
+ if (index < 0) delete style[role];
97
+ else style[role] = `index:${Math.min(255, index)}`;
98
+ i += 2;
99
+ } else if ((code === 38 || code === 48) && codes[i + 1] === 2) {
100
+ const rgb = codes.slice(i + 2, i + 5).map(
101
+ (n) => Math.max(0, Math.min(255, n)).toString(16).padStart(2, "0")
102
+ );
103
+ if (rgb.length === 3)
104
+ style[code === 38 ? "fg" : "bg"] = `#${rgb.join("")}`;
105
+ i += 4;
106
+ }
107
+ }
108
+ }
109
+ append(value.slice(previous));
110
+ return result;
111
+ }
112
+
113
+ // packages/cli/src/tui/native/components/text.ts
114
+ function plainText(value) {
115
+ return typeof value === "string" ? stripTextControls(value) : (value ?? []).map((span) => stripTextControls(span.text)).join("");
116
+ }
117
+ function textSpans(value) {
118
+ return Array.isArray(value) ? value.map((span) => ({ ...span, text: stripTextControls(span.text) })) : nativeTextFromAnsi(value ?? "");
119
+ }
120
+ function styledText(value) {
121
+ return new StyledText(textSpans(value).map((span) => ({
122
+ __isChunk: true,
123
+ text: span.text,
124
+ ...span.attributes === void 0 ? {} : { attributes: span.attributes },
125
+ ...span.link ? { link: { url: span.link } } : {}
126
+ })));
127
+ }
128
+ function palette(_theme) {
129
+ const background2 = RGBA.defaultBackground();
130
+ const foreground2 = RGBA.defaultForeground();
131
+ return {
132
+ background: background2,
133
+ foreground: foreground2,
134
+ accent: foreground2,
135
+ dim: foreground2,
136
+ border: foreground2,
137
+ warning: foreground2,
138
+ error: foreground2,
139
+ success: foreground2,
140
+ selection: background2
141
+ };
142
+ }
143
+ function installDefaultSelectionContrast(renderer2) {
144
+ const foreground2 = RGBA.defaultForeground();
145
+ const background2 = RGBA.defaultBackground();
146
+ const paintSelection = (buffer) => {
147
+ const lines = buffer.getSpanLines();
148
+ buffer.withBuffers((cells) => {
149
+ for (let y = 0; y < lines.length; y++) {
150
+ let x = 0;
151
+ for (const span of lines[y].spans) {
152
+ if (span.fg.intent === "default" && span.bg.intent === "default" && !(span.attributes & TextAttributes.INVERSE) && span.fg.equals(background2) && span.bg.equals(foreground2)) {
153
+ for (let column = x; column < x + span.width; column++)
154
+ cells.attributes[y * cells.width + column] |= TextAttributes.INVERSE;
155
+ }
156
+ x += span.width;
157
+ }
158
+ }
159
+ });
160
+ };
161
+ renderer2.addPostProcessFn(paintSelection);
162
+ let disposed = false;
163
+ const dispose = () => {
164
+ if (disposed) return;
165
+ disposed = true;
166
+ renderer2.removePostProcessFn(paintSelection);
167
+ renderer2.off(CliRenderEvents.DESTROY, dispose);
168
+ };
169
+ renderer2.once(CliRenderEvents.DESTROY, dispose);
170
+ renderer2.requestRender();
171
+ return dispose;
172
+ }
173
+
174
+ // packages/cli/src/tui/native/components/blocks.ts
175
+ function nativeProgressText(value, working = false) {
176
+ if (value === void 0 || !Number.isFinite(value)) return `[············] ${working ? "working" : "pending"}`;
177
+ const ratio = Math.max(0, Math.min(1, value));
178
+ const filled = Math.round(ratio * 12);
179
+ return `[${"•".repeat(filled)}${"·".repeat(12 - filled)}] ${Math.round(ratio * 100)}%`;
180
+ }
181
+ function nativeControlFocus(control, baseAttributes = 0) {
182
+ const update = () => {
183
+ if (!control.isDestroyed) control.attributes = baseAttributes | (control.focused ? 32 : 0);
184
+ };
185
+ control.on(RenderableEvents.FOCUSED, update);
186
+ control.on(RenderableEvents.BLURRED, update);
187
+ update();
188
+ }
189
+ var NativeBlockView = class {
190
+ constructor(context, spec, inline = false) {
191
+ this.context = context;
192
+ this.inline = inline;
193
+ this.spec = spec;
194
+ this.pixel = inline && spec.kind === "text" && Boolean(spec.actions?.length) && spec.actions.every((action) => action.id.startsWith("pixel:"));
195
+ this.root = new BoxRenderable(context.renderer, {
196
+ id: `block:${spec.id}`,
197
+ flexDirection: "column",
198
+ flexShrink: inline && !this.pixel ? 1 : 0,
199
+ flexGrow: inline && !this.pixel ? 1 : 0,
200
+ flexBasis: inline && !this.pixel ? 0 : "auto",
201
+ minWidth: inline && !this.pixel ? 0 : void 0,
202
+ width: this.pixel ? 1 : inline ? "auto" : "100%"
203
+ });
204
+ this.build();
205
+ this.update(spec);
206
+ }
207
+ context;
208
+ inline;
209
+ root;
210
+ spec;
211
+ title;
212
+ text;
213
+ body;
214
+ children;
215
+ actions;
216
+ progress;
217
+ image;
218
+ terminal;
219
+ collapsed = false;
220
+ previousModelCollapsed;
221
+ signature = "";
222
+ actionsSignature = "";
223
+ pixel;
224
+ get block() {
225
+ return this.spec;
226
+ }
227
+ build() {
228
+ const { renderer: renderer2, colors, syntax } = this.context;
229
+ const spec = this.spec;
230
+ if (spec.kind === "card") this.root.borderStyle = "single";
231
+ this.root.border = spec.kind === "card" ? ["left", "bottom"] : false;
232
+ this.root.paddingLeft = spec.kind === "card" ? 1 : 0;
233
+ this.root.paddingRight = 0;
234
+ this.root.marginBottom = 0;
235
+ this.root.flexDirection = spec.kind === "row" ? "row" : "column";
236
+ if (spec.title) {
237
+ this.title = new TextRenderable(renderer2, {
238
+ id: `block-title:${spec.id}`,
239
+ content: "",
240
+ fg: colors.foreground,
241
+ attributes: 1,
242
+ flexShrink: 0,
243
+ width: "100%",
244
+ wrapMode: "word",
245
+ selectable: false,
246
+ onMouseDown: (event) => {
247
+ if (event.button !== 0 || !this.spec.collapsible) return;
248
+ this.toggle();
249
+ event.stopPropagation();
250
+ },
251
+ onKeyDown: (key) => {
252
+ if (key.eventType === "release" || !this.spec.collapsible || key.name !== "return" && key.sequence !== " ") return;
253
+ this.toggle();
254
+ key.preventDefault();
255
+ key.stopPropagation();
256
+ }
257
+ });
258
+ nativeControlFocus(this.title, 1);
259
+ this.root.add(this.title);
260
+ }
261
+ this.body = new BoxRenderable(renderer2, {
262
+ id: `block-body:${spec.id}`,
263
+ flexDirection: spec.kind === "row" ? "row" : "column",
264
+ width: this.pixel ? "auto" : "100%",
265
+ flexShrink: 0
266
+ });
267
+ this.root.add(this.body);
268
+ if (spec.kind === "terminal") {
269
+ this.terminal = new EmbeddedTerminalRenderable(renderer2, {
270
+ id: `embedded:${spec.id}`,
271
+ width: "100%",
272
+ height: spec.height ?? 14,
273
+ maxScrollback: 1e4,
274
+ transparentBackground: true,
275
+ selectable: true,
276
+ onData: (data) => this.context.emit({ type: "embedded-data", id: this.spec.id, data: Buffer.from(data).toString("base64") }),
277
+ onTerminalResize: (columns, rows) => this.context.emit({ type: "embedded-resize", id: this.spec.id, columns, rows })
278
+ });
279
+ this.context.terminals.set(spec.id, this.terminal);
280
+ this.body.add(this.terminal);
281
+ } else if (spec.kind === "image" && spec.imagePath) {
282
+ this.image = new ImageRenderable(renderer2, {
283
+ id: `image:${spec.id}`,
284
+ source: spec.imagePath,
285
+ width: "100%",
286
+ height: spec.height ?? 12,
287
+ fit: "fit",
288
+ protocol: "auto",
289
+ onError: (error) => {
290
+ if (this.image) this.image.visible = false;
291
+ if (this.text) {
292
+ this.text.visible = true;
293
+ if (this.text instanceof TextRenderable) this.text.content = styledText(this.spec.text ?? "Image unavailable");
294
+ }
295
+ this.context.emit({ type: "action", action: "image-error", id: spec.id, text: String(error) });
296
+ },
297
+ onLoad: () => {
298
+ if (this.image) this.image.visible = true;
299
+ if (this.text) this.text.visible = false;
300
+ }
301
+ });
302
+ this.body.add(this.image);
303
+ this.text = new TextRenderable(renderer2, { id: `image-fallback:${spec.id}`, content: "", visible: false, width: "100%", wrapMode: "word", fg: colors.dim, selectable: true });
304
+ this.body.add(this.text);
305
+ } else if (spec.kind === "markdown") {
306
+ this.text = new MarkdownRenderable(renderer2, {
307
+ id: `markdown:${spec.id}`,
308
+ content: "",
309
+ syntaxStyle: syntax,
310
+ fg: colors.foreground,
311
+ bg: colors.background,
312
+ streaming: spec.streaming ?? false,
313
+ width: "100%",
314
+ flexShrink: 0,
315
+ internalBlockMode: "top-level",
316
+ conceal: true,
317
+ tableOptions: { style: "columns", widthMode: "full", wrapMode: "word", selectable: true }
318
+ });
319
+ this.body.add(this.text);
320
+ } else if (spec.kind === "code") {
321
+ this.text = new CodeRenderable(renderer2, {
322
+ id: `code:${spec.id}`,
323
+ content: "",
324
+ filetype: spec.language,
325
+ syntaxStyle: syntax,
326
+ fg: colors.foreground,
327
+ bg: colors.background,
328
+ width: "100%",
329
+ flexShrink: 0,
330
+ streaming: spec.streaming ?? false,
331
+ selectable: true,
332
+ wrapMode: "word",
333
+ drawUnstyledText: true
334
+ });
335
+ this.body.add(this.text);
336
+ } else if (spec.text !== void 0) {
337
+ this.text = new TextRenderable(renderer2, {
338
+ id: `text:${spec.id}`,
339
+ content: "",
340
+ width: this.pixel ? "auto" : "100%",
341
+ flexShrink: 0,
342
+ wrapMode: "word",
343
+ fg: colors.foreground,
344
+ bg: colors.background,
345
+ selectable: true
346
+ });
347
+ this.body.add(this.text);
348
+ }
349
+ if (spec.kind === "progress") {
350
+ this.progress = new TextRenderable(renderer2, {
351
+ id: `progress-track:${spec.id}`,
352
+ width: "100%",
353
+ height: 1,
354
+ content: "",
355
+ fg: colors.foreground,
356
+ bg: colors.background,
357
+ attributes: 2,
358
+ wrapMode: "none",
359
+ selectable: false
360
+ });
361
+ this.body.add(this.progress);
362
+ }
363
+ this.children = new NativeBlockList(this.context, this.body);
364
+ this.actions = new BoxRenderable(renderer2, {
365
+ id: `block-actions:${spec.id}`,
366
+ flexDirection: "row",
367
+ gap: 1,
368
+ flexShrink: 0
369
+ });
370
+ this.root.add(this.actions);
371
+ }
372
+ reset() {
373
+ this.children?.destroy();
374
+ if (this.terminal) this.context.terminals.delete(this.spec.id);
375
+ for (const child of [...this.root.getChildren()]) child.destroyRecursively();
376
+ this.title = this.text = this.body = this.actions = this.progress = this.image = this.terminal = void 0;
377
+ this.children = void 0;
378
+ this.actionsSignature = "";
379
+ this.build();
380
+ }
381
+ update(spec) {
382
+ const signature = JSON.stringify(spec);
383
+ if (signature === this.signature) return;
384
+ const rebuild = spec.kind !== this.spec.kind || Boolean(spec.title) !== Boolean(this.spec.title) || spec.text !== void 0 !== (this.spec.text !== void 0) || Boolean(spec.imagePath) !== Boolean(this.spec.imagePath) || spec.kind === "image" && spec.imageRevision !== this.spec.imageRevision;
385
+ this.spec = spec;
386
+ if (rebuild) this.reset();
387
+ this.signature = signature;
388
+ if (spec.collapsed !== this.previousModelCollapsed) {
389
+ this.collapsed = spec.collapsed ?? false;
390
+ this.previousModelCollapsed = spec.collapsed;
391
+ }
392
+ if (this.title) {
393
+ this.title.content = `${spec.collapsible ? this.collapsed ? "▸ " : "· " : ""}${plainText(spec.title)}`;
394
+ this.title.focusable = Boolean(spec.collapsible);
395
+ }
396
+ if (this.body) this.body.visible = !this.collapsed;
397
+ if (this.text instanceof TextRenderable) this.text.content = styledText(spec.kind === "image" ? spec.text ?? "Image unavailable" : spec.text);
398
+ else if (this.text instanceof MarkdownRenderable || this.text instanceof CodeRenderable) {
399
+ this.text.content = plainText(spec.text);
400
+ this.text.streaming = spec.streaming ?? false;
401
+ if (this.text instanceof CodeRenderable) this.text.filetype = spec.language;
402
+ }
403
+ if (this.image && spec.imagePath && this.image.source !== spec.imagePath) this.image.source = spec.imagePath;
404
+ if (this.terminal) this.terminal.height = spec.height ?? 14;
405
+ if (this.image) this.image.height = spec.height ?? 12;
406
+ if (this.progress) this.progress.content = nativeProgressText(spec.progress, spec.streaming);
407
+ this.children?.replace(spec.children ?? []);
408
+ const actionsSignature = JSON.stringify(spec.actions ?? []);
409
+ if (this.actions && actionsSignature !== this.actionsSignature) {
410
+ this.actionsSignature = actionsSignature;
411
+ for (const child of [...this.actions.getChildren()]) child.destroy();
412
+ for (const action of spec.actions ?? []) {
413
+ const button = new TextRenderable(this.context.renderer, {
414
+ id: `block-action:${spec.id}:${action.id}`,
415
+ content: plainText(action.label),
416
+ fg: this.context.colors.foreground,
417
+ bg: this.context.colors.background,
418
+ attributes: action.disabled ? 2 : action.active ? 1 : 0,
419
+ flexShrink: 0,
420
+ selectable: false,
421
+ onMouseDown: (event) => {
422
+ if (event.button !== 0 || action.disabled) return;
423
+ this.context.emit({ type: "action", action: action.command ?? action.id, id: this.spec.id });
424
+ event.stopPropagation();
425
+ },
426
+ onKeyDown: (key) => {
427
+ if (key.eventType === "release" || key.name !== "return" && key.sequence !== " " || action.disabled) return;
428
+ this.context.emit({ type: "action", action: action.command ?? action.id, id: this.spec.id });
429
+ key.preventDefault();
430
+ }
431
+ });
432
+ button.focusable = !action.disabled;
433
+ nativeControlFocus(button, action.disabled ? 2 : action.active ? 1 : 0);
434
+ if (action.id.startsWith("pixel:") && !action.disabled) {
435
+ button.onMouseOver = () => this.context.emit({ type: "action", action: `hover:${action.id}`, id: this.spec.id });
436
+ button.onMouseOut = () => this.context.emit({ type: "action", action: `hover-end:${action.id}`, id: this.spec.id });
437
+ }
438
+ this.actions.add(button);
439
+ }
440
+ this.actions.visible = (spec.actions?.length ?? 0) > 0;
441
+ }
442
+ this.applyTheme();
443
+ }
444
+ toggle() {
445
+ this.collapsed = !this.collapsed;
446
+ if (this.body) this.body.visible = !this.collapsed;
447
+ if (this.title) this.title.content = `${this.collapsed ? "▸ " : "· "}${plainText(this.spec.title)}`;
448
+ this.context.emit({ type: "action", action: "toggle-block", id: this.spec.id });
449
+ }
450
+ applyTheme() {
451
+ const colors = this.context.colors;
452
+ this.root.backgroundColor = colors.background;
453
+ if (this.spec.kind === "card") this.root.borderColor = colors.border;
454
+ else this.root.border = false;
455
+ if (this.title) {
456
+ this.title.fg = colors.foreground;
457
+ this.title.bg = colors.background;
458
+ }
459
+ if (this.body) this.body.backgroundColor = colors.background;
460
+ if (this.text) {
461
+ this.text.fg = colors.foreground;
462
+ this.text.bg = colors.background;
463
+ }
464
+ if (this.progress) {
465
+ this.progress.fg = colors.foreground;
466
+ this.progress.bg = colors.background;
467
+ }
468
+ for (const action of this.actions?.getChildren() ?? []) if (action instanceof TextRenderable) {
469
+ action.fg = colors.foreground;
470
+ action.bg = colors.background;
471
+ }
472
+ this.children?.applyTheme();
473
+ }
474
+ destroy() {
475
+ this.children?.destroy();
476
+ if (this.terminal) this.context.terminals.delete(this.spec.id);
477
+ this.root.destroyRecursively();
478
+ }
479
+ };
480
+ var NativeBlockList = class {
481
+ constructor(context, parent) {
482
+ this.context = context;
483
+ this.parent = parent;
484
+ }
485
+ context;
486
+ parent;
487
+ views = /* @__PURE__ */ new Map();
488
+ order = [];
489
+ patch(patch) {
490
+ if (patch.reset) this.clear();
491
+ for (const id of patch.remove) this.remove(id);
492
+ for (const spec of patch.upsert) {
493
+ const existing = this.views.get(spec.id);
494
+ if (existing) existing.update(spec);
495
+ else {
496
+ const direction = this.parent instanceof ScrollBoxRenderable ? this.parent.content.primaryAxis : this.parent.primaryAxis;
497
+ const view = new NativeBlockView(this.context, spec, direction === "row");
498
+ this.views.set(spec.id, view);
499
+ this.order.push(spec.id);
500
+ this.parent.add(view.root);
501
+ }
502
+ }
503
+ if (patch.order) this.reorder(patch.order);
504
+ }
505
+ replace(blocks) {
506
+ const keep = new Set(blocks.map((block) => block.id));
507
+ this.patch({ remove: [...this.views.keys()].filter((id) => !keep.has(id)), upsert: blocks, order: blocks.map((block) => block.id) });
508
+ }
509
+ reorder(order) {
510
+ const next = order.filter((id) => this.views.has(id));
511
+ if (next.length === this.order.length && next.every((id, index) => this.order[index] === id)) return;
512
+ next.forEach((id, index) => this.parent.add(this.views.get(id).root, index));
513
+ this.order = next;
514
+ }
515
+ remove(id) {
516
+ this.views.get(id)?.destroy();
517
+ this.views.delete(id);
518
+ this.order = this.order.filter((value) => value !== id);
519
+ }
520
+ applyTheme() {
521
+ for (const view of this.views.values()) view.applyTheme();
522
+ }
523
+ clear() {
524
+ for (const view of this.views.values()) view.destroy();
525
+ this.views.clear();
526
+ this.order = [];
527
+ }
528
+ destroy() {
529
+ this.clear();
530
+ }
531
+ };
532
+
533
+ // packages/cli/src/tui/native/components/modals.ts
534
+ import {
535
+ BoxRenderable as BoxRenderable2,
536
+ InputRenderable,
537
+ ScrollBoxRenderable as ScrollBoxRenderable2,
538
+ TextareaRenderable,
539
+ TextAttributes as TextAttributes4,
540
+ TextRenderable as TextRenderable2
541
+ } from "@opentui/core";
542
+
543
+ // packages/cli/src/tui/native/components/select.ts
544
+ import { SelectRenderable } from "@opentui/core";
545
+
546
+ // packages/cli/src/tui/native/components/scrollbar.ts
547
+ import { TextAttributes as TextAttributes2 } from "@opentui/core";
548
+ function drawDotRail(buffer, x, y, length, horizontal, progress, colors) {
549
+ length = Math.max(0, Math.trunc(length));
550
+ if (!length) return;
551
+ buffer.fillRect(x, y, horizontal ? length : 1, horizontal ? 1 : length, colors.background);
552
+ if (horizontal) buffer.drawText("─".repeat(length), x, y, colors.foreground, colors.background, TextAttributes2.DIM);
553
+ else for (let row = 0; row < length; row++) buffer.drawText("│", x, y + row, colors.foreground, colors.background, TextAttributes2.DIM);
554
+ const position = Math.round(Math.max(0, Math.min(1, progress)) * (length - 1));
555
+ buffer.drawText("•", x + (horizontal ? position : 0), y + (horizontal ? 0 : position), colors.foreground, colors.background, TextAttributes2.BOLD);
556
+ }
557
+ function styleNativeScrollbars(scroll, colors) {
558
+ for (const bar of [scroll.horizontalScrollBar, scroll.verticalScrollBar]) {
559
+ bar.showArrows = false;
560
+ const slider = bar.slider;
561
+ slider.foregroundColor = colors.foreground;
562
+ slider.backgroundColor = colors.background;
563
+ slider.renderAfter = (buffer) => {
564
+ const horizontal = slider.orientation === "horizontal";
565
+ drawDotRail(
566
+ buffer,
567
+ slider.x,
568
+ slider.y,
569
+ horizontal ? slider.width : slider.height,
570
+ horizontal,
571
+ slider.max > slider.min ? (slider.value - slider.min) / (slider.max - slider.min) : 0,
572
+ colors
573
+ );
574
+ };
575
+ let dragging = false;
576
+ const seek = (event) => {
577
+ const horizontal = slider.orientation === "horizontal";
578
+ const length = horizontal ? slider.width : slider.height;
579
+ const offset = horizontal ? event.x - slider.x : event.y - slider.y;
580
+ const progress = length > 1 ? Math.max(0, Math.min(1, offset / (length - 1))) : 0;
581
+ slider.value = slider.min + progress * (slider.max - slider.min);
582
+ event.preventDefault();
583
+ event.stopPropagation();
584
+ };
585
+ slider.onMouseDown = (event) => {
586
+ if (event.button === 0) {
587
+ dragging = true;
588
+ seek(event);
589
+ }
590
+ };
591
+ slider.onMouseDrag = (event) => {
592
+ if (dragging) seek(event);
593
+ };
594
+ slider.onMouseUp = (event) => {
595
+ if (dragging) seek(event);
596
+ dragging = false;
597
+ };
598
+ slider.onMouseDragEnd = () => {
599
+ dragging = false;
600
+ };
601
+ }
602
+ }
603
+
604
+ // packages/cli/src/tui/native/components/select.ts
605
+ var NativeSelectRenderable = class extends SelectRenderable {
606
+ onActivate;
607
+ draggingRail = false;
608
+ constructor(renderer2, options) {
609
+ super(renderer2, options);
610
+ const colors = palette({ mode: "system", bg: -1, accent: -1, textPrimary: -1, textDim: -1, boxColor: -1 });
611
+ this.renderAfter = (buffer) => {
612
+ const count = this.visibleCount();
613
+ if (!this.showScrollIndicator || this.options.length <= count) return;
614
+ const offset = Math.max(0, Math.min(this.getSelectedIndex() - Math.floor(count / 2), this.options.length - count));
615
+ drawDotRail(buffer, this.width - 1, 0, this.height, false, offset / (this.options.length - count), colors);
616
+ };
617
+ }
618
+ visibleCount() {
619
+ return Math.max(1, Math.floor(this.height / (this.showDescription ? 2 : 1)));
620
+ }
621
+ seekRail(event) {
622
+ const progress = this.height > 1 ? Math.max(0, Math.min(1, (event.y - this.y) / (this.height - 1))) : 0;
623
+ const target = Math.round(progress * Math.max(0, this.options.length - 1));
624
+ for (let distance = 0; distance < this.options.length; distance++) {
625
+ const index = [target + distance, target - distance].find((candidate) => candidate >= 0 && candidate < this.options.length && !this.disabled(candidate));
626
+ if (index !== void 0) {
627
+ this.setSelectedIndex(index);
628
+ break;
629
+ }
630
+ }
631
+ event.preventDefault();
632
+ event.stopPropagation();
633
+ }
634
+ disabled(index) {
635
+ const option = this.options[index];
636
+ return !option || option.value?.disabled === true;
637
+ }
638
+ moveUp(steps = 1) {
639
+ this.move(-steps);
640
+ }
641
+ moveDown(steps = 1) {
642
+ this.move(steps);
643
+ }
644
+ move(delta) {
645
+ if (this.options.length === 0) return;
646
+ const direction = delta < 0 ? -1 : 1;
647
+ let index = Math.min(this.options.length - 1, Math.max(0, this.getSelectedIndex() + delta));
648
+ for (let attempts = 0; attempts < this.options.length && this.disabled(index); attempts++) {
649
+ index += direction;
650
+ if (index < 0 || index >= this.options.length) {
651
+ if (!this.wrapSelection) return;
652
+ index = index < 0 ? this.options.length - 1 : 0;
653
+ }
654
+ }
655
+ if (!this.disabled(index)) this.setSelectedIndex(index);
656
+ }
657
+ selectCurrent() {
658
+ if (!this.disabled(this.getSelectedIndex())) super.selectCurrent();
659
+ }
660
+ onMouseEvent(event) {
661
+ if ((event.type === "drag" || event.type === "up") && this.draggingRail) {
662
+ this.seekRail(event);
663
+ if (event.type === "up") this.draggingRail = false;
664
+ return;
665
+ }
666
+ if (event.type === "scroll") {
667
+ if (event.scroll?.direction === "up") this.moveUp(1);
668
+ else if (event.scroll?.direction === "down") this.moveDown(1);
669
+ event.stopPropagation();
670
+ return;
671
+ }
672
+ if (event.type !== "down" || event.button !== 0) return;
673
+ if (this.showScrollIndicator && this.options.length > this.visibleCount() && event.x === this.x + this.width - 1) {
674
+ this.draggingRail = true;
675
+ this.seekRail(event);
676
+ return;
677
+ }
678
+ const lines = this.showDescription ? 2 : 1;
679
+ const count = Math.max(1, Math.floor(this.height / lines));
680
+ const start = Math.max(0, Math.min(this.getSelectedIndex() - Math.floor(count / 2), this.options.length - count));
681
+ const index = start + Math.floor((event.y - this.y) / lines);
682
+ if (index >= start && index < Math.min(start + count, this.options.length) && !this.disabled(index)) {
683
+ this.setSelectedIndex(index);
684
+ this.onActivate?.();
685
+ }
686
+ event.stopPropagation();
687
+ }
688
+ };
689
+
690
+ // packages/cli/src/tui/native/components/corner-frame.ts
691
+ import { RenderableEvents as RenderableEvents2, TextAttributes as TextAttributes3 } from "@opentui/core";
692
+ function drawCornerFrame(buffer, panel, colors, progress = 1) {
693
+ const { x, y, width, height } = panel;
694
+ if (width <= 0 || height <= 0) return;
695
+ const draw = (text, column, row) => buffer.drawText(text, column, row, colors.foreground, colors.background, TextAttributes3.DIM);
696
+ draw(" ".repeat(width), x, y);
697
+ if (height > 1) draw(" ".repeat(width), x, y + height - 1);
698
+ for (let row = 1; row < height - 1; row++) {
699
+ draw(" ", x, y + row);
700
+ if (width > 1) draw(" ", x + width - 1, y + row);
701
+ }
702
+ if (height === 1) {
703
+ draw("─".repeat(width), x, y);
704
+ return;
705
+ }
706
+ if (width === 1) {
707
+ for (let row = 0; row < height; row++) draw("│", x, y + row);
708
+ return;
709
+ }
710
+ const fraction = Math.max(0, Math.min(1, progress));
711
+ const horizontal = Math.round(Math.min(4, Math.max(0, Math.floor((width - 3) / 2))) * fraction);
712
+ const vertical = Math.round(Math.min(2, Math.max(0, Math.floor((height - 3) / 2))) * fraction);
713
+ draw(`┌${"─".repeat(horizontal)}`, x, y);
714
+ draw(`${"─".repeat(horizontal)}┐`, x + width - horizontal - 1, y);
715
+ draw(`└${"─".repeat(horizontal)}`, x, y + height - 1);
716
+ draw(`${"─".repeat(horizontal)}┘`, x + width - horizontal - 1, y + height - 1);
717
+ for (let arm = 1; arm <= vertical; arm++) {
718
+ for (const column of [x, x + width - 1]) {
719
+ draw("│", column, y + arm);
720
+ draw("│", column, y + height - arm - 1);
721
+ }
722
+ }
723
+ }
724
+ var NativeCornerFrame = class {
725
+ constructor(panel, colors, renderer2) {
726
+ this.panel = panel;
727
+ this.colors = colors;
728
+ this.renderer = renderer2;
729
+ this.previousRender = panel.renderAfter;
730
+ this.render = (buffer, deltaTime) => {
731
+ this.previousRender?.call(panel, buffer, deltaTime);
732
+ drawCornerFrame(buffer, panel, this.colors, this.progress);
733
+ };
734
+ panel.renderAfter = this.render;
735
+ panel.once(RenderableEvents2.DESTROYED, this.destroy);
736
+ if (process.env["OMNIUS_TUI_REDUCE_MOTION"] === "1" || process.env["REDUCE_MOTION"] === "1") this.progress = 1;
737
+ else {
738
+ let frame = 0;
739
+ const advance = () => {
740
+ if (this.disposed) return;
741
+ this.progress = ++frame / 4;
742
+ renderer2.requestRender();
743
+ if (frame < 4) this.timer = setTimeout(advance, 32);
744
+ else this.timer = void 0;
745
+ };
746
+ this.timer = setTimeout(advance, 32);
747
+ }
748
+ }
749
+ panel;
750
+ colors;
751
+ renderer;
752
+ progress = 0;
753
+ timer;
754
+ disposed = false;
755
+ previousRender;
756
+ render;
757
+ get animating() {
758
+ return this.timer !== void 0;
759
+ }
760
+ setColors(colors) {
761
+ this.colors = colors;
762
+ }
763
+ destroy = () => {
764
+ if (this.disposed) return;
765
+ this.disposed = true;
766
+ if (this.timer !== void 0) clearTimeout(this.timer);
767
+ this.timer = void 0;
768
+ this.panel.off(RenderableEvents2.DESTROYED, this.destroy);
769
+ if (this.panel.renderAfter === this.render) this.panel.renderAfter = this.previousRender;
770
+ };
771
+ };
772
+
773
+ // packages/cli/src/tui/native/components/modals.ts
774
+ var NativeModalView = class {
775
+ constructor(context, spec, close2) {
776
+ this.context = context;
777
+ this.close = close2;
778
+ this.spec = spec;
779
+ const { renderer: renderer2, colors } = context;
780
+ this.root = new BoxRenderable2(renderer2, {
781
+ id: `modal:${spec.id}`,
41
782
  position: "absolute",
783
+ top: 0,
42
784
  left: 0,
43
- top: row,
44
- width: frame.columns,
785
+ width: "100%",
786
+ height: "100%",
787
+ flexDirection: "column",
788
+ justifyContent: "center",
789
+ alignItems: "center",
790
+ zIndex: 100,
791
+ backgroundColor: "transparent",
792
+ shouldFill: false
793
+ });
794
+ this.panel = new BoxRenderable2(renderer2, {
795
+ id: `modal-panel:${spec.id}`,
796
+ flexDirection: "column",
797
+ width: "94%",
798
+ maxHeight: "94%",
799
+ border: true,
800
+ borderStyle: "single",
801
+ borderColor: colors.border,
802
+ backgroundColor: colors.background,
803
+ paddingX: 1,
804
+ paddingY: 0,
805
+ focusable: true,
806
+ onMouseDown: () => this.focus()
807
+ });
808
+ this.frame = new NativeCornerFrame(this.panel, colors, renderer2);
809
+ this.root.add(this.panel);
810
+ this.breadcrumb = this.addText(`modal-breadcrumb:${spec.id}`);
811
+ this.heading = this.addText(`modal-title:${spec.id}`);
812
+ this.description = this.addText(`modal-description:${spec.id}`);
813
+ if (spec.kind === "select") this.buildSelect();
814
+ else if (spec.kind === "input") this.buildInput();
815
+ else if (spec.kind === "document") {
816
+ this.document = new ScrollBoxRenderable2(renderer2, {
817
+ id: `modal-document:${spec.id}`,
818
+ width: "100%",
819
+ height: Math.max(5, Math.floor(renderer2.terminalHeight * 0.6)),
820
+ scrollY: true,
821
+ scrollX: false,
822
+ viewportCulling: true,
823
+ contentOptions: { flexDirection: "column" }
824
+ });
825
+ styleNativeScrollbars(this.document, colors);
826
+ this.blocks = new NativeBlockList(this.modalContext(), this.document);
827
+ this.panel.add(this.document);
828
+ } else {
829
+ this.progress = new TextRenderable2(renderer2, {
830
+ id: `modal-progress:${spec.id}`,
831
+ width: "100%",
832
+ height: 1,
833
+ content: "",
834
+ fg: colors.foreground,
835
+ bg: colors.background,
836
+ attributes: 2,
837
+ wrapMode: "none",
838
+ selectable: false
839
+ });
840
+ this.panel.add(this.progress);
841
+ const progressBlocks = new BoxRenderable2(renderer2, { id: `modal-progress-blocks:${spec.id}`, flexDirection: "column", width: "100%" });
842
+ this.panel.add(progressBlocks);
843
+ this.blocks = new NativeBlockList(this.modalContext(), progressBlocks);
844
+ }
845
+ this.error = this.addText(`modal-error:${spec.id}`);
846
+ this.hints = this.addText(`modal-hints:${spec.id}`);
847
+ this.actions = new BoxRenderable2(renderer2, { id: `modal-actions:${spec.id}`, flexDirection: "row", gap: 1, width: "100%", marginTop: 0 });
848
+ this.panel.add(this.actions);
849
+ this.update(spec);
850
+ }
851
+ context;
852
+ close;
853
+ root;
854
+ panel;
855
+ frame;
856
+ spec;
857
+ heading;
858
+ description;
859
+ breadcrumb;
860
+ error;
861
+ hints;
862
+ actions;
863
+ filterInput;
864
+ editor;
865
+ secretMask;
866
+ select;
867
+ document;
868
+ blocks;
869
+ progress;
870
+ filtered = [];
871
+ filter = "";
872
+ changing = false;
873
+ actionsSignature = "";
874
+ addText(id) {
875
+ const text = new TextRenderable2(this.context.renderer, { id, content: "", width: "100%", wrapMode: "word", flexShrink: 0, fg: this.context.colors.foreground, selectable: true });
876
+ this.panel.add(text);
877
+ return text;
878
+ }
879
+ buildSelect() {
880
+ const { renderer: renderer2, colors } = this.context;
881
+ this.filterInput = new InputRenderable(renderer2, {
882
+ id: `modal-filter:${this.spec.id}`,
883
+ width: "100%",
884
+ value: this.spec.filter ?? "",
885
+ placeholder: "Filter…",
886
+ placeholderColor: colors.foreground,
887
+ textColor: colors.foreground,
888
+ backgroundColor: colors.background,
889
+ focusedTextColor: colors.foreground,
890
+ focusedBackgroundColor: colors.background,
891
+ onContentChange: () => {
892
+ if (this.changing || !this.filterInput) return;
893
+ this.filter = this.filterInput.plainText;
894
+ this.refreshOptions();
895
+ this.emitAction("filter", this.filter);
896
+ }
897
+ });
898
+ this.filter = this.spec.filter ?? "";
899
+ this.panel.add(this.filterInput);
900
+ this.select = new NativeSelectRenderable(renderer2, {
901
+ id: `modal-select:${this.spec.id}`,
902
+ width: "100%",
903
+ height: 5,
904
+ options: [],
905
+ showDescription: false,
906
+ showScrollIndicator: true,
907
+ showSelectionIndicator: true,
908
+ wrapSelection: false,
909
+ backgroundColor: colors.background,
910
+ focusedBackgroundColor: colors.background,
911
+ textColor: colors.foreground,
912
+ focusedTextColor: colors.foreground,
913
+ selectedBackgroundColor: colors.selection,
914
+ selectedTextColor: colors.foreground
915
+ });
916
+ this.select.onActivate = () => this.confirm();
917
+ this.select.on("selectionChanged", () => {
918
+ if (!this.changing) this.emitAction("focus");
919
+ });
920
+ this.select.on("itemSelected", () => this.confirm());
921
+ this.panel.add(this.select);
922
+ }
923
+ buildInput() {
924
+ const { renderer: renderer2, colors } = this.context;
925
+ const options = {
926
+ id: `modal-input:${this.spec.id}`,
927
+ width: "100%",
928
+ textColor: colors.foreground,
929
+ backgroundColor: colors.background,
930
+ focusedTextColor: colors.foreground,
931
+ focusedBackgroundColor: colors.background,
932
+ placeholder: this.spec.placeholder ?? "",
933
+ placeholderColor: colors.foreground,
934
+ cursorColor: colors.foreground,
935
+ onContentChange: () => {
936
+ if (!this.editor || this.changing) return;
937
+ this.refreshMask();
938
+ if (!this.spec.secret) this.emitAction("change", this.editor.plainText);
939
+ },
940
+ onSubmit: () => this.confirm()
941
+ };
942
+ this.editor = this.spec.multiline ? new TextareaRenderable(renderer2, {
943
+ ...options,
944
+ initialValue: this.spec.value ?? "",
945
+ height: Math.max(3, Math.min(10, Math.floor(renderer2.terminalHeight / 3))),
946
+ wrapMode: "word",
947
+ keyBindings: [{ name: "return", ctrl: true, action: "submit" }]
948
+ }) : new InputRenderable(renderer2, { ...options, value: this.spec.value ?? "" });
949
+ if (this.spec.secret) {
950
+ this.secretMask = this.addText(`modal-secret:${this.spec.id}`);
951
+ this.secretMask.selectable = false;
952
+ this.editor.showCursor = false;
953
+ } else this.panel.add(this.editor);
954
+ }
955
+ refreshMask() {
956
+ if (this.secretMask && this.editor) this.secretMask.content = "•".repeat(Array.from(this.editor.plainText).length);
957
+ }
958
+ update(patch) {
959
+ const old = this.spec;
960
+ this.spec = { ...old, ...patch };
961
+ const spec = this.spec;
962
+ this.changing = true;
963
+ try {
964
+ this.heading.content = plainText(spec.title);
965
+ this.description.content = styledText(spec.description);
966
+ this.description.visible = plainText(spec.description).length > 0;
967
+ this.breadcrumb.content = (spec.breadcrumbs ?? []).join(" › ");
968
+ this.breadcrumb.visible = (spec.breadcrumbs?.length ?? 0) > 0;
969
+ this.error.content = spec.validationError ?? "";
970
+ this.error.visible = Boolean(spec.validationError);
971
+ this.hints.content = spec.hints ?? `${spec.deferSelection || spec.captureKeys ? "" : "Tab controls · "}${spec.kind === "select" ? "↑ ↓ select · Enter confirm · Esc return" : spec.multiline ? "Ctrl+Enter confirm · Esc return" : "Enter confirm · Esc return"}`;
972
+ if (this.filterInput && patch.filter !== void 0 && patch.filter !== old.filter) {
973
+ this.filter = patch.filter;
974
+ this.filterInput.setText(patch.filter);
975
+ }
976
+ if (this.editor && patch.value !== void 0 && patch.value !== old.value) this.editor.setText(patch.value);
977
+ this.refreshMask();
978
+ this.refreshOptions(patch.selectedIndex);
979
+ this.blocks?.replace(spec.blocks ?? []);
980
+ if (this.progress) this.progress.content = nativeProgressText(spec.progress, true);
981
+ const actionsSignature = JSON.stringify(spec.actions ?? []);
982
+ if (actionsSignature !== this.actionsSignature) {
983
+ this.actionsSignature = actionsSignature;
984
+ for (const child of [...this.actions.getChildren()]) child.destroy();
985
+ for (const action of spec.actions ?? []) {
986
+ const button = new TextRenderable2(this.context.renderer, {
987
+ id: `modal-action:${spec.id}:${action.id}`,
988
+ content: plainText(action.label),
989
+ fg: this.context.colors.foreground,
990
+ bg: this.context.colors.background,
991
+ attributes: action.disabled ? 2 : action.active ? 1 : 0,
992
+ selectable: false,
993
+ onMouseDown: (event) => {
994
+ if (event.button !== 0 || action.disabled) return;
995
+ this.emitAction(action.command ?? action.id, this.editor?.plainText);
996
+ event.stopPropagation();
997
+ },
998
+ onKeyDown: (key) => {
999
+ if (key.eventType === "release" || key.name !== "return" && key.sequence !== " " || action.disabled) return;
1000
+ this.emitAction(action.command ?? action.id, this.editor?.plainText);
1001
+ key.preventDefault();
1002
+ }
1003
+ });
1004
+ button.focusable = !action.disabled;
1005
+ nativeControlFocus(button, action.disabled ? 2 : action.active ? 1 : 0);
1006
+ this.actions.add(button);
1007
+ }
1008
+ this.actions.visible = (spec.actions?.length ?? 0) > 0;
1009
+ }
1010
+ } finally {
1011
+ this.changing = false;
1012
+ }
1013
+ this.applyTheme();
1014
+ }
1015
+ refreshOptions(selectedIndex) {
1016
+ if (!this.select) return;
1017
+ const query = this.filter.trim().toLocaleLowerCase();
1018
+ const previousKey = this.current()?.item.key;
1019
+ this.filtered = (this.spec.items ?? []).map((item, index) => ({ item, index })).filter(({ item }) => !query || `${plainText(item.label)} ${plainText(item.description)}`.toLocaleLowerCase().includes(query));
1020
+ this.select.options = this.filtered.map(({ item, index }) => ({
1021
+ name: `${item.heading ? "── " : ""}${plainText(item.label)}`,
1022
+ description: plainText(item.description),
1023
+ value: { key: item.key, index, disabled: item.disabled || item.heading }
1024
+ }));
1025
+ this.select.showDescription = this.filtered.some(({ item }) => Boolean(plainText(item.description)));
1026
+ const rowCount = this.select.showDescription ? 2 : 1;
1027
+ this.select.height = Math.max(1, Math.min(Math.max(3, Math.floor(this.context.renderer.terminalHeight * 0.55)), this.filtered.length * rowCount));
1028
+ const preferred = selectedIndex !== void 0 ? this.filtered.findIndex((entry) => entry.index === selectedIndex) : this.filtered.findIndex((entry) => entry.item.key === previousKey);
1029
+ const firstEnabled = this.filtered.findIndex((entry) => !entry.item.disabled && !entry.item.heading);
1030
+ this.select.setSelectedIndex(preferred >= 0 && !this.filtered[preferred].item.disabled && !this.filtered[preferred].item.heading ? preferred : Math.max(0, firstEnabled));
1031
+ }
1032
+ current() {
1033
+ return this.filtered[this.select?.getSelectedIndex() ?? -1];
1034
+ }
1035
+ modalContext() {
1036
+ const scoped = Object.create(this.context);
1037
+ scoped.emit = (event) => {
1038
+ if (event.type === "action") this.emitAction(event.action, event.id);
1039
+ else this.context.emit(event);
1040
+ };
1041
+ return scoped;
1042
+ }
1043
+ emitAction(action, value) {
1044
+ const current = this.current();
1045
+ this.context.emit({ type: "modal-action", id: this.spec.id, action, key: current?.item.key, index: current?.index, value });
1046
+ }
1047
+ confirm() {
1048
+ if (this.spec.kind === "progress") {
1049
+ this.emitAction("enter");
1050
+ return;
1051
+ }
1052
+ const current = this.current();
1053
+ if (this.spec.kind === "select" && (!current || current.item.disabled || current.item.heading)) return;
1054
+ const value = this.editor?.plainText;
1055
+ if (this.spec.deferSelection) {
1056
+ this.emitAction("enter", value);
1057
+ return;
1058
+ }
1059
+ this.close(this.spec.id, { confirmed: true, key: current?.item.key ?? null, index: current?.index ?? -1, ...value === void 0 ? {} : { value } });
1060
+ }
1061
+ cancel(action = "cancel") {
1062
+ if (this.spec.deferSelection) {
1063
+ this.emitAction(action);
1064
+ return;
1065
+ }
1066
+ this.close(this.spec.id, { confirmed: false, key: this.spec.breadcrumbs?.length ? "__back__" : null, index: this.current()?.index ?? -1 });
1067
+ }
1068
+ focus() {
1069
+ if (this.filterInput) this.filterInput.focus();
1070
+ else if (this.editor && !this.spec.secret) this.editor.focus();
1071
+ else if (this.document) this.document.focus();
1072
+ else this.panel.focus();
1073
+ }
1074
+ nextControl(direction) {
1075
+ const controls = [];
1076
+ const visit = (node) => {
1077
+ if (!node.visible || node.isDestroyed) return;
1078
+ if (node !== this.panel && node.focusable) controls.push(node);
1079
+ for (const child of node.getChildren()) visit(child);
1080
+ };
1081
+ visit(this.panel);
1082
+ if (!controls.length) {
1083
+ this.panel.focus();
1084
+ return;
1085
+ }
1086
+ const focused = this.context.renderer.currentFocusedRenderable;
1087
+ const current = focused ? controls.indexOf(focused) : -1;
1088
+ const next = controls[current < 0 ? direction > 0 ? 0 : controls.length - 1 : (current + direction + controls.length) % controls.length];
1089
+ next.focus();
1090
+ this.document?.scrollChildIntoView(next.id);
1091
+ }
1092
+ handleKey(key) {
1093
+ const consume = () => {
1094
+ key.preventDefault();
1095
+ key.stopPropagation();
1096
+ return true;
1097
+ };
1098
+ if (key.eventType === "release") return false;
1099
+ if (key.name === "escape" || key.ctrl && key.name === "c") {
1100
+ this.cancel();
1101
+ return consume();
1102
+ }
1103
+ if (key.name === "tab") {
1104
+ if (this.spec.deferSelection || this.spec.captureKeys) this.emitAction(key.shift ? "shift-tab" : "tab");
1105
+ else this.nextControl(key.shift ? -1 : 1);
1106
+ return consume();
1107
+ }
1108
+ const focusedId = this.context.renderer.currentFocusedRenderable?.id ?? "";
1109
+ if ((key.name === "return" || key.sequence === " ") && /^(?:block-title:|block-action:|modal-action:)/.test(focusedId)) return false;
1110
+ if (this.spec.kind === "select") {
1111
+ if (key.name === "up" || key.name === "down" || key.name === "pageup" || key.name === "pagedown") {
1112
+ const amount = key.name.startsWith("page") ? Math.max(1, Math.floor((this.select?.height ?? 5) / (this.select?.showDescription ? 2 : 1))) : 1;
1113
+ if (key.name === "up" || key.name === "pageup") this.select?.moveUp(amount);
1114
+ else this.select?.moveDown(amount);
1115
+ return consume();
1116
+ }
1117
+ if (key.name === "return") {
1118
+ this.confirm();
1119
+ return consume();
1120
+ }
1121
+ if (key.name === "left" || key.name === "right" || key.name === "delete" || key.name === "backspace" && !this.filter) {
1122
+ const action = key.name;
1123
+ if (this.spec.deferSelection) this.emitAction(action);
1124
+ else if (action === "backspace") this.cancel("backspace");
1125
+ return consume();
1126
+ }
1127
+ if (key.sequence === " " && !this.filter && this.spec.deferSelection) {
1128
+ this.emitAction("space");
1129
+ return consume();
1130
+ }
1131
+ if (key.name && key.sequence.length === 1 && !key.ctrl && !key.meta && this.spec.deferSelection && this.spec.captureKeys) {
1132
+ this.emitAction("key", key.sequence);
1133
+ return consume();
1134
+ }
1135
+ this.filterInput?.handleKeyPress(key);
1136
+ return consume();
1137
+ }
1138
+ if (this.spec.kind === "input") {
1139
+ if (key.name === "return" && (!this.spec.multiline || key.ctrl)) {
1140
+ this.confirm();
1141
+ return consume();
1142
+ }
1143
+ if (!key.ctrl && !key.meta && key.sequence && !key.sequence.startsWith("\x1B") && key.sequence.charCodeAt(0) >= 32 && this.overBudget(key.sequence, this.editor?.plainText ?? "")) return consume();
1144
+ if (this.spec.secret) {
1145
+ this.editor?.handleKeyPress(key);
1146
+ this.refreshMask();
1147
+ return consume();
1148
+ }
1149
+ return false;
1150
+ }
1151
+ if (key.name === "return") {
1152
+ this.confirm();
1153
+ return consume();
1154
+ }
1155
+ if (this.document && (key.name === "pageup" || key.name === "pagedown" || key.name === "up" || key.name === "down")) {
1156
+ this.document.scrollBy(key.name === "up" ? -1 : key.name === "down" ? 1 : key.name === "pageup" ? -1 : 1, key.name.startsWith("page") ? "viewport" : "absolute");
1157
+ return consume();
1158
+ }
1159
+ if (key.sequence.length === 1 && !key.ctrl && !key.meta) {
1160
+ this.emitAction("key", key.sequence);
1161
+ return consume();
1162
+ }
1163
+ return false;
1164
+ }
1165
+ handlePaste(event) {
1166
+ const text = new TextDecoder().decode(event.bytes);
1167
+ if (this.overBudget(text, this.filterInput?.plainText ?? this.editor?.plainText ?? "")) {
1168
+ event.preventDefault();
1169
+ event.stopPropagation();
1170
+ return true;
1171
+ }
1172
+ if (this.filterInput) this.filterInput.handlePaste(event);
1173
+ else if (this.spec.secret) {
1174
+ this.editor?.handlePaste(event);
1175
+ this.refreshMask();
1176
+ } else return false;
1177
+ event.preventDefault();
1178
+ event.stopPropagation();
1179
+ return true;
1180
+ }
1181
+ overBudget(text, current) {
1182
+ const budget = this.spec.maxBytes ?? 1024 * 1024;
1183
+ if (Buffer.byteLength(text, "utf8") + Buffer.byteLength(current, "utf8") <= budget) return false;
1184
+ this.error.content = `Input exceeds the ${budget.toLocaleString()} byte limit.`;
1185
+ this.error.visible = true;
1186
+ return true;
1187
+ }
1188
+ applyTheme() {
1189
+ const colors = this.context.colors;
1190
+ this.panel.backgroundColor = colors.background;
1191
+ this.panel.borderColor = colors.border;
1192
+ this.frame.setColors(colors);
1193
+ this.heading.fg = colors.foreground;
1194
+ this.heading.attributes = 1;
1195
+ this.description.fg = colors.foreground;
1196
+ this.breadcrumb.fg = colors.dim;
1197
+ this.breadcrumb.attributes = 2;
1198
+ this.error.fg = colors.foreground;
1199
+ this.error.attributes = 1;
1200
+ this.hints.fg = colors.dim;
1201
+ this.hints.attributes = 2;
1202
+ if (this.progress) {
1203
+ this.progress.fg = colors.foreground;
1204
+ this.progress.bg = colors.background;
1205
+ }
1206
+ if (this.document) styleNativeScrollbars(this.document, colors);
1207
+ if (this.select) {
1208
+ this.select.backgroundColor = this.select.focusedBackgroundColor = colors.background;
1209
+ this.select.textColor = this.select.focusedTextColor = colors.foreground;
1210
+ this.select.selectedBackgroundColor = colors.selection;
1211
+ this.select.selectedTextColor = colors.foreground;
1212
+ this.select.descriptionColor = colors.dim;
1213
+ this.select.selectedDescriptionColor = colors.foreground;
1214
+ }
1215
+ for (const editor of [this.editor, this.filterInput]) if (editor) {
1216
+ editor.backgroundColor = editor.focusedBackgroundColor = colors.background;
1217
+ editor.textColor = editor.focusedTextColor = colors.foreground;
1218
+ editor.selectionBg = void 0;
1219
+ editor.selectionFg = void 0;
1220
+ editor.cursorColor = colors.foreground;
1221
+ editor.placeholderColor = colors.foreground;
1222
+ }
1223
+ if (this.secretMask) this.secretMask.fg = colors.foreground;
1224
+ for (const action of this.actions.getChildren()) if (action instanceof TextRenderable2) {
1225
+ action.fg = colors.foreground;
1226
+ action.bg = colors.background;
1227
+ }
1228
+ this.blocks?.applyTheme();
1229
+ }
1230
+ destroy() {
1231
+ this.frame.destroy();
1232
+ this.blocks?.destroy();
1233
+ if (this.spec.secret) this.editor?.destroy();
1234
+ this.root.destroyRecursively();
1235
+ }
1236
+ };
1237
+ var NativeModalStack = class {
1238
+ constructor(context, restoreFocus) {
1239
+ this.context = context;
1240
+ this.restoreFocus = restoreFocus;
1241
+ context.renderer.addPostProcessFn(this.dimUnderlay);
1242
+ }
1243
+ context;
1244
+ restoreFocus;
1245
+ views = [];
1246
+ byId = /* @__PURE__ */ new Map();
1247
+ dimUnderlay = (buffer) => {
1248
+ const panel = this.active?.panel;
1249
+ if (!panel || panel.isDestroyed || !panel.visible) return;
1250
+ const { x, y, width, height } = panel;
1251
+ buffer.withBuffers((cells) => {
1252
+ for (let row = 0; row < cells.height; row++) for (let column = 0; column < cells.width; column++) {
1253
+ if (column < x || column >= x + width || row < y || row >= y + height)
1254
+ cells.attributes[row * cells.width + column] |= TextAttributes4.DIM;
1255
+ }
1256
+ });
1257
+ };
1258
+ get active() {
1259
+ return this.views.at(-1);
1260
+ }
1261
+ open(spec) {
1262
+ const existing = this.byId.get(spec.id);
1263
+ if (existing) {
1264
+ existing.update(spec);
1265
+ existing.focus();
1266
+ return;
1267
+ }
1268
+ if (this.active) this.active.root.visible = false;
1269
+ const view = new NativeModalView(this.context, spec, (id, result) => this.close(id, result, true));
1270
+ this.views.push(view);
1271
+ this.byId.set(spec.id, view);
1272
+ this.context.renderer.root.add(view.root);
1273
+ view.focus();
1274
+ }
1275
+ update(id, patch) {
1276
+ this.byId.get(id)?.update(patch);
1277
+ }
1278
+ close(id, result, notify = false) {
1279
+ const view = this.byId.get(id);
1280
+ if (!view) return;
1281
+ const wasActive = view === this.active;
1282
+ view.destroy();
1283
+ this.byId.delete(id);
1284
+ this.views = this.views.filter((candidate) => candidate !== view);
1285
+ if (wasActive) {
1286
+ if (this.active) {
1287
+ this.active.root.visible = true;
1288
+ this.active.focus();
1289
+ } else this.restoreFocus();
1290
+ }
1291
+ if (notify) this.context.emit({ type: "modal-result", id, result });
1292
+ }
1293
+ applyTheme() {
1294
+ for (const view of this.views) view.applyTheme();
1295
+ }
1296
+ destroy() {
1297
+ this.context.renderer.removePostProcessFn(this.dimUnderlay);
1298
+ for (const view of this.views) view.destroy();
1299
+ this.views = [];
1300
+ this.byId.clear();
1301
+ }
1302
+ };
1303
+
1304
+ // packages/cli/src/tui/native/components/instruments.ts
1305
+ import {
1306
+ BoxRenderable as BoxRenderable3,
1307
+ RGBA as RGBA2,
1308
+ RenderableEvents as RenderableEvents3,
1309
+ ScrollBoxRenderable as ScrollBoxRenderable3,
1310
+ TextAttributes as TextAttributes5,
1311
+ TextRenderable as TextRenderable3
1312
+ } from "@opentui/core";
1313
+
1314
+ // packages/cli/src/tui/terminal-cells.ts
1315
+ var graphemes = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1316
+ var extendedPictographic = /\p{Extended_Pictographic}/u;
1317
+ var emojiPresentation = /\p{Emoji_Presentation}/u;
1318
+ var zeroWidthGrapheme = /^[\p{Mark}\p{Cf}\u200c\u200d\ufe0e\ufe0f]+$/u;
1319
+ function escapeEnd(value, start) {
1320
+ if (value[start] !== "\x1B") return start + 1;
1321
+ const kind = value[start + 1];
1322
+ if (kind === "[") {
1323
+ for (let i = start + 2; i < value.length; i++) {
1324
+ const code = value.charCodeAt(i);
1325
+ if (code >= 64 && code <= 126) return i + 1;
1326
+ }
1327
+ return value.length;
1328
+ }
1329
+ if (kind === "]") {
1330
+ for (let i = start + 2; i < value.length; i++) {
1331
+ if (value[i] === "\x07") return i + 1;
1332
+ if (value[i] === "\x1B" && value[i + 1] === "\\") return i + 2;
1333
+ }
1334
+ return value.length;
1335
+ }
1336
+ if (kind === "P" || kind === "X" || kind === "^" || kind === "_") {
1337
+ for (let i = start + 2; i < value.length; i++) {
1338
+ if (value[i] === "\x1B" && value[i + 1] === "\\") return i + 2;
1339
+ }
1340
+ return value.length;
1341
+ }
1342
+ return Math.min(value.length, start + 2);
1343
+ }
1344
+ function stripTerminalSequences(value) {
1345
+ let out = "";
1346
+ for (let i = 0; i < value.length; ) {
1347
+ if (value[i] === "\x1B") {
1348
+ i = escapeEnd(value, i);
1349
+ continue;
1350
+ }
1351
+ const code = value.charCodeAt(i);
1352
+ if (code >= 0 && code < 32 || code === 127) {
1353
+ i++;
1354
+ continue;
1355
+ }
1356
+ const cp = value.codePointAt(i);
1357
+ out += String.fromCodePoint(cp);
1358
+ i += cp > 65535 ? 2 : 1;
1359
+ }
1360
+ return out;
1361
+ }
1362
+ function normalizeTerminalLine(value) {
1363
+ return value.replace(/\r\n|\r|\n/g, " ").replace(/\t/g, " ");
1364
+ }
1365
+ function isWideCodePoint(cp) {
1366
+ return cp >= 4352 && cp <= 4447 || cp === 9001 || cp === 9002 || cp >= 11904 && cp <= 12350 || cp >= 12352 && cp <= 42191 || cp >= 44032 && cp <= 55203 || cp >= 63744 && cp <= 64255 || cp >= 65040 && cp <= 65049 || cp >= 65072 && cp <= 65135 || cp >= 65280 && cp <= 65376 || cp >= 65504 && cp <= 65510 || cp >= 110592 && cp <= 111359 || cp >= 131072 && cp <= 262141;
1367
+ }
1368
+ function graphemeCellWidth(value) {
1369
+ if (!value || zeroWidthGrapheme.test(value)) return 0;
1370
+ const cp = value.codePointAt(0);
1371
+ if (cp < 32 || cp >= 127 && cp < 160) return 0;
1372
+ if (emojiPresentation.test(value) || value.includes("️") || value.includes("‍") && extendedPictographic.test(value) || cp >= 127462 && cp <= 127487) {
1373
+ return 2;
1374
+ }
1375
+ return isWideCodePoint(cp) ? 2 : 1;
1376
+ }
1377
+ function plainGraphemes(value) {
1378
+ return Array.from(
1379
+ graphemes.segment(stripTerminalSequences(value)),
1380
+ (part) => part.segment
1381
+ );
1382
+ }
1383
+ function terminalCellWidth(value) {
1384
+ let width = 0;
1385
+ for (const grapheme of plainGraphemes(normalizeTerminalLine(value))) {
1386
+ width += graphemeCellWidth(grapheme);
1387
+ }
1388
+ return width;
1389
+ }
1390
+
1391
+ // packages/cli/src/tui/native/components/instrument-format.ts
1392
+ var graphemes2 = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1393
+ function fitInstrumentText(value, cells, marker = "~") {
1394
+ const text = stripTerminalSequences(normalizeTerminalLine(value));
1395
+ const width = Math.max(0, Math.floor(cells));
1396
+ if (terminalCellWidth(text) <= width) return text;
1397
+ const suffix = width > 0 ? marker : "";
1398
+ const budget = Math.max(0, width - terminalCellWidth(suffix));
1399
+ let used = 0, result = "";
1400
+ for (const { segment } of graphemes2.segment(text)) {
1401
+ const size = graphemeCellWidth(segment);
1402
+ if (used + size > budget) break;
1403
+ used += size;
1404
+ result += segment;
1405
+ }
1406
+ return result + suffix;
1407
+ }
1408
+ function formatTelemetryRow(label, value, width, reserved = 12) {
1409
+ const cells = Math.max(0, Math.floor(width));
1410
+ if (!cells) return "";
1411
+ const reading = stripTerminalSequences(normalizeTerminalLine(value));
1412
+ const readingCells = terminalCellWidth(reading);
1413
+ if (readingCells >= cells) return fitInstrumentText(reading, cells);
1414
+ const field = Math.min(cells, Math.max(readingCells, Math.floor(reserved)));
1415
+ const labelCells = Math.max(0, cells - field - 1);
1416
+ const left = fitInstrumentText(label.toUpperCase(), labelCells);
1417
+ const dots = ".".repeat(Math.max(0, cells - field - terminalCellWidth(left)));
1418
+ return left + dots + " ".repeat(Math.max(0, field - readingCells)) + reading;
1419
+ }
1420
+ function instrumentNumber(value, precision = 0, unit = "") {
1421
+ return value === void 0 || !Number.isFinite(value) || value < 0 ? "--" : `${value.toFixed(Math.max(0, Math.min(6, precision)))}${unit}`;
1422
+ }
1423
+ function formatSignalMeter(value, maximum, width = 8) {
1424
+ const cells = Math.max(1, Math.floor(width));
1425
+ if (value === void 0 || maximum === void 0 || !Number.isFinite(value) || !Number.isFinite(maximum) || value < 0 || maximum <= 0)
1426
+ return `[${".".repeat(cells)}] --`;
1427
+ const ratio = Math.max(0, Math.min(1, value / maximum));
1428
+ const filled = Math.round(ratio * cells);
1429
+ return `[${"●".repeat(filled)}${"○".repeat(cells - filled)}] ${Math.round(ratio * 100)}%`;
1430
+ }
1431
+ function formatSectionBanner(title, width, expanded = true) {
1432
+ const cells = Math.max(0, Math.floor(width));
1433
+ const label = fitInstrumentText(
1434
+ `${expanded ? "-" : "+"} ${title.toUpperCase()} `,
1435
+ cells
1436
+ );
1437
+ return label + "─".repeat(Math.max(0, cells - terminalCellWidth(label)));
1438
+ }
1439
+ function instrumentStatus(value) {
1440
+ const status = (value || "unknown").toLowerCase();
1441
+ if (status === "running") return "ACTIVE";
1442
+ if (status === "completed" || status === "succeeded") return "DONE";
1443
+ if (status === "failed" || status === "error") return "ERR";
1444
+ if (status === "waiting") return "STDBY";
1445
+ if (status === "paused") return "PAUSED";
1446
+ if (status === "stopped") return "STOPPED";
1447
+ return status.toUpperCase();
1448
+ }
1449
+
1450
+ // packages/cli/src/tui/native/components/instruments.ts
1451
+ var foreground = RGBA2.defaultForeground();
1452
+ var background = RGBA2.defaultBackground();
1453
+ var SectionBanner = class extends TextRenderable3 {
1454
+ constructor(renderer2, title, id, activate) {
1455
+ super(renderer2, {
1456
+ id,
1457
+ content: `- ${title.toUpperCase()}`,
1458
+ width: "100%",
45
1459
  height: 1,
1460
+ flexShrink: 0,
1461
+ fg: foreground,
1462
+ bg: background,
1463
+ attributes: TextAttributes5.BOLD,
1464
+ selectable: false,
46
1465
  wrapMode: "none",
47
1466
  truncate: true,
1467
+ onMouseDown: (event) => {
1468
+ if (event.button === 0) {
1469
+ this.focus();
1470
+ this.activate();
1471
+ event.stopPropagation();
1472
+ }
1473
+ },
1474
+ onKeyDown: (key) => {
1475
+ if (key.eventType !== "release" && (key.name === "return" || key.sequence === " ")) {
1476
+ this.activate();
1477
+ key.preventDefault();
1478
+ }
1479
+ }
1480
+ });
1481
+ this.activate = activate;
1482
+ this.heading = title;
1483
+ this.focusable = true;
1484
+ this.on(RenderableEvents3.FOCUSED, () => {
1485
+ this.attributes = TextAttributes5.BOLD | TextAttributes5.INVERSE;
1486
+ });
1487
+ this.on(RenderableEvents3.BLURRED, () => {
1488
+ this.attributes = TextAttributes5.BOLD;
1489
+ });
1490
+ }
1491
+ activate;
1492
+ expanded = true;
1493
+ heading;
1494
+ onResize(width, height) {
1495
+ super.onResize(width, height);
1496
+ this.refresh();
1497
+ }
1498
+ update(title, expanded = this.expanded) {
1499
+ this.heading = title;
1500
+ this.expanded = expanded;
1501
+ this.refresh();
1502
+ }
1503
+ refresh() {
1504
+ const next = formatSectionBanner(this.heading, this.width, this.expanded);
1505
+ if (this.plainText !== next) this.content = next;
1506
+ }
1507
+ };
1508
+ var TelemetryRow = class extends TextRenderable3 {
1509
+ constructor(renderer2, label, id, reserved = 12) {
1510
+ super(renderer2, {
1511
+ id,
1512
+ content: "",
1513
+ width: "100%",
1514
+ height: 1,
1515
+ flexShrink: 0,
1516
+ fg: foreground,
1517
+ bg: background,
1518
+ wrapMode: "none",
1519
+ truncate: true
1520
+ });
1521
+ this.reserved = reserved;
1522
+ this.label = label;
1523
+ }
1524
+ reserved;
1525
+ label;
1526
+ value = "--";
1527
+ onResize(width, height) {
1528
+ super.onResize(width, height);
1529
+ this.refresh();
1530
+ }
1531
+ update(value, label = this.label) {
1532
+ this.label = label;
1533
+ this.value = value;
1534
+ this.refresh();
1535
+ }
1536
+ refresh() {
1537
+ const next = formatTelemetryRow(
1538
+ this.label,
1539
+ this.value,
1540
+ this.width,
1541
+ this.reserved
1542
+ );
1543
+ if (this.plainText !== next) this.content = next;
1544
+ }
1545
+ };
1546
+ var SignalMeter = class extends TelemetryRow {
1547
+ constructor(renderer2, label, id) {
1548
+ super(renderer2, label, id, 18);
1549
+ }
1550
+ updateReading(value, maximum) {
1551
+ this.update(formatSignalMeter(value, maximum));
1552
+ }
1553
+ };
1554
+ var ChannelMap = class extends BoxRenderable3 {
1555
+ constructor(renderer2, id, inspect, reveal) {
1556
+ super(renderer2, {
1557
+ id,
1558
+ width: "100%",
1559
+ flexDirection: "column",
1560
+ flexShrink: 0
1561
+ });
1562
+ this.inspect = inspect;
1563
+ this.reveal = reveal;
1564
+ }
1565
+ inspect;
1566
+ reveal;
1567
+ rows = /* @__PURE__ */ new Map();
1568
+ channels = /* @__PURE__ */ new Map();
1569
+ agents = [];
1570
+ onResize(width, height) {
1571
+ super.onResize(width, height);
1572
+ this.refresh();
1573
+ }
1574
+ update(agents) {
1575
+ this.agents = agents;
1576
+ const keep = new Set(agents.map((agent) => agent.id));
1577
+ for (const [id, row] of this.rows)
1578
+ if (!keep.has(id)) {
1579
+ row.destroyRecursively();
1580
+ this.rows.delete(id);
1581
+ }
1582
+ for (const agent of agents) {
1583
+ if (!this.channels.has(agent.id))
1584
+ this.channels.set(agent.id, this.channels.size + 1);
1585
+ if (!this.rows.has(agent.id)) {
1586
+ const row = new TextRenderable3(this._ctx, {
1587
+ id: `${this.id}:channel:${agent.id}`,
1588
+ content: "",
1589
+ width: "100%",
1590
+ height: 1,
1591
+ flexShrink: 0,
1592
+ fg: foreground,
1593
+ bg: background,
1594
+ wrapMode: "none",
1595
+ truncate: true,
1596
+ selectable: false,
1597
+ onMouseDown: (event) => {
1598
+ if (event.button === 0) {
1599
+ row.focus();
1600
+ this.inspect?.(agent.id);
1601
+ event.stopPropagation();
1602
+ }
1603
+ },
1604
+ onKeyDown: (key) => {
1605
+ if (key.eventType === "release") return;
1606
+ if (key.name === "return" || key.sequence === " ") {
1607
+ this.inspect?.(agent.id);
1608
+ key.preventDefault();
1609
+ } else if (key.name === "down" || key.name === "up") {
1610
+ const targets = this.focusTargets();
1611
+ const index = targets.indexOf(row), delta = key.name === "down" ? 1 : -1;
1612
+ targets[(index + delta + targets.length) % targets.length]?.focus();
1613
+ key.preventDefault();
1614
+ }
1615
+ }
1616
+ });
1617
+ row.focusable = !!this.inspect;
1618
+ row.on(RenderableEvents3.FOCUSED, () => {
1619
+ row.attributes = TextAttributes5.INVERSE;
1620
+ this.reveal?.(row);
1621
+ });
1622
+ row.on(RenderableEvents3.BLURRED, () => {
1623
+ row.attributes = 0;
1624
+ });
1625
+ this.rows.set(agent.id, row);
1626
+ this.add(row);
1627
+ }
1628
+ }
1629
+ this.refresh();
1630
+ }
1631
+ focusTargets() {
1632
+ return this.agents.flatMap((agent) => {
1633
+ const row = this.rows.get(agent.id);
1634
+ return row?.focusable ? [row] : [];
1635
+ });
1636
+ }
1637
+ refresh() {
1638
+ for (const agent of this.agents) {
1639
+ const row = this.rows.get(agent.id);
1640
+ if (!row) continue;
1641
+ const channel = String(this.channels.get(agent.id)).padStart(2, "0");
1642
+ const next = formatTelemetryRow(
1643
+ `${agent.selected ? ">" : " "}${channel} ${agent.label}`,
1644
+ instrumentStatus(agent.status),
1645
+ this.width,
1646
+ 7
1647
+ );
1648
+ if (row.plainText !== next) row.content = next;
1649
+ }
1650
+ }
1651
+ };
1652
+ var InstrumentRail = class extends BoxRenderable3 {
1653
+ scroll;
1654
+ inferenceHeader;
1655
+ sessionHeader;
1656
+ channels;
1657
+ inference;
1658
+ session;
1659
+ model;
1660
+ rows = /* @__PURE__ */ new Map();
1661
+ contextMeter;
1662
+ constructor(renderer2, options = {}) {
1663
+ const id = options.id ?? "omnius-instruments";
1664
+ super(renderer2, {
1665
+ id,
1666
+ width: "100%",
1667
+ height: "100%",
1668
+ minWidth: 0,
1669
+ minHeight: 1,
1670
+ flexDirection: "column",
1671
+ flexGrow: 1,
1672
+ flexShrink: 1
1673
+ });
1674
+ this.scroll = new ScrollBoxRenderable3(renderer2, {
1675
+ id: `${id}:scroll`,
1676
+ width: "100%",
1677
+ flexGrow: 1,
1678
+ minHeight: 1,
1679
+ minWidth: 0,
1680
+ scrollY: true,
1681
+ scrollX: false,
1682
+ viewportCulling: true,
1683
+ verticalScrollbarOptions: { showArrows: false },
1684
+ horizontalScrollbarOptions: { visible: false },
1685
+ contentOptions: { flexDirection: "column", paddingRight: 1 }
1686
+ });
1687
+ this.add(this.scroll);
1688
+ this.inferenceHeader = new SectionBanner(
1689
+ renderer2,
1690
+ "INFERENCE",
1691
+ `${id}:inference`,
1692
+ () => this.toggleSection("inference")
1693
+ );
1694
+ this.inferenceHeader.on(
1695
+ RenderableEvents3.FOCUSED,
1696
+ () => this.reveal(this.inferenceHeader)
1697
+ );
1698
+ this.scroll.add(this.inferenceHeader);
1699
+ this.inference = new BoxRenderable3(renderer2, {
1700
+ id: `${id}:inference-body`,
1701
+ width: "100%",
1702
+ flexDirection: "column",
1703
+ flexShrink: 0
1704
+ });
1705
+ this.scroll.add(this.inference);
1706
+ const addRow = (parent, key, label, reserved = 12) => {
1707
+ const row = new TelemetryRow(renderer2, label, `${id}:${key}`, reserved);
1708
+ this.rows.set(key, row);
1709
+ parent.add(row);
1710
+ return row;
1711
+ };
1712
+ addRow(this.inference, "route", "ROUTE");
1713
+ addRow(this.inference, "backend", "BACKEND");
1714
+ this.model = new TextRenderable3(renderer2, {
1715
+ id: `${id}:model`,
1716
+ content: "MODEL --",
1717
+ width: "100%",
1718
+ height: 1,
1719
+ fg: foreground,
1720
+ bg: background,
1721
+ wrapMode: "none",
1722
+ truncate: true,
1723
+ flexShrink: 0
1724
+ });
1725
+ this.inference.add(this.model);
1726
+ addRow(this.inference, "input", "IN TOK");
1727
+ addRow(this.inference, "output", "OUT TOK");
1728
+ addRow(this.inference, "speed", "RATE");
1729
+ addRow(this.inference, "context", "CTX TOK", 20);
1730
+ this.contextMeter = new SignalMeter(
1731
+ renderer2,
1732
+ "CTX LOAD",
1733
+ `${id}:context-load`
1734
+ );
1735
+ this.inference.add(this.contextMeter);
1736
+ addRow(this.inference, "state", "STATE");
1737
+ this.sessionHeader = new SectionBanner(
1738
+ renderer2,
1739
+ "SESSION / AGENTS",
1740
+ `${id}:session`,
1741
+ () => this.toggleSection("session")
1742
+ );
1743
+ this.sessionHeader.on(
1744
+ RenderableEvents3.FOCUSED,
1745
+ () => this.reveal(this.sessionHeader)
1746
+ );
1747
+ this.scroll.add(this.sessionHeader);
1748
+ this.session = new BoxRenderable3(renderer2, {
1749
+ id: `${id}:session-body`,
1750
+ width: "100%",
1751
+ flexDirection: "column",
1752
+ flexShrink: 0
1753
+ });
1754
+ this.scroll.add(this.session);
1755
+ addRow(this.session, "selected", "VIEW");
1756
+ addRow(this.session, "elapsed", "ELAPSED");
1757
+ addRow(this.session, "turns", "TURNS");
1758
+ addRow(this.session, "tools", "TOOLS");
1759
+ addRow(this.session, "toolResults", "TOOL OK/ERR");
1760
+ addRow(this.session, "queued", "QUEUED");
1761
+ this.channels = new ChannelMap(
1762
+ renderer2,
1763
+ `${id}:channels`,
1764
+ options.onInspectAgent,
1765
+ (row) => this.reveal(row)
1766
+ );
1767
+ this.session.add(this.channels);
1768
+ this.session.visible = false;
1769
+ this.sessionHeader.update("SESSION / AGENTS", false);
1770
+ if (options.colors) this.applyTheme(options.colors);
1771
+ }
1772
+ update(state) {
1773
+ const data = state.instruments;
1774
+ const inference = data?.inference;
1775
+ const session = data?.session;
1776
+ const set = (id, value) => this.rows.get(id).update(value);
1777
+ set("route", (inference?.route || "UNKNOWN").toUpperCase());
1778
+ set("backend", (inference?.backend || "--").toUpperCase());
1779
+ const model = `MODEL ${inference?.model || "--"}`;
1780
+ if (this.model.plainText !== model) this.model.content = model;
1781
+ set("input", instrumentNumber(inference?.inputTokens));
1782
+ set(
1783
+ "output",
1784
+ `${inference?.outputEstimated ? "~" : ""}${instrumentNumber(inference?.outputTokens)}`
1785
+ );
1786
+ set("speed", instrumentNumber(inference?.tokensPerSecond, 1, " TOK/S"));
1787
+ const used = instrumentNumber(inference?.contextUsedTokens), limit = instrumentNumber(inference?.contextLimitTokens);
1788
+ set("context", `${inference?.contextEstimated ? "~" : ""}${used}/${limit}`);
1789
+ this.contextMeter.updateReading(
1790
+ inference?.contextUsedTokens,
1791
+ inference?.contextLimitTokens
1792
+ );
1793
+ set(
1794
+ "state",
1795
+ inference?.streaming ? "STREAMING" : instrumentStatus(session?.status)
1796
+ );
1797
+ set(
1798
+ "selected",
1799
+ session?.selectedLabel || state.tabs.find((tab) => tab.id === state.activeViewId)?.label || state.activeViewId
1800
+ );
1801
+ set("elapsed", session?.elapsed ?? "--");
1802
+ set("turns", instrumentNumber(session?.turns));
1803
+ set("tools", instrumentNumber(session?.tools));
1804
+ set(
1805
+ "toolResults",
1806
+ `${instrumentNumber(session?.successfulTools)}/${instrumentNumber(session?.failedTools)}`
1807
+ );
1808
+ set("queued", instrumentNumber(session?.queued ?? state.queued.length));
1809
+ this.channels.update(
1810
+ session?.agents ?? state.tabs.map((tab) => ({
1811
+ id: tab.id,
1812
+ label: tab.label,
1813
+ status: "unknown",
1814
+ selected: tab.id === state.activeViewId
1815
+ }))
1816
+ );
1817
+ }
1818
+ applyTheme(colors) {
1819
+ this.backgroundColor = colors.background;
1820
+ const visit = (node) => {
1821
+ if (node instanceof TextRenderable3) {
1822
+ node.fg = colors.foreground;
1823
+ node.bg = colors.background;
1824
+ }
1825
+ for (const child of node.getChildren()) visit(child);
1826
+ };
1827
+ visit(this);
1828
+ styleNativeScrollbars(this.scroll, colors);
1829
+ }
1830
+ toggleSection(id) {
1831
+ const header = id === "inference" ? this.inferenceHeader : this.sessionHeader;
1832
+ const body = id === "inference" ? this.inference : this.session;
1833
+ body.visible = !body.visible;
1834
+ header.update(
1835
+ id === "inference" ? "INFERENCE" : "SESSION / AGENTS",
1836
+ body.visible
1837
+ );
1838
+ }
1839
+ focusTargets() {
1840
+ if (!this.visible) return [];
1841
+ return [
1842
+ this.inferenceHeader,
1843
+ this.sessionHeader,
1844
+ ...this.session.visible ? this.channels.focusTargets() : []
1845
+ ];
1846
+ }
1847
+ focusSection(index = 0) {
1848
+ const target = [this.inferenceHeader, this.sessionHeader][index];
1849
+ if (!target || !this.visible) return false;
1850
+ target.focus();
1851
+ return true;
1852
+ }
1853
+ reveal(target) {
1854
+ const viewport = this.scroll.viewport;
1855
+ if (target.y < viewport.y) this.scroll.scrollBy(target.y - viewport.y);
1856
+ else if (target.y + target.height > viewport.y + viewport.height)
1857
+ this.scroll.scrollBy(
1858
+ target.y + target.height - viewport.y - viewport.height
1859
+ );
1860
+ }
1861
+ };
1862
+
1863
+ // packages/cli/src/tui/native/components/frontend.ts
1864
+ var initialTheme = { mode: "system", bg: -1, accent: -1, textPrimary: -1, textDim: -1, boxColor: -1 };
1865
+ var NativeFrontend = class {
1866
+ constructor(renderer2, emit) {
1867
+ this.renderer = renderer2;
1868
+ this.emit = emit;
1869
+ const colors = palette(initialTheme);
1870
+ this.context = { renderer: renderer2, colors, syntax: SyntaxStyle2.fromStyles({}, renderer2.nativeScene), emit, terminals: /* @__PURE__ */ new Map() };
1871
+ this.disposeSelectionContrast = installDefaultSelectionContrast(renderer2);
1872
+ this.clipboard = createClipboard({ host: createHostClipboard({ timeoutMs: 1500 }), terminal: createRendererClipboardAdapter(renderer2) });
1873
+ this.root = new BoxRenderable4(renderer2, {
1874
+ id: "omnius-native",
1875
+ width: "100%",
1876
+ height: "100%",
1877
+ flexDirection: "column",
1878
+ backgroundColor: colors.background
1879
+ });
1880
+ renderer2.root.add(this.root);
1881
+ this.header = new BoxRenderable4(renderer2, {
1882
+ id: "omnius-header",
1883
+ width: "100%",
1884
+ minWidth: 0,
1885
+ flexDirection: "column",
1886
+ flexShrink: 0,
1887
+ border: ["bottom"],
1888
+ borderStyle: "single",
1889
+ borderColor: colors.border
1890
+ });
1891
+ this.root.add(this.header);
1892
+ const headerRow = new BoxRenderable4(renderer2, { id: "omnius-header-row", width: "100%", minWidth: 0, minHeight: 1, flexDirection: "row", gap: 1 });
1893
+ this.header.add(headerRow);
1894
+ this.identity = new TextRenderable4(renderer2, {
1895
+ id: "omnius-identity",
1896
+ content: "Omnius",
1897
+ fg: colors.foreground,
1898
+ flexShrink: 0,
1899
+ flexGrow: 0,
1900
+ wrapMode: "none",
1901
+ truncate: true,
1902
+ minWidth: 18,
48
1903
  selectable: false,
49
- fg: RGBA.defaultForeground(),
50
- bg: RGBA.defaultBackground()
51
- });
52
- rows.push(text);
53
- ui.root.add(text);
54
- }
55
- for (const text of rows) text.width = frame.columns;
56
- for (const line of frame.lines) {
57
- const text = rows[line.row];
58
- if (!text) continue;
59
- text.content = new StyledText(line.spans.map((span) => ({
60
- __isChunk: true,
61
- text: span.text,
62
- fg: color(span.fg, false),
63
- bg: color(span.bg, true),
64
- attributes: span.attributes
65
- })));
66
- }
67
- lastCursor = frame.cursor;
68
- ui.setCursorPosition(lastCursor.col + 1, lastCursor.row + 1, lastCursor.visible);
69
- for (const effect of frame.effects) {
70
- if (!session.write(Buffer.from(effect)))
71
- throw new Error("Native terminal control queue is full");
72
- }
73
- }
74
- async function present() {
75
- const ui = renderer;
76
- ui.requestRender();
77
- await ui.idle();
1904
+ attributes: TextAttributes6.BOLD,
1905
+ onMouseDown: (event) => {
1906
+ if (event.button === 0) {
1907
+ this.toggleHeaderPanel();
1908
+ event.stopPropagation();
1909
+ }
1910
+ },
1911
+ onKeyDown: (key) => {
1912
+ if (key.eventType !== "release" && (key.name === "return" || key.sequence === " ")) {
1913
+ this.toggleHeaderPanel();
1914
+ key.preventDefault();
1915
+ }
1916
+ }
1917
+ });
1918
+ headerRow.add(this.identity);
1919
+ this.headerActions = new ScrollBoxRenderable4(renderer2, {
1920
+ id: "omnius-header-actions",
1921
+ flexGrow: 0,
1922
+ flexShrink: 1,
1923
+ minWidth: 0,
1924
+ minHeight: 1,
1925
+ scrollX: true,
1926
+ scrollY: false,
1927
+ viewportOptions: { height: 1, minHeight: 1, maxHeight: 1, flexGrow: 0, flexShrink: 0 },
1928
+ contentOptions: { flexDirection: "row", gap: 1, justifyContent: "flex-end" },
1929
+ verticalScrollbarOptions: { visible: false }
1930
+ });
1931
+ headerRow.add(this.headerActions);
1932
+ this.headerPanel = new TextRenderable4(renderer2, { id: "omnius-header-panel", content: "", width: "100%", wrapMode: "word", visible: false });
1933
+ this.header.add(this.headerPanel);
1934
+ this.detailButton = new TextRenderable4(renderer2, {
1935
+ id: "omnius-details-toggle",
1936
+ content: "+ DETAILS",
1937
+ height: 1,
1938
+ width: 9,
1939
+ flexShrink: 0,
1940
+ fg: colors.foreground,
1941
+ attributes: TextAttributes6.DIM,
1942
+ selectable: false,
1943
+ onMouseDown: (event) => {
1944
+ if (event.button === 0) {
1945
+ this.toggleDetails();
1946
+ event.stopPropagation();
1947
+ }
1948
+ },
1949
+ onKeyDown: (key) => {
1950
+ if (key.eventType !== "release" && (key.name === "return" || key.sequence === " ")) {
1951
+ this.toggleDetails();
1952
+ key.preventDefault();
1953
+ }
1954
+ }
1955
+ });
1956
+ this.detailButton.focusable = true;
1957
+ headerRow.add(this.detailButton, 1);
1958
+ for (const control of [this.identity, this.detailButton]) {
1959
+ const attributes = control.attributes;
1960
+ control.on("focused", () => {
1961
+ control.attributes = attributes | TextAttributes6.INVERSE;
1962
+ });
1963
+ control.on("blurred", () => {
1964
+ control.attributes = attributes;
1965
+ });
1966
+ }
1967
+ this.routeWarning = new TextRenderable4(renderer2, { id: "omnius-route-warning", content: "", width: "100%", wrapMode: "word", fg: colors.warning, attributes: TextAttributes6.BOLD | TextAttributes6.UNDERLINE, visible: false, flexShrink: 0 });
1968
+ this.root.add(this.routeWarning);
1969
+ this.tabs = new TabSelectRenderable(renderer2, {
1970
+ id: "omnius-agent-tabs",
1971
+ width: "100%",
1972
+ height: 2,
1973
+ showDescription: false,
1974
+ showUnderline: true,
1975
+ showScrollArrows: false,
1976
+ tabWidth: 18,
1977
+ wrapSelection: true,
1978
+ visible: false,
1979
+ backgroundColor: colors.background,
1980
+ focusedBackgroundColor: colors.background,
1981
+ textColor: colors.dim,
1982
+ selectedTextColor: colors.foreground,
1983
+ selectedBackgroundColor: colors.selection,
1984
+ onMouseDown: (event) => {
1985
+ if (event.button !== 0) return;
1986
+ const count = Math.max(1, Math.floor(this.tabs.width / this.tabs.getTabWidth()));
1987
+ const start = Math.max(0, Math.min(this.tabs.getSelectedIndex() - Math.floor(count / 2), this.tabs.options.length - count));
1988
+ this.tabs.setSelectedIndex(start + Math.floor((event.x - this.tabs.x) / this.tabs.getTabWidth()));
1989
+ event.stopPropagation();
1990
+ }
1991
+ });
1992
+ this.tabs.on("selectionChanged", () => {
1993
+ if (this.changing) return;
1994
+ const selected = this.tabs.getSelectedOption();
1995
+ if (selected) this.emitAction("view", String(selected.value));
1996
+ this.editor.focus();
1997
+ });
1998
+ this.tabs.renderAfter = (buffer) => {
1999
+ if (this.tabs.height < 2) return;
2000
+ const colors2 = this.context.colors;
2001
+ const tabWidth = Math.min(this.tabs.getTabWidth(), this.tabs.width);
2002
+ const count = Math.max(1, Math.floor(this.tabs.width / Math.max(1, tabWidth)));
2003
+ const selected = this.tabs.getSelectedIndex();
2004
+ const start = Math.max(0, Math.min(selected - Math.floor(count / 2), this.tabs.options.length - count));
2005
+ buffer.fillRect(0, 1, this.tabs.width, 1, colors2.background);
2006
+ buffer.drawText("─".repeat(Math.max(0, tabWidth)), (selected - start) * tabWidth, 1, colors2.foreground, colors2.background, TextAttributes6.DIM);
2007
+ };
2008
+ this.root.add(this.tabs);
2009
+ this.taskPane = new BoxRenderable4(renderer2, { id: "omnius-tasks", width: "100%", flexDirection: "column", maxHeight: 6, flexShrink: 0, overflow: "hidden", visible: false });
2010
+ this.tasks = new NativeBlockList(this.context, this.taskPane);
2011
+ this.root.add(this.taskPane);
2012
+ this.body = new BoxRenderable4(renderer2, { id: "omnius-body", width: "100%", flexDirection: "row", flexGrow: 1, minHeight: 1 });
2013
+ this.root.add(this.body);
2014
+ this.scroll = new ScrollBoxRenderable4(renderer2, {
2015
+ id: "omnius-conversation",
2016
+ flexGrow: 1,
2017
+ flexShrink: 1,
2018
+ minWidth: 0,
2019
+ minHeight: 1,
2020
+ scrollY: true,
2021
+ scrollX: false,
2022
+ stickyScroll: true,
2023
+ stickyStart: "bottom",
2024
+ viewportCulling: true,
2025
+ verticalScrollbarOptions: { showArrows: false, trackOptions: { foregroundColor: colors.dim, backgroundColor: colors.background } },
2026
+ contentOptions: { flexDirection: "column", onMouseDown: (event) => {
2027
+ if (!this.selectionArmed || event.button !== 0 || !event.target) return;
2028
+ this.selectionArmed = false;
2029
+ let block = event.target;
2030
+ while (block && !block.id.startsWith("block:")) block = block.parent;
2031
+ if (!block) return;
2032
+ const selectables = [];
2033
+ const visit = (node) => {
2034
+ if (!node.visible) return;
2035
+ if (node.selectable && node.height > 0 && node.width > 0) selectables.push(node);
2036
+ for (const child of node.getChildren()) visit(child);
2037
+ };
2038
+ visit(block);
2039
+ const first = selectables[0], last = selectables.at(-1);
2040
+ if (first && last) {
2041
+ this.renderer.startSelection(first, first.x, first.y, "line");
2042
+ this.renderer.updateSelection(last, last.x + Math.max(0, last.width - 1), last.y + Math.max(0, last.height - 1), { finishDragging: true });
2043
+ }
2044
+ event.preventDefault();
2045
+ event.stopPropagation();
2046
+ } }
2047
+ });
2048
+ this.body.add(this.scroll);
2049
+ this.content = new NativeBlockList(this.context, this.scroll);
2050
+ this.instruments = new InstrumentRail(renderer2, { colors, onInspectAgent: (id) => this.emitAction("view", id) });
2051
+ this.body.add(this.instruments);
2052
+ this.embeddedPane = new BoxRenderable4(renderer2, {
2053
+ id: "omnius-editor-pane",
2054
+ width: "50%",
2055
+ flexShrink: 0,
2056
+ flexDirection: "column",
2057
+ border: true,
2058
+ borderStyle: "single",
2059
+ borderColor: colors.border,
2060
+ visible: false
2061
+ });
2062
+ this.body.add(this.embeddedPane);
2063
+ this.liveButton = new TextRenderable4(renderer2, {
2064
+ id: "omnius-live-button",
2065
+ content: "↓ Live",
2066
+ width: "100%",
2067
+ height: 1,
2068
+ visible: false,
2069
+ fg: colors.accent,
2070
+ selectable: false,
2071
+ onMouseDown: (event) => {
2072
+ if (event.button === 0) {
2073
+ this.scrollToLive();
2074
+ event.stopPropagation();
2075
+ }
2076
+ },
2077
+ onKeyDown: (key) => {
2078
+ if (key.eventType !== "release" && (key.name === "return" || key.sequence === " ")) {
2079
+ this.scrollToLive();
2080
+ key.preventDefault();
2081
+ }
2082
+ }
2083
+ });
2084
+ this.liveButton.focusable = true;
2085
+ this.root.add(this.liveButton);
2086
+ this.queue = new TextRenderable4(renderer2, { id: "omnius-steering-queue", content: "", width: "100%", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, visible: false, flexShrink: 0 });
2087
+ this.root.add(this.queue);
2088
+ this.suggestions = new NativeSelectRenderable(renderer2, {
2089
+ id: "omnius-suggestions",
2090
+ width: "100%",
2091
+ height: 1,
2092
+ visible: false,
2093
+ showDescription: false,
2094
+ showScrollIndicator: true,
2095
+ showSelectionIndicator: true,
2096
+ backgroundColor: colors.background,
2097
+ focusedBackgroundColor: colors.background,
2098
+ textColor: colors.dim,
2099
+ selectedTextColor: colors.foreground,
2100
+ selectedBackgroundColor: colors.selection
2101
+ });
2102
+ this.suggestions.onActivate = () => this.acceptSuggestion();
2103
+ this.suggestions.on("itemSelected", () => this.acceptSuggestion());
2104
+ this.root.add(this.suggestions);
2105
+ this.status = new BoxRenderable4(renderer2, { id: "omnius-live-status", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 2, visible: false });
2106
+ this.root.add(this.status);
2107
+ this.footer = new BoxRenderable4(renderer2, {
2108
+ id: "omnius-input-box",
2109
+ width: "100%",
2110
+ flexShrink: 0,
2111
+ border: ["top"],
2112
+ borderStyle: "single",
2113
+ borderColor: colors.border,
2114
+ flexDirection: "column"
2115
+ });
2116
+ this.root.add(this.footer);
2117
+ const inputRow = new BoxRenderable4(renderer2, { id: "omnius-input-row", width: "100%", flexDirection: "row", gap: 1 });
2118
+ this.footer.add(inputRow);
2119
+ this.prompt = new TextRenderable4(renderer2, { id: "omnius-prompt", content: "❯", fg: colors.accent, wrapMode: "none", selectable: false, flexShrink: 0 });
2120
+ inputRow.add(this.prompt);
2121
+ this.editor = new TextareaRenderable2(renderer2, {
2122
+ id: "omnius-input",
2123
+ flexGrow: 1,
2124
+ minWidth: 0,
2125
+ height: 1,
2126
+ maxHeight: 8,
2127
+ wrapMode: "word",
2128
+ textColor: colors.foreground,
2129
+ backgroundColor: colors.background,
2130
+ focusedTextColor: colors.foreground,
2131
+ focusedBackgroundColor: colors.background,
2132
+ placeholderColor: colors.foreground,
2133
+ cursorStyle: { style: "block", blinking: true },
2134
+ keyBindings: [{ name: "return", action: "submit" }, { name: "return", shift: true, action: "newline" }],
2135
+ onContentChange: () => this.editorChanged(),
2136
+ onCursorChange: () => this.editorChanged(),
2137
+ onSubmit: () => this.submit()
2138
+ });
2139
+ inputRow.add(this.editor);
2140
+ this.enhance = new BoxRenderable4(renderer2, { id: "omnius-enhance", flexDirection: "row", gap: 1, flexShrink: 0, visible: false });
2141
+ inputRow.add(this.enhance);
2142
+ this.activity = new TextRenderable4(renderer2, { id: "omnius-activity", content: "", flexGrow: 1, minWidth: 0, height: 1, wrapMode: "none", truncate: true, visible: false, fg: colors.accent });
2143
+ this.status.add(this.activity);
2144
+ this.media = new BoxRenderable4(renderer2, { id: "omnius-media", height: 1, minWidth: 0, flexShrink: 1, flexDirection: "row", gap: 1, visible: false });
2145
+ this.mediaText = new TextRenderable4(renderer2, { id: "omnius-media-text", content: "", flexGrow: 1, minWidth: 0, wrapMode: "none", truncate: true, fg: colors.accent });
2146
+ this.media.add(this.mediaText);
2147
+ this.micTrack = new TextRenderable4(renderer2, { id: "omnius-mic-meter", width: 12, height: 1, flexShrink: 0, fg: colors.foreground, visible: false });
2148
+ this.media.add(this.micTrack);
2149
+ this.status.add(this.media);
2150
+ this.metricsRow = new BoxRenderable4(renderer2, { id: "omnius-metrics-row", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 1 });
2151
+ this.keyHints = new TextRenderable4(renderer2, { id: "omnius-key-hints", content: "F1 HELP TAB FOCUS", height: 1, flexShrink: 0, attributes: TextAttributes6.DIM, fg: colors.foreground, selectable: false });
2152
+ this.metricsRow.add(this.keyHints);
2153
+ this.metricsStartActions = new BoxRenderable4(renderer2, { id: "omnius-metrics-start-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
2154
+ this.metricsRow.add(this.metricsStartActions);
2155
+ this.metrics = new TextRenderable4(renderer2, { id: "omnius-metrics", content: "", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, flexShrink: 1, flexGrow: 1, minWidth: 0 });
2156
+ this.metricsRow.add(this.metrics);
2157
+ this.metricsEndActions = new BoxRenderable4(renderer2, { id: "omnius-metrics-end-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
2158
+ this.metricsRow.add(this.metricsEndActions);
2159
+ this.root.add(this.metricsRow);
2160
+ this.modals = new NativeModalStack(this.context, () => this.restoreFocus());
2161
+ renderer2.keyInput.on("keypress", this.onKey);
2162
+ renderer2.keyInput.on("paste", this.onPaste);
2163
+ renderer2.on(CliRenderEvents2.FRAME, this.onFrame);
2164
+ renderer2.on(CliRenderEvents2.RESIZE, this.onResize);
2165
+ this.applyTheme(initialTheme);
2166
+ this.editor.focus();
2167
+ }
2168
+ renderer;
2169
+ emit;
2170
+ root;
2171
+ scroll;
2172
+ editor;
2173
+ content;
2174
+ modals;
2175
+ context;
2176
+ header;
2177
+ identity;
2178
+ headerActions;
2179
+ headerPanel;
2180
+ routeWarning;
2181
+ tabs;
2182
+ taskPane;
2183
+ tasks;
2184
+ body;
2185
+ instruments;
2186
+ detailButton;
2187
+ embeddedPane;
2188
+ liveButton;
2189
+ queue;
2190
+ suggestions;
2191
+ footer;
2192
+ prompt;
2193
+ enhance;
2194
+ metrics;
2195
+ keyHints;
2196
+ metricsRow;
2197
+ metricsStartActions;
2198
+ metricsEndActions;
2199
+ activity;
2200
+ status;
2201
+ media;
2202
+ mediaText;
2203
+ micTrack;
2204
+ clipboard;
2205
+ state;
2206
+ themeSignature = "";
2207
+ headerSignature = "";
2208
+ tabsSignature = "";
2209
+ suggestionSignature = "";
2210
+ metricsActionsSignature = "";
2211
+ changing = false;
2212
+ paused = false;
2213
+ editorInitialized = false;
2214
+ lastInput = "";
2215
+ lastCursor = -1;
2216
+ history = [];
2217
+ historyIndex = -1;
2218
+ savedInput = "";
2219
+ disposeSelectionContrast;
2220
+ actionSignatures = /* @__PURE__ */ new WeakMap();
2221
+ detailsOpen;
2222
+ headerPanelCollapsed = false;
2223
+ inputEventPending = false;
2224
+ viewportSignature = "";
2225
+ newEntries = 0;
2226
+ destroyed = false;
2227
+ pendingScrollRestore;
2228
+ viewScroll = /* @__PURE__ */ new Map();
2229
+ embeddedId;
2230
+ selectionArmed = false;
2231
+ update(state, patch) {
2232
+ const inputWasEnabled = this.state?.input.enabled;
2233
+ const previousView = this.state ? `${this.state.activeViewId}:${this.state.presentation ?? "stack"}` : void 0;
2234
+ const currentView = `${state.activeViewId}:${state.presentation ?? "stack"}`;
2235
+ const viewChanged = previousView !== void 0 && previousView !== currentView;
2236
+ if (viewChanged) {
2237
+ this.viewScroll.set(previousView, { top: this.scroll.scrollTop, live: this.offset === 0 });
2238
+ this.pendingScrollRestore = this.viewScroll.get(currentView) ?? { top: 0, live: true };
2239
+ this.newEntries = 0;
2240
+ }
2241
+ this.state = state;
2242
+ this.changing = true;
2243
+ try {
2244
+ this.applyTheme(state.theme);
2245
+ const headerSignature = JSON.stringify(state.header);
2246
+ if (headerSignature !== this.headerSignature) {
2247
+ this.headerSignature = headerSignature;
2248
+ this.identity.content = styledText([
2249
+ { text: `${plainText(state.header.panel) ? this.headerPanelCollapsed ? "▸" : "▾" : "·"} ${state.header.title}` },
2250
+ ...state.header.detail ? [{ text: " " }, ...textSpans(state.header.detail)] : []
2251
+ ]);
2252
+ this.headerPanel.content = styledText(state.header.panel);
2253
+ this.headerPanel.visible = plainText(state.header.panel).length > 0 && !this.headerPanelCollapsed;
2254
+ this.identity.focusable = plainText(state.header.panel).length > 0;
2255
+ this.replaceActions(this.headerActions, state.header.actions, "header");
2256
+ this.headerActions.visible = state.header.actions.length > 0;
2257
+ }
2258
+ this.routeWarning.content = state.routeWarning ? `! ${state.routeWarning}` : "";
2259
+ this.routeWarning.visible = Boolean(state.routeWarning);
2260
+ const tabsSignature = JSON.stringify(state.tabs);
2261
+ if (tabsSignature !== this.tabsSignature) {
2262
+ this.tabsSignature = tabsSignature;
2263
+ this.tabs.setOptions(state.tabs.map((tab) => ({ name: fitInstrumentText(plainText(tab.label), this.tabs.getTabWidth() - 2), description: "", value: tab.id })));
2264
+ }
2265
+ this.tabs.visible = state.tabs.length > 1;
2266
+ const activeTab = state.tabs.findIndex((tab) => tab.id === state.activeViewId);
2267
+ if (activeTab >= 0 && activeTab !== this.tabs.getSelectedIndex()) this.tabs.setSelectedIndex(activeTab);
2268
+ this.tasks.replace(state.tasks);
2269
+ this.taskPane.visible = state.tasks.length > 0;
2270
+ if (patch) {
2271
+ if (this.offset > 0 && !viewChanged && !patch.reset) this.newEntries += patch.upsert.filter((block) => !this.content.views.has(block.id)).length;
2272
+ this.content.patch(patch);
2273
+ }
2274
+ this.queue.content = state.queued.length ? `Queued (${state.queued.length}) · ${state.queued.join(" · ")}` : "";
2275
+ this.queue.visible = state.queued.length > 0;
2276
+ const suggestionSignature = JSON.stringify(state.suggestions);
2277
+ if (suggestionSignature !== this.suggestionSignature) {
2278
+ this.suggestionSignature = suggestionSignature;
2279
+ const selected = this.suggestions.getSelectedOption()?.value;
2280
+ this.suggestions.options = state.suggestions.map((item) => ({ name: item.value, description: item.description ?? "", value: item.value }));
2281
+ const index = state.suggestions.findIndex((item) => item.value === selected);
2282
+ if (index >= 0) this.suggestions.setSelectedIndex(index);
2283
+ }
2284
+ this.suggestions.visible = state.suggestions.length > 0;
2285
+ this.suggestions.height = Math.min(4, state.suggestions.length || 1);
2286
+ this.prompt.content = styledText(state.prompt.trimEnd() || "❯");
2287
+ if (!this.editorInitialized) {
2288
+ this.editor.setText(state.input.text);
2289
+ this.editor.cursorOffset = Math.max(0, Math.min(state.input.text.length, state.input.cursor));
2290
+ this.editorInitialized = true;
2291
+ }
2292
+ this.editor.focusable = state.input.enabled && !this.paused;
2293
+ if (state.input.enabled && !this.paused && !this.modals.active && (inputWasEnabled === false || !this.renderer.currentFocusedRenderable)) this.editor.focus();
2294
+ this.metrics.content = styledText(state.metrics);
2295
+ this.metrics.visible = state.metricsVisible;
2296
+ this.instruments.update(state);
2297
+ this.layoutInstruments();
2298
+ const metricsActions = JSON.stringify(state.metricsActions ?? []);
2299
+ if (metricsActions !== this.metricsActionsSignature) {
2300
+ this.metricsActionsSignature = metricsActions;
2301
+ this.replaceActions(this.metricsStartActions, (state.metricsActions ?? []).filter((action) => action.id === "agents"), "metrics");
2302
+ this.replaceActions(this.metricsEndActions, (state.metricsActions ?? []).filter((action) => action.id !== "agents"), "metrics");
2303
+ }
2304
+ this.renderEnhance();
2305
+ this.renderActivity();
2306
+ this.renderMedia();
2307
+ } finally {
2308
+ this.changing = false;
2309
+ }
2310
+ this.editorChanged();
2311
+ this.renderer.requestRender();
2312
+ }
2313
+ replaceActions(parent, actions, prefix) {
2314
+ const signature = JSON.stringify(actions);
2315
+ if (this.actionSignatures.get(parent) === signature) return;
2316
+ this.actionSignatures.set(parent, signature);
2317
+ for (const child of [...parent.getChildren()]) child.destroy();
2318
+ for (const action of actions) {
2319
+ const colors = this.context.colors;
2320
+ const attributes = action.disabled ? TextAttributes6.DIM : action.active ? TextAttributes6.BOLD | TextAttributes6.UNDERLINE : 0;
2321
+ const button = new TextRenderable4(this.renderer, {
2322
+ id: `${prefix}:${action.id}`,
2323
+ content: styledText(action.label),
2324
+ flexShrink: 0,
2325
+ wrapMode: "none",
2326
+ selectable: false,
2327
+ fg: colors.foreground,
2328
+ bg: colors.background,
2329
+ attributes,
2330
+ onMouseDown: (event) => {
2331
+ if (event.button !== 0 || action.disabled) return;
2332
+ this.emitAction(action.command ?? action.id, action.id);
2333
+ event.stopPropagation();
2334
+ },
2335
+ onKeyDown: (key) => {
2336
+ if (key.eventType === "release" || key.name !== "return" && key.sequence !== " " || action.disabled) return;
2337
+ this.emitAction(action.command ?? action.id, action.id);
2338
+ key.preventDefault();
2339
+ }
2340
+ });
2341
+ button.focusable = !action.disabled;
2342
+ if (!action.disabled) {
2343
+ button.on("focused", () => {
2344
+ button.attributes = attributes | TextAttributes6.INVERSE;
2345
+ });
2346
+ button.on("blurred", () => {
2347
+ button.attributes = attributes;
2348
+ });
2349
+ button.onMouseOver = () => {
2350
+ button.attributes = attributes | TextAttributes6.UNDERLINE | (button.focused ? TextAttributes6.INVERSE : 0);
2351
+ };
2352
+ button.onMouseOut = () => {
2353
+ button.attributes = attributes | (button.focused ? TextAttributes6.INVERSE : 0);
2354
+ };
2355
+ }
2356
+ parent.add(button);
2357
+ }
2358
+ }
2359
+ renderEnhance() {
2360
+ const state = this.state?.enhance;
2361
+ this.enhance.visible = Boolean(state && (state.state !== "idle" || this.editor.plainText.trim()));
2362
+ if (!state) return;
2363
+ const actions = state.state === "confirm" ? [{ id: "enhance-accept", label: "✓" }, { id: "enhance-reject", label: "×" }] : [{ id: "enhance", label: state.state === "busy" ? "WORK · ENHANCE" : state.label ?? "ENHANCE", disabled: state.state === "busy" }];
2364
+ this.replaceActions(this.enhance, actions, "enhance");
2365
+ }
2366
+ renderActivity() {
2367
+ const activity = this.state?.activity;
2368
+ const stage = activity?.stage === "idle" ? void 0 : activity?.stage;
2369
+ this.activity.visible = Boolean(activity?.processing || activity?.label || stage);
2370
+ this.activity.content = `${activity?.processing ? "RUN · " : ""}${activity?.label || stage || ""}`;
2371
+ this.status.visible = this.activity.visible || this.media.visible;
2372
+ }
2373
+ renderMedia() {
2374
+ const media = this.state?.media;
2375
+ this.media.visible = Boolean(media && (media.audio || media.video || media.voiceEnabled || media.recording || media.countdown > 0 || media.label));
2376
+ this.status.visible = this.activity.visible || this.media.visible;
2377
+ if (!media) return;
2378
+ this.mediaText.content = [
2379
+ media.recording ? "● REC" : void 0,
2380
+ media.speech ? "speech" : void 0,
2381
+ media.audio ? "audio" : void 0,
2382
+ media.video ? "video" : void 0,
2383
+ media.voiceEnabled ? "voice" : void 0,
2384
+ media.countdown > 0 ? `${media.countdown}s` : void 0,
2385
+ media.levelDb === void 0 ? void 0 : `${media.levelDb} dB`,
2386
+ media.label
2387
+ ].filter(Boolean).join(" · ");
2388
+ this.mediaText.fg = media.recording ? this.context.colors.warning : this.context.colors.accent;
2389
+ this.micTrack.visible = media.recording && media.levelDb !== void 0;
2390
+ const level = Math.round(Math.max(0, Math.min(1, ((media.levelDb ?? -60) + 60) / 60)) * 8);
2391
+ this.micTrack.content = `[${"●".repeat(level)}${"·".repeat(8 - level)}]`;
2392
+ }
2393
+ applyTheme(theme) {
2394
+ const signature = JSON.stringify(theme);
2395
+ if (signature === this.themeSignature) return;
2396
+ this.themeSignature = signature;
2397
+ this.headerSignature = "";
2398
+ this.metricsActionsSignature = "";
2399
+ const colors = this.context.colors = palette(theme);
2400
+ for (const [name, style] of Object.entries({
2401
+ default: { fg: colors.foreground },
2402
+ conceal: { fg: colors.foreground },
2403
+ "markup.heading": { fg: colors.accent, bold: true },
2404
+ "markup.strong": { fg: colors.foreground, bold: true },
2405
+ "markup.italic": { fg: colors.foreground, italic: true },
2406
+ "markup.link": { fg: colors.accent, underline: true },
2407
+ "markup.link.url": { fg: colors.dim },
2408
+ "markup.raw": { fg: colors.accent },
2409
+ "markup.list": { fg: colors.accent },
2410
+ "markup.quote": { fg: colors.dim },
2411
+ comment: { fg: colors.dim, italic: true },
2412
+ keyword: { fg: colors.accent, bold: true },
2413
+ string: { fg: colors.success },
2414
+ number: { fg: colors.warning },
2415
+ function: { fg: colors.foreground },
2416
+ type: { fg: colors.accent },
2417
+ punctuation: { fg: colors.dim }
2418
+ })) this.context.syntax.registerStyle(name, style);
2419
+ this.root.backgroundColor = colors.background;
2420
+ this.header.backgroundColor = this.footer.backgroundColor = colors.background;
2421
+ this.header.borderColor = this.footer.borderColor = this.embeddedPane.borderColor = colors.border;
2422
+ this.identity.fg = colors.foreground;
2423
+ this.headerPanel.fg = colors.foreground;
2424
+ this.routeWarning.fg = colors.warning;
2425
+ this.prompt.fg = this.activity.fg = this.liveButton.fg = colors.accent;
2426
+ this.micTrack.fg = colors.foreground;
2427
+ this.queue.fg = this.metrics.fg = colors.dim;
2428
+ this.editor.backgroundColor = this.editor.focusedBackgroundColor = colors.background;
2429
+ this.editor.textColor = this.editor.focusedTextColor = colors.foreground;
2430
+ this.editor.editorView.setSelectionColors(void 0, void 0);
2431
+ this.tabs.backgroundColor = this.tabs.focusedBackgroundColor = colors.background;
2432
+ this.tabs.textColor = colors.dim;
2433
+ this.tabs.selectedTextColor = colors.foreground;
2434
+ this.tabs.selectedBackgroundColor = colors.selection;
2435
+ this.suggestions.backgroundColor = this.suggestions.focusedBackgroundColor = colors.background;
2436
+ this.suggestions.textColor = colors.dim;
2437
+ this.suggestions.selectedTextColor = colors.foreground;
2438
+ this.suggestions.selectedBackgroundColor = colors.selection;
2439
+ styleNativeScrollbars(this.headerActions, colors);
2440
+ styleNativeScrollbars(this.scroll, colors);
2441
+ this.instruments.applyTheme(colors);
2442
+ this.content.applyTheme();
2443
+ this.tasks.applyTheme();
2444
+ this.modals?.applyTheme();
2445
+ }
2446
+ toggleHeaderPanel() {
2447
+ if (!this.state || !plainText(this.state.header.panel)) return;
2448
+ this.headerPanelCollapsed = !this.headerPanelCollapsed;
2449
+ this.headerSignature = "";
2450
+ this.update(this.state);
2451
+ }
2452
+ toggleDetails() {
2453
+ this.detailsOpen = !(this.detailsOpen ?? this.renderer.terminalWidth >= 110);
2454
+ this.layoutInstruments();
2455
+ this.renderer.requestRender();
2456
+ }
2457
+ layoutInstruments() {
2458
+ const width = this.renderer.terminalWidth;
2459
+ const wide = width >= 110;
2460
+ const shown = !this.embeddedPane.visible && (this.detailsOpen ?? wide);
2461
+ this.body.flexDirection = wide || this.embeddedPane.visible && width >= 80 ? "row" : "column";
2462
+ this.instruments.visible = shown;
2463
+ const railWidth = Math.floor(width * 0.38);
2464
+ this.instruments.width = wide ? railWidth : "100%";
2465
+ this.scroll.width = shown && wide ? width - railWidth : this.embeddedPane.visible && width >= 80 ? "50%" : "100%";
2466
+ this.instruments.maxHeight = wide ? "100%" : Math.max(4, Math.min(9, Math.floor(this.renderer.terminalHeight / 3)));
2467
+ this.instruments.flexGrow = 0;
2468
+ this.instruments.flexShrink = wide ? 0 : 1;
2469
+ this.instruments.border = wide ? ["left"] : ["top"];
2470
+ this.instruments.borderStyle = "single";
2471
+ this.instruments.borderColor = this.context.colors.border;
2472
+ this.instruments.paddingLeft = wide ? 1 : 0;
2473
+ this.embeddedPane.width = width >= 80 ? "50%" : "100%";
2474
+ this.embeddedPane.maxHeight = width >= 80 ? "100%" : "50%";
2475
+ this.headerActions.visible = width >= 60 && Boolean(this.state?.header.actions.length);
2476
+ const actions = this.state?.header.actions ?? [];
2477
+ const naturalWidth = actions.reduce((size, action) => size + terminalCellWidth(plainText(action.label)), 0) + Math.max(0, actions.length - 1);
2478
+ const detailWidth = width < 40 ? 1 : 9;
2479
+ const gapCount = this.headerActions.visible ? 2 : 1;
2480
+ const controlWidth = Math.min(naturalWidth, Math.max(1, width - 18 - detailWidth - gapCount));
2481
+ this.headerActions.width = controlWidth;
2482
+ this.identity.minWidth = Math.max(0, Math.min(18, width - detailWidth - gapCount));
2483
+ this.identity.width = Math.max(0, width - (this.headerActions.visible ? controlWidth : 0) - detailWidth - gapCount);
2484
+ this.detailButton.width = detailWidth;
2485
+ this.keyHints.content = width < 60 ? "F1 HELP" : "F1 HELP TAB FOCUS";
2486
+ this.detailButton.content = `${shown ? "-" : "+"}${detailWidth > 1 ? " DETAILS" : ""}`;
2487
+ for (let focused = this.renderer.currentFocusedRenderable; focused; focused = focused.parent) {
2488
+ if (!shown && focused === this.instruments || !this.headerActions.visible && focused === this.headerActions) {
2489
+ this.restoreFocus();
2490
+ break;
2491
+ }
2492
+ }
2493
+ }
2494
+ get offset() {
2495
+ return Math.max(0, this.scroll.scrollHeight - this.scroll.viewport.height - this.scroll.scrollTop);
2496
+ }
2497
+ getRenderable(id) {
2498
+ const visit = (node) => {
2499
+ if (node.id === id) return node;
2500
+ for (const child of node.getChildren()) {
2501
+ const found = visit(child);
2502
+ if (found) return found;
2503
+ }
2504
+ return void 0;
2505
+ };
2506
+ return visit(this.renderer.root);
2507
+ }
2508
+ onFrame = () => {
2509
+ if (this.destroyed) return;
2510
+ if (this.pendingScrollRestore) {
2511
+ const restore = this.pendingScrollRestore;
2512
+ this.pendingScrollRestore = void 0;
2513
+ this.scroll.scrollTo(restore.live ? this.scroll.scrollHeight : restore.top);
2514
+ this.renderer.requestRender();
2515
+ }
2516
+ const height = Math.max(1, Math.min(8, this.editor.editorView.getTotalVirtualLineCount(), this.renderer.terminalHeight - 8));
2517
+ if (height !== this.editor.height) this.editor.height = height;
2518
+ const offset = this.offset;
2519
+ if (offset === 0) this.newEntries = 0;
2520
+ this.liveButton.visible = offset > 0;
2521
+ const liveLabel = offset > 0 ? `↓ Live${this.newEntries ? ` · ${this.newEntries} new` : ""}` : "↓ Live";
2522
+ if (this.liveButton.plainText !== liveLabel) this.liveButton.content = liveLabel;
2523
+ const event = { type: "viewport", offset: this.scroll.scrollTop, height: this.scroll.viewport.height, width: this.scroll.viewport.width, total: this.scroll.scrollHeight };
2524
+ const signature = JSON.stringify(event);
2525
+ if (signature !== this.viewportSignature) {
2526
+ this.viewportSignature = signature;
2527
+ this.emit(event);
2528
+ }
2529
+ };
2530
+ onResize = () => {
2531
+ this.layoutInstruments();
2532
+ this.emit({ type: "resize", columns: this.renderer.terminalWidth, rows: this.renderer.terminalHeight });
2533
+ };
2534
+ editorChanged() {
2535
+ if (!this.editor || this.changing || this.destroyed || this.inputEventPending) return;
2536
+ this.inputEventPending = true;
2537
+ queueMicrotask(() => {
2538
+ this.inputEventPending = false;
2539
+ if (this.destroyed) return;
2540
+ this.flushLatestInput();
2541
+ const height = Math.max(1, Math.min(8, this.editor.editorView.getTotalVirtualLineCount(), this.renderer.terminalHeight - 8));
2542
+ if (height !== this.editor.height) this.editor.height = height;
2543
+ });
2544
+ }
2545
+ flushLatestInput() {
2546
+ const text = this.editor.plainText;
2547
+ const cursor = this.editor.cursorOffset;
2548
+ if (text === this.lastInput && cursor === this.lastCursor) return;
2549
+ this.lastInput = text;
2550
+ this.lastCursor = cursor;
2551
+ this.renderEnhance();
2552
+ this.emit({ type: "input", text, cursor });
2553
+ }
2554
+ submit() {
2555
+ if (this.paused || this.state?.input.enabled === false) return;
2556
+ if (this.suggestions.visible && this.editor.plainText.startsWith("/") && this.acceptSuggestion()) return;
2557
+ const text = this.editor.plainText;
2558
+ if (text.trim() && this.history[0] !== text) {
2559
+ this.history.unshift(text);
2560
+ this.history.length = Math.min(this.history.length, 1e3);
2561
+ }
2562
+ this.historyIndex = -1;
2563
+ this.savedInput = "";
2564
+ this.editor.clear();
2565
+ this.emit({ type: "submit", text });
2566
+ this.editorChanged();
2567
+ }
2568
+ navigateHistory(direction) {
2569
+ if (this.history.length === 0) return;
2570
+ if (direction < 0) {
2571
+ if (this.historyIndex < 0) this.savedInput = this.editor.plainText;
2572
+ this.historyIndex = Math.min(this.history.length - 1, this.historyIndex + 1);
2573
+ } else this.historyIndex = Math.max(-1, this.historyIndex - 1);
2574
+ this.setInput(this.historyIndex < 0 ? this.savedInput : this.history[this.historyIndex]);
2575
+ }
2576
+ acceptSuggestion() {
2577
+ const selected = this.suggestions.getSelectedOption();
2578
+ if (!selected || selected.value === this.editor.plainText.trimEnd()) return false;
2579
+ const text = String(selected.value);
2580
+ this.setInput(text + (text.endsWith(" ") ? "" : " "));
2581
+ this.suggestions.visible = false;
2582
+ this.emitAction("completion", text);
2583
+ return true;
2584
+ }
2585
+ setInput(text, cursor = text.length) {
2586
+ this.editor.replaceText(text);
2587
+ this.editor.cursorOffset = Math.max(0, Math.min(text.length, cursor));
2588
+ this.editorInitialized = true;
2589
+ this.editorChanged();
2590
+ }
2591
+ shortcut(name, key) {
2592
+ this.flushLatestInput();
2593
+ this.emit({ type: "shortcut", name });
2594
+ key.preventDefault();
2595
+ key.stopPropagation();
2596
+ }
2597
+ emitAction(action, id) {
2598
+ this.flushLatestInput();
2599
+ this.emit({ type: "action", action, ...id === void 0 ? {} : { id } });
2600
+ }
2601
+ onKey = (key) => {
2602
+ if (this.modals.active) {
2603
+ this.modals.active.handleKey(key);
2604
+ return;
2605
+ }
2606
+ if (key.eventType === "release") return;
2607
+ if (key.ctrl && key.name.toLowerCase() === "n") {
2608
+ this.shortcut("ctrl-n", key);
2609
+ return;
2610
+ }
2611
+ if (this.renderer.currentFocusedRenderable instanceof EmbeddedTerminalRenderable2) return;
2612
+ if (key.name === "f1") {
2613
+ this.emitAction("/help");
2614
+ key.preventDefault();
2615
+ key.stopPropagation();
2616
+ return;
2617
+ }
2618
+ if (key.ctrl && key.shift && key.name.toLowerCase() === "i") {
2619
+ this.toggleDetails();
2620
+ key.preventDefault();
2621
+ key.stopPropagation();
2622
+ return;
2623
+ }
2624
+ const headerButtons = (this.state?.header.actions ?? []).filter((action) => !action.disabled).map((action) => this.getRenderable(`header:${action.id}`)).filter((button) => Boolean(button));
2625
+ const headerIndex = headerButtons.indexOf(this.renderer.currentFocusedRenderable);
2626
+ if (headerIndex >= 0) {
2627
+ const direction = key.name === "left" ? -1 : key.name === "right" ? 1 : 0;
2628
+ if (direction || key.name === "home" || key.name === "end") {
2629
+ const index = key.name === "home" ? 0 : key.name === "end" ? headerButtons.length - 1 : (headerIndex + direction + headerButtons.length) % headerButtons.length;
2630
+ const button = headerButtons[index];
2631
+ button.focus();
2632
+ this.headerActions.scrollChildIntoView(button.id);
2633
+ key.preventDefault();
2634
+ key.stopPropagation();
2635
+ return;
2636
+ }
2637
+ if (key.name === "escape") {
2638
+ this.restoreFocus();
2639
+ key.preventDefault();
2640
+ key.stopPropagation();
2641
+ return;
2642
+ }
2643
+ if (key.name === "return") return;
2644
+ }
2645
+ if (key.ctrl && key.shift && key.name.toLowerCase() === "c") {
2646
+ void this.copy();
2647
+ key.preventDefault();
2648
+ return;
2649
+ }
2650
+ if (key.ctrl && key.shift && key.name.toLowerCase() === "v") {
2651
+ void this.paste();
2652
+ key.preventDefault();
2653
+ return;
2654
+ }
2655
+ if (key.ctrl && key.shift && key.name.toLowerCase() === "b") {
2656
+ this.selectionArmed = true;
2657
+ this.scroll.focus();
2658
+ this.shortcut("ctrl-shift-b", key);
2659
+ return;
2660
+ }
2661
+ if (key.ctrl && key.name === "c") {
2662
+ this.shortcut("SIGINT", key);
2663
+ return;
2664
+ }
2665
+ if (key.ctrl && key.name === "d" && !this.editor.plainText) {
2666
+ this.shortcut("close", key);
2667
+ return;
2668
+ }
2669
+ const control = { o: "ctrl-o", l: "ctrl-l", v: "ctrl-v", "\\": "ctrl-backslash", i: "ctrl-i" };
2670
+ if (key.ctrl && control[key.name]) {
2671
+ this.shortcut(control[key.name], key);
2672
+ return;
2673
+ }
2674
+ if (key.ctrl && key.name === "u" && !this.editor.plainText) {
2675
+ this.shortcut("ctrl-u", key);
2676
+ return;
2677
+ }
2678
+ if (key.name === "escape") {
2679
+ if (this.renderer.currentFocusedRenderable !== this.editor) {
2680
+ this.restoreFocus();
2681
+ key.preventDefault();
2682
+ key.stopPropagation();
2683
+ return;
2684
+ }
2685
+ if (this.suggestions.visible) this.suggestions.visible = false;
2686
+ this.shortcut("escape", key);
2687
+ return;
2688
+ }
2689
+ if (key.name === "tab") {
2690
+ if (key.ctrl) this.shortcut("ctrl-tab", key);
2691
+ else if (!key.shift && this.renderer.currentFocusedRenderable === this.editor && this.suggestions.visible) {
2692
+ this.acceptSuggestion();
2693
+ key.preventDefault();
2694
+ } else if (!key.shift && this.renderer.currentFocusedRenderable === this.editor && this.editor.plainText) this.shortcut("tab", key);
2695
+ else this.focusNext(key.shift ? -1 : 1);
2696
+ key.preventDefault();
2697
+ key.stopPropagation();
2698
+ return;
2699
+ }
2700
+ if (this.renderer.currentFocusedRenderable !== this.editor && this.renderer.currentFocusedRenderable !== this.scroll && !key.ctrl) return;
2701
+ if (key.ctrl && (key.name === "left" || key.name === "right")) {
2702
+ this.shortcut(`ctrl-${key.name}`, key);
2703
+ return;
2704
+ }
2705
+ if (!this.editor.plainText && (key.name === "left" || key.name === "right")) {
2706
+ this.shortcut(`${key.name}-empty`, key);
2707
+ return;
2708
+ }
2709
+ if (key.name === "pageup" || key.name === "pagedown") {
2710
+ this.scroll.scrollBy(key.name === "pageup" ? -1 : 1, "viewport");
2711
+ key.preventDefault();
2712
+ return;
2713
+ }
2714
+ if ((key.name === "up" || key.name === "down") && key.shift) {
2715
+ this.scroll.scrollBy(key.name === "up" ? -3 : 3, "absolute");
2716
+ key.preventDefault();
2717
+ return;
2718
+ }
2719
+ if (key.name === "end" && !this.editor.plainText) {
2720
+ this.scrollToLive();
2721
+ key.preventDefault();
2722
+ return;
2723
+ }
2724
+ if (key.name === "up" || key.name === "down") {
2725
+ if (this.suggestions.visible) {
2726
+ if (key.name === "up") this.suggestions.moveUp();
2727
+ else this.suggestions.moveDown();
2728
+ key.preventDefault();
2729
+ return;
2730
+ }
2731
+ if (this.renderer.currentFocusedRenderable === this.scroll || this.offset > 0 || this.state?.activity?.processing && !this.editor.plainText) {
2732
+ this.scroll.scrollBy(key.name === "up" ? -1 : 1, "absolute");
2733
+ key.preventDefault();
2734
+ return;
2735
+ }
2736
+ const cursor = this.editor.visualCursor;
2737
+ const absoluteVisualRow = cursor.visualRow + this.editor.scrollY;
2738
+ if (key.name === "up" && absoluteVisualRow === 0 || key.name === "down" && absoluteVisualRow >= this.editor.editorView.getTotalVirtualLineCount() - 1) {
2739
+ this.navigateHistory(key.name === "up" ? -1 : 1);
2740
+ key.preventDefault();
2741
+ return;
2742
+ }
2743
+ }
2744
+ if (this.paused || this.state?.input.enabled === false) {
2745
+ if (this.renderer.currentFocusedRenderable === this.editor) key.preventDefault();
2746
+ return;
2747
+ }
2748
+ };
2749
+ focusNext(direction) {
2750
+ const targets = [];
2751
+ const visit = (node) => {
2752
+ if (!node.visible) return;
2753
+ if (node.focusable && node !== this.scroll && !(node instanceof ScrollBoxRenderable4) && node.height > 0 && node.width > 0) targets.push(node);
2754
+ for (const child of node.getChildren()) visit(child);
2755
+ };
2756
+ visit(this.root);
2757
+ if (this.instruments.visible) {
2758
+ for (const target2 of this.instruments.focusTargets()) if (!targets.includes(target2)) targets.push(target2);
2759
+ }
2760
+ if (!targets.length) return;
2761
+ const current = targets.indexOf(this.renderer.currentFocusedRenderable);
2762
+ const target = targets[(current + direction + targets.length) % targets.length];
2763
+ target.focus();
2764
+ if (target.id.startsWith("header:")) this.headerActions.scrollChildIntoView(target.id);
2765
+ else if (target.id.startsWith("block-title:") || target.id.startsWith("block-action:")) {
2766
+ let block = target;
2767
+ while (block && !block.id.startsWith("block:")) block = block.parent;
2768
+ if (block) this.scroll.scrollChildIntoView(block.id);
2769
+ }
2770
+ }
2771
+ onPaste = (event) => {
2772
+ if (this.modals.active) {
2773
+ this.modals.active.handlePaste(event);
2774
+ return;
2775
+ }
2776
+ if (this.paused || this.state?.input.enabled === false) event.preventDefault();
2777
+ };
2778
+ scrollToLive() {
2779
+ this.scroll.scrollTo(this.scroll.scrollHeight);
2780
+ this.newEntries = 0;
2781
+ this.editor.focus();
2782
+ }
2783
+ restoreFocus() {
2784
+ if (this.modals.active) this.modals.active.focus();
2785
+ else if (!this.paused && this.state?.input.enabled !== false) this.editor.focus();
2786
+ }
2787
+ async copy(text) {
2788
+ const value = text ?? this.renderer.getSelection()?.getSelectedText() ?? this.editor.getSelectedText();
2789
+ if (value) await this.clipboard.writeText(value, { destination: "best-available" });
2790
+ }
2791
+ async paste() {
2792
+ const result = await this.clipboard.read({ preferredTypes: ["text/plain;charset=utf-8", "text/plain"] });
2793
+ if (result.status === "read") this.editor.insertText(new TextDecoder().decode(result.representation.bytes));
2794
+ }
2795
+ command(command2) {
2796
+ switch (command2.type) {
2797
+ case "resize":
2798
+ this.renderer.resize(command2.columns, command2.rows);
2799
+ break;
2800
+ case "scroll":
2801
+ if (command2.position === "bottom") this.scrollToLive();
2802
+ else if (command2.position === "top") this.scroll.scrollTo(0);
2803
+ else this.scroll.scrollBy(command2.delta ?? 0, "absolute");
2804
+ break;
2805
+ case "focus": {
2806
+ const target = command2.target === "input" ? this.editor : command2.target === "content" ? this.scroll : command2.target === "header" ? this.getRenderable(`header:${this.state?.header.actions.find((action) => !action.disabled)?.id}`) : this.context.terminals.get(command2.target) ?? this.getRenderable(command2.target);
2807
+ target?.focus();
2808
+ if (target?.id.startsWith("header:")) this.headerActions.scrollChildIntoView(target.id);
2809
+ break;
2810
+ }
2811
+ case "input":
2812
+ if (command2.expectedText !== void 0 && command2.expectedText !== this.editor.plainText || command2.expectedCursor !== void 0 && command2.expectedCursor !== this.editor.cursorOffset) {
2813
+ this.flushLatestInput();
2814
+ } else this.setInput(command2.text, command2.cursor);
2815
+ break;
2816
+ case "input-action":
2817
+ this.inputAction(command2.action, command2.value);
2818
+ break;
2819
+ case "mouse":
2820
+ this.renderer.useMouse = command2.enabled;
2821
+ break;
2822
+ case "copy":
2823
+ void this.copy(command2.text);
2824
+ break;
2825
+ case "title":
2826
+ this.renderer.setTerminalTitle(command2.text);
2827
+ break;
2828
+ case "modal-open":
2829
+ this.modals.open(command2.modal);
2830
+ break;
2831
+ case "modal-update":
2832
+ this.modals.update(command2.id, command2.patch);
2833
+ break;
2834
+ case "modal-close":
2835
+ this.modals.close(command2.id, command2.result);
2836
+ break;
2837
+ case "embedded-write":
2838
+ this.context.terminals.get(command2.id)?.write(Buffer.from(command2.data, "base64"));
2839
+ break;
2840
+ case "embedded-open":
2841
+ this.openEmbedded(command2.id, command2.title);
2842
+ break;
2843
+ case "embedded-close":
2844
+ this.closeEmbedded(command2.id);
2845
+ break;
2846
+ }
2847
+ this.renderer.requestRender();
2848
+ }
2849
+ inputAction(action, value) {
2850
+ if (action === "pause") {
2851
+ this.paused = true;
2852
+ this.editor.focusable = false;
2853
+ } else if (action === "resume") {
2854
+ this.paused = false;
2855
+ this.editor.focusable = this.state?.input.enabled !== false;
2856
+ this.restoreFocus();
2857
+ } else if (action === "history-up") this.navigateHistory(-1);
2858
+ else if (action === "history-down") this.navigateHistory(1);
2859
+ else if (action === "history-set") {
2860
+ try {
2861
+ const entries = JSON.parse(value ?? "[]");
2862
+ if (Array.isArray(entries)) this.history = entries.filter((entry) => typeof entry === "string").slice(0, 1e3);
2863
+ } catch {
2864
+ }
2865
+ } else if (action === "insert") this.editor.insertText(value ?? "");
2866
+ else if (action === "clear") this.editor.clear();
2867
+ else if (action === "submit") this.submit();
2868
+ else if (action === "complete") this.acceptSuggestion();
2869
+ else if (action === "focus") this.restoreFocus();
2870
+ else if (action === "select-block") {
2871
+ this.selectionArmed = true;
2872
+ this.scroll.focus();
2873
+ } else if (action === "close") this.paused = true;
2874
+ }
2875
+ openEmbedded(id, title) {
2876
+ if (this.embeddedId && this.embeddedId !== id) this.closeEmbedded(this.embeddedId);
2877
+ this.embeddedId = id;
2878
+ this.embeddedPane.title = title;
2879
+ this.embeddedPane.visible = true;
2880
+ let terminal = this.context.terminals.get(id);
2881
+ if (!terminal) {
2882
+ terminal = new EmbeddedTerminalRenderable2(this.renderer, {
2883
+ id: `embedded:${id}`,
2884
+ width: "100%",
2885
+ height: "100%",
2886
+ flexGrow: 1,
2887
+ maxScrollback: 1e4,
2888
+ transparentBackground: true,
2889
+ selectable: true,
2890
+ onData: (data) => this.emit({ type: "embedded-data", id, data: Buffer.from(data).toString("base64") }),
2891
+ onTerminalResize: (columns, rows) => this.emit({ type: "embedded-resize", id, columns, rows })
2892
+ });
2893
+ this.context.terminals.set(id, terminal);
2894
+ this.embeddedPane.add(terminal);
2895
+ }
2896
+ terminal.focus();
2897
+ }
2898
+ closeEmbedded(id) {
2899
+ this.context.terminals.get(id)?.destroy();
2900
+ this.context.terminals.delete(id);
2901
+ if (this.embeddedId === id) {
2902
+ this.embeddedId = void 0;
2903
+ this.embeddedPane.visible = false;
2904
+ this.restoreFocus();
2905
+ }
2906
+ }
2907
+ async dispose() {
2908
+ if (this.destroyed) return;
2909
+ this.destroyed = true;
2910
+ this.disposeSelectionContrast();
2911
+ this.renderer.keyInput.off("keypress", this.onKey);
2912
+ this.renderer.keyInput.off("paste", this.onPaste);
2913
+ this.renderer.off(CliRenderEvents2.FRAME, this.onFrame);
2914
+ this.renderer.off(CliRenderEvents2.RESIZE, this.onResize);
2915
+ this.modals.destroy();
2916
+ this.content.destroy();
2917
+ this.tasks.destroy();
2918
+ this.root.destroyRecursively();
2919
+ this.context.syntax.destroy();
2920
+ await this.clipboard.dispose();
2921
+ }
2922
+ };
2923
+
2924
+ // packages/cli/src/tui/native/components/cursor.ts
2925
+ import { CliRenderEvents as CliRenderEvents3 } from "@opentui/core";
2926
+ function installTerminalCursorDefaults(renderer2, session) {
2927
+ const reset = new TextEncoder().encode("\x1B]112\x07");
2928
+ let disposed = false;
2929
+ let requested = 0;
2930
+ let restored = 0;
2931
+ let pending;
2932
+ let observedFrame;
2933
+ const restore = () => {
2934
+ if (disposed || session.disposed) return Promise.resolve();
2935
+ requested++;
2936
+ if (pending) return pending;
2937
+ let resolve;
2938
+ let reject;
2939
+ const completion = new Promise((done, fail2) => {
2940
+ resolve = done;
2941
+ reject = fail2;
2942
+ });
2943
+ pending = completion;
2944
+ void (async () => {
2945
+ try {
2946
+ while (!disposed && !session.disposed && restored < requested) {
2947
+ const generation = requested;
2948
+ while (!session.write(reset)) await session.idle();
2949
+ await session.idle();
2950
+ restored = generation;
2951
+ }
2952
+ pending = void 0;
2953
+ resolve();
2954
+ } catch (error) {
2955
+ pending = void 0;
2956
+ reject(error);
2957
+ }
2958
+ })();
2959
+ return completion;
2960
+ };
2961
+ const afterFrame = () => {
2962
+ const current = restore();
2963
+ if (observedFrame === current) return;
2964
+ observedFrame = current;
2965
+ void current.then(() => {
2966
+ if (observedFrame === current) observedFrame = void 0;
2967
+ }, (error) => {
2968
+ if (observedFrame === current) observedFrame = void 0;
2969
+ if (!disposed && !renderer2.isDestroyed) renderer2.emit("error", error);
2970
+ });
2971
+ };
2972
+ const dispose = () => {
2973
+ if (disposed) return;
2974
+ disposed = true;
2975
+ renderer2.off(CliRenderEvents3.FRAME, afterFrame);
2976
+ renderer2.off(CliRenderEvents3.DESTROY, dispose);
2977
+ };
2978
+ renderer2.on(CliRenderEvents3.FRAME, afterFrame);
2979
+ renderer2.once(CliRenderEvents3.DESTROY, dispose);
2980
+ return { restore, dispose };
78
2981
  }
2982
+
2983
+ // packages/cli/src/tui/native/worker.ts
2984
+ var replies = createWriteStream("", { fd: 3, autoClose: false });
2985
+ var reply = (message) => replies.write(JSON.stringify(message) + "\n");
2986
+ var terminalInput = new PassThrough();
2987
+ var renderer;
2988
+ var frontend;
2989
+ var cursorDefaults;
2990
+ var stopping = false;
2991
+ var failed = false;
79
2992
  async function close() {
80
2993
  if (stopping) return;
81
2994
  stopping = true;
82
- if (renderer) {
83
- renderer.destroy();
84
- await renderer.closed;
2995
+ try {
2996
+ await frontend?.dispose();
2997
+ await cursorDefaults?.restore();
2998
+ } finally {
2999
+ cursorDefaults?.dispose();
3000
+ if (renderer) {
3001
+ renderer.destroy();
3002
+ await renderer.closed;
3003
+ }
85
3004
  }
86
3005
  }
3006
+ async function fail(error) {
3007
+ if (failed || stopping) return;
3008
+ failed = true;
3009
+ reply({ type: "error", message: error instanceof Error ? error.message : String(error) });
3010
+ await close().catch(() => {
3011
+ });
3012
+ replies.end(() => process.exit(1));
3013
+ }
87
3014
  async function command(message) {
88
- if (message.type === "input") {
89
- terminalInput.write(Buffer.from(message.data, "base64"));
3015
+ if (message.type === "command") {
3016
+ frontend.command(message.command);
90
3017
  return;
91
3018
  }
92
- if (message.type === "frame") {
93
- if (!suspended) {
94
- applyFrame(message.frame);
95
- await present();
96
- }
3019
+ if (message.type === "state") {
3020
+ frontend.update(message.snapshot, message.content);
3021
+ renderer.requestRender();
3022
+ await renderer.idle();
97
3023
  } else if (message.type === "suspend") {
3024
+ await cursorDefaults.restore();
98
3025
  await renderer.suspend();
99
- suspended = true;
100
3026
  } else if (message.type === "resume") {
101
3027
  await renderer.resume();
102
- suspended = false;
103
- } else if (message.type === "stop") {
104
- await close();
105
- }
3028
+ renderer.requestRender();
3029
+ await renderer.idle();
3030
+ await cursorDefaults.restore();
3031
+ } else if (message.type === "stop") await close();
106
3032
  reply({ type: "ack", id: message.id });
107
3033
  if (message.type === "stop") replies.end(() => process.exit(0));
108
3034
  }
109
- async function fail(error) {
110
- reply({ type: "error", message: error instanceof Error ? error.message : String(error) });
111
- await close().catch(() => {
112
- });
113
- replies.end(() => process.exit(1));
114
- }
115
3035
  try {
116
- session = new NativeSession(process.stdout);
3036
+ const session = new NativeSession(process.stdout);
117
3037
  renderer = await createCliRenderer({
118
3038
  nativeSession: session,
119
3039
  stdin: terminalInput,
120
3040
  stdout: process.stdout,
121
3041
  exitOnCtrlC: false,
122
3042
  exitSignals: [],
123
- useMouse: false,
124
- enableMouseMovement: false,
125
- useKittyKeyboard: null,
3043
+ useMouse: true,
3044
+ enableMouseMovement: true,
3045
+ useKittyKeyboard: { disambiguate: true, alternateKeys: true, events: true },
126
3046
  screenMode: "alternate-screen",
127
3047
  consoleMode: "disabled",
128
3048
  externalOutputMode: "passthrough",
129
- backgroundColor: RGBA.defaultBackground(),
3049
+ backgroundColor: RGBA3.defaultBackground(),
130
3050
  openConsoleOnError: false,
131
3051
  targetFps: 60,
132
3052
  maxFps: 60,
133
3053
  clearOnShutdown: false
134
3054
  });
135
- renderer.setCursorStyle({ style: "block", blinking: true });
3055
+ renderer.on(CliRenderEvents4.RENDER_ERROR, (event) => {
3056
+ void fail(event.error);
3057
+ });
3058
+ renderer.on(CliRenderEvents4.HANDLER_ERROR, (event) => {
3059
+ void fail(event.error);
3060
+ });
136
3061
  renderer.on("error", (event) => {
137
- void fail(event instanceof Error ? event : event.error ?? new Error("OpenTUI render failed"));
3062
+ void fail(event);
138
3063
  });
139
- let pending = Promise.resolve();
3064
+ cursorDefaults = installTerminalCursorDefaults(renderer, session);
3065
+ frontend = new NativeFrontend(renderer, (event) => reply({ type: "event", event }));
140
3066
  const controls = createInterface({ input: process.stdin, crlfDelay: Infinity });
3067
+ let pending = Promise.resolve();
141
3068
  controls.on("line", (line) => {
142
3069
  let message;
143
3070
  try {
@@ -146,11 +3073,15 @@ try {
146
3073
  void fail(error);
147
3074
  return;
148
3075
  }
149
- if (message.type === "input") {
3076
+ if (message.type === "input-bytes") {
150
3077
  terminalInput.write(Buffer.from(message.data, "base64"));
151
- return;
152
- }
153
- pending = pending.then(() => command(message)).catch(fail);
3078
+ } else if (message.type === "command") {
3079
+ try {
3080
+ frontend.command(message.command);
3081
+ } catch (error) {
3082
+ void fail(error);
3083
+ }
3084
+ } else pending = pending.then(() => command(message)).catch(fail);
154
3085
  });
155
3086
  controls.on("close", () => {
156
3087
  void pending.then(close).finally(() => process.exit(0));
@@ -160,6 +3091,9 @@ try {
160
3091
  });
161
3092
  process.on("SIGINT", () => {
162
3093
  });
3094
+ renderer.requestRender();
3095
+ await renderer.idle();
3096
+ await cursorDefaults.restore();
163
3097
  reply({ type: "ready" });
164
3098
  } catch (error) {
165
3099
  await fail(error);