omnius 1.0.862 → 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 (30) hide show
  1. package/dist/index.js +2 -2
  2. package/dist/tui/native/worker.js +1327 -237
  3. package/docs/DISCOVERY.json +797 -6
  4. package/docs/DISCOVERY.md +25 -2
  5. package/docs/work-orders/evidence/instrument-100x30.png +0 -0
  6. package/docs/work-orders/evidence/instrument-120x36.png +0 -0
  7. package/docs/work-orders/evidence/instrument-160x48.png +0 -0
  8. package/docs/work-orders/evidence/instrument-60x20.png +0 -0
  9. package/docs/work-orders/evidence/instrument-80x24.png +0 -0
  10. package/docs/work-orders/evidence/instrument-collapse-120x36.png +0 -0
  11. package/docs/work-orders/evidence/instrument-help-scroll-120x36.png +0 -0
  12. package/docs/work-orders/evidence/instrument-package-artifact-20261016.json +18 -0
  13. package/docs/work-orders/evidence/instrument-package-install-20261016.json +69 -0
  14. package/docs/work-orders/evidence/instrument-popup-api-120x36.png +0 -0
  15. package/docs/work-orders/evidence/instrument-popup-api-input-120x36.png +0 -0
  16. package/docs/work-orders/evidence/instrument-popup-config-120x36.png +0 -0
  17. package/docs/work-orders/evidence/instrument-popup-dark-120x36.png +0 -0
  18. package/docs/work-orders/evidence/instrument-popup-dark-60x20.png +0 -0
  19. package/docs/work-orders/evidence/instrument-popup-light-120x36.png +0 -0
  20. package/docs/work-orders/evidence/instrument-popup-model-120x36.png +0 -0
  21. package/docs/work-orders/evidence/instrument-popup-network-120x36.png +0 -0
  22. package/docs/work-orders/evidence/instrument-production-terminal-live.json +6228 -0
  23. package/docs/work-orders/evidence/instrument-semantic-components-20261016.json +76 -0
  24. package/docs/work-orders/evidence/instrument-targeted-verification-20261016.json +408 -0
  25. package/docs/work-orders/evidence/instrument-terminal-light-120x36.png +0 -0
  26. package/docs/work-orders/instrument-command-surface-audit.md +237 -0
  27. package/docs/work-orders/instrument-tui.md +108 -0
  28. package/docs/work-orders/native-tui-renderer.md +8 -6
  29. package/npm-shrinkwrap.json +6 -6
  30. package/package.json +1 -1
@@ -2,18 +2,19 @@
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 CliRenderEvents2, NativeSession, RGBA as RGBA2, createCliRenderer } from "@opentui/core";
5
+ import { CliRenderEvents as CliRenderEvents4, NativeSession, RGBA as RGBA3, createCliRenderer } from "@opentui/core";
6
6
 
7
7
  // packages/cli/src/tui/native/components/frontend.ts
8
8
  import {
9
- BoxRenderable as BoxRenderable3,
10
- CliRenderEvents,
9
+ BoxRenderable as BoxRenderable4,
10
+ CliRenderEvents as CliRenderEvents2,
11
11
  EmbeddedTerminalRenderable as EmbeddedTerminalRenderable2,
12
- ScrollBoxRenderable as ScrollBoxRenderable3,
12
+ ScrollBoxRenderable as ScrollBoxRenderable4,
13
13
  SyntaxStyle as SyntaxStyle2,
14
14
  TabSelectRenderable,
15
15
  TextareaRenderable as TextareaRenderable2,
16
- TextRenderable as TextRenderable3,
16
+ TextRenderable as TextRenderable4,
17
+ TextAttributes as TextAttributes6,
17
18
  createClipboard,
18
19
  createHostClipboard,
19
20
  createRendererClipboardAdapter
@@ -26,22 +27,23 @@ import {
26
27
  EmbeddedTerminalRenderable,
27
28
  ImageRenderable,
28
29
  MarkdownRenderable,
30
+ RenderableEvents,
29
31
  ScrollBoxRenderable,
30
32
  TextRenderable
31
33
  } from "@opentui/core";
32
34
 
33
35
  // packages/cli/src/tui/native/components/text.ts
34
- import { RGBA, StyledText } from "@opentui/core";
36
+ import { CliRenderEvents, RGBA, StyledText, TextAttributes } from "@opentui/core";
35
37
 
36
38
  // packages/cli/src/tui/native/text.ts
37
39
  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, "");
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, "");
39
41
  }
