omnius 1.0.860 → 1.0.862

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2010 @@
1
+ // packages/cli/src/tui/native/worker.ts
2
+ import { createWriteStream } from "node:fs";
3
+ import { createInterface } from "node:readline";
4
+ import { PassThrough } from "node:stream";
5
+ import { CliRenderEvents as CliRenderEvents2, NativeSession, RGBA as RGBA2, createCliRenderer } from "@opentui/core";
6
+
7
+ // packages/cli/src/tui/native/components/frontend.ts
8
+ import {
9
+ BoxRenderable as BoxRenderable3,
10
+ CliRenderEvents,
11
+ EmbeddedTerminalRenderable as EmbeddedTerminalRenderable2,
12
+ ScrollBoxRenderable as ScrollBoxRenderable3,
13
+ SyntaxStyle as SyntaxStyle2,
14
+ TabSelectRenderable,
15
+ TextareaRenderable as TextareaRenderable2,
16
+ TextRenderable as TextRenderable3,
17
+ createClipboard,
18
+ createHostClipboard,
19
+ createRendererClipboardAdapter
20
+ } from "@opentui/core";
21
+
22
+ // packages/cli/src/tui/native/components/blocks.ts
23
+ import {
24
+ BoxRenderable,
25
+ CodeRenderable,
26
+ EmbeddedTerminalRenderable,
27
+ ImageRenderable,
28
+ MarkdownRenderable,
29
+ ScrollBoxRenderable,
30
+ TextRenderable
31
+ } from "@opentui/core";
32
+
33
+ // packages/cli/src/tui/native/components/text.ts
34
+ import { RGBA, StyledText } from "@opentui/core";
35
+
36
+ // packages/cli/src/tui/native/text.ts
37
+ function stripTextControls(value) {
38
+ 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, "");
39
+ }
40
+ function nativeTextFromAnsi(value) {
41
+ const result = [];
42
+ let style = { attributes: 0 };
43
+ let previous = 0;
44
+ const pattern = /\x1b(?:\[[0-?]*[ -/]*[@-~]|\][\s\S]*?(?:\x07|\x1b\\)|[P_^X][\s\S]*?\x1b\\|[\s\S])/g;
45
+ const append = (text) => {
46
+ const clean = stripTextControls(text);
47
+ if (!clean) return;
48
+ const last = result.at(-1);
49
+ if (last && last.fg === style.fg && last.bg === style.bg && last.attributes === style.attributes && last.link === style.link)
50
+ last.text += clean;
51
+ else result.push({ ...style, text: clean });
52
+ };
53
+ for (const match of value.matchAll(pattern)) {
54
+ append(value.slice(previous, match.index));
55
+ previous = match.index + match[0].length;
56
+ const seq = match[0];
57
+ if (seq.startsWith("\x1B]8;")) {
58
+ const url = seq.replace(/^\x1b\]8;[^;]*;/, "").replace(/(?:\x07|\x1b\\)$/, "");
59
+ if (/^(?:https?:|file:)/.test(url)) style.link = url;
60
+ else delete style.link;
61
+ continue;
62
+ }
63
+ if (!/^\x1b\[[\d;:]*m$/.test(seq)) continue;
64
+ const codes = seq.slice(2, -1).replace(/:/g, ";").split(";").map(Number);
65
+ for (let i = 0; i < codes.length; i++) {
66
+ const code = codes[i];
67
+ const attr = style.attributes ?? 0;
68
+ if (code === 0) style = { attributes: 0 };
69
+ else if (code >= 1 && code <= 9) {
70
+ const bit = {
71
+ 1: 1,
72
+ 2: 2,
73
+ 3: 4,
74
+ 4: 8,
75
+ 5: 16,
76
+ 6: 16,
77
+ 7: 32,
78
+ 8: 64,
79
+ 9: 128
80
+ }[code];
81
+ style.attributes = attr | bit;
82
+ } else if (code >= 22 && code <= 29) {
83
+ const bit = { 22: 3, 23: 4, 24: 8, 25: 16, 27: 32, 28: 64, 29: 128 }[code] ?? 0;
84
+ style.attributes = attr & ~bit;
85
+ } else if (code === 39) delete style.fg;
86
+ else if (code === 49) delete style.bg;
87
+ else if (code >= 30 && code <= 37) style.fg = `index:${code - 30}`;
88
+ else if (code >= 90 && code <= 97) style.fg = `index:${code - 90 + 8}`;
89
+ else if (code >= 40 && code <= 47) style.bg = `index:${code - 40}`;
90
+ else if (code >= 100 && code <= 107) style.bg = `index:${code - 100 + 8}`;
91
+ else if ((code === 38 || code === 48) && codes[i + 1] === 5) {
92
+ style[code === 38 ? "fg" : "bg"] = `index:${Math.max(0, Math.min(255, codes[i + 2] ?? 0))}`;
93
+ i += 2;
94
+ } else if ((code === 38 || code === 48) && codes[i + 1] === 2) {
95
+ const rgb = codes.slice(i + 2, i + 5).map(
96
+ (n) => Math.max(0, Math.min(255, n)).toString(16).padStart(2, "0")
97
+ );
98
+ if (rgb.length === 3)
99
+ style[code === 38 ? "fg" : "bg"] = `#${rgb.join("")}`;
100
+ i += 4;
101
+ }
102
+ }
103
+ }
104
+ append(value.slice(previous));
105
+ return result;
106
+ }
107
+
108
+ // packages/cli/src/tui/native/components/text.ts
109
+ var colorCache = /* @__PURE__ */ new Map();
110
+ function nativeColor(value, background = false) {
111
+ if (value === void 0 || value === -1 || value === "default")
112
+ return background ? RGBA.defaultBackground() : RGBA.defaultForeground();
113
+ const key = String(value);
114
+ if (key === "index:-1") return background ? RGBA.defaultBackground() : RGBA.defaultForeground();
115
+ let color = colorCache.get(key);
116
+ if (!color) {
117
+ color = typeof value === "number" || /^\d+$/.test(key) ? RGBA.fromIndex(Number(value)) : key.startsWith("index:") ? RGBA.fromIndex(Number(key.slice(6))) : RGBA.fromHex(key);
118
+ if (colorCache.size >= 1024) colorCache.delete(colorCache.keys().next().value);
119
+ colorCache.set(key, color);
120
+ }
121
+ return color;
122
+ }
123
+ function plainText(value) {
124
+ return typeof value === "string" ? stripTextControls(value) : (value ?? []).map((span) => stripTextControls(span.text)).join("");
125
+ }
126
+ function textSpans(value) {
127
+ return Array.isArray(value) ? value.map((span) => ({ ...span, text: stripTextControls(span.text) })) : nativeTextFromAnsi(value ?? "");
128
+ }
129
+ function styledText(value) {
130
+ return new StyledText(textSpans(value).map((span) => ({
131
+ __isChunk: true,
132
+ text: span.text,
133
+ ...span.fg === void 0 ? {} : { fg: nativeColor(span.fg) },
134
+ ...span.bg === void 0 ? {} : { bg: nativeColor(span.bg, true) },
135
+ ...span.attributes === void 0 ? {} : { attributes: span.attributes },
136
+ ...span.link ? { link: { url: span.link } } : {}
137
+ })));
138
+ }
139
+ function palette(theme) {
140
+ return {
141
+ background: nativeColor(theme.bg, true),
142
+ foreground: nativeColor(theme.textPrimary),
143
+ accent: nativeColor(theme.accent),
144
+ dim: nativeColor(theme.textDim),
145
+ border: nativeColor(theme.boxColor),
146
+ warning: nativeColor(theme.mode === "branding" ? 180 : 214),
147
+ error: nativeColor(theme.mode === "branding" ? 173 : 198),
148
+ success: nativeColor(theme.mode === "branding" ? 114 : 78),
149
+ selection: nativeColor(theme.mode === "branding" ? 65 : theme.accent < 0 ? 240 : theme.accent)
150
+ };
151
+ }
152
+
153
+ // packages/cli/src/tui/native/components/blocks.ts
154
+ var NativeBlockView = class {
155
+ constructor(context, spec, inline = false) {
156
+ this.context = context;
157
+ this.inline = inline;
158
+ this.spec = spec;
159
+ this.pixel = inline && spec.kind === "text" && Boolean(spec.actions?.length) && spec.actions.every((action) => action.id.startsWith("pixel:"));
160
+ this.root = new BoxRenderable(context.renderer, {
161
+ id: `block:${spec.id}`,
162
+ flexDirection: "column",
163
+ flexShrink: inline && !this.pixel ? 1 : 0,
164
+ flexGrow: inline && !this.pixel ? 1 : 0,
165
+ flexBasis: inline && !this.pixel ? 0 : "auto",
166
+ minWidth: inline && !this.pixel ? 0 : void 0,
167
+ width: this.pixel ? 1 : inline ? "auto" : "100%"
168
+ });
169
+ this.build();
170
+ this.update(spec);
171
+ }
172
+ context;
173
+ inline;
174
+ root;
175
+ spec;
176
+ title;
177
+ text;
178
+ body;
179
+ children;
180
+ actions;
181
+ progressBar;
182
+ progressValue;
183
+ image;
184
+ terminal;
185
+ collapsed = false;
186
+ previousModelCollapsed;
187
+ signature = "";
188
+ pixel;
189
+ get block() {
190
+ return this.spec;
191
+ }
192
+ build() {
193
+ const { renderer: renderer2, colors, syntax } = this.context;
194
+ const spec = this.spec;
195
+ if (spec.kind === "card") this.root.borderStyle = "rounded";
196
+ this.root.border = spec.kind === "card";
197
+ this.root.paddingX = spec.kind === "card" ? 1 : 0;
198
+ this.root.marginBottom = spec.kind === "card" ? 1 : 0;
199
+ this.root.flexDirection = spec.kind === "row" ? "row" : "column";
200
+ if (spec.title) {
201
+ this.title = new TextRenderable(renderer2, {
202
+ id: `block-title:${spec.id}`,
203
+ content: "",
204
+ fg: colors.accent,
205
+ flexShrink: 0,
206
+ width: "100%",
207
+ wrapMode: "word",
208
+ selectable: false,
209
+ onMouseDown: (event) => {
210
+ if (event.button !== 0 || !this.spec.collapsible) return;
211
+ this.toggle();
212
+ event.stopPropagation();
213
+ }
214
+ });
215
+ this.root.add(this.title);
216
+ }
217
+ this.body = new BoxRenderable(renderer2, {
218
+ id: `block-body:${spec.id}`,
219
+ flexDirection: spec.kind === "row" ? "row" : "column",
220
+ width: this.pixel ? "auto" : "100%",
221
+ flexShrink: 0
222
+ });
223
+ this.root.add(this.body);
224
+ if (spec.kind === "terminal") {
225
+ this.terminal = new EmbeddedTerminalRenderable(renderer2, {
226
+ id: `embedded:${spec.id}`,
227
+ width: "100%",
228
+ height: spec.height ?? 14,
229
+ maxScrollback: 1e4,
230
+ transparentBackground: true,
231
+ selectable: true,
232
+ onData: (data) => this.context.emit({ type: "embedded-data", id: this.spec.id, data: Buffer.from(data).toString("base64") }),
233
+ onTerminalResize: (columns, rows) => this.context.emit({ type: "embedded-resize", id: this.spec.id, columns, rows })
234
+ });
235
+ this.context.terminals.set(spec.id, this.terminal);
236
+ this.body.add(this.terminal);
237
+ } else if (spec.kind === "image" && spec.imagePath) {
238
+ this.image = new ImageRenderable(renderer2, {
239
+ id: `image:${spec.id}`,
240
+ source: spec.imagePath,
241
+ width: "100%",
242
+ height: spec.height ?? 12,
243
+ fit: "fit",
244
+ protocol: "auto",
245
+ onError: (error) => {
246
+ if (this.image) this.image.visible = false;
247
+ if (this.text) {
248
+ this.text.visible = true;
249
+ if (this.text instanceof TextRenderable) this.text.content = styledText(this.spec.text ?? "Image unavailable");
250
+ }
251
+ this.context.emit({ type: "action", action: "image-error", id: spec.id, text: String(error) });
252
+ },
253
+ onLoad: () => {
254
+ if (this.image) this.image.visible = true;
255
+ if (this.text) this.text.visible = false;
256
+ }
257
+ });
258
+ this.body.add(this.image);
259
+ this.text = new TextRenderable(renderer2, { id: `image-fallback:${spec.id}`, content: "", visible: false, width: "100%", wrapMode: "word", fg: colors.dim, selectable: true });
260
+ this.body.add(this.text);
261
+ } else if (spec.kind === "markdown") {
262
+ this.text = new MarkdownRenderable(renderer2, {
263
+ id: `markdown:${spec.id}`,
264
+ content: "",
265
+ syntaxStyle: syntax,
266
+ fg: colors.foreground,
267
+ bg: colors.background,
268
+ streaming: spec.streaming ?? false,
269
+ width: "100%",
270
+ flexShrink: 0,
271
+ internalBlockMode: "top-level",
272
+ conceal: true,
273
+ tableOptions: { style: "columns", widthMode: "full", wrapMode: "word", selectable: true }
274
+ });
275
+ this.body.add(this.text);
276
+ } else if (spec.kind === "code") {
277
+ this.text = new CodeRenderable(renderer2, {
278
+ id: `code:${spec.id}`,
279
+ content: "",
280
+ filetype: spec.language,
281
+ syntaxStyle: syntax,
282
+ fg: colors.foreground,
283
+ bg: colors.background,
284
+ width: "100%",
285
+ flexShrink: 0,
286
+ streaming: spec.streaming ?? false,
287
+ selectable: true,
288
+ wrapMode: "word",
289
+ drawUnstyledText: true
290
+ });
291
+ this.body.add(this.text);
292
+ } else if (spec.text !== void 0) {
293
+ this.text = new TextRenderable(renderer2, {
294
+ id: `text:${spec.id}`,
295
+ content: "",
296
+ width: this.pixel ? "auto" : "100%",
297
+ flexShrink: 0,
298
+ wrapMode: "word",
299
+ fg: colors.foreground,
300
+ bg: colors.background,
301
+ selectable: true
302
+ });
303
+ this.body.add(this.text);
304
+ }
305
+ if (spec.kind === "progress") {
306
+ this.progressBar = new BoxRenderable(renderer2, {
307
+ id: `progress-track:${spec.id}`,
308
+ width: "100%",
309
+ height: 1,
310
+ backgroundColor: colors.dim
311
+ });
312
+ this.progressValue = new BoxRenderable(renderer2, {
313
+ id: `progress-value:${spec.id}`,
314
+ height: 1,
315
+ width: "0%",
316
+ backgroundColor: colors.accent
317
+ });
318
+ this.progressBar.add(this.progressValue);
319
+ this.body.add(this.progressBar);
320
+ }
321
+ this.children = new NativeBlockList(this.context, this.body);
322
+ this.actions = new BoxRenderable(renderer2, {
323
+ id: `block-actions:${spec.id}`,
324
+ flexDirection: "row",
325
+ gap: 2,
326
+ flexShrink: 0
327
+ });
328
+ this.root.add(this.actions);
329
+ }
330
+ reset() {
331
+ this.children?.destroy();
332
+ if (this.terminal) this.context.terminals.delete(this.spec.id);
333
+ for (const child of [...this.root.getChildren()]) child.destroyRecursively();
334
+ this.title = this.text = this.body = this.actions = this.progressBar = this.progressValue = this.image = this.terminal = void 0;
335
+ this.children = void 0;
336
+ this.build();
337
+ }
338
+ update(spec) {
339
+ const signature = JSON.stringify(spec);
340
+ if (signature === this.signature) return;
341
+ 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;
342
+ this.spec = spec;
343
+ if (rebuild) this.reset();
344
+ this.signature = signature;
345
+ if (spec.collapsed !== this.previousModelCollapsed) {
346
+ this.collapsed = spec.collapsed ?? false;
347
+ this.previousModelCollapsed = spec.collapsed;
348
+ }
349
+ if (this.title) this.title.content = styledText(`${spec.collapsible ? this.collapsed ? "▸ " : "▾ " : ""}${spec.title ?? ""}`);
350
+ if (this.body) this.body.visible = !this.collapsed;
351
+ if (this.text instanceof TextRenderable) this.text.content = styledText(spec.kind === "image" ? spec.text ?? "Image unavailable" : spec.text);
352
+ else if (this.text instanceof MarkdownRenderable || this.text instanceof CodeRenderable) {
353
+ this.text.content = plainText(spec.text);
354
+ this.text.streaming = spec.streaming ?? false;
355
+ if (this.text instanceof CodeRenderable) this.text.filetype = spec.language;
356
+ }
357
+ if (this.image && spec.imagePath && this.image.source !== spec.imagePath) this.image.source = spec.imagePath;
358
+ if (this.terminal) this.terminal.height = spec.height ?? 14;
359
+ if (this.image) this.image.height = spec.height ?? 12;
360
+ if (this.progressValue) this.progressValue.width = `${Math.round(Math.min(1, Math.max(0, spec.progress ?? 0)) * 100)}%`;
361
+ this.children?.replace(spec.children ?? []);
362
+ if (this.actions) {
363
+ for (const child of [...this.actions.getChildren()]) child.destroy();
364
+ for (const action of spec.actions ?? []) {
365
+ const button = new TextRenderable(this.context.renderer, {
366
+ id: `block-action:${spec.id}:${action.id}`,
367
+ content: styledText(action.label),
368
+ fg: action.disabled ? this.context.colors.dim : spec.color ? nativeColor(spec.color) : this.context.colors.accent,
369
+ attributes: action.active ? 33 : 0,
370
+ flexShrink: 0,
371
+ selectable: false,
372
+ onMouseDown: (event) => {
373
+ if (event.button !== 0 || action.disabled) return;
374
+ this.context.emit({ type: "action", action: action.command ?? action.id, id: this.spec.id });
375
+ event.stopPropagation();
376
+ },
377
+ onKeyDown: (key) => {
378
+ if (key.name !== "return" || action.disabled) return;
379
+ this.context.emit({ type: "action", action: action.command ?? action.id, id: this.spec.id });
380
+ key.preventDefault();
381
+ }
382
+ });
383
+ button.focusable = !action.disabled;
384
+ if (action.id.startsWith("pixel:") && !action.disabled) {
385
+ button.onMouseOver = () => this.context.emit({ type: "action", action: `hover:${action.id}`, id: this.spec.id });
386
+ button.onMouseOut = () => this.context.emit({ type: "action", action: `hover-end:${action.id}`, id: this.spec.id });
387
+ }
388
+ this.actions.add(button);
389
+ }
390
+ this.actions.visible = (spec.actions?.length ?? 0) > 0;
391
+ }
392
+ this.applyTheme();
393
+ }
394
+ toggle() {
395
+ this.collapsed = !this.collapsed;
396
+ if (this.body) this.body.visible = !this.collapsed;
397
+ if (this.title) this.title.content = styledText(`${this.collapsed ? "▸ " : "▾ "}${this.spec.title ?? ""}`);
398
+ this.context.emit({ type: "action", action: "toggle-block", id: this.spec.id });
399
+ }
400
+ applyTheme() {
401
+ const colors = this.context.colors;
402
+ this.root.backgroundColor = colors.background;
403
+ if (this.spec.kind === "card") this.root.borderColor = this.spec.color ? nativeColor(this.spec.color) : colors.border;
404
+ else this.root.border = false;
405
+ if (this.title) {
406
+ this.title.fg = this.spec.color ? nativeColor(this.spec.color) : colors.accent;
407
+ this.title.bg = colors.background;
408
+ }
409
+ if (this.body) this.body.backgroundColor = colors.background;
410
+ if (this.text) {
411
+ this.text.fg = this.spec.color ? nativeColor(this.spec.color) : colors.foreground;
412
+ this.text.bg = colors.background;
413
+ }
414
+ if (this.progressBar) this.progressBar.backgroundColor = colors.dim;
415
+ if (this.progressValue) this.progressValue.backgroundColor = colors.accent;
416
+ this.children?.applyTheme();
417
+ }
418
+ destroy() {
419
+ this.children?.destroy();
420
+ if (this.terminal) this.context.terminals.delete(this.spec.id);
421
+ this.root.destroyRecursively();
422
+ }
423
+ };
424
+ var NativeBlockList = class {
425
+ constructor(context, parent) {
426
+ this.context = context;
427
+ this.parent = parent;
428
+ }
429
+ context;
430
+ parent;
431
+ views = /* @__PURE__ */ new Map();
432
+ order = [];
433
+ patch(patch) {
434
+ if (patch.reset) this.clear();
435
+ for (const id of patch.remove) this.remove(id);
436
+ for (const spec of patch.upsert) {
437
+ const existing = this.views.get(spec.id);
438
+ if (existing) existing.update(spec);
439
+ else {
440
+ const direction = this.parent instanceof ScrollBoxRenderable ? this.parent.content.primaryAxis : this.parent.primaryAxis;
441
+ const view = new NativeBlockView(this.context, spec, direction === "row");
442
+ this.views.set(spec.id, view);
443
+ this.order.push(spec.id);
444
+ this.parent.add(view.root);
445
+ }
446
+ }
447
+ if (patch.order) this.reorder(patch.order);
448
+ }
449
+ replace(blocks) {
450
+ const keep = new Set(blocks.map((block) => block.id));
451
+ this.patch({ remove: [...this.views.keys()].filter((id) => !keep.has(id)), upsert: blocks, order: blocks.map((block) => block.id) });
452
+ }
453
+ reorder(order) {
454
+ const next = order.filter((id) => this.views.has(id));
455
+ if (next.length === this.order.length && next.every((id, index) => this.order[index] === id)) return;
456
+ next.forEach((id, index) => this.parent.add(this.views.get(id).root, index));
457
+ this.order = next;
458
+ }
459
+ remove(id) {
460
+ this.views.get(id)?.destroy();
461
+ this.views.delete(id);
462
+ this.order = this.order.filter((value) => value !== id);
463
+ }
464
+ applyTheme() {
465
+ for (const view of this.views.values()) view.applyTheme();
466
+ }
467
+ clear() {
468
+ for (const view of this.views.values()) view.destroy();
469
+ this.views.clear();
470
+ this.order = [];
471
+ }
472
+ destroy() {
473
+ this.clear();
474
+ }
475
+ };
476
+
477
+ // packages/cli/src/tui/native/components/modals.ts
478
+ import {
479
+ BoxRenderable as BoxRenderable2,
480
+ InputRenderable,
481
+ ScrollBoxRenderable as ScrollBoxRenderable2,
482
+ TextareaRenderable,
483
+ TextRenderable as TextRenderable2
484
+ } from "@opentui/core";
485
+
486
+ // packages/cli/src/tui/native/components/select.ts
487
+ import { SelectRenderable } from "@opentui/core";
488
+ var NativeSelectRenderable = class extends SelectRenderable {
489
+ onActivate;
490
+ constructor(renderer2, options) {
491
+ super(renderer2, options);
492
+ }
493
+ disabled(index) {
494
+ const option = this.options[index];
495
+ return !option || option.value?.disabled === true;
496
+ }
497
+ moveUp(steps = 1) {
498
+ this.move(-steps);
499
+ }
500
+ moveDown(steps = 1) {
501
+ this.move(steps);
502
+ }
503
+ move(delta) {
504
+ if (this.options.length === 0) return;
505
+ const direction = delta < 0 ? -1 : 1;
506
+ let index = Math.min(this.options.length - 1, Math.max(0, this.getSelectedIndex() + delta));
507
+ for (let attempts = 0; attempts < this.options.length && this.disabled(index); attempts++) {
508
+ index += direction;
509
+ if (index < 0 || index >= this.options.length) {
510
+ if (!this.wrapSelection) return;
511
+ index = index < 0 ? this.options.length - 1 : 0;
512
+ }
513
+ }
514
+ if (!this.disabled(index)) this.setSelectedIndex(index);
515
+ }
516
+ selectCurrent() {
517
+ if (!this.disabled(this.getSelectedIndex())) super.selectCurrent();
518
+ }
519
+ onMouseEvent(event) {
520
+ if (event.type === "scroll") {
521
+ if (event.scroll?.direction === "up") this.moveUp(1);
522
+ else if (event.scroll?.direction === "down") this.moveDown(1);
523
+ event.stopPropagation();
524
+ return;
525
+ }
526
+ if (event.type !== "down" || event.button !== 0) return;
527
+ const lines = this.showDescription ? 2 : 1;
528
+ const count = Math.max(1, Math.floor(this.height / lines));
529
+ const start = Math.max(0, Math.min(this.getSelectedIndex() - Math.floor(count / 2), this.options.length - count));
530
+ const index = start + Math.floor((event.y - this.y) / lines);
531
+ if (index >= start && index < Math.min(start + count, this.options.length) && !this.disabled(index)) {
532
+ this.setSelectedIndex(index);
533
+ this.onActivate?.();
534
+ }
535
+ event.stopPropagation();
536
+ }
537
+ };
538
+
539
+ // packages/cli/src/tui/native/components/modals.ts
540
+ var NativeModalView = class {
541
+ constructor(context, spec, close2) {
542
+ this.context = context;
543
+ this.close = close2;
544
+ this.spec = spec;
545
+ const { renderer: renderer2, colors } = context;
546
+ this.root = new BoxRenderable2(renderer2, {
547
+ id: `modal:${spec.id}`,
548
+ position: "absolute",
549
+ top: 0,
550
+ left: 0,
551
+ width: "100%",
552
+ height: "100%",
553
+ flexDirection: "column",
554
+ justifyContent: "center",
555
+ alignItems: "center",
556
+ zIndex: 100,
557
+ backgroundColor: colors.background
558
+ });
559
+ this.panel = new BoxRenderable2(renderer2, {
560
+ id: `modal-panel:${spec.id}`,
561
+ flexDirection: "column",
562
+ width: "94%",
563
+ maxHeight: "94%",
564
+ border: true,
565
+ borderStyle: "rounded",
566
+ borderColor: colors.border,
567
+ backgroundColor: colors.background,
568
+ paddingX: 1,
569
+ paddingY: 1,
570
+ onMouseDown: () => this.focus()
571
+ });
572
+ this.root.add(this.panel);
573
+ this.breadcrumb = this.addText(`modal-breadcrumb:${spec.id}`);
574
+ this.heading = this.addText(`modal-title:${spec.id}`);
575
+ this.description = this.addText(`modal-description:${spec.id}`);
576
+ if (spec.kind === "select") this.buildSelect();
577
+ else if (spec.kind === "input") this.buildInput();
578
+ else if (spec.kind === "document") {
579
+ this.document = new ScrollBoxRenderable2(renderer2, {
580
+ id: `modal-document:${spec.id}`,
581
+ width: "100%",
582
+ height: Math.max(5, Math.floor(renderer2.terminalHeight * 0.6)),
583
+ scrollY: true,
584
+ scrollX: false,
585
+ viewportCulling: true,
586
+ contentOptions: { flexDirection: "column" }
587
+ });
588
+ this.blocks = new NativeBlockList(this.modalContext(), this.document);
589
+ this.panel.add(this.document);
590
+ } else {
591
+ this.progressTrack = new BoxRenderable2(renderer2, { id: `modal-progress:${spec.id}`, width: "100%", height: 1, backgroundColor: colors.dim });
592
+ this.progressValue = new BoxRenderable2(renderer2, { id: `modal-progress-value:${spec.id}`, width: "0%", height: 1, backgroundColor: colors.accent });
593
+ this.progressTrack.add(this.progressValue);
594
+ this.panel.add(this.progressTrack);
595
+ const progressBlocks = new BoxRenderable2(renderer2, { id: `modal-progress-blocks:${spec.id}`, flexDirection: "column", width: "100%" });
596
+ this.panel.add(progressBlocks);
597
+ this.blocks = new NativeBlockList(this.modalContext(), progressBlocks);
598
+ }
599
+ this.error = this.addText(`modal-error:${spec.id}`);
600
+ this.hints = this.addText(`modal-hints:${spec.id}`);
601
+ this.actions = new BoxRenderable2(renderer2, { id: `modal-actions:${spec.id}`, flexDirection: "row", gap: 2, width: "100%", marginTop: 1 });
602
+ this.panel.add(this.actions);
603
+ this.update(spec);
604
+ }
605
+ context;
606
+ close;
607
+ root;
608
+ panel;
609
+ spec;
610
+ heading;
611
+ description;
612
+ breadcrumb;
613
+ error;
614
+ hints;
615
+ actions;
616
+ filterInput;
617
+ editor;
618
+ secretMask;
619
+ select;
620
+ document;
621
+ blocks;
622
+ progressTrack;
623
+ progressValue;
624
+ filtered = [];
625
+ filter = "";
626
+ changing = false;
627
+ addText(id) {
628
+ const text = new TextRenderable2(this.context.renderer, { id, content: "", width: "100%", wrapMode: "word", flexShrink: 0, fg: this.context.colors.foreground, selectable: true });
629
+ this.panel.add(text);
630
+ return text;
631
+ }
632
+ buildSelect() {
633
+ const { renderer: renderer2, colors } = this.context;
634
+ this.filterInput = new InputRenderable(renderer2, {
635
+ id: `modal-filter:${this.spec.id}`,
636
+ width: "100%",
637
+ value: this.spec.filter ?? "",
638
+ placeholder: "Filter…",
639
+ textColor: colors.foreground,
640
+ backgroundColor: colors.background,
641
+ focusedTextColor: colors.foreground,
642
+ focusedBackgroundColor: colors.background,
643
+ onContentChange: () => {
644
+ if (this.changing || !this.filterInput) return;
645
+ this.filter = this.filterInput.plainText;
646
+ this.refreshOptions();
647
+ this.emitAction("filter", this.filter);
648
+ }
649
+ });
650
+ this.filter = this.spec.filter ?? "";
651
+ this.panel.add(this.filterInput);
652
+ this.select = new NativeSelectRenderable(renderer2, {
653
+ id: `modal-select:${this.spec.id}`,
654
+ width: "100%",
655
+ height: 5,
656
+ options: [],
657
+ showDescription: false,
658
+ showScrollIndicator: true,
659
+ showSelectionIndicator: true,
660
+ wrapSelection: false,
661
+ backgroundColor: colors.background,
662
+ focusedBackgroundColor: colors.background,
663
+ textColor: colors.foreground,
664
+ focusedTextColor: colors.foreground,
665
+ selectedBackgroundColor: colors.selection,
666
+ selectedTextColor: colors.foreground
667
+ });
668
+ this.select.onActivate = () => this.confirm();
669
+ this.select.on("selectionChanged", () => {
670
+ if (!this.changing) this.emitAction("focus");
671
+ });
672
+ this.select.on("itemSelected", () => this.confirm());
673
+ this.panel.add(this.select);
674
+ }
675
+ buildInput() {
676
+ const { renderer: renderer2, colors } = this.context;
677
+ const options = {
678
+ id: `modal-input:${this.spec.id}`,
679
+ width: "100%",
680
+ textColor: colors.foreground,
681
+ backgroundColor: colors.background,
682
+ focusedTextColor: colors.foreground,
683
+ focusedBackgroundColor: colors.background,
684
+ placeholder: this.spec.placeholder ?? "",
685
+ selectionBg: colors.selection,
686
+ cursorColor: colors.accent,
687
+ onContentChange: () => {
688
+ if (!this.editor || this.changing) return;
689
+ this.refreshMask();
690
+ if (!this.spec.secret) this.emitAction("change", this.editor.plainText);
691
+ },
692
+ onSubmit: () => this.confirm()
693
+ };
694
+ this.editor = this.spec.multiline ? new TextareaRenderable(renderer2, {
695
+ ...options,
696
+ initialValue: this.spec.value ?? "",
697
+ height: Math.max(3, Math.min(10, Math.floor(renderer2.terminalHeight / 3))),
698
+ wrapMode: "word",
699
+ keyBindings: [{ name: "return", ctrl: true, action: "submit" }]
700
+ }) : new InputRenderable(renderer2, { ...options, value: this.spec.value ?? "" });
701
+ if (this.spec.secret) {
702
+ this.secretMask = this.addText(`modal-secret:${this.spec.id}`);
703
+ this.secretMask.selectable = false;
704
+ this.editor.showCursor = false;
705
+ } else this.panel.add(this.editor);
706
+ }
707
+ refreshMask() {
708
+ if (this.secretMask && this.editor) this.secretMask.content = "•".repeat(Array.from(this.editor.plainText).length);
709
+ }
710
+ update(patch) {
711
+ const old = this.spec;
712
+ this.spec = { ...old, ...patch };
713
+ const spec = this.spec;
714
+ this.changing = true;
715
+ try {
716
+ this.heading.content = styledText(spec.title);
717
+ this.description.content = styledText(spec.description);
718
+ this.description.visible = plainText(spec.description).length > 0;
719
+ this.breadcrumb.content = (spec.breadcrumbs ?? []).join(" › ");
720
+ this.breadcrumb.visible = (spec.breadcrumbs?.length ?? 0) > 0;
721
+ this.error.content = spec.validationError ?? "";
722
+ this.error.visible = Boolean(spec.validationError);
723
+ this.hints.content = spec.hints ?? (spec.kind === "select" ? "↑ ↓ select · Enter confirm · Esc return" : spec.multiline ? "Ctrl+Enter confirm · Esc return" : "Enter confirm · Esc return");
724
+ if (this.filterInput && patch.filter !== void 0 && patch.filter !== old.filter) {
725
+ this.filter = patch.filter;
726
+ this.filterInput.setText(patch.filter);
727
+ }
728
+ if (this.editor && patch.value !== void 0 && patch.value !== old.value) this.editor.setText(patch.value);
729
+ this.refreshMask();
730
+ this.refreshOptions(patch.selectedIndex);
731
+ this.blocks?.replace(spec.blocks ?? []);
732
+ if (this.progressValue) this.progressValue.width = `${Math.round(Math.max(0, Math.min(1, spec.progress ?? 0)) * 100)}%`;
733
+ for (const child of [...this.actions.getChildren()]) child.destroy();
734
+ for (const action of spec.actions ?? []) {
735
+ const button = new TextRenderable2(this.context.renderer, {
736
+ id: `modal-action:${spec.id}:${action.id}`,
737
+ content: styledText(action.label),
738
+ fg: action.disabled ? this.context.colors.dim : this.context.colors.accent,
739
+ selectable: false,
740
+ onMouseDown: (event) => {
741
+ if (event.button !== 0 || action.disabled) return;
742
+ this.emitAction(action.command ?? action.id, this.editor?.plainText);
743
+ event.stopPropagation();
744
+ },
745
+ onKeyDown: (key) => {
746
+ if (key.name !== "return" || action.disabled) return;
747
+ this.emitAction(action.command ?? action.id, this.editor?.plainText);
748
+ key.preventDefault();
749
+ }
750
+ });
751
+ button.focusable = !action.disabled;
752
+ this.actions.add(button);
753
+ }
754
+ this.actions.visible = (spec.actions?.length ?? 0) > 0;
755
+ } finally {
756
+ this.changing = false;
757
+ }
758
+ this.applyTheme();
759
+ }
760
+ refreshOptions(selectedIndex) {
761
+ if (!this.select) return;
762
+ const query = this.filter.trim().toLocaleLowerCase();
763
+ const previousKey = this.current()?.item.key;
764
+ this.filtered = (this.spec.items ?? []).map((item, index) => ({ item, index })).filter(({ item }) => !query || `${plainText(item.label)} ${plainText(item.description)}`.toLocaleLowerCase().includes(query));
765
+ this.select.options = this.filtered.map(({ item, index }) => ({
766
+ name: `${item.heading ? "── " : ""}${plainText(item.label)}`,
767
+ description: plainText(item.description),
768
+ value: { key: item.key, index, disabled: item.disabled || item.heading }
769
+ }));
770
+ this.select.showDescription = this.filtered.some(({ item }) => Boolean(plainText(item.description)));
771
+ const rowCount = this.select.showDescription ? 2 : 1;
772
+ this.select.height = Math.max(1, Math.min(Math.max(3, Math.floor(this.context.renderer.terminalHeight * 0.55)), this.filtered.length * rowCount));
773
+ const preferred = selectedIndex !== void 0 ? this.filtered.findIndex((entry) => entry.index === selectedIndex) : this.filtered.findIndex((entry) => entry.item.key === previousKey);
774
+ const firstEnabled = this.filtered.findIndex((entry) => !entry.item.disabled && !entry.item.heading);
775
+ this.select.setSelectedIndex(preferred >= 0 && !this.filtered[preferred].item.disabled && !this.filtered[preferred].item.heading ? preferred : Math.max(0, firstEnabled));
776
+ }
777
+ current() {
778
+ return this.filtered[this.select?.getSelectedIndex() ?? -1];
779
+ }
780
+ modalContext() {
781
+ const scoped = Object.create(this.context);
782
+ scoped.emit = (event) => {
783
+ if (event.type === "action") this.emitAction(event.action, event.id);
784
+ else this.context.emit(event);
785
+ };
786
+ return scoped;
787
+ }
788
+ emitAction(action, value) {
789
+ const current = this.current();
790
+ this.context.emit({ type: "modal-action", id: this.spec.id, action, key: current?.item.key, index: current?.index, value });
791
+ }
792
+ confirm() {
793
+ if (this.spec.kind === "progress") {
794
+ this.emitAction("enter");
795
+ return;
796
+ }
797
+ const current = this.current();
798
+ if (this.spec.kind === "select" && (!current || current.item.disabled || current.item.heading)) return;
799
+ const value = this.editor?.plainText;
800
+ if (this.spec.deferSelection) {
801
+ this.emitAction("enter", value);
802
+ return;
803
+ }
804
+ this.close(this.spec.id, { confirmed: true, key: current?.item.key ?? null, index: current?.index ?? -1, ...value === void 0 ? {} : { value } });
805
+ }
806
+ cancel(action = "cancel") {
807
+ if (this.spec.deferSelection) {
808
+ this.emitAction(action);
809
+ return;
810
+ }
811
+ this.close(this.spec.id, { confirmed: false, key: this.spec.breadcrumbs?.length ? "__back__" : null, index: this.current()?.index ?? -1 });
812
+ }
813
+ focus() {
814
+ if (this.filterInput) this.filterInput.focus();
815
+ else if (this.editor && !this.spec.secret) this.editor.focus();
816
+ else if (this.document) this.document.focus();
817
+ else this.panel.focus();
818
+ }
819
+ handleKey(key) {
820
+ const consume = () => {
821
+ key.preventDefault();
822
+ key.stopPropagation();
823
+ return true;
824
+ };
825
+ if (key.name === "escape" || key.ctrl && key.name === "c") {
826
+ this.cancel();
827
+ return consume();
828
+ }
829
+ if (key.name === "tab") {
830
+ this.emitAction(key.shift ? "shift-tab" : "tab");
831
+ return consume();
832
+ }
833
+ if (this.spec.kind === "select") {
834
+ if (key.name === "up" || key.name === "down" || key.name === "pageup" || key.name === "pagedown") {
835
+ const amount = key.name.startsWith("page") ? Math.max(1, Math.floor((this.select?.height ?? 5) / (this.select?.showDescription ? 2 : 1))) : 1;
836
+ if (key.name === "up" || key.name === "pageup") this.select?.moveUp(amount);
837
+ else this.select?.moveDown(amount);
838
+ return consume();
839
+ }
840
+ if (key.name === "return") {
841
+ this.confirm();
842
+ return consume();
843
+ }
844
+ if (key.name === "left" || key.name === "right" || key.name === "delete" || key.name === "backspace" && !this.filter) {
845
+ const action = key.name;
846
+ if (this.spec.deferSelection) this.emitAction(action);
847
+ else if (action === "backspace") this.cancel("backspace");
848
+ return consume();
849
+ }
850
+ if (key.sequence === " " && !this.filter && this.spec.deferSelection) {
851
+ this.emitAction("space");
852
+ return consume();
853
+ }
854
+ if (key.name && key.sequence.length === 1 && !key.ctrl && !key.meta && this.spec.deferSelection && this.spec.captureKeys) {
855
+ this.emitAction("key", key.sequence);
856
+ return consume();
857
+ }
858
+ this.filterInput?.handleKeyPress(key);
859
+ return consume();
860
+ }
861
+ if (this.spec.kind === "input") {
862
+ if (key.name === "return" && (!this.spec.multiline || key.ctrl)) {
863
+ this.confirm();
864
+ return consume();
865
+ }
866
+ 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();
867
+ if (this.spec.secret) {
868
+ this.editor?.handleKeyPress(key);
869
+ this.refreshMask();
870
+ return consume();
871
+ }
872
+ return false;
873
+ }
874
+ if (key.name === "return") {
875
+ this.confirm();
876
+ return consume();
877
+ }
878
+ if (this.document && (key.name === "pageup" || key.name === "pagedown" || key.name === "up" || key.name === "down")) {
879
+ this.document.scrollBy(key.name === "up" ? -1 : key.name === "down" ? 1 : key.name === "pageup" ? -1 : 1, key.name.startsWith("page") ? "viewport" : "absolute");
880
+ return consume();
881
+ }
882
+ if (key.sequence.length === 1 && !key.ctrl && !key.meta) {
883
+ this.emitAction("key", key.sequence);
884
+ return consume();
885
+ }
886
+ return false;
887
+ }
888
+ handlePaste(event) {
889
+ const text = new TextDecoder().decode(event.bytes);
890
+ if (this.overBudget(text, this.filterInput?.plainText ?? this.editor?.plainText ?? "")) {
891
+ event.preventDefault();
892
+ event.stopPropagation();
893
+ return true;
894
+ }
895
+ if (this.filterInput) this.filterInput.handlePaste(event);
896
+ else if (this.spec.secret) {
897
+ this.editor?.handlePaste(event);
898
+ this.refreshMask();
899
+ } else return false;
900
+ event.preventDefault();
901
+ event.stopPropagation();
902
+ return true;
903
+ }
904
+ overBudget(text, current) {
905
+ const budget = this.spec.maxBytes ?? 1024 * 1024;
906
+ if (Buffer.byteLength(text, "utf8") + Buffer.byteLength(current, "utf8") <= budget) return false;
907
+ this.error.content = `Input exceeds the ${budget.toLocaleString()} byte limit.`;
908
+ this.error.visible = true;
909
+ return true;
910
+ }
911
+ applyTheme() {
912
+ const colors = this.context.colors;
913
+ this.root.backgroundColor = colors.background;
914
+ this.panel.backgroundColor = colors.background;
915
+ this.panel.borderColor = colors.border;
916
+ this.heading.fg = colors.accent;
917
+ this.description.fg = colors.foreground;
918
+ this.breadcrumb.fg = colors.dim;
919
+ this.error.fg = colors.error;
920
+ this.hints.fg = colors.dim;
921
+ if (this.select) {
922
+ this.select.backgroundColor = this.select.focusedBackgroundColor = colors.background;
923
+ this.select.textColor = this.select.focusedTextColor = colors.foreground;
924
+ this.select.selectedBackgroundColor = colors.selection;
925
+ this.select.selectedTextColor = colors.foreground;
926
+ this.select.descriptionColor = colors.dim;
927
+ this.select.selectedDescriptionColor = colors.foreground;
928
+ }
929
+ for (const editor of [this.editor, this.filterInput]) if (editor) {
930
+ editor.backgroundColor = editor.focusedBackgroundColor = colors.background;
931
+ editor.textColor = editor.focusedTextColor = colors.foreground;
932
+ editor.selectionBg = colors.selection;
933
+ editor.cursorColor = colors.accent;
934
+ }
935
+ if (this.secretMask) this.secretMask.fg = colors.foreground;
936
+ this.blocks?.applyTheme();
937
+ }
938
+ destroy() {
939
+ this.blocks?.destroy();
940
+ if (this.spec.secret) this.editor?.destroy();
941
+ this.root.destroyRecursively();
942
+ }
943
+ };
944
+ var NativeModalStack = class {
945
+ constructor(context, restoreFocus) {
946
+ this.context = context;
947
+ this.restoreFocus = restoreFocus;
948
+ }
949
+ context;
950
+ restoreFocus;
951
+ views = [];
952
+ byId = /* @__PURE__ */ new Map();
953
+ get active() {
954
+ return this.views.at(-1);
955
+ }
956
+ open(spec) {
957
+ const existing = this.byId.get(spec.id);
958
+ if (existing) {
959
+ existing.update(spec);
960
+ existing.focus();
961
+ return;
962
+ }
963
+ if (this.active) this.active.root.visible = false;
964
+ const view = new NativeModalView(this.context, spec, (id, result) => this.close(id, result, true));
965
+ this.views.push(view);
966
+ this.byId.set(spec.id, view);
967
+ this.context.renderer.root.add(view.root);
968
+ view.focus();
969
+ }
970
+ update(id, patch) {
971
+ this.byId.get(id)?.update(patch);
972
+ }
973
+ close(id, result, notify = false) {
974
+ const view = this.byId.get(id);
975
+ if (!view) return;
976
+ const wasActive = view === this.active;
977
+ view.destroy();
978
+ this.byId.delete(id);
979
+ this.views = this.views.filter((candidate) => candidate !== view);
980
+ if (wasActive) {
981
+ if (this.active) {
982
+ this.active.root.visible = true;
983
+ this.active.focus();
984
+ } else this.restoreFocus();
985
+ }
986
+ if (notify) this.context.emit({ type: "modal-result", id, result });
987
+ }
988
+ applyTheme() {
989
+ for (const view of this.views) view.applyTheme();
990
+ }
991
+ destroy() {
992
+ for (const view of this.views) view.destroy();
993
+ this.views = [];
994
+ this.byId.clear();
995
+ }
996
+ };
997
+
998
+ // packages/cli/src/tui/native/components/frontend.ts
999
+ var initialTheme = { mode: "branding", bg: -1, accent: 108, textPrimary: 254, textDim: 145, boxColor: 108 };
1000
+ var spinner = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
1001
+ var NativeFrontend = class {
1002
+ constructor(renderer2, emit) {
1003
+ this.renderer = renderer2;
1004
+ this.emit = emit;
1005
+ const colors = palette(initialTheme);
1006
+ this.context = { renderer: renderer2, colors, syntax: SyntaxStyle2.fromStyles({}, renderer2.nativeScene), emit, terminals: /* @__PURE__ */ new Map() };
1007
+ this.clipboard = createClipboard({ host: createHostClipboard({ timeoutMs: 1500 }), terminal: createRendererClipboardAdapter(renderer2) });
1008
+ this.root = new BoxRenderable3(renderer2, {
1009
+ id: "omnius-native",
1010
+ width: "100%",
1011
+ height: "100%",
1012
+ flexDirection: "column",
1013
+ backgroundColor: colors.background
1014
+ });
1015
+ renderer2.root.add(this.root);
1016
+ this.header = new BoxRenderable3(renderer2, {
1017
+ id: "omnius-header",
1018
+ width: "100%",
1019
+ flexDirection: "column",
1020
+ flexShrink: 0,
1021
+ border: true,
1022
+ borderStyle: "rounded",
1023
+ borderColor: colors.border,
1024
+ paddingX: 1
1025
+ });
1026
+ this.root.add(this.header);
1027
+ const headerRow = new BoxRenderable3(renderer2, { id: "omnius-header-row", width: "100%", height: 1, flexDirection: "row", gap: 2 });
1028
+ this.header.add(headerRow);
1029
+ this.identity = new TextRenderable3(renderer2, {
1030
+ id: "omnius-identity",
1031
+ content: "Omnius",
1032
+ fg: colors.foreground,
1033
+ flexShrink: 1,
1034
+ flexGrow: 1,
1035
+ wrapMode: "none",
1036
+ truncate: true,
1037
+ minWidth: 8,
1038
+ selectable: false
1039
+ });
1040
+ headerRow.add(this.identity);
1041
+ this.headerActions = new ScrollBoxRenderable3(renderer2, {
1042
+ id: "omnius-header-actions",
1043
+ width: "50%",
1044
+ flexGrow: 0,
1045
+ flexShrink: 0,
1046
+ minWidth: 0,
1047
+ height: 1,
1048
+ scrollX: true,
1049
+ scrollY: false,
1050
+ contentOptions: { flexDirection: "row", gap: 2 },
1051
+ verticalScrollbarOptions: { visible: false },
1052
+ horizontalScrollbarOptions: { visible: false }
1053
+ });
1054
+ headerRow.add(this.headerActions);
1055
+ this.headerPanel = new TextRenderable3(renderer2, { id: "omnius-header-panel", content: "", width: "100%", wrapMode: "word", visible: false });
1056
+ this.header.add(this.headerPanel);
1057
+ this.routeWarning = new TextRenderable3(renderer2, { id: "omnius-route-warning", content: "", width: "100%", wrapMode: "word", fg: colors.warning, visible: false, flexShrink: 0 });
1058
+ this.root.add(this.routeWarning);
1059
+ this.tabs = new TabSelectRenderable(renderer2, {
1060
+ id: "omnius-agent-tabs",
1061
+ width: "100%",
1062
+ height: 1,
1063
+ showDescription: false,
1064
+ showUnderline: false,
1065
+ showScrollArrows: true,
1066
+ tabWidth: 18,
1067
+ wrapSelection: true,
1068
+ visible: false,
1069
+ backgroundColor: colors.background,
1070
+ focusedBackgroundColor: colors.background,
1071
+ textColor: colors.dim,
1072
+ selectedTextColor: colors.foreground,
1073
+ selectedBackgroundColor: colors.selection,
1074
+ onMouseDown: (event) => {
1075
+ if (event.button !== 0) return;
1076
+ const count = Math.max(1, Math.floor(this.tabs.width / this.tabs.getTabWidth()));
1077
+ const start = Math.max(0, Math.min(this.tabs.getSelectedIndex() - Math.floor(count / 2), this.tabs.options.length - count));
1078
+ this.tabs.setSelectedIndex(start + Math.floor((event.x - this.tabs.x) / this.tabs.getTabWidth()));
1079
+ event.stopPropagation();
1080
+ }
1081
+ });
1082
+ this.tabs.on("selectionChanged", () => {
1083
+ if (this.changing) return;
1084
+ const selected = this.tabs.getSelectedOption();
1085
+ if (selected) this.emit({ type: "action", action: "view", id: String(selected.value) });
1086
+ this.editor.focus();
1087
+ });
1088
+ this.root.add(this.tabs);
1089
+ this.taskPane = new BoxRenderable3(renderer2, { id: "omnius-tasks", width: "100%", flexDirection: "column", maxHeight: 6, flexShrink: 0, overflow: "hidden", visible: false });
1090
+ this.tasks = new NativeBlockList(this.context, this.taskPane);
1091
+ this.root.add(this.taskPane);
1092
+ this.body = new BoxRenderable3(renderer2, { id: "omnius-body", width: "100%", flexDirection: "row", flexGrow: 1, minHeight: 1 });
1093
+ this.root.add(this.body);
1094
+ this.scroll = new ScrollBoxRenderable3(renderer2, {
1095
+ id: "omnius-conversation",
1096
+ flexGrow: 1,
1097
+ flexShrink: 1,
1098
+ minWidth: 0,
1099
+ minHeight: 1,
1100
+ scrollY: true,
1101
+ scrollX: false,
1102
+ stickyScroll: true,
1103
+ stickyStart: "bottom",
1104
+ viewportCulling: true,
1105
+ verticalScrollbarOptions: { showArrows: false, trackOptions: { foregroundColor: colors.dim, backgroundColor: colors.background } },
1106
+ contentOptions: { flexDirection: "column", onMouseDown: (event) => {
1107
+ if (!this.selectionArmed || event.button !== 0 || !event.target) return;
1108
+ this.selectionArmed = false;
1109
+ let block = event.target;
1110
+ while (block && !block.id.startsWith("block:")) block = block.parent;
1111
+ if (!block) return;
1112
+ const selectables = [];
1113
+ const visit = (node) => {
1114
+ if (!node.visible) return;
1115
+ if (node.selectable && node.height > 0 && node.width > 0) selectables.push(node);
1116
+ for (const child of node.getChildren()) visit(child);
1117
+ };
1118
+ visit(block);
1119
+ const first = selectables[0], last = selectables.at(-1);
1120
+ if (first && last) {
1121
+ this.renderer.startSelection(first, first.x, first.y, "line");
1122
+ this.renderer.updateSelection(last, last.x + Math.max(0, last.width - 1), last.y + Math.max(0, last.height - 1), { finishDragging: true });
1123
+ }
1124
+ event.preventDefault();
1125
+ event.stopPropagation();
1126
+ } }
1127
+ });
1128
+ this.body.add(this.scroll);
1129
+ this.content = new NativeBlockList(this.context, this.scroll);
1130
+ this.embeddedPane = new BoxRenderable3(renderer2, {
1131
+ id: "omnius-editor-pane",
1132
+ width: "50%",
1133
+ flexShrink: 0,
1134
+ flexDirection: "column",
1135
+ border: true,
1136
+ borderStyle: "rounded",
1137
+ borderColor: colors.border,
1138
+ visible: false
1139
+ });
1140
+ this.body.add(this.embeddedPane);
1141
+ this.liveButton = new TextRenderable3(renderer2, {
1142
+ id: "omnius-live-button",
1143
+ content: "↓ Live",
1144
+ width: "100%",
1145
+ height: 1,
1146
+ visible: false,
1147
+ fg: colors.accent,
1148
+ selectable: false,
1149
+ onMouseDown: (event) => {
1150
+ if (event.button === 0) {
1151
+ this.scrollToLive();
1152
+ event.stopPropagation();
1153
+ }
1154
+ },
1155
+ onKeyDown: (key) => {
1156
+ if (key.name === "return") {
1157
+ this.scrollToLive();
1158
+ key.preventDefault();
1159
+ }
1160
+ }
1161
+ });
1162
+ this.liveButton.focusable = true;
1163
+ this.root.add(this.liveButton);
1164
+ this.queue = new TextRenderable3(renderer2, { id: "omnius-steering-queue", content: "", width: "100%", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, visible: false, flexShrink: 0 });
1165
+ this.root.add(this.queue);
1166
+ this.suggestions = new NativeSelectRenderable(renderer2, {
1167
+ id: "omnius-suggestions",
1168
+ width: "100%",
1169
+ height: 1,
1170
+ visible: false,
1171
+ showDescription: false,
1172
+ showScrollIndicator: true,
1173
+ showSelectionIndicator: true,
1174
+ backgroundColor: colors.background,
1175
+ focusedBackgroundColor: colors.background,
1176
+ textColor: colors.dim,
1177
+ selectedTextColor: colors.foreground,
1178
+ selectedBackgroundColor: colors.selection
1179
+ });
1180
+ this.suggestions.onActivate = () => this.acceptSuggestion();
1181
+ this.suggestions.on("itemSelected", () => this.acceptSuggestion());
1182
+ this.root.add(this.suggestions);
1183
+ this.footer = new BoxRenderable3(renderer2, {
1184
+ id: "omnius-input-box",
1185
+ width: "100%",
1186
+ flexShrink: 0,
1187
+ border: true,
1188
+ borderStyle: "rounded",
1189
+ borderColor: colors.border,
1190
+ flexDirection: "column",
1191
+ paddingX: 1
1192
+ });
1193
+ this.root.add(this.footer);
1194
+ const inputRow = new BoxRenderable3(renderer2, { id: "omnius-input-row", width: "100%", flexDirection: "row", gap: 1 });
1195
+ this.footer.add(inputRow);
1196
+ this.prompt = new TextRenderable3(renderer2, { id: "omnius-prompt", content: "❯", fg: colors.accent, wrapMode: "none", selectable: false, flexShrink: 0 });
1197
+ inputRow.add(this.prompt);
1198
+ this.editor = new TextareaRenderable2(renderer2, {
1199
+ id: "omnius-input",
1200
+ flexGrow: 1,
1201
+ minWidth: 0,
1202
+ height: 1,
1203
+ maxHeight: 8,
1204
+ wrapMode: "word",
1205
+ textColor: colors.foreground,
1206
+ backgroundColor: colors.background,
1207
+ focusedTextColor: colors.foreground,
1208
+ focusedBackgroundColor: colors.background,
1209
+ selectionBg: colors.selection,
1210
+ cursorColor: colors.accent,
1211
+ cursorStyle: { style: "block", blinking: true },
1212
+ keyBindings: [{ name: "return", action: "submit" }, { name: "return", shift: true, action: "newline" }],
1213
+ onContentChange: () => this.editorChanged(),
1214
+ onCursorChange: () => this.editorChanged(),
1215
+ onSubmit: () => this.submit()
1216
+ });
1217
+ inputRow.add(this.editor);
1218
+ this.enhance = new BoxRenderable3(renderer2, { id: "omnius-enhance", flexDirection: "row", gap: 1, flexShrink: 0, visible: false });
1219
+ inputRow.add(this.enhance);
1220
+ this.activity = new TextRenderable3(renderer2, { id: "omnius-activity", content: "", width: "100%", height: 1, wrapMode: "none", truncate: true, visible: false, fg: colors.accent });
1221
+ this.footer.add(this.activity);
1222
+ this.media = new BoxRenderable3(renderer2, { id: "omnius-media", width: "100%", height: 1, flexDirection: "row", gap: 1, visible: false });
1223
+ this.mediaText = new TextRenderable3(renderer2, { id: "omnius-media-text", content: "", flexGrow: 1, minWidth: 0, wrapMode: "none", truncate: true, fg: colors.accent });
1224
+ this.media.add(this.mediaText);
1225
+ this.micTrack = new BoxRenderable3(renderer2, { id: "omnius-mic-meter", width: 12, height: 1, flexShrink: 0, backgroundColor: colors.dim, visible: false });
1226
+ this.micLevel = new BoxRenderable3(renderer2, { id: "omnius-mic-level", width: "0%", height: 1, backgroundColor: colors.accent });
1227
+ this.micTrack.add(this.micLevel);
1228
+ this.media.add(this.micTrack);
1229
+ this.footer.add(this.media);
1230
+ this.metricsRow = new BoxRenderable3(renderer2, { id: "omnius-metrics-row", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 1 });
1231
+ this.metricsStartActions = new BoxRenderable3(renderer2, { id: "omnius-metrics-start-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
1232
+ this.metricsRow.add(this.metricsStartActions);
1233
+ this.metrics = new TextRenderable3(renderer2, { id: "omnius-metrics", content: "", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, flexShrink: 1, flexGrow: 1, minWidth: 0 });
1234
+ this.metricsRow.add(this.metrics);
1235
+ this.metricsEndActions = new BoxRenderable3(renderer2, { id: "omnius-metrics-end-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
1236
+ this.metricsRow.add(this.metricsEndActions);
1237
+ this.root.add(this.metricsRow);
1238
+ this.modals = new NativeModalStack(this.context, () => this.restoreFocus());
1239
+ renderer2.keyInput.on("keypress", this.onKey);
1240
+ renderer2.keyInput.on("paste", this.onPaste);
1241
+ renderer2.on(CliRenderEvents.FRAME, this.onFrame);
1242
+ renderer2.on(CliRenderEvents.RESIZE, this.onResize);
1243
+ this.applyTheme(initialTheme);
1244
+ this.editor.focus();
1245
+ }
1246
+ renderer;
1247
+ emit;
1248
+ root;
1249
+ scroll;
1250
+ editor;
1251
+ content;
1252
+ modals;
1253
+ context;
1254
+ header;
1255
+ identity;
1256
+ headerActions;
1257
+ headerPanel;
1258
+ routeWarning;
1259
+ tabs;
1260
+ taskPane;
1261
+ tasks;
1262
+ body;
1263
+ embeddedPane;
1264
+ liveButton;
1265
+ queue;
1266
+ suggestions;
1267
+ footer;
1268
+ prompt;
1269
+ enhance;
1270
+ metrics;
1271
+ metricsRow;
1272
+ metricsStartActions;
1273
+ metricsEndActions;
1274
+ activity;
1275
+ media;
1276
+ mediaText;
1277
+ micTrack;
1278
+ micLevel;
1279
+ clipboard;
1280
+ state;
1281
+ themeSignature = "";
1282
+ headerSignature = "";
1283
+ tabsSignature = "";
1284
+ suggestionSignature = "";
1285
+ metricsActionsSignature = "";
1286
+ changing = false;
1287
+ paused = false;
1288
+ editorInitialized = false;
1289
+ lastInput = "";
1290
+ lastCursor = -1;
1291
+ history = [];
1292
+ historyIndex = -1;
1293
+ savedInput = "";
1294
+ animation;
1295
+ animationIndex = 0;
1296
+ inputEventPending = false;
1297
+ viewportSignature = "";
1298
+ newEntries = 0;
1299
+ destroyed = false;
1300
+ pendingScrollRestore;
1301
+ viewScroll = /* @__PURE__ */ new Map();
1302
+ embeddedId;
1303
+ selectionArmed = false;
1304
+ update(state, patch) {
1305
+ const inputWasEnabled = this.state?.input.enabled;
1306
+ const previousView = this.state ? `${this.state.activeViewId}:${this.state.presentation ?? "stack"}` : void 0;
1307
+ const currentView = `${state.activeViewId}:${state.presentation ?? "stack"}`;
1308
+ const viewChanged = previousView !== void 0 && previousView !== currentView;
1309
+ if (viewChanged) {
1310
+ this.viewScroll.set(previousView, { top: this.scroll.scrollTop, live: this.offset === 0 });
1311
+ this.pendingScrollRestore = this.viewScroll.get(currentView) ?? { top: 0, live: true };
1312
+ this.newEntries = 0;
1313
+ }
1314
+ this.state = state;
1315
+ this.changing = true;
1316
+ try {
1317
+ this.applyTheme(state.theme);
1318
+ const headerSignature = JSON.stringify(state.header);
1319
+ if (headerSignature !== this.headerSignature) {
1320
+ this.headerSignature = headerSignature;
1321
+ this.identity.content = styledText([
1322
+ { text: state.header.title, ...state.theme.accent < 0 ? {} : { fg: `index:${state.theme.accent}` } },
1323
+ ...state.header.detail ? [{ text: " " }, ...textSpans(state.header.detail)] : []
1324
+ ]);
1325
+ this.headerPanel.content = styledText(state.header.panel);
1326
+ this.headerPanel.visible = plainText(state.header.panel).length > 0;
1327
+ this.replaceActions(this.headerActions, state.header.actions, "header");
1328
+ this.headerActions.visible = state.header.actions.length > 0;
1329
+ }
1330
+ this.routeWarning.content = state.routeWarning ?? "";
1331
+ this.routeWarning.visible = Boolean(state.routeWarning);
1332
+ const tabsSignature = JSON.stringify(state.tabs);
1333
+ if (tabsSignature !== this.tabsSignature) {
1334
+ this.tabsSignature = tabsSignature;
1335
+ this.tabs.setOptions(state.tabs.map((tab) => ({ name: plainText(tab.label), description: "", value: tab.id })));
1336
+ }
1337
+ this.tabs.visible = state.tabs.length > 1;
1338
+ const activeTab = state.tabs.findIndex((tab) => tab.id === state.activeViewId);
1339
+ if (activeTab >= 0 && activeTab !== this.tabs.getSelectedIndex()) this.tabs.setSelectedIndex(activeTab);
1340
+ this.tasks.replace(state.tasks);
1341
+ this.taskPane.visible = state.tasks.length > 0;
1342
+ if (patch) {
1343
+ if (this.offset > 0 && !viewChanged && !patch.reset) this.newEntries += patch.upsert.filter((block) => !this.content.views.has(block.id)).length;
1344
+ this.content.patch(patch);
1345
+ }
1346
+ this.queue.content = state.queued.length ? `Queued (${state.queued.length}) · ${state.queued.join(" · ")}` : "";
1347
+ this.queue.visible = state.queued.length > 0;
1348
+ const suggestionSignature = JSON.stringify(state.suggestions);
1349
+ if (suggestionSignature !== this.suggestionSignature) {
1350
+ this.suggestionSignature = suggestionSignature;
1351
+ const selected = this.suggestions.getSelectedOption()?.value;
1352
+ this.suggestions.options = state.suggestions.map((item) => ({ name: item.value, description: item.description ?? "", value: item.value }));
1353
+ const index = state.suggestions.findIndex((item) => item.value === selected);
1354
+ if (index >= 0) this.suggestions.setSelectedIndex(index);
1355
+ }
1356
+ this.suggestions.visible = state.suggestions.length > 0;
1357
+ this.suggestions.height = Math.min(4, state.suggestions.length || 1);
1358
+ this.prompt.content = styledText(state.prompt.trimEnd() || "❯");
1359
+ if (!this.editorInitialized) {
1360
+ this.editor.setText(state.input.text);
1361
+ this.editor.cursorOffset = Math.max(0, Math.min(state.input.text.length, state.input.cursor));
1362
+ this.editorInitialized = true;
1363
+ }
1364
+ this.editor.focusable = state.input.enabled && !this.paused;
1365
+ if (state.input.enabled && !this.paused && !this.modals.active && (inputWasEnabled === false || !this.renderer.currentFocusedRenderable)) this.editor.focus();
1366
+ this.metrics.content = styledText(state.metrics);
1367
+ this.metricsRow.visible = state.metricsVisible;
1368
+ const metricsActions = JSON.stringify(state.metricsActions ?? []);
1369
+ if (metricsActions !== this.metricsActionsSignature) {
1370
+ this.metricsActionsSignature = metricsActions;
1371
+ this.replaceActions(this.metricsStartActions, (state.metricsActions ?? []).filter((action) => action.id === "agents"), "metrics");
1372
+ this.replaceActions(this.metricsEndActions, (state.metricsActions ?? []).filter((action) => action.id !== "agents"), "metrics");
1373
+ }
1374
+ this.renderEnhance();
1375
+ this.renderActivity();
1376
+ this.renderMedia();
1377
+ this.updateAnimation();
1378
+ } finally {
1379
+ this.changing = false;
1380
+ }
1381
+ this.editorChanged();
1382
+ this.renderer.requestRender();
1383
+ }
1384
+ replaceActions(parent, actions, prefix) {
1385
+ for (const child of [...parent.getChildren()]) child.destroy();
1386
+ for (const action of actions) {
1387
+ const colors = this.context.colors;
1388
+ const filled = prefix === "enhance" || prefix === "header" && !action.id.startsWith("header-");
1389
+ const inverse = filled && prefix === "header" && (this.state?.theme.mode === "system" || this.state?.theme.accent === -1);
1390
+ const backing = prefix === "enhance" ? colors.warning : colors.accent;
1391
+ const [r, g, b] = backing.toInts();
1392
+ const foreground = filled && !inverse ? nativeColor(r * 0.299 + g * 0.587 + b * 0.114 > 140 ? 16 : 231) : colors.accent;
1393
+ const button = new TextRenderable3(this.renderer, {
1394
+ id: `${prefix}:${action.id}`,
1395
+ content: styledText(filled ? ` ${action.label} ` : action.label),
1396
+ flexShrink: 0,
1397
+ wrapMode: "none",
1398
+ selectable: false,
1399
+ fg: action.disabled ? colors.dim : foreground,
1400
+ bg: filled && !inverse ? backing : colors.background,
1401
+ attributes: (action.active ? 1 : 0) | (inverse ? 32 : 0),
1402
+ onMouseDown: (event) => {
1403
+ if (event.button !== 0 || action.disabled) return;
1404
+ this.emit({ type: "action", action: action.command ?? action.id, id: action.id });
1405
+ event.stopPropagation();
1406
+ },
1407
+ onKeyDown: (key) => {
1408
+ if (key.name !== "return" || action.disabled) return;
1409
+ this.emit({ type: "action", action: action.command ?? action.id, id: action.id });
1410
+ key.preventDefault();
1411
+ }
1412
+ });
1413
+ button.focusable = !action.disabled;
1414
+ if (!action.disabled) {
1415
+ button.onMouseOver = () => {
1416
+ button.bg = colors.selection;
1417
+ button.fg = colors.foreground;
1418
+ };
1419
+ button.onMouseOut = () => {
1420
+ button.bg = filled && !inverse ? backing : colors.background;
1421
+ button.fg = foreground;
1422
+ };
1423
+ }
1424
+ parent.add(button);
1425
+ }
1426
+ }
1427
+ renderEnhance() {
1428
+ const state = this.state?.enhance;
1429
+ this.enhance.visible = Boolean(state);
1430
+ if (!state) return;
1431
+ const actions = state.state === "confirm" ? [{ id: "enhance-accept", label: "✓" }, { id: "enhance-reject", label: "×" }] : [{ id: "enhance", label: state.state === "busy" ? `${spinner[this.animationIndex % spinner.length]} Enhance` : state.label ?? "Enhance", disabled: state.state === "busy" }];
1432
+ this.replaceActions(this.enhance, actions, "enhance");
1433
+ }
1434
+ renderActivity() {
1435
+ const activity = this.state?.activity;
1436
+ const stage = activity?.stage === "idle" ? void 0 : activity?.stage;
1437
+ this.activity.visible = Boolean(activity?.processing || activity?.label || stage);
1438
+ this.activity.content = `${activity?.processing ? spinner[this.animationIndex % spinner.length] + " " : ""}${[activity?.label, stage].filter(Boolean).join(" · ")}`;
1439
+ }
1440
+ renderMedia() {
1441
+ const media = this.state?.media;
1442
+ this.media.visible = Boolean(media && (media.audio || media.video || media.voiceEnabled || media.recording || media.countdown > 0 || media.label));
1443
+ if (!media) return;
1444
+ this.mediaText.content = [
1445
+ media.recording ? `${this.animationIndex % 10 < 5 ? "●" : "○"} REC` : void 0,
1446
+ media.speech ? "speech" : void 0,
1447
+ media.audio ? "audio" : void 0,
1448
+ media.video ? "video" : void 0,
1449
+ media.voiceEnabled ? "voice" : void 0,
1450
+ media.countdown > 0 ? `${media.countdown}s` : void 0,
1451
+ media.levelDb === void 0 ? void 0 : `${media.levelDb} dB`,
1452
+ media.label
1453
+ ].filter(Boolean).join(" · ");
1454
+ this.mediaText.fg = media.recording ? this.context.colors.warning : this.context.colors.accent;
1455
+ this.micTrack.visible = media.recording && media.levelDb !== void 0;
1456
+ this.micLevel.width = `${Math.round(Math.max(0, Math.min(1, ((media.levelDb ?? -60) + 60) / 60)) * 100)}%`;
1457
+ this.micLevel.backgroundColor = media.speech ? this.context.colors.success : this.context.colors.accent;
1458
+ }
1459
+ updateAnimation() {
1460
+ const active = this.state?.activity?.processing || this.state?.enhance?.state === "busy" || this.state?.media?.recording;
1461
+ if (active && !this.animation) {
1462
+ this.animation = setInterval(() => {
1463
+ this.animationIndex++;
1464
+ this.renderActivity();
1465
+ this.renderMedia();
1466
+ if (this.state?.enhance?.state === "busy") this.renderEnhance();
1467
+ }, 100);
1468
+ this.animation.unref();
1469
+ } else if (!active && this.animation) {
1470
+ clearInterval(this.animation);
1471
+ this.animation = void 0;
1472
+ }
1473
+ }
1474
+ applyTheme(theme) {
1475
+ const signature = JSON.stringify(theme);
1476
+ if (signature === this.themeSignature) return;
1477
+ this.themeSignature = signature;
1478
+ this.headerSignature = "";
1479
+ this.metricsActionsSignature = "";
1480
+ const colors = this.context.colors = palette(theme);
1481
+ for (const [name, style] of Object.entries({
1482
+ default: { fg: colors.foreground },
1483
+ "markup.heading": { fg: colors.accent, bold: true },
1484
+ "markup.strong": { fg: colors.foreground, bold: true },
1485
+ "markup.italic": { fg: colors.foreground, italic: true },
1486
+ "markup.link": { fg: colors.accent, underline: true },
1487
+ "markup.link.url": { fg: colors.dim },
1488
+ "markup.raw": { fg: colors.accent },
1489
+ "markup.list": { fg: colors.accent },
1490
+ "markup.quote": { fg: colors.dim },
1491
+ comment: { fg: colors.dim, italic: true },
1492
+ keyword: { fg: colors.accent, bold: true },
1493
+ string: { fg: colors.success },
1494
+ number: { fg: colors.warning },
1495
+ function: { fg: colors.foreground },
1496
+ type: { fg: colors.accent },
1497
+ punctuation: { fg: colors.dim }
1498
+ })) this.context.syntax.registerStyle(name, style);
1499
+ this.root.backgroundColor = colors.background;
1500
+ this.header.backgroundColor = this.footer.backgroundColor = colors.background;
1501
+ this.header.borderColor = this.footer.borderColor = this.embeddedPane.borderColor = colors.border;
1502
+ this.identity.fg = colors.foreground;
1503
+ this.headerPanel.fg = colors.foreground;
1504
+ this.routeWarning.fg = colors.warning;
1505
+ this.prompt.fg = this.activity.fg = this.liveButton.fg = colors.accent;
1506
+ this.micTrack.backgroundColor = colors.dim;
1507
+ this.micLevel.backgroundColor = colors.accent;
1508
+ this.queue.fg = this.metrics.fg = colors.dim;
1509
+ this.editor.backgroundColor = this.editor.focusedBackgroundColor = colors.background;
1510
+ this.editor.textColor = this.editor.focusedTextColor = colors.foreground;
1511
+ this.editor.cursorColor = colors.accent;
1512
+ this.editor.selectionBg = colors.selection;
1513
+ this.tabs.backgroundColor = this.tabs.focusedBackgroundColor = colors.background;
1514
+ this.tabs.textColor = colors.dim;
1515
+ this.tabs.selectedTextColor = colors.foreground;
1516
+ this.tabs.selectedBackgroundColor = colors.selection;
1517
+ this.suggestions.backgroundColor = this.suggestions.focusedBackgroundColor = colors.background;
1518
+ this.suggestions.textColor = colors.dim;
1519
+ this.suggestions.selectedTextColor = colors.foreground;
1520
+ this.suggestions.selectedBackgroundColor = colors.selection;
1521
+ this.content.applyTheme();
1522
+ this.tasks.applyTheme();
1523
+ this.modals?.applyTheme();
1524
+ }
1525
+ get offset() {
1526
+ return Math.max(0, this.scroll.scrollHeight - this.scroll.viewport.height - this.scroll.scrollTop);
1527
+ }
1528
+ getRenderable(id) {
1529
+ const visit = (node) => {
1530
+ if (node.id === id) return node;
1531
+ for (const child of node.getChildren()) {
1532
+ const found = visit(child);
1533
+ if (found) return found;
1534
+ }
1535
+ return void 0;
1536
+ };
1537
+ return visit(this.renderer.root);
1538
+ }
1539
+ onFrame = () => {
1540
+ if (this.destroyed) return;
1541
+ if (this.pendingScrollRestore) {
1542
+ const restore = this.pendingScrollRestore;
1543
+ this.pendingScrollRestore = void 0;
1544
+ this.scroll.scrollTo(restore.live ? this.scroll.scrollHeight : restore.top);
1545
+ this.renderer.requestRender();
1546
+ }
1547
+ const height = Math.max(1, Math.min(8, this.editor.editorView.getTotalVirtualLineCount(), this.renderer.terminalHeight - 8));
1548
+ if (height !== this.editor.height) this.editor.height = height;
1549
+ const offset = this.offset;
1550
+ if (offset === 0) this.newEntries = 0;
1551
+ this.liveButton.visible = offset > 0;
1552
+ const liveLabel = offset > 0 ? `↓ Live${this.newEntries ? ` · ${this.newEntries} new` : ""}` : "↓ Live";
1553
+ if (this.liveButton.plainText !== liveLabel) this.liveButton.content = liveLabel;
1554
+ const event = { type: "viewport", offset: this.scroll.scrollTop, height: this.scroll.viewport.height, width: this.scroll.viewport.width, total: this.scroll.scrollHeight };
1555
+ const signature = JSON.stringify(event);
1556
+ if (signature !== this.viewportSignature) {
1557
+ this.viewportSignature = signature;
1558
+ this.emit(event);
1559
+ }
1560
+ };
1561
+ onResize = () => {
1562
+ this.emit({ type: "resize", columns: this.renderer.terminalWidth, rows: this.renderer.terminalHeight });
1563
+ };
1564
+ editorChanged() {
1565
+ if (!this.editor || this.changing || this.destroyed || this.inputEventPending) return;
1566
+ this.inputEventPending = true;
1567
+ queueMicrotask(() => {
1568
+ this.inputEventPending = false;
1569
+ if (this.destroyed) return;
1570
+ this.flushLatestInput();
1571
+ const height = Math.max(1, Math.min(8, this.editor.editorView.getTotalVirtualLineCount(), this.renderer.terminalHeight - 8));
1572
+ if (height !== this.editor.height) this.editor.height = height;
1573
+ });
1574
+ }
1575
+ flushLatestInput() {
1576
+ const text = this.editor.plainText;
1577
+ const cursor = this.editor.cursorOffset;
1578
+ if (text === this.lastInput && cursor === this.lastCursor) return;
1579
+ this.lastInput = text;
1580
+ this.lastCursor = cursor;
1581
+ this.emit({ type: "input", text, cursor });
1582
+ }
1583
+ submit() {
1584
+ if (this.paused || this.state?.input.enabled === false) return;
1585
+ if (this.suggestions.visible && this.editor.plainText.startsWith("/") && this.acceptSuggestion()) return;
1586
+ const text = this.editor.plainText;
1587
+ if (text.trim() && this.history[0] !== text) {
1588
+ this.history.unshift(text);
1589
+ this.history.length = Math.min(this.history.length, 1e3);
1590
+ }
1591
+ this.historyIndex = -1;
1592
+ this.savedInput = "";
1593
+ this.editor.clear();
1594
+ this.emit({ type: "submit", text });
1595
+ this.editorChanged();
1596
+ }
1597
+ navigateHistory(direction) {
1598
+ if (this.history.length === 0) return;
1599
+ if (direction < 0) {
1600
+ if (this.historyIndex < 0) this.savedInput = this.editor.plainText;
1601
+ this.historyIndex = Math.min(this.history.length - 1, this.historyIndex + 1);
1602
+ } else this.historyIndex = Math.max(-1, this.historyIndex - 1);
1603
+ this.setInput(this.historyIndex < 0 ? this.savedInput : this.history[this.historyIndex]);
1604
+ }
1605
+ acceptSuggestion() {
1606
+ const selected = this.suggestions.getSelectedOption();
1607
+ if (!selected || selected.value === this.editor.plainText.trimEnd()) return false;
1608
+ const text = String(selected.value);
1609
+ this.setInput(text + (text.endsWith(" ") ? "" : " "));
1610
+ this.suggestions.visible = false;
1611
+ this.emit({ type: "action", action: "completion", id: text });
1612
+ return true;
1613
+ }
1614
+ setInput(text, cursor = text.length) {
1615
+ this.editor.replaceText(text);
1616
+ this.editor.cursorOffset = Math.max(0, Math.min(text.length, cursor));
1617
+ this.editorInitialized = true;
1618
+ this.editorChanged();
1619
+ }
1620
+ shortcut(name, key) {
1621
+ this.flushLatestInput();
1622
+ this.emit({ type: "shortcut", name });
1623
+ key.preventDefault();
1624
+ key.stopPropagation();
1625
+ }
1626
+ onKey = (key) => {
1627
+ if (this.modals.active) {
1628
+ this.modals.active.handleKey(key);
1629
+ return;
1630
+ }
1631
+ if (key.eventType === "release") return;
1632
+ if (key.ctrl && key.name.toLowerCase() === "n") {
1633
+ this.shortcut("ctrl-n", key);
1634
+ return;
1635
+ }
1636
+ if (this.renderer.currentFocusedRenderable instanceof EmbeddedTerminalRenderable2) return;
1637
+ const headerButtons = (this.state?.header.actions ?? []).filter((action) => !action.disabled).map((action) => this.getRenderable(`header:${action.id}`)).filter((button) => Boolean(button));
1638
+ const headerIndex = headerButtons.indexOf(this.renderer.currentFocusedRenderable);
1639
+ if (headerIndex >= 0) {
1640
+ const direction = key.name === "left" || key.name === "tab" && key.shift ? -1 : key.name === "right" || key.name === "tab" ? 1 : 0;
1641
+ if (direction || key.name === "home" || key.name === "end") {
1642
+ const index = key.name === "home" ? 0 : key.name === "end" ? headerButtons.length - 1 : (headerIndex + direction + headerButtons.length) % headerButtons.length;
1643
+ const button = headerButtons[index];
1644
+ button.focus();
1645
+ this.headerActions.scrollChildIntoView(button.id);
1646
+ key.preventDefault();
1647
+ key.stopPropagation();
1648
+ return;
1649
+ }
1650
+ if (key.name === "escape") {
1651
+ this.restoreFocus();
1652
+ key.preventDefault();
1653
+ key.stopPropagation();
1654
+ return;
1655
+ }
1656
+ if (key.name === "return") return;
1657
+ }
1658
+ if (key.ctrl && key.shift && key.name.toLowerCase() === "c") {
1659
+ void this.copy();
1660
+ key.preventDefault();
1661
+ return;
1662
+ }
1663
+ if (key.ctrl && key.shift && key.name.toLowerCase() === "v") {
1664
+ void this.paste();
1665
+ key.preventDefault();
1666
+ return;
1667
+ }
1668
+ if (key.ctrl && key.shift && key.name.toLowerCase() === "b") {
1669
+ this.selectionArmed = true;
1670
+ this.scroll.focus();
1671
+ this.shortcut("ctrl-shift-b", key);
1672
+ return;
1673
+ }
1674
+ if (key.ctrl && key.name === "c") {
1675
+ this.shortcut("SIGINT", key);
1676
+ return;
1677
+ }
1678
+ if (key.ctrl && key.name === "d" && !this.editor.plainText) {
1679
+ this.shortcut("close", key);
1680
+ return;
1681
+ }
1682
+ const control = { o: "ctrl-o", l: "ctrl-l", v: "ctrl-v", "\\": "ctrl-backslash", i: "ctrl-i" };
1683
+ if (key.ctrl && control[key.name]) {
1684
+ this.shortcut(control[key.name], key);
1685
+ return;
1686
+ }
1687
+ if (key.ctrl && key.name === "u" && !this.editor.plainText) {
1688
+ this.shortcut("ctrl-u", key);
1689
+ return;
1690
+ }
1691
+ if (key.name === "escape") {
1692
+ if (this.suggestions.visible) this.suggestions.visible = false;
1693
+ this.shortcut("escape", key);
1694
+ return;
1695
+ }
1696
+ if (key.name === "tab") {
1697
+ if (key.ctrl) this.shortcut("ctrl-tab", key);
1698
+ else if (this.suggestions.visible) {
1699
+ this.acceptSuggestion();
1700
+ key.preventDefault();
1701
+ } else this.shortcut("tab", key);
1702
+ return;
1703
+ }
1704
+ if (key.ctrl && (key.name === "left" || key.name === "right")) {
1705
+ this.shortcut(`ctrl-${key.name}`, key);
1706
+ return;
1707
+ }
1708
+ if (!this.editor.plainText && (key.name === "left" || key.name === "right")) {
1709
+ this.shortcut(`${key.name}-empty`, key);
1710
+ return;
1711
+ }
1712
+ if (key.name === "pageup" || key.name === "pagedown") {
1713
+ this.scroll.scrollBy(key.name === "pageup" ? -1 : 1, "viewport");
1714
+ key.preventDefault();
1715
+ return;
1716
+ }
1717
+ if ((key.name === "up" || key.name === "down") && key.shift) {
1718
+ this.scroll.scrollBy(key.name === "up" ? -3 : 3, "absolute");
1719
+ key.preventDefault();
1720
+ return;
1721
+ }
1722
+ if (key.name === "end" && !this.editor.plainText) {
1723
+ this.scrollToLive();
1724
+ key.preventDefault();
1725
+ return;
1726
+ }
1727
+ if (key.name === "up" || key.name === "down") {
1728
+ if (this.suggestions.visible) {
1729
+ if (key.name === "up") this.suggestions.moveUp();
1730
+ else this.suggestions.moveDown();
1731
+ key.preventDefault();
1732
+ return;
1733
+ }
1734
+ if (this.renderer.currentFocusedRenderable === this.scroll || this.offset > 0 || this.state?.activity?.processing && !this.editor.plainText) {
1735
+ this.scroll.scrollBy(key.name === "up" ? -1 : 1, "absolute");
1736
+ key.preventDefault();
1737
+ return;
1738
+ }
1739
+ const cursor = this.editor.visualCursor;
1740
+ const absoluteVisualRow = cursor.visualRow + this.editor.scrollY;
1741
+ if (key.name === "up" && absoluteVisualRow === 0 || key.name === "down" && absoluteVisualRow >= this.editor.editorView.getTotalVirtualLineCount() - 1) {
1742
+ this.navigateHistory(key.name === "up" ? -1 : 1);
1743
+ key.preventDefault();
1744
+ return;
1745
+ }
1746
+ }
1747
+ if (this.paused || this.state?.input.enabled === false) {
1748
+ if (this.renderer.currentFocusedRenderable === this.editor) key.preventDefault();
1749
+ return;
1750
+ }
1751
+ };
1752
+ onPaste = (event) => {
1753
+ if (this.modals.active) {
1754
+ this.modals.active.handlePaste(event);
1755
+ return;
1756
+ }
1757
+ if (this.paused || this.state?.input.enabled === false) event.preventDefault();
1758
+ };
1759
+ scrollToLive() {
1760
+ this.scroll.scrollTo(this.scroll.scrollHeight);
1761
+ this.newEntries = 0;
1762
+ this.editor.focus();
1763
+ }
1764
+ restoreFocus() {
1765
+ if (this.modals.active) this.modals.active.focus();
1766
+ else if (!this.paused && this.state?.input.enabled !== false) this.editor.focus();
1767
+ }
1768
+ async copy(text) {
1769
+ const value = text ?? this.renderer.getSelection()?.getSelectedText() ?? this.editor.getSelectedText();
1770
+ if (value) await this.clipboard.writeText(value, { destination: "best-available" });
1771
+ }
1772
+ async paste() {
1773
+ const result = await this.clipboard.read({ preferredTypes: ["text/plain;charset=utf-8", "text/plain"] });
1774
+ if (result.status === "read") this.editor.insertText(new TextDecoder().decode(result.representation.bytes));
1775
+ }
1776
+ command(command2) {
1777
+ switch (command2.type) {
1778
+ case "resize":
1779
+ this.renderer.resize(command2.columns, command2.rows);
1780
+ break;
1781
+ case "scroll":
1782
+ if (command2.position === "bottom") this.scrollToLive();
1783
+ else if (command2.position === "top") this.scroll.scrollTo(0);
1784
+ else this.scroll.scrollBy(command2.delta ?? 0, "absolute");
1785
+ break;
1786
+ case "focus": {
1787
+ 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);
1788
+ target?.focus();
1789
+ if (target?.id.startsWith("header:")) this.headerActions.scrollChildIntoView(target.id);
1790
+ break;
1791
+ }
1792
+ case "input":
1793
+ if (command2.expectedText !== void 0 && command2.expectedText !== this.editor.plainText || command2.expectedCursor !== void 0 && command2.expectedCursor !== this.editor.cursorOffset) {
1794
+ this.flushLatestInput();
1795
+ } else this.setInput(command2.text, command2.cursor);
1796
+ break;
1797
+ case "input-action":
1798
+ this.inputAction(command2.action, command2.value);
1799
+ break;
1800
+ case "mouse":
1801
+ this.renderer.useMouse = command2.enabled;
1802
+ break;
1803
+ case "copy":
1804
+ void this.copy(command2.text);
1805
+ break;
1806
+ case "title":
1807
+ this.renderer.setTerminalTitle(command2.text);
1808
+ break;
1809
+ case "modal-open":
1810
+ this.modals.open(command2.modal);
1811
+ break;
1812
+ case "modal-update":
1813
+ this.modals.update(command2.id, command2.patch);
1814
+ break;
1815
+ case "modal-close":
1816
+ this.modals.close(command2.id, command2.result);
1817
+ break;
1818
+ case "embedded-write":
1819
+ this.context.terminals.get(command2.id)?.write(Buffer.from(command2.data, "base64"));
1820
+ break;
1821
+ case "embedded-open":
1822
+ this.openEmbedded(command2.id, command2.title);
1823
+ break;
1824
+ case "embedded-close":
1825
+ this.closeEmbedded(command2.id);
1826
+ break;
1827
+ }
1828
+ this.renderer.requestRender();
1829
+ }
1830
+ inputAction(action, value) {
1831
+ if (action === "pause") {
1832
+ this.paused = true;
1833
+ this.editor.focusable = false;
1834
+ } else if (action === "resume") {
1835
+ this.paused = false;
1836
+ this.editor.focusable = this.state?.input.enabled !== false;
1837
+ this.restoreFocus();
1838
+ } else if (action === "history-up") this.navigateHistory(-1);
1839
+ else if (action === "history-down") this.navigateHistory(1);
1840
+ else if (action === "history-set") {
1841
+ try {
1842
+ const entries = JSON.parse(value ?? "[]");
1843
+ if (Array.isArray(entries)) this.history = entries.filter((entry) => typeof entry === "string").slice(0, 1e3);
1844
+ } catch {
1845
+ }
1846
+ } else if (action === "insert") this.editor.insertText(value ?? "");
1847
+ else if (action === "clear") this.editor.clear();
1848
+ else if (action === "submit") this.submit();
1849
+ else if (action === "complete") this.acceptSuggestion();
1850
+ else if (action === "focus") this.restoreFocus();
1851
+ else if (action === "select-block") {
1852
+ this.selectionArmed = true;
1853
+ this.scroll.focus();
1854
+ } else if (action === "close") this.paused = true;
1855
+ }
1856
+ openEmbedded(id, title) {
1857
+ if (this.embeddedId && this.embeddedId !== id) this.closeEmbedded(this.embeddedId);
1858
+ this.embeddedId = id;
1859
+ this.embeddedPane.title = title;
1860
+ this.embeddedPane.visible = true;
1861
+ let terminal = this.context.terminals.get(id);
1862
+ if (!terminal) {
1863
+ terminal = new EmbeddedTerminalRenderable2(this.renderer, {
1864
+ id: `embedded:${id}`,
1865
+ width: "100%",
1866
+ height: "100%",
1867
+ flexGrow: 1,
1868
+ maxScrollback: 1e4,
1869
+ transparentBackground: true,
1870
+ selectable: true,
1871
+ onData: (data) => this.emit({ type: "embedded-data", id, data: Buffer.from(data).toString("base64") }),
1872
+ onTerminalResize: (columns, rows) => this.emit({ type: "embedded-resize", id, columns, rows })
1873
+ });
1874
+ this.context.terminals.set(id, terminal);
1875
+ this.embeddedPane.add(terminal);
1876
+ }
1877
+ terminal.focus();
1878
+ }
1879
+ closeEmbedded(id) {
1880
+ this.context.terminals.get(id)?.destroy();
1881
+ this.context.terminals.delete(id);
1882
+ if (this.embeddedId === id) {
1883
+ this.embeddedId = void 0;
1884
+ this.embeddedPane.visible = false;
1885
+ this.restoreFocus();
1886
+ }
1887
+ }
1888
+ async dispose() {
1889
+ if (this.destroyed) return;
1890
+ this.destroyed = true;
1891
+ if (this.animation) clearInterval(this.animation);
1892
+ this.renderer.keyInput.off("keypress", this.onKey);
1893
+ this.renderer.keyInput.off("paste", this.onPaste);
1894
+ this.renderer.off(CliRenderEvents.FRAME, this.onFrame);
1895
+ this.renderer.off(CliRenderEvents.RESIZE, this.onResize);
1896
+ this.modals.destroy();
1897
+ this.content.destroy();
1898
+ this.tasks.destroy();
1899
+ this.root.destroyRecursively();
1900
+ this.context.syntax.destroy();
1901
+ await this.clipboard.dispose();
1902
+ }
1903
+ };
1904
+
1905
+ // packages/cli/src/tui/native/worker.ts
1906
+ var replies = createWriteStream("", { fd: 3, autoClose: false });
1907
+ var reply = (message) => replies.write(JSON.stringify(message) + "\n");
1908
+ var terminalInput = new PassThrough();
1909
+ var renderer;
1910
+ var frontend;
1911
+ var stopping = false;
1912
+ var failed = false;
1913
+ async function close() {
1914
+ if (stopping) return;
1915
+ stopping = true;
1916
+ await frontend?.dispose();
1917
+ if (renderer) {
1918
+ renderer.destroy();
1919
+ await renderer.closed;
1920
+ }
1921
+ }
1922
+ async function fail(error) {
1923
+ if (failed || stopping) return;
1924
+ failed = true;
1925
+ reply({ type: "error", message: error instanceof Error ? error.message : String(error) });
1926
+ await close().catch(() => {
1927
+ });
1928
+ replies.end(() => process.exit(1));
1929
+ }
1930
+ async function command(message) {
1931
+ if (message.type === "command") {
1932
+ frontend.command(message.command);
1933
+ return;
1934
+ }
1935
+ if (message.type === "state") {
1936
+ frontend.update(message.snapshot, message.content);
1937
+ renderer.requestRender();
1938
+ await renderer.idle();
1939
+ } else if (message.type === "suspend") await renderer.suspend();
1940
+ else if (message.type === "resume") {
1941
+ await renderer.resume();
1942
+ renderer.requestRender();
1943
+ await renderer.idle();
1944
+ } else if (message.type === "stop") await close();
1945
+ reply({ type: "ack", id: message.id });
1946
+ if (message.type === "stop") replies.end(() => process.exit(0));
1947
+ }
1948
+ try {
1949
+ renderer = await createCliRenderer({
1950
+ nativeSession: new NativeSession(process.stdout),
1951
+ stdin: terminalInput,
1952
+ stdout: process.stdout,
1953
+ exitOnCtrlC: false,
1954
+ exitSignals: [],
1955
+ useMouse: true,
1956
+ enableMouseMovement: true,
1957
+ useKittyKeyboard: { disambiguate: true, alternateKeys: true, events: true },
1958
+ screenMode: "alternate-screen",
1959
+ consoleMode: "disabled",
1960
+ externalOutputMode: "passthrough",
1961
+ backgroundColor: RGBA2.defaultBackground(),
1962
+ openConsoleOnError: false,
1963
+ targetFps: 60,
1964
+ maxFps: 60,
1965
+ clearOnShutdown: false
1966
+ });
1967
+ renderer.on(CliRenderEvents2.RENDER_ERROR, (event) => {
1968
+ void fail(event.error);
1969
+ });
1970
+ renderer.on(CliRenderEvents2.HANDLER_ERROR, (event) => {
1971
+ void fail(event.error);
1972
+ });
1973
+ renderer.on("error", (event) => {
1974
+ void fail(event);
1975
+ });
1976
+ frontend = new NativeFrontend(renderer, (event) => reply({ type: "event", event }));
1977
+ const controls = createInterface({ input: process.stdin, crlfDelay: Infinity });
1978
+ let pending = Promise.resolve();
1979
+ controls.on("line", (line) => {
1980
+ let message;
1981
+ try {
1982
+ message = JSON.parse(line);
1983
+ } catch (error) {
1984
+ void fail(error);
1985
+ return;
1986
+ }
1987
+ if (message.type === "input-bytes") {
1988
+ terminalInput.write(Buffer.from(message.data, "base64"));
1989
+ } else if (message.type === "command") {
1990
+ try {
1991
+ frontend.command(message.command);
1992
+ } catch (error) {
1993
+ void fail(error);
1994
+ }
1995
+ } else pending = pending.then(() => command(message)).catch(fail);
1996
+ });
1997
+ controls.on("close", () => {
1998
+ void pending.then(close).finally(() => process.exit(0));
1999
+ });
2000
+ process.on("SIGTERM", () => {
2001
+ void close().finally(() => process.exit(0));
2002
+ });
2003
+ process.on("SIGINT", () => {
2004
+ });
2005
+ renderer.requestRender();
2006
+ await renderer.idle();
2007
+ reply({ type: "ready" });
2008
+ } catch (error) {
2009
+ await fail(error);
2010
+ }