40
42
  function nativeTextFromAnsi(value) {
41
43
  const result = [];
42
44
  let style = { attributes: 0 };
43
45
  let previous = 0;
44
- const pattern = /\x1b(?:\[[0-?]*[ -/]*[@-~]|\][\s\S]*?(?:\x07|\x1b\\)|[P_^X][\s\S]*?\x1b\\|[\s\S])/g;
46
+ const pattern = /\x1b(?:\[[-0-?]*[ -/]*[@-~]|\][\s\S]*?(?:\x07|\x1b\\)|[P_^X][\s\S]*?\x1b\\|[\s\S])/g;
45
47
  const append = (text) => {
46
48
  const clean = stripTextControls(text);
47
49
  if (!clean) return;
@@ -60,7 +62,7 @@ function nativeTextFromAnsi(value) {
60
62
  else delete style.link;
61
63
  continue;
62
64
  }
63
- if (!/^\x1b\[[\d;:]*m$/.test(seq)) continue;
65
+ if (!/^\x1b\[[-\d;:]*m$/.test(seq)) continue;
64
66
  const codes = seq.slice(2, -1).replace(/:/g, ";").split(";").map(Number);
65
67
  for (let i = 0; i < codes.length; i++) {
66
68
  const code = codes[i];
@@ -89,7 +91,10 @@ function nativeTextFromAnsi(value) {
89
91
  else if (code >= 40 && code <= 47) style.bg = `index:${code - 40}`;
90
92
  else if (code >= 100 && code <= 107) style.bg = `index:${code - 100 + 8}`;
91
93
  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))}`;
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)}`;
93
98
  i += 2;
94
99
  } else if ((code === 38 || code === 48) && codes[i + 1] === 2) {
95
100
  const rgb = codes.slice(i + 2, i + 5).map(
@@ -106,20 +111,6 @@ function nativeTextFromAnsi(value) {
106
111
  }
107
112
 
108
113
  // 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
114
  function plainText(value) {
124
115
  return typeof value === "string" ? stripTextControls(value) : (value ?? []).map((span) => stripTextControls(span.text)).join("");
125
116
  }
@@ -130,27 +121,71 @@ function styledText(value) {
130
121
  return new StyledText(textSpans(value).map((span) => ({
131
122
  __isChunk: true,
132
123
  text: span.text,
133
- ...span.fg === void 0 ? {} : { fg: nativeColor(span.fg) },
134
- ...span.bg === void 0 ? {} : { bg: nativeColor(span.bg, true) },
135
124
  ...span.attributes === void 0 ? {} : { attributes: span.attributes },
136
125
  ...span.link ? { link: { url: span.link } } : {}
137
126
  })));
138
127
  }
139
- function palette(theme) {
128
+ function palette(_theme) {
129
+ const background2 = RGBA.defaultBackground();
130
+ const foreground2 = RGBA.defaultForeground();
140
131
  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)
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
150
141
  };
151
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
+ }
152
173
 
153
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
+ }
154
189
  var NativeBlockView = class {
155
190
  constructor(context, spec, inline = false) {
156
191
  this.context = context;
@@ -178,13 +213,13 @@ var NativeBlockView = class {
178
213
  body;
179
214
  children;
180
215
  actions;
181
- progressBar;
182
- progressValue;
216
+ progress;
183
217
  image;
184
218
  terminal;
185
219
  collapsed = false;
186
220
  previousModelCollapsed;
187
221
  signature = "";
222
+ actionsSignature = "";
188
223
  pixel;
189
224
  get block() {
190
225
  return this.spec;
@@ -192,16 +227,18 @@ var NativeBlockView = class {
192
227
  build() {
193
228
  const { renderer: renderer2, colors, syntax } = this.context;
194
229
  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;
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;
199
235
  this.root.flexDirection = spec.kind === "row" ? "row" : "column";
200
236
  if (spec.title) {
201
237
  this.title = new TextRenderable(renderer2, {
202
238
  id: `block-title:${spec.id}`,
203
239
  content: "",
204
- fg: colors.accent,
240
+ fg: colors.foreground,
241
+ attributes: 1,
205
242
  flexShrink: 0,
206
243
  width: "100%",
207
244
  wrapMode: "word",
@@ -210,8 +247,15 @@ var NativeBlockView = class {
210
247
  if (event.button !== 0 || !this.spec.collapsible) return;
211
248
  this.toggle();
212
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();
213
256
  }
214
257
  });
258
+ nativeControlFocus(this.title, 1);
215
259
  this.root.add(this.title);
216
260
  }
217
261
  this.body = new BoxRenderable(renderer2, {
@@ -303,26 +347,24 @@ var NativeBlockView = class {
303
347
  this.body.add(this.text);
304
348
  }
305
349
  if (spec.kind === "progress") {
306
- this.progressBar = new BoxRenderable(renderer2, {
350
+ this.progress = new TextRenderable(renderer2, {
307
351
  id: `progress-track:${spec.id}`,
308
352
  width: "100%",
309
353
  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
354
+ content: "",
355
+ fg: colors.foreground,
356
+ bg: colors.background,
357
+ attributes: 2,
358
+ wrapMode: "none",
359
+ selectable: false
317
360
  });
318
- this.progressBar.add(this.progressValue);
319
- this.body.add(this.progressBar);
361
+ this.body.add(this.progress);
320
362
  }
321
363
  this.children = new NativeBlockList(this.context, this.body);
322
364
  this.actions = new BoxRenderable(renderer2, {
323
365
  id: `block-actions:${spec.id}`,
324
366
  flexDirection: "row",
325
- gap: 2,
367
+ gap: 1,
326
368
  flexShrink: 0
327
369
  });
328
370
  this.root.add(this.actions);
@@ -331,8 +373,9 @@ var NativeBlockView = class {
331
373
  this.children?.destroy();
332
374
  if (this.terminal) this.context.terminals.delete(this.spec.id);
333
375
  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;
376
+ this.title = this.text = this.body = this.actions = this.progress = this.image = this.terminal = void 0;
335
377
  this.children = void 0;
378
+ this.actionsSignature = "";
336
379
  this.build();
337
380
  }
338
381
  update(spec) {
@@ -346,7 +389,10 @@ var NativeBlockView = class {
346
389
  this.collapsed = spec.collapsed ?? false;
347
390
  this.previousModelCollapsed = spec.collapsed;
348
391
  }
349
- if (this.title) this.title.content = styledText(`${spec.collapsible ? this.collapsed ? "▸ " : "▾ " : ""}${spec.title ?? ""}`);
392
+ if (this.title) {
393
+ this.title.content = `${spec.collapsible ? this.collapsed ? "▸ " : "· " : ""}${plainText(spec.title)}`;
394
+ this.title.focusable = Boolean(spec.collapsible);
395
+ }
350
396
  if (this.body) this.body.visible = !this.collapsed;
351
397
  if (this.text instanceof TextRenderable) this.text.content = styledText(spec.kind === "image" ? spec.text ?? "Image unavailable" : spec.text);
352
398
  else if (this.text instanceof MarkdownRenderable || this.text instanceof CodeRenderable) {
@@ -357,16 +403,19 @@ var NativeBlockView = class {
357
403
  if (this.image && spec.imagePath && this.image.source !== spec.imagePath) this.image.source = spec.imagePath;
358
404
  if (this.terminal) this.terminal.height = spec.height ?? 14;
359
405
  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)}%`;
406
+ if (this.progress) this.progress.content = nativeProgressText(spec.progress, spec.streaming);
361
407
  this.children?.replace(spec.children ?? []);
362
- if (this.actions) {
408
+ const actionsSignature = JSON.stringify(spec.actions ?? []);
409
+ if (this.actions && actionsSignature !== this.actionsSignature) {
410
+ this.actionsSignature = actionsSignature;
363
411
  for (const child of [...this.actions.getChildren()]) child.destroy();
364
412
  for (const action of spec.actions ?? []) {
365
413
  const button = new TextRenderable(this.context.renderer, {
366
414
  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,
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,
370
419
  flexShrink: 0,
371
420
  selectable: false,
372
421
  onMouseDown: (event) => {
@@ -375,12 +424,13 @@ var NativeBlockView = class {
375
424
  event.stopPropagation();
376
425
  },
377
426
  onKeyDown: (key) => {
378
- if (key.name !== "return" || action.disabled) return;
427
+ if (key.eventType === "release" || key.name !== "return" && key.sequence !== " " || action.disabled) return;
379
428
  this.context.emit({ type: "action", action: action.command ?? action.id, id: this.spec.id });
380
429
  key.preventDefault();
381
430
  }
382
431
  });
383
432
  button.focusable = !action.disabled;
433
+ nativeControlFocus(button, action.disabled ? 2 : action.active ? 1 : 0);
384
434
  if (action.id.startsWith("pixel:") && !action.disabled) {
385
435
  button.onMouseOver = () => this.context.emit({ type: "action", action: `hover:${action.id}`, id: this.spec.id });
386
436
  button.onMouseOut = () => this.context.emit({ type: "action", action: `hover-end:${action.id}`, id: this.spec.id });
@@ -394,25 +444,31 @@ var NativeBlockView = class {
394
444
  toggle() {
395
445
  this.collapsed = !this.collapsed;
396
446
  if (this.body) this.body.visible = !this.collapsed;
397
- if (this.title) this.title.content = styledText(`${this.collapsed ? "▸ " : "▾ "}${this.spec.title ?? ""}`);
447
+ if (this.title) this.title.content = `${this.collapsed ? "▸ " : "· "}${plainText(this.spec.title)}`;
398
448
  this.context.emit({ type: "action", action: "toggle-block", id: this.spec.id });
399
449
  }
400
450
  applyTheme() {
401
451
  const colors = this.context.colors;
402
452
  this.root.backgroundColor = colors.background;
403
- if (this.spec.kind === "card") this.root.borderColor = this.spec.color ? nativeColor(this.spec.color) : colors.border;
453
+ if (this.spec.kind === "card") this.root.borderColor = colors.border;
404
454
  else this.root.border = false;
405
455
  if (this.title) {
406
- this.title.fg = this.spec.color ? nativeColor(this.spec.color) : colors.accent;
456
+ this.title.fg = colors.foreground;
407
457
  this.title.bg = colors.background;
408
458
  }
409
459
  if (this.body) this.body.backgroundColor = colors.background;
410
460
  if (this.text) {
411
- this.text.fg = this.spec.color ? nativeColor(this.spec.color) : colors.foreground;
461
+ this.text.fg = colors.foreground;
412
462
  this.text.bg = colors.background;
413
463
  }
414
- if (this.progressBar) this.progressBar.backgroundColor = colors.dim;
415
- if (this.progressValue) this.progressValue.backgroundColor = colors.accent;
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
+ }
416
472
  this.children?.applyTheme();
417
473
  }
418
474
  destroy() {
@@ -480,15 +536,100 @@ import {
480
536
  InputRenderable,
481
537
  ScrollBoxRenderable as ScrollBoxRenderable2,
482
538
  TextareaRenderable,
539
+ TextAttributes as TextAttributes4,
483
540
  TextRenderable as TextRenderable2
484
541
  } from "@opentui/core";
485
542
 
486
543
  // packages/cli/src/tui/native/components/select.ts
487
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
488
605
  var NativeSelectRenderable = class extends SelectRenderable {
489
606
  onActivate;
607
+ draggingRail = false;
490
608
  constructor(renderer2, options) {
491
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();
492
633
  }
493
634
  disabled(index) {
494
635
  const option = this.options[index];
@@ -517,6 +658,11 @@ var NativeSelectRenderable = class extends SelectRenderable {
517
658
  if (!this.disabled(this.getSelectedIndex())) super.selectCurrent();
518
659
  }
519
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
+ }
520
666
  if (event.type === "scroll") {
521
667
  if (event.scroll?.direction === "up") this.moveUp(1);
522
668
  else if (event.scroll?.direction === "down") this.moveDown(1);
@@ -524,6 +670,11 @@ var NativeSelectRenderable = class extends SelectRenderable {
524
670
  return;
525
671
  }
526
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
+ }
527
678
  const lines = this.showDescription ? 2 : 1;
528
679
  const count = Math.max(1, Math.floor(this.height / lines));
529
680
  const start = Math.max(0, Math.min(this.getSelectedIndex() - Math.floor(count / 2), this.options.length - count));
@@ -536,6 +687,89 @@ var NativeSelectRenderable = class extends SelectRenderable {
536
687
  }
537
688
  };
538
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
+
539
773
  // packages/cli/src/tui/native/components/modals.ts
540
774
  var NativeModalView = class {
541
775
  constructor(context, spec, close2) {
@@ -554,7 +788,8 @@ var NativeModalView = class {
554
788
  justifyContent: "center",
555
789
  alignItems: "center",
556
790
  zIndex: 100,
557
- backgroundColor: colors.background
791
+ backgroundColor: "transparent",
792
+ shouldFill: false
558
793
  });
559
794
  this.panel = new BoxRenderable2(renderer2, {
560
795
  id: `modal-panel:${spec.id}`,
@@ -562,13 +797,15 @@ var NativeModalView = class {
562
797
  width: "94%",
563
798
  maxHeight: "94%",
564
799
  border: true,
565
- borderStyle: "rounded",
800
+ borderStyle: "single",
566
801
  borderColor: colors.border,
567
802
  backgroundColor: colors.background,
568
803
  paddingX: 1,
569
- paddingY: 1,
804
+ paddingY: 0,
805
+ focusable: true,
570
806
  onMouseDown: () => this.focus()
571
807
  });
808
+ this.frame = new NativeCornerFrame(this.panel, colors, renderer2);
572
809
  this.root.add(this.panel);
573
810
  this.breadcrumb = this.addText(`modal-breadcrumb:${spec.id}`);
574
811
  this.heading = this.addText(`modal-title:${spec.id}`);
@@ -585,20 +822,29 @@ var NativeModalView = class {
585
822
  viewportCulling: true,
586
823
  contentOptions: { flexDirection: "column" }
587
824
  });
825
+ styleNativeScrollbars(this.document, colors);
588
826
  this.blocks = new NativeBlockList(this.modalContext(), this.document);
589
827
  this.panel.add(this.document);
590
828
  } 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);
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);
595
841
  const progressBlocks = new BoxRenderable2(renderer2, { id: `modal-progress-blocks:${spec.id}`, flexDirection: "column", width: "100%" });
596
842
  this.panel.add(progressBlocks);
597
843
  this.blocks = new NativeBlockList(this.modalContext(), progressBlocks);
598
844
  }
599
845
  this.error = this.addText(`modal-error:${spec.id}`);
600
846
  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 });
847
+ this.actions = new BoxRenderable2(renderer2, { id: `modal-actions:${spec.id}`, flexDirection: "row", gap: 1, width: "100%", marginTop: 0 });
602
848
  this.panel.add(this.actions);
603
849
  this.update(spec);
604
850
  }
@@ -606,6 +852,7 @@ var NativeModalView = class {
606
852
  close;
607
853
  root;
608
854
  panel;
855
+ frame;
609
856
  spec;
610
857
  heading;
611
858
  description;
@@ -619,11 +866,11 @@ var NativeModalView = class {
619
866
  select;
620
867
  document;
621
868
  blocks;
622
- progressTrack;
623
- progressValue;
869
+ progress;
624
870
  filtered = [];
625
871
  filter = "";
626
872
  changing = false;
873
+ actionsSignature = "";
627
874
  addText(id) {
628
875
  const text = new TextRenderable2(this.context.renderer, { id, content: "", width: "100%", wrapMode: "word", flexShrink: 0, fg: this.context.colors.foreground, selectable: true });
629
876
  this.panel.add(text);
@@ -636,6 +883,7 @@ var NativeModalView = class {
636
883
  width: "100%",
637
884
  value: this.spec.filter ?? "",
638
885
  placeholder: "Filter…",
886
+ placeholderColor: colors.foreground,
639
887
  textColor: colors.foreground,
640
888
  backgroundColor: colors.background,
641
889
  focusedTextColor: colors.foreground,
@@ -682,8 +930,8 @@ var NativeModalView = class {
682
930
  focusedTextColor: colors.foreground,
683
931
  focusedBackgroundColor: colors.background,
684
932
  placeholder: this.spec.placeholder ?? "",
685
- selectionBg: colors.selection,
686
- cursorColor: colors.accent,
933
+ placeholderColor: colors.foreground,
934
+ cursorColor: colors.foreground,
687
935
  onContentChange: () => {
688
936
  if (!this.editor || this.changing) return;
689
937
  this.refreshMask();
@@ -713,14 +961,14 @@ var NativeModalView = class {
713
961
  const spec = this.spec;
714
962
  this.changing = true;
715
963
  try {
716
- this.heading.content = styledText(spec.title);
964
+ this.heading.content = plainText(spec.title);
717
965
  this.description.content = styledText(spec.description);
718
966
  this.description.visible = plainText(spec.description).length > 0;
719
967
  this.breadcrumb.content = (spec.breadcrumbs ?? []).join(" › ");
720
968
  this.breadcrumb.visible = (spec.breadcrumbs?.length ?? 0) > 0;
721
969
  this.error.content = spec.validationError ?? "";
722
970
  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");
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"}`;
724
972
  if (this.filterInput && patch.filter !== void 0 && patch.filter !== old.filter) {
725
973
  this.filter = patch.filter;
726
974
  this.filterInput.setText(patch.filter);
@@ -729,29 +977,36 @@ var NativeModalView = class {
729
977
  this.refreshMask();
730
978
  this.refreshOptions(patch.selectedIndex);
731
979
  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);
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;
753
1009
  }
754
- this.actions.visible = (spec.actions?.length ?? 0) > 0;
755
1010
  } finally {
756
1011
  this.changing = false;
757
1012
  }
@@ -816,20 +1071,42 @@ var NativeModalView = class {
816
1071
  else if (this.document) this.document.focus();
817
1072
  else this.panel.focus();
818
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
+ }
819
1092
  handleKey(key) {
820
1093
  const consume = () => {
821
1094
  key.preventDefault();
822
1095
  key.stopPropagation();
823
1096
  return true;
824
1097
  };
1098
+ if (key.eventType === "release") return false;
825
1099
  if (key.name === "escape" || key.ctrl && key.name === "c") {
826
1100
  this.cancel();
827
1101
  return consume();
828
1102
  }
829
1103
  if (key.name === "tab") {
830
- this.emitAction(key.shift ? "shift-tab" : "tab");
1104
+ if (this.spec.deferSelection || this.spec.captureKeys) this.emitAction(key.shift ? "shift-tab" : "tab");
1105
+ else this.nextControl(key.shift ? -1 : 1);
831
1106
  return consume();
832
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;
833
1110
  if (this.spec.kind === "select") {
834
1111
  if (key.name === "up" || key.name === "down" || key.name === "pageup" || key.name === "pagedown") {
835
1112
  const amount = key.name.startsWith("page") ? Math.max(1, Math.floor((this.select?.height ?? 5) / (this.select?.showDescription ? 2 : 1))) : 1;
@@ -910,14 +1187,23 @@ var NativeModalView = class {
910
1187
  }
911
1188
  applyTheme() {
912
1189
  const colors = this.context.colors;
913
- this.root.backgroundColor = colors.background;
914
1190
  this.panel.backgroundColor = colors.background;
915
1191
  this.panel.borderColor = colors.border;
916
- this.heading.fg = colors.accent;
1192
+ this.frame.setColors(colors);
1193
+ this.heading.fg = colors.foreground;
1194
+ this.heading.attributes = 1;
917
1195
  this.description.fg = colors.foreground;
918
1196
  this.breadcrumb.fg = colors.dim;
919
- this.error.fg = colors.error;
1197
+ this.breadcrumb.attributes = 2;
1198
+ this.error.fg = colors.foreground;
1199
+ this.error.attributes = 1;
920
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);
921
1207
  if (this.select) {
922
1208
  this.select.backgroundColor = this.select.focusedBackgroundColor = colors.background;
923
1209
  this.select.textColor = this.select.focusedTextColor = colors.foreground;
@@ -929,13 +1215,20 @@ var NativeModalView = class {
929
1215
  for (const editor of [this.editor, this.filterInput]) if (editor) {
930
1216
  editor.backgroundColor = editor.focusedBackgroundColor = colors.background;
931
1217
  editor.textColor = editor.focusedTextColor = colors.foreground;
932
- editor.selectionBg = colors.selection;
933
- editor.cursorColor = colors.accent;
1218
+ editor.selectionBg = void 0;
1219
+ editor.selectionFg = void 0;
1220
+ editor.cursorColor = colors.foreground;
1221
+ editor.placeholderColor = colors.foreground;
934
1222
  }
935
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
+ }
936
1228
  this.blocks?.applyTheme();
937
1229
  }
938
1230
  destroy() {
1231
+ this.frame.destroy();
939
1232
  this.blocks?.destroy();
940
1233
  if (this.spec.secret) this.editor?.destroy();
941
1234
  this.root.destroyRecursively();
@@ -945,11 +1238,23 @@ var NativeModalStack = class {
945
1238
  constructor(context, restoreFocus) {
946
1239
  this.context = context;
947
1240
  this.restoreFocus = restoreFocus;
1241
+ context.renderer.addPostProcessFn(this.dimUnderlay);
948
1242
  }
949
1243
  context;
950
1244
  restoreFocus;
951
1245
  views = [];
952
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
+ };
953
1258
  get active() {
954
1259
  return this.views.at(-1);
955
1260
  }
@@ -989,23 +1294,583 @@ var NativeModalStack = class {
989
1294
  for (const view of this.views) view.applyTheme();
990
1295
  }
991
1296
  destroy() {
1297
+ this.context.renderer.removePostProcessFn(this.dimUnderlay);
992
1298
  for (const view of this.views) view.destroy();
993
1299
  this.views = [];
994
1300
  this.byId.clear();
995
1301
  }
996
1302
  };
997
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%",
1459
+ height: 1,
1460
+ flexShrink: 0,
1461
+ fg: foreground,
1462
+ bg: background,
1463
+ attributes: TextAttributes5.BOLD,
1464
+ selectable: false,
1465
+ wrapMode: "none",
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
+
998
1863
  // 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 = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
1864
+ var initialTheme = { mode: "system", bg: -1, accent: -1, textPrimary: -1, textDim: -1, boxColor: -1 };
1001
1865
  var NativeFrontend = class {
1002
1866
  constructor(renderer2, emit) {
1003
1867
  this.renderer = renderer2;
1004
1868
  this.emit = emit;
1005
1869
  const colors = palette(initialTheme);
1006
1870
  this.context = { renderer: renderer2, colors, syntax: SyntaxStyle2.fromStyles({}, renderer2.nativeScene), emit, terminals: /* @__PURE__ */ new Map() };
1871
+ this.disposeSelectionContrast = installDefaultSelectionContrast(renderer2);
1007
1872
  this.clipboard = createClipboard({ host: createHostClipboard({ timeoutMs: 1500 }), terminal: createRendererClipboardAdapter(renderer2) });
1008
- this.root = new BoxRenderable3(renderer2, {
1873
+ this.root = new BoxRenderable4(renderer2, {
1009
1874
  id: "omnius-native",
1010
1875
  width: "100%",
1011
1876
  height: "100%",
@@ -1013,56 +1878,101 @@ var NativeFrontend = class {
1013
1878
  backgroundColor: colors.background
1014
1879
  });
1015
1880
  renderer2.root.add(this.root);
1016
- this.header = new BoxRenderable3(renderer2, {
1881
+ this.header = new BoxRenderable4(renderer2, {
1017
1882
  id: "omnius-header",
1018
1883
  width: "100%",
1884
+ minWidth: 0,
1019
1885
  flexDirection: "column",
1020
1886
  flexShrink: 0,
1021
- border: true,
1022
- borderStyle: "rounded",
1023
- borderColor: colors.border,
1024
- paddingX: 1
1887
+ border: ["bottom"],
1888
+ borderStyle: "single",
1889
+ borderColor: colors.border
1025
1890
  });
1026
1891
  this.root.add(this.header);
1027
- const headerRow = new BoxRenderable3(renderer2, { id: "omnius-header-row", width: "100%", height: 1, flexDirection: "row", gap: 2 });
1892
+ const headerRow = new BoxRenderable4(renderer2, { id: "omnius-header-row", width: "100%", minWidth: 0, minHeight: 1, flexDirection: "row", gap: 1 });
1028
1893
  this.header.add(headerRow);
1029
- this.identity = new TextRenderable3(renderer2, {
1894
+ this.identity = new TextRenderable4(renderer2, {
1030
1895
  id: "omnius-identity",
1031
1896
  content: "Omnius",
1032
1897
  fg: colors.foreground,
1033
- flexShrink: 1,
1034
- flexGrow: 1,
1898
+ flexShrink: 0,
1899
+ flexGrow: 0,
1035
1900
  wrapMode: "none",
1036
1901
  truncate: true,
1037
- minWidth: 8,
1038
- selectable: false
1902
+ minWidth: 18,
1903
+ selectable: false,
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
+ }
1039
1917
  });
1040
1918
  headerRow.add(this.identity);
1041
- this.headerActions = new ScrollBoxRenderable3(renderer2, {
1919
+ this.headerActions = new ScrollBoxRenderable4(renderer2, {
1042
1920
  id: "omnius-header-actions",
1043
- width: "50%",
1044
1921
  flexGrow: 0,
1045
- flexShrink: 0,
1922
+ flexShrink: 1,
1046
1923
  minWidth: 0,
1047
- height: 1,
1924
+ minHeight: 1,
1048
1925
  scrollX: true,
1049
1926
  scrollY: false,
1050
- contentOptions: { flexDirection: "row", gap: 2 },
1051
- verticalScrollbarOptions: { visible: false },
1052
- horizontalScrollbarOptions: { visible: 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 }
1053
1930
  });
1054
1931
  headerRow.add(this.headerActions);
1055
- this.headerPanel = new TextRenderable3(renderer2, { id: "omnius-header-panel", content: "", width: "100%", wrapMode: "word", visible: false });
1932
+ this.headerPanel = new TextRenderable4(renderer2, { id: "omnius-header-panel", content: "", width: "100%", wrapMode: "word", visible: false });
1056
1933
  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 });
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 });
1058
1968
  this.root.add(this.routeWarning);
1059
1969
  this.tabs = new TabSelectRenderable(renderer2, {
1060
1970
  id: "omnius-agent-tabs",
1061
1971
  width: "100%",
1062
- height: 1,
1972
+ height: 2,
1063
1973
  showDescription: false,
1064
- showUnderline: false,
1065
- showScrollArrows: true,
1974
+ showUnderline: true,
1975
+ showScrollArrows: false,
1066
1976
  tabWidth: 18,
1067
1977
  wrapSelection: true,
1068
1978
  visible: false,
@@ -1082,16 +1992,26 @@ var NativeFrontend = class {
1082
1992
  this.tabs.on("selectionChanged", () => {
1083
1993
  if (this.changing) return;
1084
1994
  const selected = this.tabs.getSelectedOption();
1085
- if (selected) this.emit({ type: "action", action: "view", id: String(selected.value) });
1995
+ if (selected) this.emitAction("view", String(selected.value));
1086
1996
  this.editor.focus();
1087
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
+ };
1088
2008
  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 });
2009
+ this.taskPane = new BoxRenderable4(renderer2, { id: "omnius-tasks", width: "100%", flexDirection: "column", maxHeight: 6, flexShrink: 0, overflow: "hidden", visible: false });
1090
2010
  this.tasks = new NativeBlockList(this.context, this.taskPane);
1091
2011
  this.root.add(this.taskPane);
1092
- this.body = new BoxRenderable3(renderer2, { id: "omnius-body", width: "100%", flexDirection: "row", flexGrow: 1, minHeight: 1 });
2012
+ this.body = new BoxRenderable4(renderer2, { id: "omnius-body", width: "100%", flexDirection: "row", flexGrow: 1, minHeight: 1 });
1093
2013
  this.root.add(this.body);
1094
- this.scroll = new ScrollBoxRenderable3(renderer2, {
2014
+ this.scroll = new ScrollBoxRenderable4(renderer2, {
1095
2015
  id: "omnius-conversation",
1096
2016
  flexGrow: 1,
1097
2017
  flexShrink: 1,
@@ -1127,18 +2047,20 @@ var NativeFrontend = class {
1127
2047
  });
1128
2048
  this.body.add(this.scroll);
1129
2049
  this.content = new NativeBlockList(this.context, this.scroll);
1130
- this.embeddedPane = new BoxRenderable3(renderer2, {
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, {
1131
2053
  id: "omnius-editor-pane",
1132
2054
  width: "50%",
1133
2055
  flexShrink: 0,
1134
2056
  flexDirection: "column",
1135
2057
  border: true,
1136
- borderStyle: "rounded",
2058
+ borderStyle: "single",
1137
2059
  borderColor: colors.border,
1138
2060
  visible: false
1139
2061
  });
1140
2062
  this.body.add(this.embeddedPane);
1141
- this.liveButton = new TextRenderable3(renderer2, {
2063
+ this.liveButton = new TextRenderable4(renderer2, {
1142
2064
  id: "omnius-live-button",
1143
2065
  content: "↓ Live",
1144
2066
  width: "100%",
@@ -1153,7 +2075,7 @@ var NativeFrontend = class {
1153
2075
  }
1154
2076
  },
1155
2077
  onKeyDown: (key) => {
1156
- if (key.name === "return") {
2078
+ if (key.eventType !== "release" && (key.name === "return" || key.sequence === " ")) {
1157
2079
  this.scrollToLive();
1158
2080
  key.preventDefault();
1159
2081
  }
@@ -1161,7 +2083,7 @@ var NativeFrontend = class {
1161
2083
  });
1162
2084
  this.liveButton.focusable = true;
1163
2085
  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 });
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 });
1165
2087
  this.root.add(this.queue);
1166
2088
  this.suggestions = new NativeSelectRenderable(renderer2, {
1167
2089
  id: "omnius-suggestions",
@@ -1180,20 +2102,21 @@ var NativeFrontend = class {
1180
2102
  this.suggestions.onActivate = () => this.acceptSuggestion();
1181
2103
  this.suggestions.on("itemSelected", () => this.acceptSuggestion());
1182
2104
  this.root.add(this.suggestions);
1183
- this.footer = new BoxRenderable3(renderer2, {
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, {
1184
2108
  id: "omnius-input-box",
1185
2109
  width: "100%",
1186
2110
  flexShrink: 0,
1187
- border: true,
1188
- borderStyle: "rounded",
2111
+ border: ["top"],
2112
+ borderStyle: "single",
1189
2113
  borderColor: colors.border,
1190
- flexDirection: "column",
1191
- paddingX: 1
2114
+ flexDirection: "column"
1192
2115
  });
1193
2116
  this.root.add(this.footer);
1194
- const inputRow = new BoxRenderable3(renderer2, { id: "omnius-input-row", width: "100%", flexDirection: "row", gap: 1 });
2117
+ const inputRow = new BoxRenderable4(renderer2, { id: "omnius-input-row", width: "100%", flexDirection: "row", gap: 1 });
1195
2118
  this.footer.add(inputRow);
1196
- this.prompt = new TextRenderable3(renderer2, { id: "omnius-prompt", content: "❯", fg: colors.accent, wrapMode: "none", selectable: false, flexShrink: 0 });
2119
+ this.prompt = new TextRenderable4(renderer2, { id: "omnius-prompt", content: "❯", fg: colors.accent, wrapMode: "none", selectable: false, flexShrink: 0 });
1197
2120
  inputRow.add(this.prompt);
1198
2121
  this.editor = new TextareaRenderable2(renderer2, {
1199
2122
  id: "omnius-input",
@@ -1206,8 +2129,7 @@ var NativeFrontend = class {
1206
2129
  backgroundColor: colors.background,
1207
2130
  focusedTextColor: colors.foreground,
1208
2131
  focusedBackgroundColor: colors.background,
1209
- selectionBg: colors.selection,
1210
- cursorColor: colors.accent,
2132
+ placeholderColor: colors.foreground,
1211
2133
  cursorStyle: { style: "block", blinking: true },
1212
2134
  keyBindings: [{ name: "return", action: "submit" }, { name: "return", shift: true, action: "newline" }],
1213
2135
  onContentChange: () => this.editorChanged(),
@@ -1215,31 +2137,31 @@ var NativeFrontend = class {
1215
2137
  onSubmit: () => this.submit()
1216
2138
  });
1217
2139
  inputRow.add(this.editor);
1218
- this.enhance = new BoxRenderable3(renderer2, { id: "omnius-enhance", flexDirection: "row", gap: 1, flexShrink: 0, visible: false });
2140
+ this.enhance = new BoxRenderable4(renderer2, { id: "omnius-enhance", flexDirection: "row", gap: 1, flexShrink: 0, visible: false });
1219
2141
  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 });
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 });
1224
2146
  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);
2147
+ this.micTrack = new TextRenderable4(renderer2, { id: "omnius-mic-meter", width: 12, height: 1, flexShrink: 0, fg: colors.foreground, visible: false });
1228
2148
  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 });
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 });
1232
2154
  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 });
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 });
1234
2156
  this.metricsRow.add(this.metrics);
1235
- this.metricsEndActions = new BoxRenderable3(renderer2, { id: "omnius-metrics-end-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
2157
+ this.metricsEndActions = new BoxRenderable4(renderer2, { id: "omnius-metrics-end-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
1236
2158
  this.metricsRow.add(this.metricsEndActions);
1237
2159
  this.root.add(this.metricsRow);
1238
2160
  this.modals = new NativeModalStack(this.context, () => this.restoreFocus());
1239
2161
  renderer2.keyInput.on("keypress", this.onKey);
1240
2162
  renderer2.keyInput.on("paste", this.onPaste);
1241
- renderer2.on(CliRenderEvents.FRAME, this.onFrame);
1242
- renderer2.on(CliRenderEvents.RESIZE, this.onResize);
2163
+ renderer2.on(CliRenderEvents2.FRAME, this.onFrame);
2164
+ renderer2.on(CliRenderEvents2.RESIZE, this.onResize);
1243
2165
  this.applyTheme(initialTheme);
1244
2166
  this.editor.focus();
1245
2167
  }
@@ -1260,6 +2182,8 @@ var NativeFrontend = class {
1260
2182
  taskPane;
1261
2183
  tasks;
1262
2184
  body;
2185
+ instruments;
2186
+ detailButton;
1263
2187
  embeddedPane;
1264
2188
  liveButton;
1265
2189
  queue;
@@ -1268,14 +2192,15 @@ var NativeFrontend = class {
1268
2192
  prompt;
1269
2193
  enhance;
1270
2194
  metrics;
2195
+ keyHints;
1271
2196
  metricsRow;
1272
2197
  metricsStartActions;
1273
2198
  metricsEndActions;
1274
2199
  activity;
2200
+ status;
1275
2201
  media;
1276
2202
  mediaText;
1277
2203
  micTrack;
1278
- micLevel;
1279
2204
  clipboard;
1280
2205
  state;
1281
2206
  themeSignature = "";
@@ -1291,8 +2216,10 @@ var NativeFrontend = class {
1291
2216
  history = [];
1292
2217
  historyIndex = -1;
1293
2218
  savedInput = "";
1294
- animation;
1295
- animationIndex = 0;
2219
+ disposeSelectionContrast;
2220
+ actionSignatures = /* @__PURE__ */ new WeakMap();
2221
+ detailsOpen;
2222
+ headerPanelCollapsed = false;
1296
2223
  inputEventPending = false;
1297
2224
  viewportSignature = "";
1298
2225
  newEntries = 0;
@@ -1319,20 +2246,21 @@ var NativeFrontend = class {
1319
2246
  if (headerSignature !== this.headerSignature) {
1320
2247
  this.headerSignature = headerSignature;
1321
2248
  this.identity.content = styledText([
1322
- { text: state.header.title, ...state.theme.accent < 0 ? {} : { fg: `index:${state.theme.accent}` } },
2249
+ { text: `${plainText(state.header.panel) ? this.headerPanelCollapsed ? "▸" : "▾" : "·"} ${state.header.title}` },
1323
2250
  ...state.header.detail ? [{ text: " " }, ...textSpans(state.header.detail)] : []
1324
2251
  ]);
1325
2252
  this.headerPanel.content = styledText(state.header.panel);
1326
- this.headerPanel.visible = plainText(state.header.panel).length > 0;
2253
+ this.headerPanel.visible = plainText(state.header.panel).length > 0 && !this.headerPanelCollapsed;
2254
+ this.identity.focusable = plainText(state.header.panel).length > 0;
1327
2255
  this.replaceActions(this.headerActions, state.header.actions, "header");
1328
2256
  this.headerActions.visible = state.header.actions.length > 0;
1329
2257
  }
1330
- this.routeWarning.content = state.routeWarning ?? "";
2258
+ this.routeWarning.content = state.routeWarning ? `! ${state.routeWarning}` : "";
1331
2259
  this.routeWarning.visible = Boolean(state.routeWarning);
1332
2260
  const tabsSignature = JSON.stringify(state.tabs);
1333
2261
  if (tabsSignature !== this.tabsSignature) {
1334
2262
  this.tabsSignature = tabsSignature;
1335
- this.tabs.setOptions(state.tabs.map((tab) => ({ name: plainText(tab.label), description: "", value: tab.id })));
2263
+ this.tabs.setOptions(state.tabs.map((tab) => ({ name: fitInstrumentText(plainText(tab.label), this.tabs.getTabWidth() - 2), description: "", value: tab.id })));
1336
2264
  }
1337
2265
  this.tabs.visible = state.tabs.length > 1;
1338
2266
  const activeTab = state.tabs.findIndex((tab) => tab.id === state.activeViewId);
@@ -1364,7 +2292,9 @@ var NativeFrontend = class {
1364
2292
  this.editor.focusable = state.input.enabled && !this.paused;
1365
2293
  if (state.input.enabled && !this.paused && !this.modals.active && (inputWasEnabled === false || !this.renderer.currentFocusedRenderable)) this.editor.focus();
1366
2294
  this.metrics.content = styledText(state.metrics);
1367
- this.metricsRow.visible = state.metricsVisible;
2295
+ this.metrics.visible = state.metricsVisible;
2296
+ this.instruments.update(state);
2297
+ this.layoutInstruments();
1368
2298
  const metricsActions = JSON.stringify(state.metricsActions ?? []);
1369
2299
  if (metricsActions !== this.metricsActionsSignature) {
1370
2300
  this.metricsActionsSignature = metricsActions;
@@ -1374,7 +2304,6 @@ var NativeFrontend = class {
1374
2304
  this.renderEnhance();
1375
2305
  this.renderActivity();
1376
2306
  this.renderMedia();
1377
- this.updateAnimation();
1378
2307
  } finally {
1379
2308
  this.changing = false;
1380
2309
  }
@@ -1382,43 +2311,46 @@ var NativeFrontend = class {
1382
2311
  this.renderer.requestRender();
1383
2312
  }
1384
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);
1385
2317
  for (const child of [...parent.getChildren()]) child.destroy();
1386
2318
  for (const action of actions) {
1387
2319
  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, {
2320
+ const attributes = action.disabled ? TextAttributes6.DIM : action.active ? TextAttributes6.BOLD | TextAttributes6.UNDERLINE : 0;
2321
+ const button = new TextRenderable4(this.renderer, {
1394
2322
  id: `${prefix}:${action.id}`,
1395
- content: styledText(filled ? ` ${action.label} ` : action.label),
2323
+ content: styledText(action.label),
1396
2324
  flexShrink: 0,
1397
2325
  wrapMode: "none",
1398
2326
  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),
2327
+ fg: colors.foreground,
2328
+ bg: colors.background,
2329
+ attributes,
1402
2330
  onMouseDown: (event) => {
1403
2331
  if (event.button !== 0 || action.disabled) return;
1404
- this.emit({ type: "action", action: action.command ?? action.id, id: action.id });
2332
+ this.emitAction(action.command ?? action.id, action.id);
1405
2333
  event.stopPropagation();
1406
2334
  },
1407
2335
  onKeyDown: (key) => {
1408
- if (key.name !== "return" || action.disabled) return;
1409
- this.emit({ type: "action", action: action.command ?? action.id, id: action.id });
2336
+ if (key.eventType === "release" || key.name !== "return" && key.sequence !== " " || action.disabled) return;
2337
+ this.emitAction(action.command ?? action.id, action.id);
1410
2338
  key.preventDefault();
1411
2339
  }
1412
2340
  });
1413
2341
  button.focusable = !action.disabled;
1414
2342
  if (!action.disabled) {
2343
+ button.on("focused", () => {
2344
+ button.attributes = attributes | TextAttributes6.INVERSE;
2345
+ });
2346
+ button.on("blurred", () => {
2347
+ button.attributes = attributes;
2348
+ });
1415
2349
  button.onMouseOver = () => {
1416
- button.bg = colors.selection;
1417
- button.fg = colors.foreground;
2350
+ button.attributes = attributes | TextAttributes6.UNDERLINE | (button.focused ? TextAttributes6.INVERSE : 0);
1418
2351
  };
1419
2352
  button.onMouseOut = () => {
1420
- button.bg = filled && !inverse ? backing : colors.background;
1421
- button.fg = foreground;
2353
+ button.attributes = attributes | (button.focused ? TextAttributes6.INVERSE : 0);
1422
2354
  };
1423
2355
  }
1424
2356
  parent.add(button);
@@ -1426,23 +2358,25 @@ var NativeFrontend = class {
1426
2358
  }
1427
2359
  renderEnhance() {
1428
2360
  const state = this.state?.enhance;
1429
- this.enhance.visible = Boolean(state);
2361
+ this.enhance.visible = Boolean(state && (state.state !== "idle" || this.editor.plainText.trim()));
1430
2362
  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" }];
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" }];
1432
2364
  this.replaceActions(this.enhance, actions, "enhance");
1433
2365
  }
1434
2366
  renderActivity() {
1435
2367
  const activity = this.state?.activity;
1436
2368
  const stage = activity?.stage === "idle" ? void 0 : activity?.stage;
1437
2369
  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(" · ")}`;
2370
+ this.activity.content = `${activity?.processing ? "RUN · " : ""}${activity?.label || stage || ""}`;
2371
+ this.status.visible = this.activity.visible || this.media.visible;
1439
2372
  }
1440
2373
  renderMedia() {
1441
2374
  const media = this.state?.media;
1442
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;
1443
2377
  if (!media) return;
1444
2378
  this.mediaText.content = [
1445
- media.recording ? `${this.animationIndex % 10 < 5 ? "●" : "○"} REC` : void 0,
2379
+ media.recording ? "● REC" : void 0,
1446
2380
  media.speech ? "speech" : void 0,
1447
2381
  media.audio ? "audio" : void 0,
1448
2382
  media.video ? "video" : void 0,
@@ -1453,23 +2387,8 @@ var NativeFrontend = class {
1453
2387
  ].filter(Boolean).join(" · ");
1454
2388
  this.mediaText.fg = media.recording ? this.context.colors.warning : this.context.colors.accent;
1455
2389
  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
- }
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)}]`;
1473
2392
  }
1474
2393
  applyTheme(theme) {
1475
2394
  const signature = JSON.stringify(theme);
@@ -1480,6 +2399,7 @@ var NativeFrontend = class {
1480
2399
  const colors = this.context.colors = palette(theme);
1481
2400
  for (const [name, style] of Object.entries({
1482
2401
  default: { fg: colors.foreground },
2402
+ conceal: { fg: colors.foreground },
1483
2403
  "markup.heading": { fg: colors.accent, bold: true },
1484
2404
  "markup.strong": { fg: colors.foreground, bold: true },
1485
2405
  "markup.italic": { fg: colors.foreground, italic: true },
@@ -1503,13 +2423,11 @@ var NativeFrontend = class {
1503
2423
  this.headerPanel.fg = colors.foreground;
1504
2424
  this.routeWarning.fg = colors.warning;
1505
2425
  this.prompt.fg = this.activity.fg = this.liveButton.fg = colors.accent;
1506
- this.micTrack.backgroundColor = colors.dim;
1507
- this.micLevel.backgroundColor = colors.accent;
2426
+ this.micTrack.fg = colors.foreground;
1508
2427
  this.queue.fg = this.metrics.fg = colors.dim;
1509
2428
  this.editor.backgroundColor = this.editor.focusedBackgroundColor = colors.background;
1510
2429
  this.editor.textColor = this.editor.focusedTextColor = colors.foreground;
1511
- this.editor.cursorColor = colors.accent;
1512
- this.editor.selectionBg = colors.selection;
2430
+ this.editor.editorView.setSelectionColors(void 0, void 0);
1513
2431
  this.tabs.backgroundColor = this.tabs.focusedBackgroundColor = colors.background;
1514
2432
  this.tabs.textColor = colors.dim;
1515
2433
  this.tabs.selectedTextColor = colors.foreground;
@@ -1518,10 +2436,61 @@ var NativeFrontend = class {
1518
2436
  this.suggestions.textColor = colors.dim;
1519
2437
  this.suggestions.selectedTextColor = colors.foreground;
1520
2438
  this.suggestions.selectedBackgroundColor = colors.selection;
2439
+ styleNativeScrollbars(this.headerActions, colors);
2440
+ styleNativeScrollbars(this.scroll, colors);
2441
+ this.instruments.applyTheme(colors);
1521
2442
  this.content.applyTheme();
1522
2443
  this.tasks.applyTheme();
1523
2444
  this.modals?.applyTheme();
1524
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
+ }
1525
2494
  get offset() {
1526
2495
  return Math.max(0, this.scroll.scrollHeight - this.scroll.viewport.height - this.scroll.scrollTop);
1527
2496
  }
@@ -1559,6 +2528,7 @@ var NativeFrontend = class {
1559
2528
  }
1560
2529
  };
1561
2530
  onResize = () => {
2531
+ this.layoutInstruments();
1562
2532
  this.emit({ type: "resize", columns: this.renderer.terminalWidth, rows: this.renderer.terminalHeight });
1563
2533
  };
1564
2534
  editorChanged() {
@@ -1578,6 +2548,7 @@ var NativeFrontend = class {
1578
2548
  if (text === this.lastInput && cursor === this.lastCursor) return;
1579
2549
  this.lastInput = text;
1580
2550
  this.lastCursor = cursor;
2551
+ this.renderEnhance();
1581
2552
  this.emit({ type: "input", text, cursor });
1582
2553
  }
1583
2554
  submit() {
@@ -1608,7 +2579,7 @@ var NativeFrontend = class {
1608
2579
  const text = String(selected.value);
1609
2580
  this.setInput(text + (text.endsWith(" ") ? "" : " "));
1610
2581
  this.suggestions.visible = false;
1611
- this.emit({ type: "action", action: "completion", id: text });
2582
+ this.emitAction("completion", text);
1612
2583
  return true;
1613
2584
  }
1614
2585
  setInput(text, cursor = text.length) {
@@ -1623,6 +2594,10 @@ var NativeFrontend = class {
1623
2594
  key.preventDefault();
1624
2595
  key.stopPropagation();
1625
2596
  }
2597
+ emitAction(action, id) {
2598
+ this.flushLatestInput();
2599
+ this.emit({ type: "action", action, ...id === void 0 ? {} : { id } });
2600
+ }
1626
2601
  onKey = (key) => {
1627
2602
  if (this.modals.active) {
1628
2603
  this.modals.active.handleKey(key);
@@ -1634,10 +2609,22 @@ var NativeFrontend = class {
1634
2609
  return;
1635
2610
  }
1636
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
+ }
1637
2624
  const headerButtons = (this.state?.header.actions ?? []).filter((action) => !action.disabled).map((action) => this.getRenderable(`header:${action.id}`)).filter((button) => Boolean(button));
1638
2625
  const headerIndex = headerButtons.indexOf(this.renderer.currentFocusedRenderable);
1639
2626
  if (headerIndex >= 0) {
1640
- const direction = key.name === "left" || key.name === "tab" && key.shift ? -1 : key.name === "right" || key.name === "tab" ? 1 : 0;
2627
+ const direction = key.name === "left" ? -1 : key.name === "right" ? 1 : 0;
1641
2628
  if (direction || key.name === "home" || key.name === "end") {
1642
2629
  const index = key.name === "home" ? 0 : key.name === "end" ? headerButtons.length - 1 : (headerIndex + direction + headerButtons.length) % headerButtons.length;
1643
2630
  const button = headerButtons[index];
@@ -1689,18 +2676,28 @@ var NativeFrontend = class {
1689
2676
  return;
1690
2677
  }
1691
2678
  if (key.name === "escape") {
2679
+ if (this.renderer.currentFocusedRenderable !== this.editor) {
2680
+ this.restoreFocus();
2681
+ key.preventDefault();
2682
+ key.stopPropagation();
2683
+ return;
2684
+ }
1692
2685
  if (this.suggestions.visible) this.suggestions.visible = false;
1693
2686
  this.shortcut("escape", key);
1694
2687
  return;
1695
2688
  }
1696
2689
  if (key.name === "tab") {
1697
2690
  if (key.ctrl) this.shortcut("ctrl-tab", key);
1698
- else if (this.suggestions.visible) {
2691
+ else if (!key.shift && this.renderer.currentFocusedRenderable === this.editor && this.suggestions.visible) {
1699
2692
  this.acceptSuggestion();
1700
2693
  key.preventDefault();
1701
- } else this.shortcut("tab", key);
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();
1702
2698
  return;
1703
2699
  }
2700
+ if (this.renderer.currentFocusedRenderable !== this.editor && this.renderer.currentFocusedRenderable !== this.scroll && !key.ctrl) return;
1704
2701
  if (key.ctrl && (key.name === "left" || key.name === "right")) {
1705
2702
  this.shortcut(`ctrl-${key.name}`, key);
1706
2703
  return;
@@ -1749,6 +2746,28 @@ var NativeFrontend = class {
1749
2746
  return;
1750
2747
  }
1751
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
+ }
1752
2771
  onPaste = (event) => {
1753
2772
  if (this.modals.active) {
1754
2773
  this.modals.active.handlePaste(event);
@@ -1888,11 +2907,11 @@ var NativeFrontend = class {
1888
2907
  async dispose() {
1889
2908
  if (this.destroyed) return;
1890
2909
  this.destroyed = true;
1891
- if (this.animation) clearInterval(this.animation);
2910
+ this.disposeSelectionContrast();
1892
2911
  this.renderer.keyInput.off("keypress", this.onKey);
1893
2912
  this.renderer.keyInput.off("paste", this.onPaste);
1894
- this.renderer.off(CliRenderEvents.FRAME, this.onFrame);
1895
- this.renderer.off(CliRenderEvents.RESIZE, this.onResize);
2913
+ this.renderer.off(CliRenderEvents2.FRAME, this.onFrame);
2914
+ this.renderer.off(CliRenderEvents2.RESIZE, this.onResize);
1896
2915
  this.modals.destroy();
1897
2916
  this.content.destroy();
1898
2917
  this.tasks.destroy();
@@ -1902,21 +2921,86 @@ var NativeFrontend = class {
1902
2921
  }
1903
2922
  };
1904
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 };
2981
+ }
2982
+
1905
2983
  // packages/cli/src/tui/native/worker.ts
1906
2984
  var replies = createWriteStream("", { fd: 3, autoClose: false });
1907
2985
  var reply = (message) => replies.write(JSON.stringify(message) + "\n");
1908
2986
  var terminalInput = new PassThrough();
1909
2987
  var renderer;
1910
2988
  var frontend;
2989
+ var cursorDefaults;
1911
2990
  var stopping = false;
1912
2991
  var failed = false;
1913
2992
  async function close() {
1914
2993
  if (stopping) return;
1915
2994
  stopping = true;
1916
- await frontend?.dispose();
1917
- if (renderer) {
1918
- renderer.destroy();
1919
- 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
+ }
1920
3004
  }
1921
3005
  }
1922
3006
  async function fail(error) {
@@ -1936,18 +3020,22 @@ async function command(message) {
1936
3020
  frontend.update(message.snapshot, message.content);
1937
3021
  renderer.requestRender();
1938
3022
  await renderer.idle();
1939
- } else if (message.type === "suspend") await renderer.suspend();
1940
- else if (message.type === "resume") {
3023
+ } else if (message.type === "suspend") {
3024
+ await cursorDefaults.restore();
3025
+ await renderer.suspend();
3026
+ } else if (message.type === "resume") {
1941
3027
  await renderer.resume();
1942
3028
  renderer.requestRender();
1943
3029
  await renderer.idle();
3030
+ await cursorDefaults.restore();
1944
3031
  } else if (message.type === "stop") await close();
1945
3032
  reply({ type: "ack", id: message.id });
1946
3033
  if (message.type === "stop") replies.end(() => process.exit(0));
1947
3034
  }
1948
3035
  try {
3036
+ const session = new NativeSession(process.stdout);
1949
3037
  renderer = await createCliRenderer({
1950
- nativeSession: new NativeSession(process.stdout),
3038
+ nativeSession: session,
1951
3039
  stdin: terminalInput,
1952
3040
  stdout: process.stdout,
1953
3041
  exitOnCtrlC: false,
@@ -1958,21 +3046,22 @@ try {
1958
3046
  screenMode: "alternate-screen",
1959
3047
  consoleMode: "disabled",
1960
3048
  externalOutputMode: "passthrough",
1961
- backgroundColor: RGBA2.defaultBackground(),
3049
+ backgroundColor: RGBA3.defaultBackground(),
1962
3050
  openConsoleOnError: false,
1963
3051
  targetFps: 60,
1964
3052
  maxFps: 60,
1965
3053
  clearOnShutdown: false
1966
3054
  });
1967
- renderer.on(CliRenderEvents2.RENDER_ERROR, (event) => {
3055
+ renderer.on(CliRenderEvents4.RENDER_ERROR, (event) => {
1968
3056
  void fail(event.error);
1969
3057
  });
1970
- renderer.on(CliRenderEvents2.HANDLER_ERROR, (event) => {
3058
+ renderer.on(CliRenderEvents4.HANDLER_ERROR, (event) => {
1971
3059
  void fail(event.error);
1972
3060
  });
1973
3061
  renderer.on("error", (event) => {
1974
3062
  void fail(event);
1975
3063
  });
3064
+ cursorDefaults = installTerminalCursorDefaults(renderer, session);
1976
3065
  frontend = new NativeFrontend(renderer, (event) => reply({ type: "event", event }));
1977
3066
  const controls = createInterface({ input: process.stdin, crlfDelay: Infinity });
1978
3067
  let pending = Promise.resolve();
@@ -2004,6 +3093,7 @@ try {
2004
3093
  });
2005
3094
  renderer.requestRender();
2006
3095
  await renderer.idle();
3096
+ await cursorDefaults.restore();
2007
3097
  reply({ type: "ready" });
2008
3098
  } catch (error) {
2009
3099
  await fail(error);