omnius 1.0.863 → 1.0.865

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 (75) hide show
  1. package/assets/tray/omnius-checking.ico +0 -0
  2. package/assets/tray/omnius-checking.png +0 -0
  3. package/assets/tray/omnius-checking.svg +7 -4
  4. package/assets/tray/omnius-offline.ico +0 -0
  5. package/assets/tray/omnius-offline.png +0 -0
  6. package/assets/tray/omnius-offline.svg +6 -4
  7. package/assets/tray/omnius-online.ico +0 -0
  8. package/assets/tray/omnius-online.png +0 -0
  9. package/assets/tray/omnius-online.svg +5 -4
  10. package/dist/index.js +2 -2
  11. package/dist/library.js +11 -11
  12. package/dist/tui/native/worker.js +2644 -148
  13. package/dist/update-worker.js +21 -0
  14. package/docs/DISCOVERY.json +1930 -488
  15. package/docs/DISCOVERY.md +40 -0
  16. package/docs/work-orders/2026-10-16-browser-instrument-dashboard.md +74 -0
  17. package/docs/work-orders/2026-10-16-interface-delivery.md +47 -0
  18. package/docs/work-orders/desktop-projects.md +48 -0
  19. package/docs/work-orders/evidence/desktop-indicator-registration-20261016.json +28 -0
  20. package/docs/work-orders/evidence/inference-device-policy-readonly-20261016.json +56 -0
  21. package/docs/work-orders/evidence/instrument-100x30.png +0 -0
  22. package/docs/work-orders/evidence/instrument-120x36.png +0 -0
  23. package/docs/work-orders/evidence/instrument-160x48.png +0 -0
  24. package/docs/work-orders/evidence/instrument-60x20.png +0 -0
  25. package/docs/work-orders/evidence/instrument-80x24.png +0 -0
  26. package/docs/work-orders/evidence/instrument-clean-transcript-120x36.png +0 -0
  27. package/docs/work-orders/evidence/instrument-collapse-120x36.png +0 -0
  28. package/docs/work-orders/evidence/instrument-devices-expanded-120x36.png +0 -0
  29. package/docs/work-orders/evidence/instrument-divider-collapsed-160x48.png +0 -0
  30. package/docs/work-orders/evidence/instrument-divider-resized-160x48.png +0 -0
  31. package/docs/work-orders/evidence/instrument-help-scroll-120x36.png +0 -0
  32. package/docs/work-orders/evidence/instrument-package-install-20261016-updated.json +110 -0
  33. package/docs/work-orders/evidence/instrument-popup-api-120x36.png +0 -0
  34. package/docs/work-orders/evidence/instrument-popup-api-input-120x36.png +0 -0
  35. package/docs/work-orders/evidence/instrument-popup-config-120x36.png +0 -0
  36. package/docs/work-orders/evidence/instrument-popup-dark-120x36.png +0 -0
  37. package/docs/work-orders/evidence/instrument-popup-dark-60x20.png +0 -0
  38. package/docs/work-orders/evidence/instrument-popup-light-120x36.png +0 -0
  39. package/docs/work-orders/evidence/instrument-popup-model-120x36.png +0 -0
  40. package/docs/work-orders/evidence/instrument-popup-network-120x36.png +0 -0
  41. package/docs/work-orders/evidence/instrument-production-terminal-live.json +8523 -1810
  42. package/docs/work-orders/evidence/instrument-report-broker-120x36.png +0 -0
  43. package/docs/work-orders/evidence/instrument-report-gpu-120x36.png +0 -0
  44. package/docs/work-orders/evidence/instrument-report-stats-120x36.png +0 -0
  45. package/docs/work-orders/evidence/instrument-selection-copied-120x36.png +0 -0
  46. package/docs/work-orders/evidence/instrument-selection-copy-menu-120x36.png +0 -0
  47. package/docs/work-orders/evidence/instrument-semantic-components-20261016-updated.json +80 -0
  48. package/docs/work-orders/evidence/instrument-targeted-verification-20261016-updated.json +430 -0
  49. package/docs/work-orders/evidence/instrument-terminal-light-120x36.png +0 -0
  50. package/docs/work-orders/evidence/instrument-transcript-selection-120x36.png +0 -0
  51. package/docs/work-orders/evidence/instrument-vim-composer-120x36.png +0 -0
  52. package/docs/work-orders/evidence/interface-daemon-deployment-20261016.json +56 -0
  53. package/docs/work-orders/evidence/interface-targeted-verification-20261016-final.json +463 -0
  54. package/docs/work-orders/evidence/tray-checked-menu-final.png +0 -0
  55. package/docs/work-orders/evidence/tray-native-host-checking.png +0 -0
  56. package/docs/work-orders/evidence/tray-native-host-dark.png +0 -0
  57. package/docs/work-orders/evidence/tray-native-host-offline.png +0 -0
  58. package/docs/work-orders/evidence/tray-native-host.png +0 -0
  59. package/docs/work-orders/evidence/tray-project-3.png +0 -0
  60. package/docs/work-orders/evidence/tray-project-4.png +0 -0
  61. package/docs/work-orders/evidence/tray-project-5.png +0 -0
  62. package/docs/work-orders/evidence/tray-project-7.png +0 -0
  63. package/docs/work-orders/evidence/tray-projects-live.json +598 -0
  64. package/docs/work-orders/evidence/tray-projects-long-menu.png +0 -0
  65. package/docs/work-orders/evidence/tray-projects-menu.png +0 -0
  66. package/docs/work-orders/evidence/web-instrument-dark-desktop-20261016.png +0 -0
  67. package/docs/work-orders/evidence/web-instrument-dark-phone-20261016.png +0 -0
  68. package/docs/work-orders/evidence/web-instrument-dashboard-live-20261016.json +110 -0
  69. package/docs/work-orders/evidence/web-instrument-light-desktop-20261016.png +0 -0
  70. package/docs/work-orders/evidence/web-instrument-light-phone-20261016.png +0 -0
  71. package/docs/work-orders/evidence/web-media-runtime-verification-20261016.json +75 -0
  72. package/docs/work-orders/instrument-command-surface-audit.md +215 -148
  73. package/docs/work-orders/instrument-tui.md +93 -13
  74. package/npm-shrinkwrap.json +2 -2
  75. package/package.json +1 -1
@@ -2,19 +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 CliRenderEvents4, NativeSession, RGBA as RGBA3, createCliRenderer } from "@opentui/core";
5
+ import { CliRenderEvents as CliRenderEvents6, 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 BoxRenderable4,
10
- CliRenderEvents as CliRenderEvents2,
9
+ BoxRenderable as BoxRenderable6,
10
+ CliRenderEvents as CliRenderEvents4,
11
11
  EmbeddedTerminalRenderable as EmbeddedTerminalRenderable2,
12
12
  ScrollBoxRenderable as ScrollBoxRenderable4,
13
13
  SyntaxStyle as SyntaxStyle2,
14
14
  TabSelectRenderable,
15
15
  TextareaRenderable as TextareaRenderable2,
16
- TextRenderable as TextRenderable4,
17
- TextAttributes as TextAttributes6,
16
+ TextRenderable as TextRenderable5,
17
+ TextAttributes as TextAttributes7,
18
18
  createClipboard,
19
19
  createHostClipboard,
20
20
  createRendererClipboardAdapter
@@ -171,6 +171,215 @@ function installDefaultSelectionContrast(renderer2) {
171
171
  return dispose;
172
172
  }
173
173
 
174
+ // packages/cli/src/tui/native/components/code.ts
175
+ var aliases = {
176
+ js: "javascript",
177
+ jsx: "javascript",
178
+ ts: "typescript",
179
+ tsx: "typescriptreact",
180
+ py: "python",
181
+ sh: "bash",
182
+ shell: "bash",
183
+ yml: "yaml",
184
+ jsonl: "json",
185
+ json5: "json",
186
+ jsonc: "json"
187
+ };
188
+ function nativeCodeLanguage(language) {
189
+ const name = language?.trim().split(/\s+/)[0]?.toLowerCase();
190
+ return name ? aliases[name] ?? name : void 0;
191
+ }
192
+ function prettyNativeJson(source) {
193
+ try {
194
+ JSON.parse(source);
195
+ } catch {
196
+ return void 0;
197
+ }
198
+ let compact = "", quoted = false, escaped = false;
199
+ for (const char of source) {
200
+ if (quoted || !/[\t\r\n ]/.test(char)) compact += char;
201
+ if (quoted) {
202
+ if (escaped) escaped = false;
203
+ else if (char === "\\") escaped = true;
204
+ else if (char === '"') quoted = false;
205
+ } else if (char === '"') quoted = true;
206
+ }
207
+ let result = "", depth = 0;
208
+ quoted = escaped = false;
209
+ const newline = () => "\n" + " ".repeat(depth);
210
+ for (let index = 0; index < compact.length; index++) {
211
+ const char = compact[index];
212
+ if (quoted) {
213
+ result += char;
214
+ if (escaped) escaped = false;
215
+ else if (char === "\\") escaped = true;
216
+ else if (char === '"') quoted = false;
217
+ } else if (char === '"') {
218
+ quoted = true;
219
+ result += char;
220
+ } else if (char === "{" || char === "[") {
221
+ result += char;
222
+ depth++;
223
+ if (compact[index + 1] !== (char === "{" ? "}" : "]"))
224
+ result += newline();
225
+ } else if (char === "}" || char === "]") {
226
+ depth--;
227
+ if (compact[index - 1] !== (char === "}" ? "{" : "["))
228
+ result += newline();
229
+ result += char;
230
+ } else if (char === ",") result += char + newline();
231
+ else if (char === ":") result += ": ";
232
+ else result += char;
233
+ }
234
+ return result;
235
+ }
236
+ function nativeCodeContent(source, language) {
237
+ const selected = nativeCodeLanguage(language);
238
+ if (selected === "json" || !selected && /^[\s]*[\[{]/.test(source)) {
239
+ const pretty = prettyNativeJson(source);
240
+ if (pretty !== void 0) return { content: pretty, language: "json" };
241
+ }
242
+ return { content: source, language: selected };
243
+ }
244
+ function nativeCodeHighlights(source) {
245
+ const highlights = [];
246
+ const tokens = /("(?:\\.|[^"\\])*"?|'(?:\\.|[^'\\])*'?|`(?:\\.|[^`\\])*`?)|(\/\/[^\n]*|\/\*[\s\S]*?(?:\*\/|$)|\#[^\n]*)|\b(true|false|null|None|True|False|nil)\b|\b(const|let|var|function|class|def|return|if|else|elif|for|while|in|import|from|export|async|await|try|catch|except|throw|raise|new|public|private|static|void|int|bool|struct|enum|fn|pub|use|match|impl|package|func|select|switch|case|break|continue|end|do|then|fi|done)\b|(?<![\w])(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|([{}\[\](),:;])/g;
247
+ for (const match of source.matchAll(tokens)) {
248
+ const start = match.index, end = start + match[0].length;
249
+ const group = match[1] ? /^\s*:/.test(source.slice(end)) ? "property" : "string" : match[2] ? "comment" : match[3] ? "constant" : match[4] ? "keyword" : match[5] ? "number" : "punctuation";
250
+ highlights.push([start, end, group]);
251
+ }
252
+ return highlights;
253
+ }
254
+ function installNativeSyntax(syntax, colors) {
255
+ const base = { fg: colors.foreground, bg: colors.background };
256
+ for (const [name, attributes] of Object.entries({
257
+ default: {},
258
+ conceal: { dim: true },
259
+ "markup.heading": { bold: true },
260
+ "markup.strong": { bold: true },
261
+ "markup.italic": { italic: true },
262
+ "markup.link": { underline: true },
263
+ "markup.link.url": { dim: true },
264
+ "markup.raw": {},
265
+ "markup.list": { dim: true },
266
+ "markup.quote": { dim: true },
267
+ comment: { dim: true, italic: true },
268
+ keyword: { bold: true },
269
+ property: { bold: true },
270
+ "string.special.key": { bold: true },
271
+ string: { italic: true },
272
+ "string.escape": { bold: true },
273
+ number: { bold: true },
274
+ boolean: { bold: true },
275
+ constant: { bold: true },
276
+ function: { bold: true },
277
+ type: { underline: true },
278
+ punctuation: { dim: true },
279
+ operator: {},
280
+ variable: {},
281
+ tag: { bold: true },
282
+ attribute: { italic: true }
283
+ }))
284
+ syntax.registerStyle(name, { ...base, ...attributes });
285
+ }
286
+ function nativeMarkdownSections(owner, source, streaming = false) {
287
+ const bare = !source.includes("\n```") && !source.includes("\n~~~") && /^[\s]*[\[{]/.test(source) ? prettyNativeJson(source) : void 0;
288
+ if (bare !== void 0)
289
+ return [
290
+ {
291
+ id: `${owner}:json`,
292
+ kind: "code",
293
+ title: "JSON",
294
+ language: "json",
295
+ text: bare,
296
+ collapsible: true,
297
+ streaming
298
+ }
299
+ ];
300
+ const rows = source.split("\n"), offsets = [];
301
+ let offset = 0;
302
+ for (const row of rows) {
303
+ offsets.push(offset);
304
+ offset += row.length + 1;
305
+ }
306
+ const output = [], stack = [
307
+ { level: 0, children: output }
308
+ ];
309
+ let prose = [], proseStart = 0, segmented = false;
310
+ const current = () => stack.at(-1).children;
311
+ const flush = () => {
312
+ if (prose.join("\n").trim())
313
+ current().push({
314
+ id: `${owner}:prose:${proseStart}`,
315
+ kind: "markdown",
316
+ text: prose.join("\n"),
317
+ streaming
318
+ });
319
+ prose = [];
320
+ };
321
+ for (let index = 0; index < rows.length; index++) {
322
+ const row = rows[index];
323
+ const fence = row.match(/^([ >]*)(`{3,}|~{3,})([^`]*)$/);
324
+ if (fence) {
325
+ flush();
326
+ segmented = true;
327
+ const start = offsets[index], contents = [], marker = fence[2];
328
+ const quoteDepth = (fence[1].match(/>/g) ?? []).length;
329
+ const indentation = Math.max(
330
+ 0,
331
+ fence[1].split(">").at(-1).length - (quoteDepth ? 1 : 0)
332
+ );
333
+ const containerContent = (line) => {
334
+ for (let depth = 0; depth < quoteDepth; depth++)
335
+ line = line.replace(/^ {0,3}> ?/, "");
336
+ return line.replace(new RegExp(`^ {0,${indentation}}`), "");
337
+ };
338
+ while (++index < rows.length) {
339
+ const line = containerContent(rows[index]);
340
+ const closing = line.match(/^ {0,3}(`{3,}|~{3,})\s*$/);
341
+ if (closing && closing[1][0] === marker[0] && closing[1].length >= marker.length)
342
+ break;
343
+ contents.push(line);
344
+ }
345
+ const code = nativeCodeContent(contents.join("\n"), fence[3]);
346
+ current().push({
347
+ id: `${owner}:code:${start}`,
348
+ kind: "code",
349
+ title: code.language?.toUpperCase() ?? "CODE",
350
+ language: code.language,
351
+ text: code.content,
352
+ collapsible: true,
353
+ streaming: streaming && index >= rows.length
354
+ });
355
+ continue;
356
+ }
357
+ const atx = row.match(/^ {0,3}(#{1,6})(?:\s+(.*?)\s*#*\s*|\s*)$/);
358
+ const setext = row.trim() && rows[index + 1]?.match(/^ {0,3}(=+|-+)\s*$/);
359
+ if (atx || setext) {
360
+ flush();
361
+ segmented = true;
362
+ const level = atx ? atx[1].length : setext[1][0] === "=" ? 1 : 2;
363
+ while (stack.length > 1 && stack.at(-1).level >= level) stack.pop();
364
+ const section = {
365
+ id: `${owner}:section:${offsets[index]}`,
366
+ kind: "column",
367
+ title: atx ? atx[2] ?? "" : row.trim(),
368
+ collapsible: true,
369
+ children: []
370
+ };
371
+ current().push(section);
372
+ stack.push({ level, children: section.children });
373
+ if (!atx) index++;
374
+ } else {
375
+ if (!prose.length) proseStart = offsets[index];
376
+ prose.push(row);
377
+ }
378
+ }
379
+ flush();
380
+ return segmented ? output : void 0;
381
+ }
382
+
174
383
  // packages/cli/src/tui/native/components/blocks.ts
175
384
  function nativeProgressText(value, working = false) {
176
385
  if (value === void 0 || !Number.isFinite(value)) return `[············] ${working ? "working" : "pending"}`;
@@ -212,6 +421,7 @@ var NativeBlockView = class {
212
421
  text;
213
422
  body;
214
423
  children;
424
+ markdownSections;
215
425
  actions;
216
426
  progress;
217
427
  image;
@@ -314,14 +524,31 @@ var NativeBlockView = class {
314
524
  flexShrink: 0,
315
525
  internalBlockMode: "top-level",
316
526
  conceal: true,
317
- tableOptions: { style: "columns", widthMode: "full", wrapMode: "word", selectable: true }
527
+ tableOptions: { style: "columns", widthMode: "full", wrapMode: "word", selectable: true },
528
+ renderNode: (token) => {
529
+ if (token.type !== "code") return;
530
+ const value = nativeCodeContent(token.text, token.lang);
531
+ return new CodeRenderable(renderer2, {
532
+ content: value.content,
533
+ filetype: value.language ?? "text",
534
+ syntaxStyle: syntax,
535
+ fg: colors.foreground,
536
+ bg: colors.background,
537
+ width: "100%",
538
+ flexShrink: 0,
539
+ wrapMode: "word",
540
+ selectable: true,
541
+ streaming: this.spec.streaming ?? false,
542
+ onHighlight: (highlights, context) => highlights.length ? void 0 : nativeCodeHighlights(context.content)
543
+ });
544
+ }
318
545
  });
319
546
  this.body.add(this.text);
320
547
  } else if (spec.kind === "code") {
321
548
  this.text = new CodeRenderable(renderer2, {
322
549
  id: `code:${spec.id}`,
323
550
  content: "",
324
- filetype: spec.language,
551
+ filetype: spec.language ?? "text",
325
552
  syntaxStyle: syntax,
326
553
  fg: colors.foreground,
327
554
  bg: colors.background,
@@ -330,7 +557,8 @@ var NativeBlockView = class {
330
557
  streaming: spec.streaming ?? false,
331
558
  selectable: true,
332
559
  wrapMode: "word",
333
- drawUnstyledText: true
560
+ drawUnstyledText: true,
561
+ onHighlight: (highlights, context) => highlights.length ? void 0 : nativeCodeHighlights(context.content)
334
562
  });
335
563
  this.body.add(this.text);
336
564
  } else if (spec.text !== void 0) {
@@ -371,6 +599,8 @@ var NativeBlockView = class {
371
599
  }
372
600
  reset() {
373
601
  this.children?.destroy();
602
+ this.markdownSections?.destroy();
603
+ this.markdownSections = void 0;
374
604
  if (this.terminal) this.context.terminals.delete(this.spec.id);
375
605
  for (const child of [...this.root.getChildren()]) child.destroyRecursively();
376
606
  this.title = this.text = this.body = this.actions = this.progress = this.image = this.terminal = void 0;
@@ -394,11 +624,21 @@ var NativeBlockView = class {
394
624
  this.title.focusable = Boolean(spec.collapsible);
395
625
  }
396
626
  if (this.body) this.body.visible = !this.collapsed;
627
+ const sections = spec.kind === "markdown" ? nativeMarkdownSections(spec.id, plainText(spec.text), spec.streaming) : void 0;
628
+ if (this.text && spec.kind === "markdown") this.text.visible = !sections;
629
+ if (sections && this.body) {
630
+ this.markdownSections ??= new NativeBlockList(this.context, this.body);
631
+ this.markdownSections.replace(sections);
632
+ } else if (this.markdownSections) {
633
+ this.markdownSections.destroy();
634
+ this.markdownSections = void 0;
635
+ }
397
636
  if (this.text instanceof TextRenderable) this.text.content = styledText(spec.kind === "image" ? spec.text ?? "Image unavailable" : spec.text);
398
637
  else if (this.text instanceof MarkdownRenderable || this.text instanceof CodeRenderable) {
399
- this.text.content = plainText(spec.text);
638
+ const code = this.text instanceof CodeRenderable ? nativeCodeContent(plainText(spec.text), spec.language) : void 0;
639
+ this.text.content = sections ? "" : code?.content ?? plainText(spec.text);
400
640
  this.text.streaming = spec.streaming ?? false;
401
- if (this.text instanceof CodeRenderable) this.text.filetype = spec.language;
641
+ if (this.text instanceof CodeRenderable) this.text.filetype = code?.language ?? "text";
402
642
  }
403
643
  if (this.image && spec.imagePath && this.image.source !== spec.imagePath) this.image.source = spec.imagePath;
404
644
  if (this.terminal) this.terminal.height = spec.height ?? 14;
@@ -470,9 +710,11 @@ var NativeBlockView = class {
470
710
  action.bg = colors.background;
471
711
  }
472
712
  this.children?.applyTheme();
713
+ this.markdownSections?.applyTheme();
473
714
  }
474
715
  destroy() {
475
716
  this.children?.destroy();
717
+ this.markdownSections?.destroy();
476
718
  if (this.terminal) this.context.terminals.delete(this.spec.id);
477
719
  this.root.destroyRecursively();
478
720
  }
@@ -532,7 +774,7 @@ var NativeBlockList = class {
532
774
 
533
775
  // packages/cli/src/tui/native/components/modals.ts
534
776
  import {
535
- BoxRenderable as BoxRenderable2,
777
+ BoxRenderable as BoxRenderable3,
536
778
  InputRenderable,
537
779
  ScrollBoxRenderable as ScrollBoxRenderable2,
538
780
  TextareaRenderable,
@@ -688,7 +930,7 @@ var NativeSelectRenderable = class extends SelectRenderable {
688
930
  };
689
931
 
690
932
  // packages/cli/src/tui/native/components/corner-frame.ts
691
- import { RenderableEvents as RenderableEvents2, TextAttributes as TextAttributes3 } from "@opentui/core";
933
+ import { BoxRenderable as BoxRenderable2, CliRenderEvents as CliRenderEvents2, RenderableEvents as RenderableEvents2, TextAttributes as TextAttributes3 } from "@opentui/core";
692
934
  function drawCornerFrame(buffer, panel, colors, progress = 1) {
693
935
  const { x, y, width, height } = panel;
694
936
  if (width <= 0 || height <= 0) return;
@@ -722,43 +964,122 @@ function drawCornerFrame(buffer, panel, colors, progress = 1) {
722
964
  }
723
965
  }
724
966
  var NativeCornerFrame = class {
725
- constructor(panel, colors, renderer2) {
967
+ constructor(panel, colors, renderer2, container = panel.parent) {
726
968
  this.panel = panel;
727
969
  this.colors = colors;
728
970
  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);
971
+ this.container = container;
972
+ this.chrome = new BoxRenderable2(renderer2, {
973
+ id: `${panel.id}:corners`,
974
+ position: "absolute",
975
+ left: 0,
976
+ top: 0,
977
+ width: 1,
978
+ height: 1,
979
+ backgroundColor: colors.background,
980
+ shouldFill: true,
981
+ zIndex: 0,
982
+ visible: false
983
+ });
984
+ this.chrome.renderAfter = (buffer) => {
985
+ drawCornerFrame(buffer, this.chrome, this.colors, this.progress);
986
+ if (this.progress < 0.1 && this.chrome.width >= 3 && this.chrome.height >= 3)
987
+ buffer.drawText("■", this.chrome.x + Math.floor(this.chrome.width / 2), this.chrome.y + Math.floor(this.chrome.height / 2), this.colors.foreground, this.colors.background);
988
+ this.painted = true;
733
989
  };
734
- panel.renderAfter = this.render;
990
+ (container ?? renderer2.root).add(this.chrome, 0);
991
+ panel.border = false;
992
+ panel.shouldFill = false;
993
+ panel.zIndex = 1;
994
+ panel.opacity = this.reduced ? 1 : 0;
735
995
  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);
996
+ if (this.reduced) {
997
+ this.progress = 1;
998
+ this.phase = "open";
747
999
  }
1000
+ renderer2.on(CliRenderEvents2.FRAME, this.onFrame);
748
1001
  }
749
1002
  panel;
750
1003
  colors;
751
1004
  renderer;
1005
+ container;
1006
+ chrome;
752
1007
  progress = 0;
753
1008
  timer;
754
1009
  disposed = false;
755
- previousRender;
756
- render;
1010
+ phase = "opening";
1011
+ started = false;
1012
+ painted = false;
1013
+ geometry = "";
1014
+ reduced = process.env["OMNIUS_TUI_REDUCE_MOTION"] === "1" || process.env["REDUCE_MOTION"] === "1";
757
1015
  get animating() {
758
- return this.timer !== void 0;
1016
+ return !this.disposed && (this.phase === "opening" || this.phase === "closing");
1017
+ }
1018
+ get closing() {
1019
+ return this.phase === "closing" || this.phase === "closed";
1020
+ }
1021
+ get bounds() {
1022
+ return this.chrome;
759
1023
  }
760
1024
  setColors(colors) {
761
1025
  this.colors = colors;
1026
+ this.chrome.backgroundColor = colors.background;
1027
+ }
1028
+ onFrame = () => {
1029
+ this.layout();
1030
+ if (!this.started && this.painted && this.panel.width > 0 && this.panel.height > 0) {
1031
+ this.started = true;
1032
+ if (this.phase === "opening") this.animate(1, 10);
1033
+ }
1034
+ };
1035
+ layout() {
1036
+ if (this.disposed || this.panel.width < 1 || this.panel.height < 1) return;
1037
+ const target = this.panel;
1038
+ const width = Math.round(Math.min(5, target.width) + (target.width - Math.min(5, target.width)) * this.progress);
1039
+ const height = Math.round(Math.min(3, target.height) + (target.height - Math.min(3, target.height)) * this.progress);
1040
+ const left = target.x - (this.container?.x ?? 0) + Math.floor((target.width - width) / 2);
1041
+ const top = target.y - (this.container?.y ?? 0) + Math.floor((target.height - height) / 2);
1042
+ const geometry = `${left}:${top}:${width}:${height}`;
1043
+ if (this.geometry === geometry) return;
1044
+ this.geometry = geometry;
1045
+ this.chrome.left = left;
1046
+ this.chrome.top = top;
1047
+ this.chrome.width = width;
1048
+ this.chrome.height = height;
1049
+ this.chrome.visible = true;
1050
+ this.renderer.requestRender();
1051
+ }
1052
+ animate(to, steps, done) {
1053
+ if (this.timer) clearTimeout(this.timer);
1054
+ const from = this.progress;
1055
+ let step = 0;
1056
+ const advance = () => {
1057
+ if (this.disposed) return;
1058
+ const t = ++step / steps;
1059
+ this.progress = from + (to - from) * (t * t * (3 - 2 * t));
1060
+ this.layout();
1061
+ this.renderer.requestRender();
1062
+ if (step < steps) this.timer = setTimeout(advance, 16);
1063
+ else if (to === 1) {
1064
+ this.phase = "open";
1065
+ this.panel.opacity = 1;
1066
+ this.timer = void 0;
1067
+ } else this.timer = setTimeout(() => {
1068
+ this.timer = void 0;
1069
+ this.phase = "closed";
1070
+ done?.();
1071
+ }, 32);
1072
+ };
1073
+ this.timer = setTimeout(advance, 16);
1074
+ }
1075
+ close(done) {
1076
+ if (this.closing || this.disposed) return;
1077
+ this.phase = "closing";
1078
+ this.panel.opacity = 0;
1079
+ if (this.reduced) {
1080
+ this.phase = "closed";
1081
+ done();
1082
+ } else this.animate(0, 8, done);
762
1083
  }
763
1084
  destroy = () => {
764
1085
  if (this.disposed) return;
@@ -766,7 +1087,8 @@ var NativeCornerFrame = class {
766
1087
  if (this.timer !== void 0) clearTimeout(this.timer);
767
1088
  this.timer = void 0;
768
1089
  this.panel.off(RenderableEvents2.DESTROYED, this.destroy);
769
- if (this.panel.renderAfter === this.render) this.panel.renderAfter = this.previousRender;
1090
+ this.renderer.off(CliRenderEvents2.FRAME, this.onFrame);
1091
+ this.chrome.destroyRecursively();
770
1092
  };
771
1093
  };
772
1094
 
@@ -777,7 +1099,7 @@ var NativeModalView = class {
777
1099
  this.close = close2;
778
1100
  this.spec = spec;
779
1101
  const { renderer: renderer2, colors } = context;
780
- this.root = new BoxRenderable2(renderer2, {
1102
+ this.root = new BoxRenderable3(renderer2, {
781
1103
  id: `modal:${spec.id}`,
782
1104
  position: "absolute",
783
1105
  top: 0,
@@ -789,24 +1111,31 @@ var NativeModalView = class {
789
1111
  alignItems: "center",
790
1112
  zIndex: 100,
791
1113
  backgroundColor: "transparent",
792
- shouldFill: false
1114
+ shouldFill: false,
1115
+ onMouseDown: (event) => {
1116
+ const bounds = this.frame.bounds;
1117
+ if (event.button === 0 && (event.x < bounds.x || event.x >= bounds.x + bounds.width || event.y < bounds.y || event.y >= bounds.y + bounds.height)) {
1118
+ this.cancel();
1119
+ event.preventDefault();
1120
+ event.stopPropagation();
1121
+ }
1122
+ }
793
1123
  });
794
- this.panel = new BoxRenderable2(renderer2, {
1124
+ this.panel = new BoxRenderable3(renderer2, {
795
1125
  id: `modal-panel:${spec.id}`,
796
1126
  flexDirection: "column",
797
1127
  width: "94%",
798
1128
  maxHeight: "94%",
799
- border: true,
800
- borderStyle: "single",
801
- borderColor: colors.border,
1129
+ border: false,
802
1130
  backgroundColor: colors.background,
803
- paddingX: 1,
804
- paddingY: 0,
1131
+ shouldFill: false,
1132
+ paddingX: 2,
1133
+ paddingY: 1,
805
1134
  focusable: true,
806
1135
  onMouseDown: () => this.focus()
807
1136
  });
808
- this.frame = new NativeCornerFrame(this.panel, colors, renderer2);
809
1137
  this.root.add(this.panel);
1138
+ this.frame = new NativeCornerFrame(this.panel, colors, renderer2, this.root);
810
1139
  this.breadcrumb = this.addText(`modal-breadcrumb:${spec.id}`);
811
1140
  this.heading = this.addText(`modal-title:${spec.id}`);
812
1141
  this.description = this.addText(`modal-description:${spec.id}`);
@@ -838,13 +1167,13 @@ var NativeModalView = class {
838
1167
  selectable: false
839
1168
  });
840
1169
  this.panel.add(this.progress);
841
- const progressBlocks = new BoxRenderable2(renderer2, { id: `modal-progress-blocks:${spec.id}`, flexDirection: "column", width: "100%" });
1170
+ const progressBlocks = new BoxRenderable3(renderer2, { id: `modal-progress-blocks:${spec.id}`, flexDirection: "column", width: "100%" });
842
1171
  this.panel.add(progressBlocks);
843
1172
  this.blocks = new NativeBlockList(this.modalContext(), progressBlocks);
844
1173
  }
845
1174
  this.error = this.addText(`modal-error:${spec.id}`);
846
1175
  this.hints = this.addText(`modal-hints:${spec.id}`);
847
- this.actions = new BoxRenderable2(renderer2, { id: `modal-actions:${spec.id}`, flexDirection: "row", gap: 1, width: "100%", marginTop: 0 });
1176
+ this.actions = new BoxRenderable3(renderer2, { id: `modal-actions:${spec.id}`, flexDirection: "row", gap: 1, width: "100%", marginTop: 0 });
848
1177
  this.panel.add(this.actions);
849
1178
  this.update(spec);
850
1179
  }
@@ -1045,6 +1374,7 @@ var NativeModalView = class {
1045
1374
  this.context.emit({ type: "modal-action", id: this.spec.id, action, key: current?.item.key, index: current?.index, value });
1046
1375
  }
1047
1376
  confirm() {
1377
+ if (this.frame.closing) return;
1048
1378
  if (this.spec.kind === "progress") {
1049
1379
  this.emitAction("enter");
1050
1380
  return;
@@ -1059,6 +1389,7 @@ var NativeModalView = class {
1059
1389
  this.close(this.spec.id, { confirmed: true, key: current?.item.key ?? null, index: current?.index ?? -1, ...value === void 0 ? {} : { value } });
1060
1390
  }
1061
1391
  cancel(action = "cancel") {
1392
+ if (this.frame.closing) return;
1062
1393
  if (this.spec.deferSelection) {
1063
1394
  this.emitAction(action);
1064
1395
  return;
@@ -1066,6 +1397,7 @@ var NativeModalView = class {
1066
1397
  this.close(this.spec.id, { confirmed: false, key: this.spec.breadcrumbs?.length ? "__back__" : null, index: this.current()?.index ?? -1 });
1067
1398
  }
1068
1399
  focus() {
1400
+ if (this.frame.closing) return;
1069
1401
  if (this.filterInput) this.filterInput.focus();
1070
1402
  else if (this.editor && !this.spec.secret) this.editor.focus();
1071
1403
  else if (this.document) this.document.focus();
@@ -1095,6 +1427,7 @@ var NativeModalView = class {
1095
1427
  key.stopPropagation();
1096
1428
  return true;
1097
1429
  };
1430
+ if (this.frame.closing) return consume();
1098
1431
  if (key.eventType === "release") return false;
1099
1432
  if (key.name === "escape" || key.ctrl && key.name === "c") {
1100
1433
  this.cancel();
@@ -1163,6 +1496,11 @@ var NativeModalView = class {
1163
1496
  return false;
1164
1497
  }
1165
1498
  handlePaste(event) {
1499
+ if (this.frame.closing) {
1500
+ event.preventDefault();
1501
+ event.stopPropagation();
1502
+ return true;
1503
+ }
1166
1504
  const text = new TextDecoder().decode(event.bytes);
1167
1505
  if (this.overBudget(text, this.filterInput?.plainText ?? this.editor?.plainText ?? "")) {
1168
1506
  event.preventDefault();
@@ -1188,7 +1526,6 @@ var NativeModalView = class {
1188
1526
  applyTheme() {
1189
1527
  const colors = this.context.colors;
1190
1528
  this.panel.backgroundColor = colors.background;
1191
- this.panel.borderColor = colors.border;
1192
1529
  this.frame.setColors(colors);
1193
1530
  this.heading.fg = colors.foreground;
1194
1531
  this.heading.attributes = 1;
@@ -1247,7 +1584,7 @@ var NativeModalStack = class {
1247
1584
  dimUnderlay = (buffer) => {
1248
1585
  const panel = this.active?.panel;
1249
1586
  if (!panel || panel.isDestroyed || !panel.visible) return;
1250
- const { x, y, width, height } = panel;
1587
+ const { x, y, width, height } = this.active.frame.bounds;
1251
1588
  buffer.withBuffers((cells) => {
1252
1589
  for (let row = 0; row < cells.height; row++) for (let column = 0; column < cells.width; column++) {
1253
1590
  if (column < x || column >= x + width || row < y || row >= y + height)
@@ -1278,17 +1615,19 @@ var NativeModalStack = class {
1278
1615
  close(id, result, notify = false) {
1279
1616
  const view = this.byId.get(id);
1280
1617
  if (!view) return;
1281
- const wasActive = view === this.active;
1282
- view.destroy();
1283
- this.byId.delete(id);
1284
- this.views = this.views.filter((candidate) => candidate !== view);
1285
- if (wasActive) {
1286
- if (this.active) {
1287
- this.active.root.visible = true;
1288
- this.active.focus();
1289
- } else this.restoreFocus();
1290
- }
1291
- if (notify) this.context.emit({ type: "modal-result", id, result });
1618
+ view.frame.close(() => {
1619
+ const wasActive = view === this.active;
1620
+ view.destroy();
1621
+ this.byId.delete(id);
1622
+ this.views = this.views.filter((candidate) => candidate !== view);
1623
+ if (wasActive) {
1624
+ if (this.active) {
1625
+ this.active.root.visible = true;
1626
+ this.active.focus();
1627
+ } else this.restoreFocus();
1628
+ }
1629
+ if (notify) this.context.emit({ type: "modal-result", id, result });
1630
+ });
1292
1631
  }
1293
1632
  applyTheme() {
1294
1633
  for (const view of this.views) view.applyTheme();
@@ -1303,7 +1642,7 @@ var NativeModalStack = class {
1303
1642
 
1304
1643
  // packages/cli/src/tui/native/components/instruments.ts
1305
1644
  import {
1306
- BoxRenderable as BoxRenderable3,
1645
+ BoxRenderable as BoxRenderable4,
1307
1646
  RGBA as RGBA2,
1308
1647
  RenderableEvents as RenderableEvents3,
1309
1648
  ScrollBoxRenderable as ScrollBoxRenderable3,
@@ -1311,6 +1650,58 @@ import {
1311
1650
  TextRenderable as TextRenderable3
1312
1651
  } from "@opentui/core";
1313
1652
 
1653
+ // packages/cli/src/tui/native/components/device-history.ts
1654
+ var DeviceHistory = class {
1655
+ samples = [];
1656
+ append(sample) {
1657
+ if (!Number.isFinite(sample.at) || sample.at <= (this.samples.at(-1)?.at ?? 0)) return;
1658
+ this.samples.push(sample);
1659
+ if (this.samples.length > 120) this.samples.splice(0, this.samples.length - 120);
1660
+ }
1661
+ };
1662
+ function deviceHistorySparkline(samples, field, width) {
1663
+ const columns = Math.max(0, Math.floor(width));
1664
+ const latest = samples.at(-1)?.at;
1665
+ const values = Array(columns).fill(void 0);
1666
+ if (latest !== void 0) for (const sample of samples) {
1667
+ const x = columns - 1 - Math.round((latest - sample.at) / 2e3);
1668
+ if (x >= 0 && x < columns) values[x] = sample[field];
1669
+ }
1670
+ return values.map((value) => value === void 0 || !Number.isFinite(value) || value < 0 ? " " : "▁▂▃▄▅▆▇█"[Math.round(Math.min(100, value) * 7 / 100)]).join("");
1671
+ }
1672
+ function deviceHistoryGraph(samples, field, width) {
1673
+ const columns = Math.max(0, Math.floor(width) - 5), pixels = columns * 2;
1674
+ if (columns < 2) return "";
1675
+ const cells = Array.from({ length: 3 }, () => Array(columns).fill(0));
1676
+ const latest = samples.at(-1)?.at;
1677
+ const dots = [[1, 8], [2, 16], [4, 32], [64, 128]];
1678
+ const point = (x, y) => {
1679
+ if (x < 0 || x >= pixels || y < 0 || y >= 12) return;
1680
+ cells[Math.floor(y / 4)][Math.floor(x / 2)] |= dots[y % 4][x % 2];
1681
+ };
1682
+ let previous;
1683
+ if (latest !== void 0) for (const sample of samples) {
1684
+ const value = sample[field];
1685
+ const x = pixels - 1 - Math.round((latest - sample.at) / 1e3);
1686
+ if (x < 0) {
1687
+ previous = void 0;
1688
+ continue;
1689
+ }
1690
+ if (value === void 0 || !Number.isFinite(value) || value < 0) {
1691
+ previous = void 0;
1692
+ continue;
1693
+ }
1694
+ const y = Math.round(11 * (1 - Math.min(100, value) / 100));
1695
+ if (previous && sample.at - previous.at <= 5e3) {
1696
+ const distance = Math.max(1, Math.abs(x - previous.x), Math.abs(y - previous.y));
1697
+ for (let step = 0; step <= distance; step++) point(Math.round(previous.x + (x - previous.x) * step / distance), Math.round(previous.y + (y - previous.y) * step / distance));
1698
+ } else point(x, y);
1699
+ previous = { x, y, at: sample.at };
1700
+ }
1701
+ const labels = ["100%│", " 50%│", " 0%└"];
1702
+ return cells.map((row, y) => labels[y] + row.map((bits, x) => bits ? String.fromCodePoint(10240 + bits) : x % 4 === 0 ? "·" : " ").join("")).join("\n");
1703
+ }
1704
+
1314
1705
  // packages/cli/src/tui/terminal-cells.ts
1315
1706
  var graphemes = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1316
1707
  var extendedPictographic = /\p{Extended_Pictographic}/u;
@@ -1390,6 +1781,13 @@ function terminalCellWidth(value) {
1390
1781
 
1391
1782
  // packages/cli/src/tui/native/components/instrument-format.ts
1392
1783
  var graphemes2 = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1784
+ function formatSignalDivider(width) {
1785
+ const cells = Math.max(0, Math.floor(width));
1786
+ if (cells < 32) return "─".repeat(cells);
1787
+ const plateau = Math.min(12, Math.max(6, Math.floor(cells / 8)));
1788
+ const left = Math.floor((cells - plateau - 2) / 2);
1789
+ return "▁".repeat(left) + "╱" + "▔".repeat(plateau) + "╲" + "▁".repeat(cells - left - plateau - 2);
1790
+ }
1393
1791
  function fitInstrumentText(value, cells, marker = "~") {
1394
1792
  const text = stripTerminalSequences(normalizeTerminalLine(value));
1395
1793
  const width = Math.max(0, Math.floor(cells));
@@ -1551,7 +1949,357 @@ var SignalMeter = class extends TelemetryRow {
1551
1949
  this.update(formatSignalMeter(value, maximum));
1552
1950
  }
1553
1951
  };
1554
- var ChannelMap = class extends BoxRenderable3 {
1952
+ var InstrumentDivider = class extends TextRenderable3 {
1953
+ constructor(renderer2, actions) {
1954
+ super(renderer2, {
1955
+ id: "omnius-instrument-divider",
1956
+ width: 1,
1957
+ minWidth: 1,
1958
+ maxWidth: 1,
1959
+ height: "100%",
1960
+ flexGrow: 0,
1961
+ flexShrink: 0,
1962
+ fg: foreground,
1963
+ bg: background,
1964
+ selectable: false,
1965
+ wrapMode: "none",
1966
+ attributes: TextAttributes5.DIM,
1967
+ onMouseDown: (event) => {
1968
+ if (event.button !== 0) return;
1969
+ this.held = true;
1970
+ this.moved = false;
1971
+ this.startX = event.x;
1972
+ this.actions.start?.();
1973
+ this.attributes = TextAttributes5.BOLD;
1974
+ event.preventDefault();
1975
+ event.stopPropagation();
1976
+ },
1977
+ onMouseDrag: (event) => {
1978
+ this.handleDrag(event);
1979
+ },
1980
+ onMouseUp: (event) => {
1981
+ this.handleRelease(event);
1982
+ },
1983
+ onMouseDragEnd: (event) => {
1984
+ this.handleRelease(event);
1985
+ },
1986
+ onMouseOver: () => {
1987
+ this.attributes = TextAttributes5.BOLD;
1988
+ },
1989
+ onMouseOut: () => {
1990
+ if (!this.held) this.attributes = this.focused ? TextAttributes5.INVERSE : TextAttributes5.DIM;
1991
+ },
1992
+ onKeyDown: (key) => {
1993
+ if (key.eventType === "release") return;
1994
+ if (key.name === "return" || key.sequence === " ") this.actions.toggle();
1995
+ else if (key.name === "left" || key.name === "right") this.actions.resize(this.x + (key.name === "left" ? -1 : 1) * (key.shift ? 8 : 2));
1996
+ else return;
1997
+ key.preventDefault();
1998
+ key.stopPropagation();
1999
+ }
2000
+ });
2001
+ this.actions = actions;
2002
+ this.focusable = true;
2003
+ this.on(RenderableEvents3.FOCUSED, () => {
2004
+ this.attributes = TextAttributes5.INVERSE;
2005
+ });
2006
+ this.on(RenderableEvents3.BLURRED, () => {
2007
+ this.attributes = TextAttributes5.DIM;
2008
+ });
2009
+ }
2010
+ actions;
2011
+ collapsed = false;
2012
+ held = false;
2013
+ moved = false;
2014
+ startX = 0;
2015
+ setCollapsed(collapsed) {
2016
+ this.collapsed = collapsed;
2017
+ this.refresh();
2018
+ }
2019
+ handleDrag(event) {
2020
+ if (!this.held) return;
2021
+ if (event.x !== this.startX) this.moved = true;
2022
+ if (this.moved) this.actions.resize(event.x);
2023
+ event.preventDefault();
2024
+ event.stopPropagation();
2025
+ }
2026
+ handleRelease(event) {
2027
+ if (!this.held) return;
2028
+ this.release(event.x);
2029
+ event.preventDefault();
2030
+ event.stopPropagation();
2031
+ }
2032
+ onResize(width, height) {
2033
+ super.onResize(width, height);
2034
+ this.refresh();
2035
+ }
2036
+ refresh() {
2037
+ const rows = Array.from({ length: Math.max(0, this.height) }, (_, row) => row === Math.floor(this.height / 2) ? this.collapsed ? "‹" : "›" : "│");
2038
+ const text = rows.join("\n");
2039
+ if (this.plainText !== text) this.content = text;
2040
+ }
2041
+ release(column) {
2042
+ if (this.moved) this.actions.resize(column);
2043
+ else this.actions.toggle();
2044
+ this.held = false;
2045
+ this.actions.finish?.();
2046
+ this.attributes = this.focused ? TextAttributes5.INVERSE : TextAttributes5.DIM;
2047
+ }
2048
+ };
2049
+ var DeviceMonitor = class extends BoxRenderable4 {
2050
+ constructor(renderer2, action) {
2051
+ super(renderer2, { id: "omnius-devices", width: "100%", height: 5, minHeight: 5, maxHeight: "60%", flexShrink: 1, flexDirection: "column", visible: false });
2052
+ this.action = action;
2053
+ this.heading = new SectionBanner(renderer2, "DEVICES", "omnius-devices:heading", () => this.toggle());
2054
+ this.heading.update("DEVICES", false);
2055
+ this.add(this.heading);
2056
+ this.summary = new TextRenderable3(renderer2, { id: "omnius-devices:summary", content: "--", width: "100%", height: 1, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true });
2057
+ this.add(this.summary);
2058
+ this.compactLoad = new TextRenderable3(renderer2, { id: "omnius-devices:load-history", content: "", width: "100%", height: 1, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true });
2059
+ this.compactMemory = new TextRenderable3(renderer2, { id: "omnius-devices:memory-history", content: "", width: "100%", height: 1, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true });
2060
+ this.add(this.compactLoad);
2061
+ this.add(this.compactMemory);
2062
+ this.scroll = new ScrollBoxRenderable3(renderer2, {
2063
+ id: "omnius-devices:scroll",
2064
+ width: "100%",
2065
+ flexGrow: 1,
2066
+ flexShrink: 1,
2067
+ minHeight: 0,
2068
+ visible: false,
2069
+ scrollY: true,
2070
+ scrollX: false,
2071
+ viewportCulling: true,
2072
+ horizontalScrollbarOptions: { visible: false },
2073
+ verticalScrollbarOptions: { showArrows: false },
2074
+ contentOptions: { flexDirection: "column" }
2075
+ });
2076
+ this.add(this.scroll);
2077
+ this.notes = new TextRenderable3(renderer2, {
2078
+ id: "omnius-devices:notes",
2079
+ content: "",
2080
+ width: "100%",
2081
+ height: 1,
2082
+ flexShrink: 0,
2083
+ fg: foreground,
2084
+ bg: background,
2085
+ wrapMode: "none",
2086
+ truncate: true
2087
+ });
2088
+ this.scroll.add(this.notes);
2089
+ const controls = new BoxRenderable4(renderer2, { id: "omnius-devices:controls", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 2 });
2090
+ this.add(controls);
2091
+ this.policy = this.button(renderer2, "omnius-devices:policy", "GPU POLICY", () => {
2092
+ if (this.canSelect()) this.action?.("/gpu");
2093
+ });
2094
+ this.status = this.button(renderer2, "omnius-devices:status", "STATUS", () => this.action?.("/gpu status"));
2095
+ controls.add(this.policy);
2096
+ controls.add(this.status);
2097
+ }
2098
+ action;
2099
+ heading;
2100
+ scroll;
2101
+ summary;
2102
+ compactLoad;
2103
+ compactMemory;
2104
+ policy;
2105
+ status;
2106
+ notes;
2107
+ expanded = false;
2108
+ availableRows = 100;
2109
+ snapshot;
2110
+ historySource = "";
2111
+ rows = /* @__PURE__ */ new Map();
2112
+ button(renderer2, id, label, activate) {
2113
+ const button = new TextRenderable3(renderer2, {
2114
+ id,
2115
+ content: label,
2116
+ height: 1,
2117
+ flexShrink: 0,
2118
+ fg: foreground,
2119
+ bg: background,
2120
+ selectable: false,
2121
+ wrapMode: "none",
2122
+ truncate: true,
2123
+ onMouseDown: (event) => {
2124
+ if (event.button === 0 && button.focusable) {
2125
+ button.focus();
2126
+ activate();
2127
+ event.preventDefault();
2128
+ event.stopPropagation();
2129
+ }
2130
+ },
2131
+ onKeyDown: (key) => {
2132
+ if (key.eventType !== "release" && (key.name === "return" || key.sequence === " ") && button.focusable) {
2133
+ activate();
2134
+ key.preventDefault();
2135
+ key.stopPropagation();
2136
+ }
2137
+ }
2138
+ });
2139
+ button.focusable = Boolean(this.action);
2140
+ button.on(RenderableEvents3.FOCUSED, () => {
2141
+ button.attributes = TextAttributes5.INVERSE;
2142
+ });
2143
+ button.on(RenderableEvents3.BLURRED, () => {
2144
+ button.attributes = button.focusable ? 0 : TextAttributes5.DIM;
2145
+ });
2146
+ return button;
2147
+ }
2148
+ canSelect() {
2149
+ return Boolean(this.action && this.snapshot?.selectionAvailable && !this.snapshot.selectionLocked);
2150
+ }
2151
+ canSelectDevice(device) {
2152
+ return this.canSelect() && device.eligible === true && !device.unifiedMemory && /^GPU-[\w-]+$/.test(device.uuid);
2153
+ }
2154
+ update(snapshot) {
2155
+ this.snapshot = snapshot;
2156
+ this.visible = Boolean(snapshot) && this.availableRows >= 3;
2157
+ const historySource = `${snapshot?.source}:${snapshot?.local}`;
2158
+ if (this.historySource !== historySource) {
2159
+ for (const row of this.rows.values()) row.history.samples.splice(0);
2160
+ this.historySource = historySource;
2161
+ }
2162
+ const allowed = this.canSelect();
2163
+ if (this.policy.focused && !allowed) this.heading.focus();
2164
+ this.policy.focusable = allowed;
2165
+ this.policy.content = snapshot?.selectionLocked ? "POLICY LOCKED" : "GPU POLICY";
2166
+ this.policy.attributes = allowed ? this.policy.focused ? TextAttributes5.INVERSE : 0 : TextAttributes5.DIM;
2167
+ const keep = new Set(snapshot?.devices.map((device) => device.uuid) ?? []);
2168
+ for (const [uuid, row] of this.rows) if (!keep.has(uuid)) {
2169
+ if (row.title.focused) this.heading.focus();
2170
+ row.root.destroyRecursively();
2171
+ this.rows.delete(uuid);
2172
+ }
2173
+ for (const device of snapshot?.devices ?? []) {
2174
+ let row = this.rows.get(device.uuid);
2175
+ if (!row) {
2176
+ const root = new BoxRenderable4(this._ctx, {
2177
+ id: `omnius-device:${device.uuid}`,
2178
+ width: "100%",
2179
+ flexDirection: "column",
2180
+ flexShrink: 0,
2181
+ onSizeChange: () => {
2182
+ if (!row) return;
2183
+ for (const child of [row.title, row.load, row.memory, row.reason, row.loadGraph, row.memoryGraph]) child.width = row.root.width;
2184
+ this.refresh();
2185
+ }
2186
+ });
2187
+ const title = this.button(this._ctx, `${root.id}:select`, "", () => {
2188
+ const current = this.snapshot?.devices.find((item) => item.uuid === device.uuid);
2189
+ if (current && this.canSelectDevice(current)) this.action?.(`/gpu select ${current.uuid}`);
2190
+ });
2191
+ title.width = "100%";
2192
+ title.on(RenderableEvents3.FOCUSED, () => this.scroll.scrollChildIntoView(root.id));
2193
+ const load = new TelemetryRow(this._ctx, "LOAD", `${root.id}:load`, 16);
2194
+ const memory = new TelemetryRow(this._ctx, "VRAM", `${root.id}:memory`, 16);
2195
+ const plot = (name) => new TextRenderable3(this._ctx, { id: `${root.id}:${name}-history`, content: "", width: "100%", height: 3, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true, selectable: false });
2196
+ const loadGraph = plot("load"), memoryGraph = plot("memory"), history = new DeviceHistory();
2197
+ const reason = new TextRenderable3(this._ctx, {
2198
+ id: `${root.id}:reason`,
2199
+ content: "",
2200
+ width: "100%",
2201
+ height: 1,
2202
+ fg: foreground,
2203
+ bg: background,
2204
+ attributes: TextAttributes5.DIM,
2205
+ wrapMode: "none",
2206
+ truncate: true,
2207
+ visible: false
2208
+ });
2209
+ root.add(title);
2210
+ root.add(load);
2211
+ root.add(loadGraph);
2212
+ root.add(memory);
2213
+ root.add(memoryGraph);
2214
+ root.add(reason);
2215
+ this.scroll.add(root);
2216
+ row = { root, title, load, memory, reason, history, loadGraph, memoryGraph };
2217
+ this.rows.set(device.uuid, row);
2218
+ }
2219
+ if (snapshot?.sampledAt !== void 0) row.history.append({
2220
+ at: snapshot.sampledAt,
2221
+ load: device.utilization,
2222
+ memory: device.memoryUsedMB !== void 0 && device.memoryUsedMB >= 0 && device.memoryTotalMB !== void 0 && device.memoryTotalMB > 0 ? device.memoryUsedMB * 100 / device.memoryTotalMB : void 0
2223
+ });
2224
+ const selectable = this.canSelectDevice(device);
2225
+ if (row.title.focused && !selectable) this.heading.focus();
2226
+ row.title.focusable = selectable;
2227
+ row.title.attributes = row.title.focused ? TextAttributes5.INVERSE : device.preferred ? TextAttributes5.BOLD : selectable ? 0 : TextAttributes5.DIM;
2228
+ row.load.update(formatSignalMeter(device.utilization, 100, 6));
2229
+ const used = device.memoryUsedMB === void 0 ? "--" : instrumentNumber(device.memoryUsedMB / 1024, 1);
2230
+ const total = device.memoryTotalMB === void 0 || device.memoryTotalMB <= 0 ? "--" : instrumentNumber(device.memoryTotalMB / 1024, 1);
2231
+ row.memory.update(`${used}/${total} GiB`, device.unifiedMemory ? "UMA" : "VRAM");
2232
+ row.reason.content = device.policyReason ?? "";
2233
+ row.reason.visible = Boolean(device.policyReason);
2234
+ }
2235
+ this.refresh();
2236
+ }
2237
+ toggle() {
2238
+ this.expanded = !this.expanded;
2239
+ this.scroll.visible = this.expanded;
2240
+ this.scroll.minHeight = this.expanded ? 1 : 0;
2241
+ this.fitHeight(this.availableRows + 2);
2242
+ this.refresh();
2243
+ }
2244
+ fitHeight(railRows) {
2245
+ this.availableRows = Math.max(0, railRows - 2);
2246
+ this.visible = Boolean(this.snapshot) && this.availableRows >= 3;
2247
+ this.minHeight = Math.min(5, this.availableRows);
2248
+ this.maxHeight = this.availableRows;
2249
+ this.summary.visible = this.availableRows >= 5;
2250
+ this.compactLoad.visible = !this.expanded;
2251
+ this.compactMemory.visible = !this.expanded && this.availableRows >= 4;
2252
+ this.height = Math.min(this.availableRows, this.expanded ? Math.min(18, Math.max(6, Math.floor(railRows * 0.6))) : 5);
2253
+ }
2254
+ onResize(width, height) {
2255
+ super.onResize(width, height);
2256
+ this.refresh();
2257
+ }
2258
+ refresh() {
2259
+ if (!this.heading) return;
2260
+ const data = this.snapshot, devices = data?.devices ?? [];
2261
+ this.heading.update(data?.source === "jtop" ? "DEVICES / JETSON" : "DEVICES", this.expanded);
2262
+ const measured = devices.map((device) => device.utilization).filter((value) => value !== void 0 && Number.isFinite(value) && value >= 0);
2263
+ const on = devices.filter((device) => device.enabled === true).length;
2264
+ const policyKnown = devices.every((device) => device.enabled !== void 0);
2265
+ this.summary.content = !devices.length ? data?.source === "unavailable" ? "Telemetry unavailable" : "No local GPUs reported" : `${devices.length} GPU · ${policyKnown ? on : "--"} ON · ${measured.length === devices.length ? Math.round(Math.max(...measured)) : "--"}% PEAK`;
2266
+ this.notes.content = [
2267
+ data?.local === false ? "REMOTE" : void 0,
2268
+ data ? `${data.source.toUpperCase()} · ${data.policy.toUpperCase()} · ${data.broker.toUpperCase()}` : void 0,
2269
+ "2s/col",
2270
+ data?.warnings.length ? `${data.warnings.length} notices` : void 0
2271
+ ].filter(Boolean).join(" · ");
2272
+ const primary = devices.find((device) => device.preferred) ?? devices.find((device) => device.enabled) ?? devices[0];
2273
+ const compactHistory = primary ? this.rows.get(primary.uuid)?.history.samples ?? [] : [];
2274
+ for (const [element, field, label] of [[this.compactLoad, "load", "UTIL"], [this.compactMemory, "memory", primary?.unifiedMemory ? "UMA" : "VRAM"]]) {
2275
+ const current = primary ? field === "load" ? primary.utilization : primary.memoryUsedMB !== void 0 && primary.memoryTotalMB ? primary.memoryUsedMB / primary.memoryTotalMB * 100 : void 0 : void 0;
2276
+ const value = instrumentNumber(current, 0, "%");
2277
+ const caption = `${label}${primary?.index ?? "-"}`;
2278
+ const plotWidth = Math.max(0, this.width - caption.length - value.length - 2);
2279
+ const next = `${caption} ${deviceHistorySparkline(compactHistory, field, plotWidth)} ${value}`;
2280
+ if (element.plainText !== next) element.content = next;
2281
+ }
2282
+ for (const device of devices) {
2283
+ const row = this.rows.get(device.uuid);
2284
+ if (!row) continue;
2285
+ const state = device.enabled === void 0 ? "--" : device.enabled ? "ON" : "OFF";
2286
+ row.title.content = formatTelemetryRow(`GPU ${device.index} ${device.name}`, `${state}${device.preferred ? " *" : ""}`, row.root.width, 6);
2287
+ for (const [plot, field] of [[row.loadGraph, "load"], [row.memoryGraph, "memory"]]) {
2288
+ const next = deviceHistoryGraph(row.history.samples, field, row.root.width);
2289
+ if (plot.plainText !== next) plot.content = next;
2290
+ }
2291
+ }
2292
+ }
2293
+ focusTargets() {
2294
+ return !this.visible ? [] : [
2295
+ this.heading,
2296
+ ...this.policy.focusable ? [this.policy] : [],
2297
+ ...this.status.focusable ? [this.status] : [],
2298
+ ...this.expanded ? [...this.rows.values()].map((row) => row.title).filter((title) => title.focusable) : []
2299
+ ];
2300
+ }
2301
+ };
2302
+ var ChannelMap = class extends BoxRenderable4 {
1555
2303
  constructor(renderer2, id, inspect, reveal) {
1556
2304
  super(renderer2, {
1557
2305
  id,
@@ -1649,16 +2397,23 @@ var ChannelMap = class extends BoxRenderable3 {
1649
2397
  }
1650
2398
  }
1651
2399
  };
1652
- var InstrumentRail = class extends BoxRenderable3 {
2400
+ var InstrumentRail = class extends BoxRenderable4 {
1653
2401
  scroll;
1654
2402
  inferenceHeader;
1655
2403
  sessionHeader;
1656
2404
  channels;
2405
+ devices;
1657
2406
  inference;
1658
2407
  session;
1659
2408
  model;
1660
2409
  rows = /* @__PURE__ */ new Map();
1661
2410
  contextMeter;
2411
+ contextHeader;
2412
+ contextDetails;
2413
+ onResize(width, height) {
2414
+ super.onResize(width, height);
2415
+ this.devices?.fitHeight(height);
2416
+ }
1662
2417
  constructor(renderer2, options = {}) {
1663
2418
  const id = options.id ?? "omnius-instruments";
1664
2419
  super(renderer2, {
@@ -1696,7 +2451,7 @@ var InstrumentRail = class extends BoxRenderable3 {
1696
2451
  () => this.reveal(this.inferenceHeader)
1697
2452
  );
1698
2453
  this.scroll.add(this.inferenceHeader);
1699
- this.inference = new BoxRenderable3(renderer2, {
2454
+ this.inference = new BoxRenderable4(renderer2, {
1700
2455
  id: `${id}:inference-body`,
1701
2456
  width: "100%",
1702
2457
  flexDirection: "column",
@@ -1734,6 +2489,20 @@ var InstrumentRail = class extends BoxRenderable3 {
1734
2489
  );
1735
2490
  this.inference.add(this.contextMeter);
1736
2491
  addRow(this.inference, "state", "STATE");
2492
+ this.contextHeader = new SectionBanner(renderer2, "CONTEXT DETAILS", `${id}:context-details`, () => {
2493
+ this.contextDetails.visible = !this.contextDetails.visible;
2494
+ this.contextHeader.update("CONTEXT DETAILS", this.contextDetails.visible);
2495
+ });
2496
+ this.contextHeader.on(RenderableEvents3.FOCUSED, () => this.reveal(this.contextHeader));
2497
+ this.inference.add(this.contextHeader);
2498
+ this.contextDetails = new BoxRenderable4(renderer2, { id: `${id}:context-detail-body`, width: "100%", flexDirection: "column", flexShrink: 0, visible: false });
2499
+ this.inference.add(this.contextDetails);
2500
+ this.contextHeader.update("CONTEXT DETAILS", false);
2501
+ addRow(this.contextDetails, "messages", "MESSAGES");
2502
+ addRow(this.contextDetails, "headroom", "HEADROOM");
2503
+ addRow(this.contextDetails, "context-tools", "TOOL CALLS");
2504
+ addRow(this.contextDetails, "adversary", "ADVERSARY");
2505
+ addRow(this.contextDetails, "duration", "LAST GEN");
1737
2506
  this.sessionHeader = new SectionBanner(
1738
2507
  renderer2,
1739
2508
  "SESSION / AGENTS",
@@ -1745,7 +2514,7 @@ var InstrumentRail = class extends BoxRenderable3 {
1745
2514
  () => this.reveal(this.sessionHeader)
1746
2515
  );
1747
2516
  this.scroll.add(this.sessionHeader);
1748
- this.session = new BoxRenderable3(renderer2, {
2517
+ this.session = new BoxRenderable4(renderer2, {
1749
2518
  id: `${id}:session-body`,
1750
2519
  width: "100%",
1751
2520
  flexDirection: "column",
@@ -1767,6 +2536,8 @@ var InstrumentRail = class extends BoxRenderable3 {
1767
2536
  this.session.add(this.channels);
1768
2537
  this.session.visible = false;
1769
2538
  this.sessionHeader.update("SESSION / AGENTS", false);
2539
+ this.devices = new DeviceMonitor(renderer2, options.onAction);
2540
+ this.add(this.devices);
1770
2541
  if (options.colors) this.applyTheme(options.colors);
1771
2542
  }
1772
2543
  update(state) {
@@ -1774,6 +2545,14 @@ var InstrumentRail = class extends BoxRenderable3 {
1774
2545
  const inference = data?.inference;
1775
2546
  const session = data?.session;
1776
2547
  const set = (id, value) => this.rows.get(id).update(value);
2548
+ set("messages", instrumentNumber(inference?.contextMessages));
2549
+ set("headroom", inference?.contextHeadroom !== void 0 && Number.isFinite(inference.contextHeadroom) ? String(Math.round(inference.contextHeadroom)) : "--");
2550
+ set("context-tools", instrumentNumber(inference?.contextToolCalls));
2551
+ set("adversary", instrumentNumber(inference?.contextAdversaryOutcomes));
2552
+ set("duration", inference?.generationDurationMs === void 0 ? "--" : instrumentNumber(inference.generationDurationMs / 1e3, 1, "s"));
2553
+ this.contextHeader.visible = [inference?.contextMessages, inference?.contextHeadroom, inference?.contextToolCalls, inference?.contextAdversaryOutcomes, inference?.generationDurationMs].some((value) => value !== void 0);
2554
+ this.contextDetails.visible = this.contextHeader.visible && this.contextHeader.expanded;
2555
+ this.devices.update(data?.devices);
1777
2556
  set("route", (inference?.route || "UNKNOWN").toUpperCase());
1778
2557
  set("backend", (inference?.backend || "--").toUpperCase());
1779
2558
  const model = `MODEL ${inference?.model || "--"}`;
@@ -1826,6 +2605,7 @@ var InstrumentRail = class extends BoxRenderable3 {
1826
2605
  };
1827
2606
  visit(this);
1828
2607
  styleNativeScrollbars(this.scroll, colors);
2608
+ styleNativeScrollbars(this.devices.scroll, colors);
1829
2609
  }
1830
2610
  toggleSection(id) {
1831
2611
  const header = id === "inference" ? this.inferenceHeader : this.sessionHeader;
@@ -1840,8 +2620,10 @@ var InstrumentRail = class extends BoxRenderable3 {
1840
2620
  if (!this.visible) return [];
1841
2621
  return [
1842
2622
  this.inferenceHeader,
2623
+ ...this.inference.visible && this.contextHeader.visible ? [this.contextHeader] : [],
1843
2624
  this.sessionHeader,
1844
- ...this.session.visible ? this.channels.focusTargets() : []
2625
+ ...this.session.visible ? this.channels.focusTargets() : [],
2626
+ ...this.devices.focusTargets()
1845
2627
  ];
1846
2628
  }
1847
2629
  focusSection(index = 0) {
@@ -1860,17 +2642,1584 @@ var InstrumentRail = class extends BoxRenderable3 {
1860
2642
  }
1861
2643
  };
1862
2644
 
2645
+ // packages/cli/src/tui/command-registry.ts
2646
+ var COMMAND_SIGNATURES = [
2647
+ ["/model", "Select from available models"],
2648
+ ["/model <name>", "Switch to a specific model"],
2649
+ ["/models", "List all available models"],
2650
+ ["/endpoint", "Show current endpoint"],
2651
+ ["/endpoint <url>", "Set backend URL (auto-detects type)"],
2652
+ ["/endpoint <url> --auth <t>", "Set endpoint with Bearer auth"],
2653
+ ["/fallback", "Show remote inference fallback mode"],
2654
+ ["/fallback status", "Show remote inference fallback mode"],
2655
+ ["/fallback auto", "Automatically use remote inference after local failure (default)"],
2656
+ ["/fallback manual", "Ask before remote inference after each local failure"],
2657
+ ["/config", "Show current configuration"],
2658
+ ["/color", "Use terminal colors and thin section rules"],
2659
+ ["/color status", "Show terminal appearance preferences"],
2660
+ ["/color frill on|off|toggle|status", "Legacy text ornament preference"],
2661
+ [
2662
+ "/color flow on|off|toggle|status",
2663
+ "Legacy border animation preference"
2664
+ ],
2665
+ ["/theme", "Alias for /color"],
2666
+ ["/setup", "Open guided cloud setup; /setup local opens local model setup"],
2667
+ ["/wizard", "Alias for /setup"],
2668
+ ["/parallel", "Show current Ollama parallel inference slots"],
2669
+ ["/parallel <1-15>", "Set parallel slots (restarts Ollama, max 15)"],
2670
+ ["/gpu", "Select enabled GPUs with arrow keys and Space"],
2671
+ ["/gpu status|reset", "Inspect or reset the host-local GPU policy"],
2672
+ ["/gpu select <UUID>", "Open the GPU policy selector focused on a device"],
2673
+ [
2674
+ "/broker",
2675
+ "Show resource broker status — loaded models, in-flight loads, RAM/VRAM headroom"
2676
+ ],
2677
+ [
2678
+ "/broker evict <host>:<name>",
2679
+ "Force eviction of a tracked model (e.g. /broker evict ollama:moondream)"
2680
+ ],
2681
+ [
2682
+ "/broker threshold <ram|vram|idle> <v>",
2683
+ "Tune broker thresholds (RAM/VRAM in MB, idle in seconds)"
2684
+ ],
2685
+ ["/ollama cleanup", "Dry-run stale Ollama pool process cleanup"],
2686
+ ["/ollama cleanup --execute", "Terminate guarded stale Ollama pool runners"],
2687
+ ["/mcp", "Show MCP server/tool status and controls"],
2688
+ ["/mcp reload", "Reload MCP servers and tools"],
2689
+ ["/indicator", "Start the native system indicator and report readiness"],
2690
+ ["/indicator start", "Start the native system indicator and report readiness"],
2691
+ ["/indicator status", "Report native system indicator readiness and daemon health"],
2692
+ ["/tray", "Alias for /indicator"],
2693
+ ["/tray start", "Alias for /indicator start"],
2694
+ ["/tray status", "Alias for /indicator status"],
2695
+ ["/update", "Check for updates and auto-install"],
2696
+ ["/upgrade", "Alias for /update"],
2697
+ ["/update auto", "Auto-install updates, restart, and resume work (default)"],
2698
+ ["/update manual", "Disable auto-update (only update on /update)"],
2699
+ ["/agents", "Open the agent command center"],
2700
+ [
2701
+ "/update check",
2702
+ "Check for Omnius and Ollama updates without opening the menu"
2703
+ ],
2704
+ ["/update quick", "Install the latest Omnius package non-interactively"],
2705
+ [
2706
+ "/update full",
2707
+ "Run full non-interactive update: package, deps, rebuild, Python, cloudflared"
2708
+ ],
2709
+ ["/voice", "Toggle TTS voice feedback"],
2710
+ [
2711
+ "/voice <model>",
2712
+ "Set voice: glados, overwatch, luxtts:announcer-testchamber03, kokoro, luxtts, misotts, supertonic, or a selected voicebox_* model"
2713
+ ],
2714
+ ["/voice clone <file>", "Set voice clone reference audio (wav/mp3/ogg/flac)"],
2715
+ ["/voice asr", "Open ASR system and model selection"],
2716
+ ["/voice asr status", "Show selected ASR engine/model and activation state"],
2717
+ ["/voice asr list", "List ASR engines, models, and live/file capabilities"],
2718
+ ["/voice asr use <engine> [model]", "Select and activate an installed ASR engine/model"],
2719
+ ["/voice asr setup <engine> [model] [device=N]", "Install pinned ASR runtime/weights and activate on an exact CUDA device"],
2720
+ [
2721
+ "/voice clone glados",
2722
+ "Generate clone ref from GLaDOS for LuxTTS/MisoTTS voice cloning"
2723
+ ],
2724
+ [
2725
+ "/voice clone overwatch",
2726
+ "Generate clone ref from Overwatch for LuxTTS/MisoTTS voice cloning"
2727
+ ],
2728
+ ["/think", "Toggle thinking/reasoning mode (Qwen3, DeepSeek-R1, etc.)"],
2729
+ ["/stream", "Toggle real-time token streaming (earth syntax highlighting)"],
2730
+ ["/tree", "Switch between the action waterfall and an explorable action tree"],
2731
+ ["/tree on|off|status", "Show, hide, or inspect the action-tree presentation"],
2732
+ ["/realtime", "Toggle short spoken-dialogue mode for ASR/TTS-backed chat"],
2733
+ ["/realtime on|off|status", "Set or inspect realtime conversation mode"],
2734
+ ["/mouse", "Toggle terminal mouse tracking"],
2735
+ ["/neovim", "Toggle embedded Neovim editor (Ctrl+N to switch focus)"],
2736
+ ["/neovim <file>", "Open file in embedded Neovim editor"],
2737
+ ["/nvim <file>", "Alias for /neovim <file>"],
2738
+ ["/vim", "Toggle Vim composer editing (Esc normal; i insert)"],
2739
+ ["/vim on|off|status", "Set or inspect Vim composer editing"],
2740
+ [
2741
+ "/dream",
2742
+ "Start dream mode - creative idle exploration (writes to .omnius/dreams/)"
2743
+ ],
2744
+ [
2745
+ "/dream deep",
2746
+ "Deep dream - multi-cycle exploration with sleep architecture"
2747
+ ],
2748
+ [
2749
+ "/dream lucid",
2750
+ "Lucid dream - implements proposals, tests, evaluates in cycles"
2751
+ ],
2752
+ ["/dream stop", "Wake up - stop dreaming"],
2753
+ ["/listen", "Toggle live microphone transcription (Whisper)"],
2754
+ ["/mic", "Alias for /listen"],
2755
+ ["/listen <model>", "Set model: tiny, base, small, medium, large"],
2756
+ ["/listen manual", "Manual mode - press Enter to submit (turns off auto)"],
2757
+ ["/listen auto", "Auto-submit after 3s silence (blinking indicator)"],
2758
+ ["/listen stop", "Stop listening"],
2759
+ ["/live", "Open live sensor stream menu for video/audio context"],
2760
+ ["/live status", "Show live sensor devices, selected streams, and latest snapshot"],
2761
+ ["/live run [agent]", "Start low-latency video/audio feedback; agent enables background PFC review"],
2762
+ ["/live chat", "Start low-latency voicechat with live audio/video context and async agent review"],
2763
+ ["/livechat", "Alias for /live chat, used by the top live button"],
2764
+ ["/live camera <device>", "Select a camera and render an ASCII preview"],
2765
+ ["/live resolution [camera] 720p|1080p|1440p|4k|native", "Set per-camera live stream resolution (default 1080p)"],
2766
+ ["/live fps [camera] 1|2|4|8|12|15|24|30", "Set per-camera live stream FPS (default 8)"],
2767
+ ["/live camera-on|camera-off [camera]", "Enable or disable an individual live camera stream"],
2768
+ ["/live rotate auto|cw|ccw|180|none [camera]", "Detect or set persistent camera orientation correction"],
2769
+ ["/live infer [now|on|off]", "Toggle or run camera object/location inference"],
2770
+ ["/live clip [on|off]", "Toggle CLIP visual-memory recognition on live frames"],
2771
+ ["/live audio|asr|sounds on|off", "Toggle live audio context streams"],
2772
+ ["/live stop", "Disable all live sensor streams"],
2773
+ ["/camera", "Open compact camera preset menu shared with /live"],
2774
+ ["/camera status", "Show selected camera and per-device presets"],
2775
+ ["/camera select|preview|resolution|fps|rotate", "Manage camera source, preview, resolution, FPS, and orientation"],
2776
+ ["/paste", "Attach clipboard image content to the current or next prompt"],
2777
+ ["/image", "Open image-generation model/setup menu"],
2778
+ [
2779
+ "/image <prompt>",
2780
+ "Generate an image from a prompt and show an ASCII preview"
2781
+ ],
2782
+ ["/image --model <model> <prompt>", "Generate with an explicit image model"],
2783
+ [
2784
+ "/image setup <ollama|diffusers|sdcpp>",
2785
+ "Show setup commands for an image-generation backend"
2786
+ ],
2787
+ [
2788
+ "/image list",
2789
+ "List image models by category, quality, size, and hardware fit"
2790
+ ],
2791
+ ["/sound", "Open sound-effect model/setup menu"],
2792
+ ["/sound <prompt>", "Generate a sound effect or ambience clip from a prompt"],
2793
+ [
2794
+ "/sound --model <model> <prompt>",
2795
+ "Generate sound with an explicit audio model"
2796
+ ],
2797
+ [
2798
+ "/sound setup <diffusers|audiocraft|stable-audio|tangoflux>",
2799
+ "Show setup commands for a sound-generation backend"
2800
+ ],
2801
+ [
2802
+ "/sound list",
2803
+ "List sound models by category, quality, size, and hardware fit"
2804
+ ],
2805
+ ["/music", "Open music-generation model/setup menu"],
2806
+ ["/music <prompt>", "Generate a music clip from a prompt"],
2807
+ [
2808
+ "/music --model <model> <prompt>",
2809
+ "Generate music with an explicit music model"
2810
+ ],
2811
+ [
2812
+ "/music setup <transformers|audiocraft|stable-audio|diffusers>",
2813
+ "Show setup commands for a music-generation backend"
2814
+ ],
2815
+ [
2816
+ "/music list",
2817
+ "List music models by category, quality, size, and hardware fit"
2818
+ ],
2819
+ ["/video", "Open video-generation model/setup menu"],
2820
+ [
2821
+ "/video <prompt>",
2822
+ "Generate a short video from a prompt and show an ASCII thumbnail"
2823
+ ],
2824
+ ["/video --model <model> <prompt>", "Generate video with an explicit model"],
2825
+ [
2826
+ "/video --image <path|url> <prompt>",
2827
+ "Image-to-video animation from a still"
2828
+ ],
2829
+ [
2830
+ "/video setup <diffusers|comfyui>",
2831
+ "Show setup commands for a video-generation backend"
2832
+ ],
2833
+ [
2834
+ "/video list",
2835
+ "List video models by category, quality, size, and hardware fit"
2836
+ ],
2837
+ ["/video prewarm", "Download and warm up the selected video model"],
2838
+ ["/pointcloud", "Open point-cloud generation model/setup menu"],
2839
+ [
2840
+ "/pointcloud --image <path>",
2841
+ "Generate a 3D point cloud PLY from an image using depth estimation"
2842
+ ],
2843
+ [
2844
+ "/pointcloud --image <path> --model <model>",
2845
+ "Generate with an explicit depth model (depth-anything-v3-* or mda-*)"
2846
+ ],
2847
+ [
2848
+ "/pointcloud --image <path1,path2> --backend mda",
2849
+ "Multi-view depth fusion from comma-separated image paths"
2850
+ ],
2851
+ [
2852
+ "/pointcloud setup <depth-anything|mda>",
2853
+ "Show setup commands for a point-cloud backend"
2854
+ ],
2855
+ [
2856
+ "/pointcloud list",
2857
+ "List point-cloud models by backend, quality, and VRAM fit"
2858
+ ],
2859
+ ["/pointcloud prewarm", "Download and warm up the selected depth model"],
2860
+ ["/points <args>", "Alias for /pointcloud"],
2861
+ ["/pcl <args>", "Alias for /pointcloud"],
2862
+ ["/call", "Start voice call session (cloudflared tunnel + ASR/TTS)"],
2863
+ ["/hangup", "End active call session"],
2864
+ [
2865
+ "/queue <prompt>",
2866
+ "Queue a prompt for the next turn without interrupting the current run"
2867
+ ],
2868
+ ["/q <prompt>", "Alias for /queue <prompt>"],
2869
+ ["/retry", "Retry the last submitted request"],
2870
+ ["/undo", "Undo the last queued or saved exchange when available"],
2871
+ ["/title [name]", "Show or set the current session title"],
2872
+ [
2873
+ "/rollback [checkpoint]",
2874
+ "List checkpoints or restore an explicit checkpoint when policy allows"
2875
+ ],
2876
+ ["/background <prompt>", "Run a prompt in an independent background session"],
2877
+ ["/bg <prompt>", "Alias for /background <prompt>"],
2878
+ ["/cost", "Show session token cost breakdown"],
2879
+ ["/usage", "Show current session token count and cost"],
2880
+ ["/insights", "Show historical session insights"],
2881
+ ["/evaluate", "Evaluate last completed task (LLM quality scoring)"],
2882
+ [
2883
+ "/score",
2884
+ "Show inference capability scorecard (memory, compute, speed, models)"
2885
+ ],
2886
+ [
2887
+ "/task-type",
2888
+ "Set task type (code, document, analysis, plan, general, auto)"
2889
+ ],
2890
+ ["/stats", "Show session dashboard (metrics, tool usage, task history)"],
2891
+ [
2892
+ "/metrics",
2893
+ "Show runtime metrics: tokens, context window, throughput, and tool usage"
2894
+ ],
2895
+ ["/sessions", "Browse saved sessions"],
2896
+ ["/new", "Start a fresh chat in the current project"],
2897
+ [
2898
+ "/pause",
2899
+ "Pause after current turn finishes (gentle halt, /resume to continue)"
2900
+ ],
2901
+ [
2902
+ "/stop",
2903
+ "Kill current inference immediately and save state (/resume to continue)"
2904
+ ],
2905
+ [
2906
+ "/resume",
2907
+ "Resume a paused task or choose a saved project chat"
2908
+ ],
2909
+ ["/resume <session>", "Resume a saved project chat by id or title"],
2910
+ [
2911
+ "/destroy processes",
2912
+ "Find and kill orphaned Omnius child processes (this project)"
2913
+ ],
2914
+ [
2915
+ "/destroy processes --global",
2916
+ "Kill ALL orphaned Omnius processes system-wide"
2917
+ ],
2918
+ [
2919
+ "/destroy project",
2920
+ "Remove .omnius folder, kill all tasks, clear console, and exit"
2921
+ ],
2922
+ ["/context save", "Force-save session context to .omnius/context/"],
2923
+ ["/context restore", "Restore context from previous sessions into next task"],
2924
+ ["/context show", "Show saved session context status"],
2925
+ ["/compact", "Force context compaction now (default strategy)"],
2926
+ ["/compacted", "Review applied, held, rejected, and failed compaction attempts"],
2927
+ [
2928
+ "/compact <strategy>",
2929
+ "Compact with strategy: aggressive, decisions, errors, summary, structured"
2930
+ ],
2931
+ ["/bruteforce", "Toggle brute-force mode (auto re-engage on turn limit)"],
2932
+ [
2933
+ "/deep",
2934
+ "Toggle deep context - relaxes compaction so large models use 85% of context"
2935
+ ],
2936
+ [
2937
+ "/flow",
2938
+ "Toggle FlowState protocol - immediate action, tight loops, goal clarity"
2939
+ ],
2940
+ ["/emojis", "Toggle emoji rendering in TUI messages"],
2941
+ ["/tools", "List agent-created custom tools"],
2942
+ ["/skills", "List available AIWG skills"],
2943
+ ["/skill", "Alias for /skills"],
2944
+ ["/skills <keyword>", "Filter skills by name or trigger"],
2945
+ ["/skillify [request]", "Extract a reusable skill from the current session"],
2946
+ ["/<skill-name> [args]", "Invoke an AIWG skill directly"],
2947
+ [
2948
+ "/full-send-bless",
2949
+ "Infinite warm loop - DMN self-reflection chains tasks autonomously"
2950
+ ],
2951
+ ["/bless stop", "End blessed mode + DMN"],
2952
+ [
2953
+ "/telegram --key <token>",
2954
+ "Save Telegram bot token (persisted to settings)"
2955
+ ],
2956
+ [
2957
+ "/telegram --admin <userid>",
2958
+ "Set admin user filter (only this user can interact)"
2959
+ ],
2960
+ ["/telegram", "Toggle Telegram bridge on/off (uses saved key)"],
2961
+ ["/telegram status", "Show Telegram bridge status"],
2962
+ ["/telegram stop", "Disconnect Telegram bridge"],
2963
+ ["/telegram disable", "Persistently disable Telegram long polling"],
2964
+ ["/telegram enable", "Re-enable Telegram long polling"],
2965
+ [
2966
+ "/telegram mode auto|chat|action",
2967
+ "Set Telegram interaction routing: auto, fast chat, or action sub-agent"
2968
+ ],
2969
+ [
2970
+ "/telegram subagents <1-5>",
2971
+ "Set global Telegram work-slot limit across all chats/groups"
2972
+ ],
2973
+ [
2974
+ "/telegram model",
2975
+ "Pick the Telegram-isolated model (model list from the Telegram endpoint)"
2976
+ ],
2977
+ [
2978
+ "/telegram model <name|reset>",
2979
+ "Set or clear the Telegram-isolated model (separate from main agent)"
2980
+ ],
2981
+ [
2982
+ "/telegram endpoint",
2983
+ "Pick the Telegram-isolated backend endpoint (same menu as /endpoint, bot-scoped)"
2984
+ ],
2985
+ [
2986
+ "/telegram endpoint <url|reset> [--auth <t>]",
2987
+ "Set or clear the Telegram-isolated endpoint"
2988
+ ],
2989
+ [
2990
+ "/telegram auth",
2991
+ "Show a TUI-only one-time code for Telegram admin authentication"
2992
+ ],
2993
+ [
2994
+ "/telegram auth cancel",
2995
+ "Cancel the pending Telegram admin authentication code"
2996
+ ],
2997
+ [
2998
+ "/telegram bot <username> <text>",
2999
+ "Send a Bot API bot-to-bot message by @username"
3000
+ ],
3001
+ [
3002
+ "/telegram poll <chat> <question> | <option> [| <option>]",
3003
+ "Send a Telegram poll"
3004
+ ],
3005
+ [
3006
+ "/telegram live-photo <chat> <live_photo> <photo> [caption]",
3007
+ "Send a Telegram live photo"
3008
+ ],
3009
+ [
3010
+ "/telegram draft <chat_id> <draft_id> [text]",
3011
+ "Set or clear a Telegram message draft"
3012
+ ],
3013
+ [
3014
+ "/telegram admins <chat> [--bots]",
3015
+ "List chat administrators, optionally including bots"
3016
+ ],
3017
+ [
3018
+ "/telegram personal <user_id> <limit>",
3019
+ "Fetch last messages from a user's profile personal chat"
3020
+ ],
3021
+ [
3022
+ "/telegram access get <managed_bot_user_id>",
3023
+ "Show managed-bot access restriction settings"
3024
+ ],
3025
+ [
3026
+ "/telegram access set <managed_bot_user_id> <restricted|open> [added_user_ids]",
3027
+ "Set managed-bot access restrictions"
3028
+ ],
3029
+ [
3030
+ "/telegram tools [group <chat>|panel <chat> [policy_chat]]",
3031
+ "Show unified Telegram tool policy or send inline admin toggle buttons"
3032
+ ],
3033
+ [
3034
+ "/telegram delete-message <chat> <message> [reason]",
3035
+ "Delete one Telegram message after policy and Bot API rights checks"
3036
+ ],
3037
+ [
3038
+ "/telegram delete-messages <chat> <message,message> [reason]",
3039
+ "Delete multiple Telegram messages after policy and Bot API rights checks"
3040
+ ],
3041
+ [
3042
+ "/telegram delete-reaction <chat> <message> [--user <id>|--actor <chat>]",
3043
+ "Delete a Telegram message reaction"
3044
+ ],
3045
+ [
3046
+ "/telegram delete-reactions <chat> [--user <id>|--actor <chat>]",
3047
+ "Delete recent Telegram message reactions by user or chat"
3048
+ ],
3049
+ ["/platforms", "Show gateway platform state"],
3050
+ ["/platforms status", "Show gateway platform state"],
3051
+ ["/platforms menu", "Open interactive platform connectivity menu"],
3052
+ ["/platforms setup", "Open platform onboarding menu"],
3053
+ ["/platforms onboard", "Open platform onboarding menu"],
3054
+ ["/platforms config", "Show redacted platform adapter configuration"],
3055
+ ["/platforms enable <id>", "Enable a platform adapter"],
3056
+ ["/platforms disable <id>", "Disable a platform adapter"],
3057
+ [
3058
+ "/platforms set <id> token-env <ENV>",
3059
+ "Set adapter token environment variable"
3060
+ ],
3061
+ ["/platforms set <id> base-url <url>", "Set adapter base URL or webhook URL"],
3062
+ [
3063
+ "/platforms set <id> target <target>",
3064
+ "Set adapter default channel, room, or target"
3065
+ ],
3066
+ [
3067
+ "/platforms set <id> mode <polling|webhook>",
3068
+ "Set adapter polling or webhook mode"
3069
+ ],
3070
+ [
3071
+ "/platforms clear <id> <key>",
3072
+ "Clear saved adapter token, token-env, base-url, or target"
3073
+ ],
3074
+ ["/gateway", "Alias for /platforms"],
3075
+ ["/gateway menu", "Alias for /platforms menu"],
3076
+ [
3077
+ "/cohere",
3078
+ "Toggle COHERE cognitive commons - join distributed memory/identity mesh"
3079
+ ],
3080
+ [
3081
+ "/cohere stats",
3082
+ "Show network stats - queries in/out, model usage, peer transparency"
3083
+ ],
3084
+ ["/cohere models", "List downloaded models with COHERE exposure status"],
3085
+ ["/cohere allow <model>", "Allow a model for remote COHERE queries"],
3086
+ ["/cohere deny <model>", "Hide a model from remote COHERE queries"],
3087
+ ["/nexus", "Show nexus P2P network status"],
3088
+ ["/nexus connect", "Connect to the agent mesh network"],
3089
+ [
3090
+ "/nexus name <name>",
3091
+ "Set custom agent name (persists globally across all Omnius instances)"
3092
+ ],
3093
+ ["/nexus name", "Show current agent name"],
3094
+ ["/nexus restart", "Kill daemon and reconnect (picks up new version)"],
3095
+ ["/nexus restart --clean", "Restart + clear metering data for fresh stats"],
3096
+ ["/nexus disconnect", "Kill daemon without reconnecting"],
3097
+ ["/nexus wallet", "Show wallet address and x402 payment status"],
3098
+ ["/ipfs", "Show IPFS/Helia status"],
3099
+ ["/ipfs pin <cid>", "Pin an IPFS CID"],
3100
+ ["/ipfs publish", "Publish identity snapshot to IPFS"],
3101
+ ["/ipfs share <tool|skill> <name>", "Share a tool or skill through IPFS"],
3102
+ ["/ipfs import <cid>", "Import a shared IPFS tool or skill reference"],
3103
+ ["/sponsor", "Sponsor inference - onboarding wizard or dashboard"],
3104
+ ["/sponsor status", "Show sponsor dashboard with usage metrics"],
3105
+ ["/sponsor image <prompt>", "Generate an image on a sponsored peer"],
3106
+ ["/sponsor video <prompt>", "Generate a video on a sponsored peer"],
3107
+ ["/sponsor sound <prompt>", "Generate a sound effect on a sponsored peer"],
3108
+ ["/sponsor music <prompt>", "Generate music on a sponsored peer"],
3109
+ ["/sponsor pause", "Pause active sponsorship"],
3110
+ ["/sponsor remove", "Remove sponsorship entirely"],
3111
+ [
3112
+ "/endpoint sponsor",
3113
+ "Browse and connect to sponsored inference from the nexus mesh"
3114
+ ],
3115
+ ["/expose <backend>", "Expose local inference via libp2p (default)"],
3116
+ ["/expose <backend> --tunnel", "Expose via cloudflared tunnel"],
3117
+ ["/expose passthrough", "Forward configured /endpoint through libp2p P2P"],
3118
+ [
3119
+ "/expose forward --loadbalance",
3120
+ "Passthrough with distributed rate-limit budget"
3121
+ ],
3122
+ ["/expose config", "Interactive expose configuration menu"],
3123
+ ["/expose status", "Show gateway stats (requests, models, budget)"],
3124
+ ["/expose stop", "Stop all expose gateways"],
3125
+ ["/expose stop --tunnel", "Stop tunnel gateway only"],
3126
+ ["/expose stop --libp2p", "Stop libp2p gateway only"],
3127
+ ["/access", "Show access policy and host binding"],
3128
+ [
3129
+ "/access <loopback|lan|any>",
3130
+ "Set access policy (OMNIUS_ACCESS) and restart daemon"
3131
+ ],
3132
+ ["/k <any|lan|loopback|config>", "Quick network access helper"],
3133
+ ["/scheduler", "Scheduled tasks control panel (list/kill/toggle)"],
3134
+ ["/cron", "Cron/scheduled job management"],
3135
+ ["/cron list", "List cron jobs"],
3136
+ ["/cron add <schedule> <prompt>", "Create a new cron job"],
3137
+ ["/cron edit <id> <field> <val>", "Update a cron job field"],
3138
+ ["/cron pause <id>", "Pause a cron job"],
3139
+ ["/cron resume <id>", "Resume a paused cron job"],
3140
+ ["/cron run <id>", "Execute a cron job immediately"],
3141
+ ["/cron remove <id>", "Delete a cron job"],
3142
+ ["/cron outputs <id>", "Show saved output files for a job"],
3143
+ ["/cron tick", "Run scheduler tick"],
3144
+ ["/scheduler menu", "Interactive scheduler menu (toggle/kill)"],
3145
+ ["/scheduler list", "List all scheduled tasks and timers"],
3146
+ [
3147
+ "/scheduler kill",
3148
+ "Kill schedulers + active runs (with escalation if needed)"
3149
+ ],
3150
+ ["/reminder [list|all]", "List scoped TUI reminders"],
3151
+ [
3152
+ "/remind <when> <message>",
3153
+ "Set a scoped person notification; a due time is required"
3154
+ ],
3155
+ [
3156
+ "/remind in 30m <message>",
3157
+ "Set a scoped person notification with a relative due time"
3158
+ ],
3159
+ ["/reminders", "Alias for /reminder"],
3160
+ ["/daemon", "Show or manage the local API daemon"],
3161
+ ["/daemon <start|stop|restart|takeover>", "Control the local API daemon"],
3162
+ ["/apikey", "Show current API key (for pasting into Web UI / clients)"],
3163
+ ["/apikey copy", "Copy the API key to clipboard"],
3164
+ ["/apikey new", "Rotate to a new API key (regenerate)"],
3165
+ ["/secrets", "List registered secrets"],
3166
+ ["/secrets set <name> <value>", "Register a secret for redacted tool use"],
3167
+ ["/secrets delete <name>", "Remove a registered secret"],
3168
+ ["/key", "Alias for /apikey"],
3169
+ ["/codegraph", "Code-graph snapshot: stats, top files, recent activity"],
3170
+ [
3171
+ "/codegraph watch [N]",
3172
+ "Subscribe to live code-graph events for N seconds (default 30)"
3173
+ ],
3174
+ ["/cg", "Alias for /codegraph"],
3175
+ ["/storage", "Show .omnius storage usage and safety summary"],
3176
+ ["/files", "Show searchable working-directory file overview"],
3177
+ ["/files <query>", "Filter the working-directory file overview"],
3178
+ ["/files menu", "Open searchable workspace file selector"],
3179
+ ["/workspace", "Alias for /files"],
3180
+ ["/workspace <query>", "Alias for /files <query>"],
3181
+ ["/workspace menu", "Alias for /files menu"],
3182
+ ["/ingest <file>", "Ingest an audio, PDF, or text file into memory"],
3183
+ ["/transcribe <file>", "Alias for /ingest <file>"],
3184
+ ["/fortemi", "Manage Fortemi React bridge integration"],
3185
+ [
3186
+ "/host <host[:port]>",
3187
+ "Set bind host:port (OMNIUS_HOST) and restart daemon"
3188
+ ],
3189
+ ["/network config", "Interactive network access menu"],
3190
+ ["/p2p start", "Join the P2P agent mesh network"],
3191
+ ["/p2p status", "Show mesh peers and rooms"],
3192
+ ["/p2p stop", "Leave the mesh network"],
3193
+ ["/style", "Show current response style"],
3194
+ ["/style <preset>", "Set style: concise, balanced, verbose, pedagogical"],
3195
+ [
3196
+ "/personality <preset>",
3197
+ "Alias family for response style/personality controls"
3198
+ ],
3199
+ ["/commands auto", "Allow agent to invoke slash commands as tools"],
3200
+ ["/cmds auto", "Alias for /commands auto"],
3201
+ ["/commands manual", "Slash commands are user-only (default)"],
3202
+ [
3203
+ "/selfmodify",
3204
+ "Toggle agent self-modify mode for natural-language runtime commands"
3205
+ ],
3206
+ [
3207
+ "/selfmodify on",
3208
+ "Allow the agent to decide when to invoke self-modifying slash commands"
3209
+ ],
3210
+ [
3211
+ "/selfmodify off",
3212
+ "Disable agent self-modifying slash-command access (default)"
3213
+ ],
3214
+ ["/selfmodify status", "Show current self-modify mode"],
3215
+ [
3216
+ "/selfimprove status",
3217
+ "Show durable review/proposal backlog, leases, retries, and last error"
3218
+ ],
3219
+ [
3220
+ "/selfimprove start",
3221
+ "Enable review mode and drain eligible work with the parent model"
3222
+ ],
3223
+ ["/selfimprove stop", "Disable processing while retaining the durable backlog"],
3224
+ ["/selfimprove disable", "Alias for /selfimprove stop"],
3225
+ [
3226
+ "/selfimprove review",
3227
+ "Run evidence review and candidate generation without active promotion"
3228
+ ],
3229
+ [
3230
+ "/selfimprove autonomous",
3231
+ "Opt in to downstream candidate experiment and promotion orchestration"
3232
+ ],
3233
+ [
3234
+ "/selfimprove rollback <promotion-id> [reason]",
3235
+ "Restore the exact pre-promotion baseline and verify the rollback"
3236
+ ],
3237
+ ["/debug", "Toggle debug mode — show/hide trust_tier wrappers and REG fires"],
3238
+ ["/debug on", "Show trust_tier wrappers and REG fires in terminal"],
3239
+ ["/debug off", "Hide trust_tier wrappers and REG fires (default)"],
3240
+ ["/debug", "Toggle debug mode — show/hide trust_tier wrappers and REG fires"],
3241
+ ["/debug on", "Show trust_tier wrappers and REG fires in terminal"],
3242
+ ["/debug off", "Hide trust_tier wrappers and REG fires (default)"],
3243
+ ["/hf", "Show HF token status (set / not set)"],
3244
+ [
3245
+ "/hf <token>",
3246
+ "Save Hugging Face access token to ~/.omnius/hf_token and unblock the active gated download"
3247
+ ],
3248
+ ["/hf clear", "Remove the persisted HF token"],
3249
+ [
3250
+ "/hf cancel",
3251
+ "Cancel any pending HF token prompt and fall back immediately"
3252
+ ],
3253
+ [
3254
+ "/models",
3255
+ "Show unified ~/.omnius model store: disk usage, cached repos, last-used"
3256
+ ],
3257
+ ["/models 3d", "Select CAD/3D generation and reconstruction model adapters"],
3258
+ ["/models cad", "Select text-to-CAD model adapters such as cad0"],
3259
+ [
3260
+ "/models viewer [file]",
3261
+ "Start a temporary standalone CAD/3D viewer endpoint"
3262
+ ],
3263
+ [
3264
+ "/models hf list --modality 3d",
3265
+ "List curated and installed Hugging Face CAD/3D adapters"
3266
+ ],
3267
+ [
3268
+ "/models evict <repo>",
3269
+ "Remove a cached HF repo from the unified store to reclaim disk"
3270
+ ],
3271
+ [
3272
+ "/models evict --free <GB>",
3273
+ "LRU-evict cached models until <GB> of free disk is available"
3274
+ ],
3275
+ [
3276
+ "/models cleanup",
3277
+ "Migrate legacy per-repo .omnius/{image,video,audio}-gen caches into the unified store and delete the originals"
3278
+ ],
3279
+ [
3280
+ "/models cleanup --dry-run",
3281
+ "Preview /models cleanup without touching disk"
3282
+ ],
3283
+ ["/voicechat", "Start voice chat session (async voice conversation)"],
3284
+ ["/voicechat stop", "Stop voice chat session"],
3285
+ ["/memory", "Open the interactive memory pixel map and provenance trace"],
3286
+ ["/memory legacy", "Open the legacy list-based memory browser"],
3287
+ [
3288
+ "/metabolize [--dry-run] [--verbose] [--min-score <n>]",
3289
+ "Run memory maintenance: import deferred memory, prune episodes, compact graph stores"
3290
+ ],
3291
+ [
3292
+ "/prune [now|dry|infer|aggressive|stats] [--target <n>]",
3293
+ "Shrink knowledge.db in the background: evict low-signal graph nodes (archived), then VACUUM. 'infer' = model-guided sleep consolidation. No arg shows stats + usage"
3294
+ ],
3295
+ ["/verbose", "Toggle verbose mode"],
3296
+ ["/clear", "Clear the screen"],
3297
+ ["/help", "Show this help"],
3298
+ ["/quit", "Exit omnius"]
3299
+ ].map(([signature, description]) => ({ signature, description }));
3300
+ var PLANNED_SIGNATURES = [
3301
+ ["/provider", "Show or select provider profile"],
3302
+ ["/prompt", "Show or edit the active prompt template"],
3303
+ ["/reasoning", "Alias family for thinking/reasoning controls"],
3304
+ ["/statusbar", "Configure the status bar"],
3305
+ ["/sb", "Alias for /statusbar"],
3306
+ ["/skin", "Configure the visual skin/theme bundle"],
3307
+ ["/toolsets", "Show or select tool exposure sets"],
3308
+ ["/browser connect|disconnect|status", "Manage browser-agent connectivity"],
3309
+ ["/reload-mcp", "Reload MCP servers and tools"],
3310
+ ["/plugins", "List or manage plugins"],
3311
+ ["/status", "Show overall runtime status"],
3312
+ ["/sethome", "Set the project home for gateway/session use"],
3313
+ ["/approve", "Approve a pending gateway/admin action"],
3314
+ ["/deny", "Deny a pending gateway/admin action"]
3315
+ ].map(([signature, description]) => ({
3316
+ signature,
3317
+ description,
3318
+ implementationStatus: "planned"
3319
+ }));
3320
+ var CATEGORY_OVERRIDES = {
3321
+ model: "model",
3322
+ models: "model",
3323
+ endpoint: "network",
3324
+ fallback: "network",
3325
+ provider: "network",
3326
+ parallel: "runtime",
3327
+ gpu: "runtime",
3328
+ broker: "runtime",
3329
+ setup: "runtime",
3330
+ wizard: "runtime",
3331
+ mcp: "tools",
3332
+ mcps: "tools",
3333
+ update: "admin",
3334
+ upgrade: "admin",
3335
+ voice: "media",
3336
+ voicechat: "media",
3337
+ listen: "media",
3338
+ mic: "media",
3339
+ paste: "media",
3340
+ image: "media",
3341
+ video: "media",
3342
+ pointcloud: "media",
3343
+ points: "media",
3344
+ pcl: "media",
3345
+ ingest: "media",
3346
+ transcribe: "media",
3347
+ call: "media",
3348
+ hangup: "media",
3349
+ queue: "session",
3350
+ q: "session",
3351
+ retry: "session",
3352
+ undo: "session",
3353
+ title: "session",
3354
+ rollback: "session",
3355
+ background: "session",
3356
+ bg: "session",
3357
+ pause: "session",
3358
+ stop: "session",
3359
+ resume: "session",
3360
+ new: "session",
3361
+ context: "session",
3362
+ compact: "session",
3363
+ compacted: "session",
3364
+ gc: "session",
3365
+ sessions: "session",
3366
+ session: "session",
3367
+ dream: "runtime",
3368
+ "full-send-bless": "runtime",
3369
+ bless: "runtime",
3370
+ telegram: "gateway",
3371
+ cohere: "network",
3372
+ nexus: "network",
3373
+ ipfs: "network",
3374
+ storage: "admin",
3375
+ files: "tools",
3376
+ workspace: "tools",
3377
+ "task-type": "runtime",
3378
+ tasktype: "runtime",
3379
+ sponsor: "network",
3380
+ expose: "gateway",
3381
+ access: "network",
3382
+ host: "network",
3383
+ network: "network",
3384
+ k: "network",
3385
+ scheduler: "runtime",
3386
+ reminder: "runtime",
3387
+ daemon: "runtime",
3388
+ indicator: "ui",
3389
+ apikey: "secret",
3390
+ key: "secret",
3391
+ secrets: "secret",
3392
+ secret: "secret",
3393
+ vault: "secret",
3394
+ codegraph: "tools",
3395
+ cg: "tools",
3396
+ p2p: "network",
3397
+ tools: "tools",
3398
+ skills: "tools",
3399
+ skill: "tools",
3400
+ skillify: "tools",
3401
+ memory: "memory",
3402
+ mem: "memory",
3403
+ metabolize: "memory",
3404
+ prune: "memory",
3405
+ mouse: "ui",
3406
+ color: "ui",
3407
+ colors: "ui",
3408
+ theme: "ui",
3409
+ neovim: "ui",
3410
+ nvim: "ui",
3411
+ vim: "ui",
3412
+ emojis: "ui",
3413
+ emoji: "ui",
3414
+ statusbar: "ui",
3415
+ sb: "ui",
3416
+ skin: "ui",
3417
+ prompt: "ui",
3418
+ personality: "ui",
3419
+ reasoning: "ui",
3420
+ selfmodify: "runtime",
3421
+ selfimprove: "runtime",
3422
+ "self-improve": "runtime",
3423
+ debug: "runtime",
3424
+ selfmod: "runtime",
3425
+ "self-modify": "runtime",
3426
+ stats: "session",
3427
+ metrics: "session"
3428
+ };
3429
+ var ALIASES = {
3430
+ help: ["h", "?"],
3431
+ quit: ["exit"],
3432
+ queue: ["q"],
3433
+ background: ["bg"],
3434
+ clear: ["cls"],
3435
+ verbose: ["v"],
3436
+ config: ["cfg"],
3437
+ color: ["colors", "theme"],
3438
+ setup: ["wizard"],
3439
+ mcp: ["mcps"],
3440
+ update: ["upgrade"],
3441
+ indicator: ["tray"],
3442
+ listen: ["mic"],
3443
+ pointcloud: ["points", "pcl", "point"],
3444
+ neovim: ["nvim"],
3445
+ apikey: ["key"],
3446
+ secrets: ["secret", "vault"],
3447
+ sessions: ["session"],
3448
+ cost: ["costs"],
3449
+ evaluate: ["eval"],
3450
+ score: ["inference"],
3451
+ "task-type": ["tasktype", "tt"],
3452
+ stats: ["dashboard"],
3453
+ memory: ["mem"],
3454
+ metabolize: ["metab"],
3455
+ files: ["workspace"],
3456
+ skills: ["skill"],
3457
+ commands: ["cmds"],
3458
+ selfmodify: ["selfmod", "self-modify"],
3459
+ selfimprove: ["self-improve"],
3460
+ codegraph: ["cg"],
3461
+ endpoint: ["ep"],
3462
+ telegram: ["tg"],
3463
+ bruteforce: ["brute"],
3464
+ deep: ["deep-context"],
3465
+ flow: ["flowstate"],
3466
+ emojis: ["emoji"],
3467
+ compact: ["gc"],
3468
+ network: ["k"],
3469
+ scheduler: [],
3470
+ reminder: ["remind", "reminders"],
3471
+ statusbar: ["sb"],
3472
+ platforms: ["gateway"]
3473
+ };
3474
+ var USER_ONLY = /* @__PURE__ */ new Set([
3475
+ "fallback",
3476
+ "quit",
3477
+ "exit",
3478
+ "clear",
3479
+ "cls",
3480
+ "q",
3481
+ "queue",
3482
+ "model",
3483
+ "endpoint",
3484
+ "parallel",
3485
+ "gpu",
3486
+ "memory",
3487
+ "broker",
3488
+ "commands",
3489
+ "cmds",
3490
+ "selfmodify",
3491
+ "selfmod",
3492
+ "self-modify",
3493
+ "selfimprove",
3494
+ "self-improve",
3495
+ "debug",
3496
+ "dbg",
3497
+ "hf",
3498
+ "models",
3499
+ "mcp",
3500
+ "mcps",
3501
+ "update",
3502
+ "upgrade",
3503
+ "setup",
3504
+ "wizard",
3505
+ "telegram",
3506
+ "call",
3507
+ "hangup",
3508
+ "listen",
3509
+ "mic",
3510
+ "paste",
3511
+ "image",
3512
+ "video",
3513
+ "pointcloud",
3514
+ "points",
3515
+ "pcl",
3516
+ "ingest",
3517
+ "transcribe",
3518
+ "expose",
3519
+ "p2p",
3520
+ "ipfs",
3521
+ "storage",
3522
+ "files",
3523
+ "workspace",
3524
+ "secrets",
3525
+ "secret",
3526
+ "vault",
3527
+ "skillify",
3528
+ "dream",
3529
+ "bless",
3530
+ "full-send-bless",
3531
+ "pause",
3532
+ "stop",
3533
+ "resume",
3534
+ "new",
3535
+ "retry",
3536
+ "undo",
3537
+ "rollback",
3538
+ "background",
3539
+ "bg",
3540
+ "apikey",
3541
+ "key",
3542
+ "access",
3543
+ "host",
3544
+ "network",
3545
+ "k",
3546
+ "scheduler",
3547
+ "cron",
3548
+ "daemon",
3549
+ "indicator",
3550
+ "compacted",
3551
+ "fortemi",
3552
+ "mouse",
3553
+ "approve",
3554
+ "deny",
3555
+ "sethome"
3556
+ ]);
3557
+ var DESTRUCTIVE = /* @__PURE__ */ new Set([
3558
+ "destroy",
3559
+ "stop",
3560
+ "rollback",
3561
+ "update",
3562
+ "upgrade",
3563
+ "deny",
3564
+ "daemon",
3565
+ "ollama"
3566
+ ]);
3567
+ var NETWORKED = /* @__PURE__ */ new Set([
3568
+ "endpoint",
3569
+ "update",
3570
+ "upgrade",
3571
+ "telegram",
3572
+ "call",
3573
+ "listen",
3574
+ "mic",
3575
+ "image",
3576
+ "video",
3577
+ "pointcloud",
3578
+ "points",
3579
+ "pcl",
3580
+ "cohere",
3581
+ "nexus",
3582
+ "ipfs",
3583
+ "sponsor",
3584
+ "expose",
3585
+ "access",
3586
+ "host",
3587
+ "network",
3588
+ "k",
3589
+ "p2p",
3590
+ "daemon",
3591
+ "fortemi",
3592
+ "mcp",
3593
+ "mcps",
3594
+ "browser",
3595
+ "cron",
3596
+ "reload-mcp",
3597
+ "plugins",
3598
+ "platforms",
3599
+ "gateway"
3600
+ ]);
3601
+ var SECRET_BEARING = /* @__PURE__ */ new Set([
3602
+ "endpoint",
3603
+ "apikey",
3604
+ "key",
3605
+ "telegram",
3606
+ "secrets",
3607
+ "secret",
3608
+ "vault",
3609
+ "hf"
3610
+ ]);
3611
+ var PROFILE_GATED = /* @__PURE__ */ new Set([
3612
+ "destroy",
3613
+ "update",
3614
+ "upgrade",
3615
+ "telegram",
3616
+ "call",
3617
+ "listen",
3618
+ "mic",
3619
+ "expose",
3620
+ "access",
3621
+ "host",
3622
+ "network",
3623
+ "k",
3624
+ "p2p",
3625
+ "daemon",
3626
+ "fortemi",
3627
+ "mcp",
3628
+ "mcps",
3629
+ "ipfs",
3630
+ "approve",
3631
+ "deny",
3632
+ "sethome"
3633
+ ]);
3634
+ var REST_BLOCKED = /* @__PURE__ */ new Set([
3635
+ "pause",
3636
+ "resume",
3637
+ "new",
3638
+ "retry",
3639
+ "undo",
3640
+ "rollback",
3641
+ "background",
3642
+ "bg",
3643
+ "paste",
3644
+ "indicator"
3645
+ ]);
3646
+ var LOCAL_TUI_ONLY = /* @__PURE__ */ new Set(["indicator", "compacted", "gpu", "memory"]);
3647
+ var CANONICAL_BY_ALIAS = /* @__PURE__ */ new Map();
3648
+ for (const [canonical, aliases2] of Object.entries(ALIASES)) {
3649
+ for (const alias of aliases2) CANONICAL_BY_ALIAS.set(alias, canonical);
3650
+ }
3651
+ var DYNAMIC_COMMANDS = [];
3652
+ function getSlashHelpEntries(opts = {}) {
3653
+ const includePlanned = opts.includePlanned === true;
3654
+ const seen = /* @__PURE__ */ new Set();
3655
+ const out = [];
3656
+ for (const item of [
3657
+ ...DYNAMIC_COMMANDS,
3658
+ ...COMMAND_SIGNATURES,
3659
+ ...PLANNED_SIGNATURES
3660
+ ]) {
3661
+ if (!includePlanned && item.implementationStatus === "planned") continue;
3662
+ if (seen.has(item.signature)) continue;
3663
+ seen.add(item.signature);
3664
+ out.push([item.signature, item.description]);
3665
+ }
3666
+ return out;
3667
+ }
3668
+ var SLASH_COMMANDS = getSlashHelpEntries();
3669
+ function listCommandRegistry(opts = {}) {
3670
+ const includePlanned = opts.includePlanned !== false;
3671
+ const grouped = /* @__PURE__ */ new Map();
3672
+ for (const sig of [
3673
+ ...COMMAND_SIGNATURES,
3674
+ ...PLANNED_SIGNATURES,
3675
+ ...DYNAMIC_COMMANDS
3676
+ ]) {
3677
+ if (!includePlanned && sig.implementationStatus === "planned") continue;
3678
+ const name = commandNameFromSignature(sig.signature);
3679
+ if (!name || name.startsWith("<")) continue;
3680
+ if (!grouped.has(name)) grouped.set(name, []);
3681
+ grouped.get(name).push(sig);
3682
+ }
3683
+ return Array.from(grouped.entries()).map(
3684
+ ([name, signatures]) => buildCommandDef(name, signatures)
3685
+ );
3686
+ }
3687
+ function findCommand(name) {
3688
+ const normalized = normalizeCommandName(name);
3689
+ const all = listCommandRegistry({ includePlanned: true });
3690
+ return all.find(
3691
+ (cmd) => cmd.name === normalized || cmd.aliases.includes(normalized)
3692
+ );
3693
+ }
3694
+ function buildCommandDef(name, signatures) {
3695
+ const status = signatures.every(
3696
+ (sig) => sig.implementationStatus === "planned"
3697
+ ) ? "planned" : "implemented";
3698
+ const safety = {
3699
+ userOnly: USER_ONLY.has(name),
3700
+ destructive: DESTRUCTIVE.has(name),
3701
+ networked: NETWORKED.has(name),
3702
+ secretBearing: SECRET_BEARING.has(name),
3703
+ profileGated: PROFILE_GATED.has(name)
3704
+ };
3705
+ const aliases2 = Array.from(new Set(ALIASES[name] ?? []));
3706
+ const category = CATEGORY_OVERRIDES[name] ?? (status === "planned" ? "planned" : "core");
3707
+ const subcommands = Array.from(
3708
+ new Set(
3709
+ signatures.map((sig) => subcommandFromSignature(sig.signature)).filter((sub) => !!sub)
3710
+ )
3711
+ );
3712
+ const argsHint = inferArgsHint(signatures[0]?.signature ?? `/${name}`);
3713
+ const surfaces = {
3714
+ tui: true,
3715
+ rest: status === "implemented" && !["quit", "exit"].includes(name) && !safety.destructive && !safety.secretBearing && !REST_BLOCKED.has(name) && !LOCAL_TUI_ONLY.has(name),
3716
+ gateway: status === "implemented" && !safety.destructive && !safety.secretBearing && !LOCAL_TUI_ONLY.has(name),
3717
+ agentTool: status === "implemented" && !safety.userOnly && !safety.destructive && !safety.secretBearing && !safety.profileGated && !LOCAL_TUI_ONLY.has(name)
3718
+ };
3719
+ return {
3720
+ name,
3721
+ aliases: aliases2,
3722
+ description: signatures[0]?.description ?? "",
3723
+ category,
3724
+ argsHint,
3725
+ subcommands,
3726
+ signatures,
3727
+ surfaces,
3728
+ safety,
3729
+ implementationStatus: status
3730
+ };
3731
+ }
3732
+ function commandNameFromSignature(signature) {
3733
+ const raw = normalizeCommandName(
3734
+ (signature.trim().split(/\s+/)[0] ?? "").replace(/^\//, "")
3735
+ );
3736
+ return CANONICAL_BY_ALIAS.get(raw) ?? raw;
3737
+ }
3738
+ function subcommandFromSignature(signature) {
3739
+ const parts = signature.trim().split(/\s+/);
3740
+ if (parts.length < 2) return null;
3741
+ const sub = parts[1] ?? "";
3742
+ if (!sub || sub.startsWith("<") || sub.startsWith("[") || sub.includes("|"))
3743
+ return null;
3744
+ return sub.replace(/[|].*$/, "");
3745
+ }
3746
+ function inferArgsHint(signature) {
3747
+ return signature.trim().split(/\s+/).slice(1).join(" ");
3748
+ }
3749
+ function normalizeCommandName(name) {
3750
+ return name.replace(/^\//, "").trim().toLowerCase();
3751
+ }
3752
+
3753
+ // packages/cli/src/tui/native/components/vim-composer.ts
3754
+ var VimComposer = class {
3755
+ constructor(editor, changed) {
3756
+ this.editor = editor;
3757
+ this.changed = changed;
3758
+ }
3759
+ editor;
3760
+ changed;
3761
+ enabled = false;
3762
+ mode = "INSERT";
3763
+ count = "";
3764
+ operator = "";
3765
+ operatorCount = 1;
3766
+ anchor = 0;
3767
+ register = "";
3768
+ linewise = false;
3769
+ find = "";
3770
+ search;
3771
+ lastSearch;
3772
+ setEnabled(enabled) {
3773
+ if (enabled === this.enabled) return;
3774
+ this.enabled = enabled;
3775
+ this.mode = "INSERT";
3776
+ this.reset();
3777
+ this.editor.clearSelection();
3778
+ this.changed();
3779
+ }
3780
+ get label() {
3781
+ return this.search ? `${this.search.backward ? "?" : "/"}${this.search.value}` : this.mode;
3782
+ }
3783
+ reset() {
3784
+ this.count = "";
3785
+ this.operator = "";
3786
+ this.operatorCount = 1;
3787
+ this.find = "";
3788
+ this.search = void 0;
3789
+ }
3790
+ normal() {
3791
+ this.mode = "NORMAL";
3792
+ this.editor.clearSelection();
3793
+ this.reset();
3794
+ this.changed();
3795
+ }
3796
+ end() {
3797
+ return this.editor.editBuffer.measureText(this.editor.plainText);
3798
+ }
3799
+ takeCount() {
3800
+ const n = Math.max(1, Math.min(1e4, Number(this.count) || 1));
3801
+ this.count = "";
3802
+ return n;
3803
+ }
3804
+ select(start, end) {
3805
+ this.editor.setSelection(Math.min(start, end), Math.max(start, end));
3806
+ }
3807
+ operate(start, end, operation, linewise = false) {
3808
+ this.select(start, end);
3809
+ this.register = this.editor.getSelectedText();
3810
+ this.linewise = linewise;
3811
+ if (linewise && this.register && !this.register.endsWith("\n")) this.register += "\n";
3812
+ if (operation !== "y") this.editor.deleteSelection();
3813
+ else {
3814
+ this.editor.clearSelection();
3815
+ this.editor.cursorOffset = Math.min(start, end);
3816
+ }
3817
+ this.reset();
3818
+ this.mode = operation === "c" ? "INSERT" : "NORMAL";
3819
+ this.changed();
3820
+ }
3821
+ lineRange(count) {
3822
+ const row = this.editor.logicalCursor.row;
3823
+ const start = this.editor.editBuffer.getLineStartOffset(row);
3824
+ const next = row + count;
3825
+ let end = next < this.editor.lineCount ? this.editor.editBuffer.getLineStartOffset(next) : this.end();
3826
+ if (end === start && row > 0) return [start - 1, end];
3827
+ return [start, end];
3828
+ }
3829
+ motion(key, count) {
3830
+ const editor = this.editor;
3831
+ if (key === "0") editor.gotoLineHome();
3832
+ else if (key === "$") {
3833
+ if (count > 1) editor.gotoLine(Math.min(editor.lineCount - 1, editor.logicalCursor.row + count - 1));
3834
+ editor.gotoLineEnd();
3835
+ } else if (key === "^") {
3836
+ editor.gotoLineHome();
3837
+ const line = editor.getTextRange(editor.cursorOffset, this.end()).split("\n")[0];
3838
+ for (const char of line) {
3839
+ if (!/\s/.test(char)) break;
3840
+ editor.moveCursorRight();
3841
+ }
3842
+ } else if (key === "G") {
3843
+ editor.gotoLine(count > 1 ? Math.min(editor.lineCount - 1, count - 1) : editor.lineCount - 1);
3844
+ editor.gotoLineHome();
3845
+ } else if (key === "gg") {
3846
+ editor.gotoLine(Math.max(0, count - 1));
3847
+ editor.gotoLineHome();
3848
+ } else if (key === "e") {
3849
+ for (let i = 0; i < count; i++) {
3850
+ const start = editor.cursorOffset;
3851
+ const endWord = () => {
3852
+ editor.moveWordForward();
3853
+ const nextWord2 = editor.cursorOffset;
3854
+ editor.moveCursorLeft();
3855
+ while (editor.cursorOffset > start && /\s/u.test(editor.getTextRange(editor.cursorOffset, nextWord2).slice(0, 1))) editor.moveCursorLeft();
3856
+ return nextWord2;
3857
+ };
3858
+ const nextWord = endWord();
3859
+ if (editor.cursorOffset <= start && nextWord < this.end()) {
3860
+ editor.cursorOffset = nextWord;
3861
+ endWord();
3862
+ }
3863
+ }
3864
+ } else if (["h", "l", "j", "k", "w", "b", "left", "right", "up", "down"].includes(key)) {
3865
+ for (let i = 0; i < count; i++) {
3866
+ if (key === "h" || key === "left") editor.moveCursorLeft();
3867
+ else if (key === "l" || key === "right") editor.moveCursorRight();
3868
+ else if (key === "j" || key === "down") editor.moveCursorDown();
3869
+ else if (key === "k" || key === "up") editor.moveCursorUp();
3870
+ else if (key === "w") editor.moveWordForward();
3871
+ else editor.moveWordBackward();
3872
+ }
3873
+ } else return false;
3874
+ return true;
3875
+ }
3876
+ searchFor(value, backward) {
3877
+ if (!value) return;
3878
+ const text = this.editor.plainText;
3879
+ const character = this.editor.getTextRange(0, this.editor.cursorOffset).length;
3880
+ const index = backward ? character > 0 ? text.lastIndexOf(value, character - 1) : -1 : text.indexOf(value, character + 1);
3881
+ const found = index >= 0 ? index : backward ? text.lastIndexOf(value) : text.indexOf(value);
3882
+ if (found >= 0) this.editor.cursorOffset = this.editor.editBuffer.measureText(text.slice(0, found));
3883
+ }
3884
+ handle(key) {
3885
+ if (!this.enabled || key.eventType === "release") return false;
3886
+ const consume = () => {
3887
+ key.preventDefault();
3888
+ key.stopPropagation();
3889
+ return true;
3890
+ };
3891
+ if (this.mode === "INSERT") {
3892
+ if (key.name !== "escape") return false;
3893
+ if (this.editor.logicalCursor.col > 0) this.editor.moveCursorLeft();
3894
+ this.normal();
3895
+ return consume();
3896
+ }
3897
+ if (key.name === "escape") {
3898
+ this.normal();
3899
+ return consume();
3900
+ }
3901
+ if (key.ctrl && key.name === "r") {
3902
+ this.editor.redo();
3903
+ return consume();
3904
+ }
3905
+ if (key.ctrl || key.meta || key.name === "return" && !this.search || key.name === "tab") return false;
3906
+ const char = key.sequence.length === 1 ? key.sequence : key.name;
3907
+ if (this.search) {
3908
+ if (key.name === "return") {
3909
+ this.lastSearch = this.search;
3910
+ this.searchFor(this.search.value, this.search.backward);
3911
+ this.search = void 0;
3912
+ } else if (key.name === "backspace") this.search.value = [...this.search.value].slice(0, -1).join("");
3913
+ else if (key.sequence.length === 1) this.search.value += key.sequence;
3914
+ this.changed();
3915
+ return consume();
3916
+ }
3917
+ if (this.find) {
3918
+ if (this.find === "r") {
3919
+ const start2 = this.editor.cursorOffset;
3920
+ this.editor.moveCursorRight();
3921
+ if (this.editor.cursorOffset !== start2) {
3922
+ this.select(start2, this.editor.cursorOffset);
3923
+ this.editor.insertText(char);
3924
+ this.editor.cursorOffset = start2;
3925
+ }
3926
+ this.find = "";
3927
+ return consume();
3928
+ }
3929
+ const forward = this.find === "f" || this.find === "t", till = this.find === "t" || this.find === "T";
3930
+ const start = this.editor.cursorOffset, row = this.editor.logicalCursor.row;
3931
+ for (let i = 0; i < this.editor.plainText.length; i++) {
3932
+ const previous = this.editor.cursorOffset;
3933
+ if (forward) this.editor.moveCursorRight();
3934
+ else this.editor.moveCursorLeft();
3935
+ const current = this.editor.cursorOffset;
3936
+ if (current === previous || this.editor.logicalCursor.row !== row) {
3937
+ this.editor.cursorOffset = start;
3938
+ break;
3939
+ }
3940
+ const end = current;
3941
+ this.editor.moveCursorRight();
3942
+ const next = this.editor.cursorOffset;
3943
+ this.editor.cursorOffset = end;
3944
+ if (this.editor.getTextRange(end, next) === char) {
3945
+ if (till) {
3946
+ if (forward) this.editor.moveCursorLeft();
3947
+ else this.editor.moveCursorRight();
3948
+ }
3949
+ break;
3950
+ }
3951
+ }
3952
+ this.find = "";
3953
+ this.changed();
3954
+ return consume();
3955
+ }
3956
+ if (/^[1-9]$/.test(char) || char === "0" && this.count) {
3957
+ this.count = (this.count + char).slice(0, 5);
3958
+ return consume();
3959
+ }
3960
+ const count = this.takeCount();
3961
+ if (this.operator === "g") {
3962
+ this.operator = "";
3963
+ if (char === "g") {
3964
+ this.motion("gg", this.operatorCount);
3965
+ return consume();
3966
+ }
3967
+ }
3968
+ if (this.operator && this.operator !== "g") {
3969
+ const op = this.operator, repeat = count * this.operatorCount, start = this.editor.cursorOffset;
3970
+ if (char === op) {
3971
+ const range = this.lineRange(repeat);
3972
+ this.operate(...range, op, true);
3973
+ return consume();
3974
+ }
3975
+ if (this.motion(char, Math.min(1e4, repeat))) this.operate(start, this.editor.cursorOffset, op);
3976
+ else this.reset();
3977
+ return consume();
3978
+ }
3979
+ if (this.mode === "VISUAL" && ["d", "x", "c", "y"].includes(char)) {
3980
+ const selection = this.editor.getSelection();
3981
+ if (selection) this.operate(selection.start, selection.end, char === "x" ? "d" : char);
3982
+ return consume();
3983
+ }
3984
+ if (this.motion(char, count)) {
3985
+ if (this.mode === "VISUAL") this.select(this.anchor, this.editor.cursorOffset);
3986
+ return consume();
3987
+ }
3988
+ if (char === "g") {
3989
+ this.operator = "g";
3990
+ this.operatorCount = count;
3991
+ return consume();
3992
+ }
3993
+ if (["d", "c", "y"].includes(char)) {
3994
+ this.operator = char;
3995
+ this.operatorCount = count;
3996
+ return consume();
3997
+ }
3998
+ if (char === "i" || char === "a" || char === "I" || char === "A") {
3999
+ if (char === "a" && this.editor.cursorOffset < this.end()) this.editor.moveCursorRight();
4000
+ if (char === "I") this.motion("^", 1);
4001
+ if (char === "A") this.editor.gotoLineEnd();
4002
+ this.mode = "INSERT";
4003
+ this.reset();
4004
+ this.changed();
4005
+ return consume();
4006
+ }
4007
+ if (char === "o" || char === "O") {
4008
+ if (char === "o") {
4009
+ this.editor.gotoLineEnd();
4010
+ this.editor.newLine();
4011
+ } else {
4012
+ this.editor.gotoLineHome();
4013
+ this.editor.insertText("\n");
4014
+ this.editor.moveCursorLeft();
4015
+ }
4016
+ this.mode = "INSERT";
4017
+ this.changed();
4018
+ return consume();
4019
+ }
4020
+ if (char === "v") {
4021
+ if (this.mode === "VISUAL") this.normal();
4022
+ else {
4023
+ this.mode = "VISUAL";
4024
+ this.anchor = this.editor.cursorOffset;
4025
+ this.changed();
4026
+ }
4027
+ return consume();
4028
+ }
4029
+ if (char === "x" || char === "X") {
4030
+ const start = this.editor.cursorOffset;
4031
+ for (let i = 0; i < count; i++) char === "x" ? this.editor.moveCursorRight() : this.editor.moveCursorLeft();
4032
+ this.operate(start, this.editor.cursorOffset, "d");
4033
+ return consume();
4034
+ }
4035
+ if (char === "D" || char === "C") {
4036
+ const start = this.editor.cursorOffset;
4037
+ this.editor.gotoLineEnd();
4038
+ this.operate(start, this.editor.cursorOffset, char === "C" ? "c" : "d");
4039
+ return consume();
4040
+ }
4041
+ if (char === "p" || char === "P") {
4042
+ if (this.register) {
4043
+ if (this.linewise) {
4044
+ if (char === "P") this.editor.gotoLineHome();
4045
+ else {
4046
+ this.editor.gotoLineEnd();
4047
+ if (this.editor.cursorOffset < this.end()) this.editor.moveCursorRight();
4048
+ else this.editor.insertText("\n");
4049
+ }
4050
+ } else if (char === "p") this.editor.moveCursorRight();
4051
+ this.editor.insertText(this.register.repeat(Math.min(1e3, count)));
4052
+ }
4053
+ return consume();
4054
+ }
4055
+ if (char === "u") {
4056
+ for (let i = 0; i < count; i++) this.editor.undo();
4057
+ return consume();
4058
+ }
4059
+ if (["f", "F", "t", "T", "r"].includes(char)) {
4060
+ this.find = char;
4061
+ return consume();
4062
+ }
4063
+ if (char === "/" || char === "?") {
4064
+ this.search = { value: "", backward: char === "?" };
4065
+ this.changed();
4066
+ return consume();
4067
+ }
4068
+ if ((char === "n" || char === "N") && this.lastSearch) {
4069
+ this.searchFor(this.lastSearch.value, this.lastSearch.backward !== (char === "N"));
4070
+ return consume();
4071
+ }
4072
+ return consume();
4073
+ }
4074
+ };
4075
+
4076
+ // packages/cli/src/tui/native/components/selection-menu.ts
4077
+ import { BoxRenderable as BoxRenderable5, CliRenderEvents as CliRenderEvents3, TextAttributes as TextAttributes6, TextRenderable as TextRenderable4 } from "@opentui/core";
4078
+ var NativeSelectionMenu = class {
4079
+ constructor(renderer2, colors, copy, restoreFocus) {
4080
+ this.renderer = renderer2;
4081
+ this.colors = colors;
4082
+ this.copy = copy;
4083
+ this.restoreFocus = restoreFocus;
4084
+ renderer2.on(CliRenderEvents3.RESIZE, this.position);
4085
+ }
4086
+ renderer;
4087
+ colors;
4088
+ copy;
4089
+ restoreFocus;
4090
+ overlay;
4091
+ panel;
4092
+ frame;
4093
+ previousFocus;
4094
+ anchor = { x: 0, y: 0 };
4095
+ selectedText = "";
4096
+ get active() {
4097
+ return Boolean(this.overlay);
4098
+ }
4099
+ open(x, y, text) {
4100
+ if (!text) return;
4101
+ this.close();
4102
+ this.selectedText = text;
4103
+ this.anchor = { x, y };
4104
+ this.previousFocus = this.renderer.currentFocusedRenderable;
4105
+ this.overlay = new BoxRenderable5(this.renderer, {
4106
+ id: "omnius-selection-menu",
4107
+ position: "absolute",
4108
+ left: 0,
4109
+ top: 0,
4110
+ width: "100%",
4111
+ height: "100%",
4112
+ backgroundColor: "transparent",
4113
+ shouldFill: false,
4114
+ zIndex: 120,
4115
+ onMouseDown: (event) => {
4116
+ this.close();
4117
+ event.preventDefault();
4118
+ event.stopPropagation();
4119
+ }
4120
+ });
4121
+ this.panel = new BoxRenderable5(this.renderer, {
4122
+ id: "omnius-selection-menu-panel",
4123
+ position: "absolute",
4124
+ width: 14,
4125
+ height: 3,
4126
+ flexDirection: "column",
4127
+ paddingX: 2,
4128
+ paddingY: 1,
4129
+ backgroundColor: this.colors.background,
4130
+ onMouseDown: (event) => {
4131
+ event.preventDefault();
4132
+ event.stopPropagation();
4133
+ }
4134
+ });
4135
+ const action = new TextRenderable4(this.renderer, {
4136
+ id: "omnius-selection-copy",
4137
+ content: "COPY",
4138
+ height: 1,
4139
+ width: "100%",
4140
+ selectable: false,
4141
+ fg: this.colors.foreground,
4142
+ bg: this.colors.background,
4143
+ attributes: TextAttributes6.BOLD | TextAttributes6.INVERSE,
4144
+ onMouseDown: (event) => {
4145
+ if (event.button === 0) this.confirm();
4146
+ event.preventDefault();
4147
+ event.stopPropagation();
4148
+ }
4149
+ });
4150
+ action.focusable = true;
4151
+ this.panel.add(action);
4152
+ this.overlay.add(this.panel);
4153
+ this.frame = new NativeCornerFrame(this.panel, this.colors, this.renderer, this.overlay);
4154
+ this.renderer.root.add(this.overlay);
4155
+ this.position();
4156
+ action.focus();
4157
+ this.renderer.requestRender();
4158
+ }
4159
+ position = () => {
4160
+ if (!this.panel) return;
4161
+ const width = Math.min(14, this.renderer.terminalWidth);
4162
+ this.panel.width = width;
4163
+ this.panel.left = Math.max(0, Math.min(this.anchor.x, this.renderer.terminalWidth - width));
4164
+ this.panel.top = Math.max(0, Math.min(this.anchor.y + 1, this.renderer.terminalHeight - 3));
4165
+ };
4166
+ handleKey(key) {
4167
+ if (key.eventType !== "release") {
4168
+ if (key.name === "return" || key.sequence === " " || key.ctrl && key.shift && key.name.toLowerCase() === "c") this.confirm();
4169
+ else if (key.name === "escape" || key.name === "tab") this.close();
4170
+ }
4171
+ key.preventDefault();
4172
+ key.stopPropagation();
4173
+ }
4174
+ confirm() {
4175
+ const text = this.selectedText;
4176
+ this.close();
4177
+ this.copy(text);
4178
+ }
4179
+ applyTheme(colors) {
4180
+ this.colors = colors;
4181
+ if (!this.panel) return;
4182
+ this.panel.backgroundColor = colors.background;
4183
+ this.frame?.setColors(colors);
4184
+ for (const child of this.panel.getChildren()) if (child instanceof TextRenderable4) {
4185
+ child.fg = colors.foreground;
4186
+ child.bg = colors.background;
4187
+ }
4188
+ }
4189
+ close() {
4190
+ if (!this.overlay) return;
4191
+ const previous = this.previousFocus;
4192
+ this.previousFocus = void 0;
4193
+ this.frame?.destroy();
4194
+ this.frame = void 0;
4195
+ this.overlay.destroyRecursively();
4196
+ this.overlay = void 0;
4197
+ this.panel = void 0;
4198
+ this.selectedText = "";
4199
+ if (previous && !previous.isDestroyed && previous.visible && previous.focusable) previous.focus();
4200
+ else this.restoreFocus();
4201
+ this.renderer.requestRender();
4202
+ }
4203
+ destroy() {
4204
+ this.close();
4205
+ this.renderer.off(CliRenderEvents3.RESIZE, this.position);
4206
+ }
4207
+ };
4208
+
1863
4209
  // packages/cli/src/tui/native/components/frontend.ts
1864
4210
  var initialTheme = { mode: "system", bg: -1, accent: -1, textPrimary: -1, textDim: -1, boxColor: -1 };
1865
4211
  var NativeFrontend = class {
1866
- constructor(renderer2, emit) {
4212
+ constructor(renderer2, emit, options = {}) {
1867
4213
  this.renderer = renderer2;
1868
4214
  this.emit = emit;
1869
4215
  const colors = palette(initialTheme);
1870
4216
  this.context = { renderer: renderer2, colors, syntax: SyntaxStyle2.fromStyles({}, renderer2.nativeScene), emit, terminals: /* @__PURE__ */ new Map() };
1871
4217
  this.disposeSelectionContrast = installDefaultSelectionContrast(renderer2);
1872
- this.clipboard = createClipboard({ host: createHostClipboard({ timeoutMs: 1500 }), terminal: createRendererClipboardAdapter(renderer2) });
1873
- this.root = new BoxRenderable4(renderer2, {
4218
+ this.clipboard = options.clipboard ?? createClipboard({ host: createHostClipboard({ timeoutMs: 1500 }), terminal: createRendererClipboardAdapter(renderer2) });
4219
+ this.selectionMenu = new NativeSelectionMenu(renderer2, colors, (text) => {
4220
+ void this.copy(text);
4221
+ }, () => this.restoreFocus());
4222
+ this.root = new BoxRenderable6(renderer2, {
1874
4223
  id: "omnius-native",
1875
4224
  width: "100%",
1876
4225
  height: "100%",
@@ -1878,7 +4227,7 @@ var NativeFrontend = class {
1878
4227
  backgroundColor: colors.background
1879
4228
  });
1880
4229
  renderer2.root.add(this.root);
1881
- this.header = new BoxRenderable4(renderer2, {
4230
+ this.header = new BoxRenderable6(renderer2, {
1882
4231
  id: "omnius-header",
1883
4232
  width: "100%",
1884
4233
  minWidth: 0,
@@ -1889,9 +4238,19 @@ var NativeFrontend = class {
1889
4238
  borderColor: colors.border
1890
4239
  });
1891
4240
  this.root.add(this.header);
1892
- const headerRow = new BoxRenderable4(renderer2, { id: "omnius-header-row", width: "100%", minWidth: 0, minHeight: 1, flexDirection: "row", gap: 1 });
4241
+ this.header.renderAfter = (buffer) => {
4242
+ if (this.header.height > 0) buffer.drawText(
4243
+ formatSignalDivider(this.header.width),
4244
+ this.header.x,
4245
+ this.header.y + this.header.height - 1,
4246
+ this.context.colors.foreground,
4247
+ this.context.colors.background,
4248
+ TextAttributes7.DIM
4249
+ );
4250
+ };
4251
+ const headerRow = new BoxRenderable6(renderer2, { id: "omnius-header-row", width: "100%", minWidth: 0, minHeight: 1, flexDirection: "row", gap: 1 });
1893
4252
  this.header.add(headerRow);
1894
- this.identity = new TextRenderable4(renderer2, {
4253
+ this.identity = new TextRenderable5(renderer2, {
1895
4254
  id: "omnius-identity",
1896
4255
  content: "Omnius",
1897
4256
  fg: colors.foreground,
@@ -1901,7 +4260,7 @@ var NativeFrontend = class {
1901
4260
  truncate: true,
1902
4261
  minWidth: 18,
1903
4262
  selectable: false,
1904
- attributes: TextAttributes6.BOLD,
4263
+ attributes: TextAttributes7.BOLD,
1905
4264
  onMouseDown: (event) => {
1906
4265
  if (event.button === 0) {
1907
4266
  this.toggleHeaderPanel();
@@ -1929,16 +4288,16 @@ var NativeFrontend = class {
1929
4288
  verticalScrollbarOptions: { visible: false }
1930
4289
  });
1931
4290
  headerRow.add(this.headerActions);
1932
- this.headerPanel = new TextRenderable4(renderer2, { id: "omnius-header-panel", content: "", width: "100%", wrapMode: "word", visible: false });
4291
+ this.headerPanel = new TextRenderable5(renderer2, { id: "omnius-header-panel", content: "", width: "100%", wrapMode: "word", visible: false });
1933
4292
  this.header.add(this.headerPanel);
1934
- this.detailButton = new TextRenderable4(renderer2, {
4293
+ this.detailButton = new TextRenderable5(renderer2, {
1935
4294
  id: "omnius-details-toggle",
1936
4295
  content: "+ DETAILS",
1937
4296
  height: 1,
1938
4297
  width: 9,
1939
4298
  flexShrink: 0,
1940
4299
  fg: colors.foreground,
1941
- attributes: TextAttributes6.DIM,
4300
+ attributes: TextAttributes7.DIM,
1942
4301
  selectable: false,
1943
4302
  onMouseDown: (event) => {
1944
4303
  if (event.button === 0) {
@@ -1958,13 +4317,13 @@ var NativeFrontend = class {
1958
4317
  for (const control of [this.identity, this.detailButton]) {
1959
4318
  const attributes = control.attributes;
1960
4319
  control.on("focused", () => {
1961
- control.attributes = attributes | TextAttributes6.INVERSE;
4320
+ control.attributes = attributes | TextAttributes7.INVERSE;
1962
4321
  });
1963
4322
  control.on("blurred", () => {
1964
4323
  control.attributes = attributes;
1965
4324
  });
1966
4325
  }
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 });
4326
+ this.routeWarning = new TextRenderable5(renderer2, { id: "omnius-route-warning", content: "", width: "100%", wrapMode: "word", fg: colors.warning, attributes: TextAttributes7.BOLD | TextAttributes7.UNDERLINE, visible: false, flexShrink: 0 });
1968
4327
  this.root.add(this.routeWarning);
1969
4328
  this.tabs = new TabSelectRenderable(renderer2, {
1970
4329
  id: "omnius-agent-tabs",
@@ -2003,13 +4362,13 @@ var NativeFrontend = class {
2003
4362
  const selected = this.tabs.getSelectedIndex();
2004
4363
  const start = Math.max(0, Math.min(selected - Math.floor(count / 2), this.tabs.options.length - count));
2005
4364
  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);
4365
+ buffer.drawText("─".repeat(Math.max(0, tabWidth)), (selected - start) * tabWidth, 1, colors2.foreground, colors2.background, TextAttributes7.DIM);
2007
4366
  };
2008
4367
  this.root.add(this.tabs);
2009
- this.taskPane = new BoxRenderable4(renderer2, { id: "omnius-tasks", width: "100%", flexDirection: "column", maxHeight: 6, flexShrink: 0, overflow: "hidden", visible: false });
4368
+ this.taskPane = new BoxRenderable6(renderer2, { id: "omnius-tasks", width: "100%", flexDirection: "column", maxHeight: 6, flexShrink: 0, overflow: "hidden", visible: false });
2010
4369
  this.tasks = new NativeBlockList(this.context, this.taskPane);
2011
4370
  this.root.add(this.taskPane);
2012
- this.body = new BoxRenderable4(renderer2, { id: "omnius-body", width: "100%", flexDirection: "row", flexGrow: 1, minHeight: 1 });
4371
+ this.body = new BoxRenderable6(renderer2, { id: "omnius-body", width: "100%", flexDirection: "row", flexGrow: 1, minHeight: 1 });
2013
4372
  this.root.add(this.body);
2014
4373
  this.scroll = new ScrollBoxRenderable4(renderer2, {
2015
4374
  id: "omnius-conversation",
@@ -2024,6 +4383,15 @@ var NativeFrontend = class {
2024
4383
  viewportCulling: true,
2025
4384
  verticalScrollbarOptions: { showArrows: false, trackOptions: { foregroundColor: colors.dim, backgroundColor: colors.background } },
2026
4385
  contentOptions: { flexDirection: "column", onMouseDown: (event) => {
4386
+ if (event.button === 2) {
4387
+ const selected = this.renderer.getSelection()?.getSelectedText();
4388
+ if (selected) {
4389
+ this.selectionMenu.open(event.x, event.y, selected);
4390
+ event.preventDefault();
4391
+ event.stopPropagation();
4392
+ }
4393
+ return;
4394
+ }
2027
4395
  if (!this.selectionArmed || event.button !== 0 || !event.target) return;
2028
4396
  this.selectionArmed = false;
2029
4397
  let block = event.target;
@@ -2043,13 +4411,39 @@ var NativeFrontend = class {
2043
4411
  }
2044
4412
  event.preventDefault();
2045
4413
  event.stopPropagation();
4414
+ }, onMouseDrag: (event) => {
4415
+ if (!event.isDragging || !event.target || !this.renderer.getSelection()?.isDragging) return;
4416
+ let container = this.renderer.getSelectionContainer();
4417
+ while (container) {
4418
+ let current = event.target;
4419
+ while (current && current !== container) current = current.parent;
4420
+ if (current) break;
4421
+ this.renderer.updateSelection(event.target, event.x, event.y);
4422
+ const next = this.renderer.getSelectionContainer();
4423
+ if (next === container) break;
4424
+ container = next;
4425
+ }
2046
4426
  } }
2047
4427
  });
2048
4428
  this.body.add(this.scroll);
2049
4429
  this.content = new NativeBlockList(this.context, this.scroll);
2050
- this.instruments = new InstrumentRail(renderer2, { colors, onInspectAgent: (id) => this.emitAction("view", id) });
4430
+ this.instrumentDivider = new InstrumentDivider(renderer2, {
4431
+ resize: (column) => this.resizeInstruments(column),
4432
+ toggle: () => this.toggleDetails(),
4433
+ start: () => {
4434
+ this.sidebarDragWidth = this.sidebarWidth ?? Math.floor(this.renderer.terminalWidth * 0.38);
4435
+ },
4436
+ finish: () => {
4437
+ this.sidebarDragWidth = void 0;
4438
+ }
4439
+ });
4440
+ this.body.add(this.instrumentDivider);
4441
+ this.root.onMouseDrag = (event) => this.instrumentDivider.handleDrag(event);
4442
+ this.root.onMouseDragEnd = (event) => this.instrumentDivider.handleRelease(event);
4443
+ this.root.onMouseUp = (event) => this.instrumentDivider.handleRelease(event);
4444
+ this.instruments = new InstrumentRail(renderer2, { colors, onInspectAgent: (id) => this.emitAction("view", id), onAction: (command2) => this.emitAction(command2) });
2051
4445
  this.body.add(this.instruments);
2052
- this.embeddedPane = new BoxRenderable4(renderer2, {
4446
+ this.embeddedPane = new BoxRenderable6(renderer2, {
2053
4447
  id: "omnius-editor-pane",
2054
4448
  width: "50%",
2055
4449
  flexShrink: 0,
@@ -2060,7 +4454,7 @@ var NativeFrontend = class {
2060
4454
  visible: false
2061
4455
  });
2062
4456
  this.body.add(this.embeddedPane);
2063
- this.liveButton = new TextRenderable4(renderer2, {
4457
+ this.liveButton = new TextRenderable5(renderer2, {
2064
4458
  id: "omnius-live-button",
2065
4459
  content: "↓ Live",
2066
4460
  width: "100%",
@@ -2083,7 +4477,7 @@ var NativeFrontend = class {
2083
4477
  });
2084
4478
  this.liveButton.focusable = true;
2085
4479
  this.root.add(this.liveButton);
2086
- this.queue = new TextRenderable4(renderer2, { id: "omnius-steering-queue", content: "", width: "100%", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, visible: false, flexShrink: 0 });
4480
+ this.queue = new TextRenderable5(renderer2, { id: "omnius-steering-queue", content: "", width: "100%", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, visible: false, flexShrink: 0 });
2087
4481
  this.root.add(this.queue);
2088
4482
  this.suggestions = new NativeSelectRenderable(renderer2, {
2089
4483
  id: "omnius-suggestions",
@@ -2102,9 +4496,9 @@ var NativeFrontend = class {
2102
4496
  this.suggestions.onActivate = () => this.acceptSuggestion();
2103
4497
  this.suggestions.on("itemSelected", () => this.acceptSuggestion());
2104
4498
  this.root.add(this.suggestions);
2105
- this.status = new BoxRenderable4(renderer2, { id: "omnius-live-status", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 2, visible: false });
4499
+ this.status = new BoxRenderable6(renderer2, { id: "omnius-live-status", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 2, visible: false });
2106
4500
  this.root.add(this.status);
2107
- this.footer = new BoxRenderable4(renderer2, {
4501
+ this.footer = new BoxRenderable6(renderer2, {
2108
4502
  id: "omnius-input-box",
2109
4503
  width: "100%",
2110
4504
  flexShrink: 0,
@@ -2114,9 +4508,19 @@ var NativeFrontend = class {
2114
4508
  flexDirection: "column"
2115
4509
  });
2116
4510
  this.root.add(this.footer);
2117
- const inputRow = new BoxRenderable4(renderer2, { id: "omnius-input-row", width: "100%", flexDirection: "row", gap: 1 });
4511
+ this.footer.renderAfter = (buffer) => {
4512
+ if (this.footer.height > 0) buffer.drawText(
4513
+ formatSignalDivider(this.footer.width),
4514
+ this.footer.x,
4515
+ this.footer.y,
4516
+ this.context.colors.foreground,
4517
+ this.context.colors.background,
4518
+ TextAttributes7.DIM
4519
+ );
4520
+ };
4521
+ const inputRow = new BoxRenderable6(renderer2, { id: "omnius-input-row", width: "100%", flexDirection: "row", gap: 1 });
2118
4522
  this.footer.add(inputRow);
2119
- this.prompt = new TextRenderable4(renderer2, { id: "omnius-prompt", content: "❯", fg: colors.accent, wrapMode: "none", selectable: false, flexShrink: 0 });
4523
+ this.prompt = new TextRenderable5(renderer2, { id: "omnius-prompt", content: "❯", fg: colors.accent, wrapMode: "none", selectable: false, flexShrink: 0 });
2120
4524
  inputRow.add(this.prompt);
2121
4525
  this.editor = new TextareaRenderable2(renderer2, {
2122
4526
  id: "omnius-input",
@@ -2136,32 +4540,63 @@ var NativeFrontend = class {
2136
4540
  onCursorChange: () => this.editorChanged(),
2137
4541
  onSubmit: () => this.submit()
2138
4542
  });
4543
+ this.vimMode = new TextRenderable5(renderer2, {
4544
+ id: "omnius-vim-mode",
4545
+ content: "INSERT",
4546
+ height: 1,
4547
+ maxWidth: 12,
4548
+ flexShrink: 0,
4549
+ fg: colors.foreground,
4550
+ attributes: TextAttributes7.DIM,
4551
+ wrapMode: "none",
4552
+ truncate: true,
4553
+ visible: false,
4554
+ selectable: false
4555
+ });
4556
+ inputRow.add(this.vimMode);
4557
+ this.vimComposer = new VimComposer(this.editor, () => {
4558
+ this.vimMode.visible = this.vimComposer.enabled;
4559
+ this.vimMode.content = this.vimComposer.label;
4560
+ this.editorChanged();
4561
+ this.renderer.requestRender();
4562
+ });
2139
4563
  inputRow.add(this.editor);
2140
- this.enhance = new BoxRenderable4(renderer2, { id: "omnius-enhance", flexDirection: "row", gap: 1, flexShrink: 0, visible: false });
4564
+ this.enhance = new BoxRenderable6(renderer2, { id: "omnius-enhance", flexDirection: "row", gap: 1, flexShrink: 0, visible: false });
2141
4565
  inputRow.add(this.enhance);
2142
- this.activity = new TextRenderable4(renderer2, { id: "omnius-activity", content: "", flexGrow: 1, minWidth: 0, height: 1, wrapMode: "none", truncate: true, visible: false, fg: colors.accent });
4566
+ this.activity = new TextRenderable5(renderer2, { id: "omnius-activity", content: "", flexGrow: 1, minWidth: 0, height: 1, wrapMode: "none", truncate: true, visible: false, fg: colors.accent });
2143
4567
  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 });
4568
+ this.media = new BoxRenderable6(renderer2, { id: "omnius-media", height: 1, minWidth: 0, flexShrink: 1, flexDirection: "row", gap: 1, visible: false });
4569
+ this.mediaText = new TextRenderable5(renderer2, { id: "omnius-media-text", content: "", flexGrow: 1, minWidth: 0, wrapMode: "none", truncate: true, fg: colors.accent });
2146
4570
  this.media.add(this.mediaText);
2147
- this.micTrack = new TextRenderable4(renderer2, { id: "omnius-mic-meter", width: 12, height: 1, flexShrink: 0, fg: colors.foreground, visible: false });
4571
+ this.micTrack = new TextRenderable5(renderer2, { id: "omnius-mic-meter", width: 12, height: 1, flexShrink: 0, fg: colors.foreground, visible: false });
2148
4572
  this.media.add(this.micTrack);
2149
4573
  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 });
4574
+ this.copyFeedback = new TextRenderable5(renderer2, {
4575
+ id: "omnius-copy-feedback",
4576
+ content: "",
4577
+ height: 1,
4578
+ flexShrink: 0,
4579
+ fg: colors.foreground,
4580
+ attributes: TextAttributes7.DIM,
4581
+ selectable: false,
4582
+ visible: false
4583
+ });
4584
+ this.status.add(this.copyFeedback);
4585
+ this.metricsRow = new BoxRenderable6(renderer2, { id: "omnius-metrics-row", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 1 });
4586
+ this.keyHints = new TextRenderable5(renderer2, { id: "omnius-key-hints", content: "F1 HELP TAB FOCUS", height: 1, flexShrink: 0, attributes: TextAttributes7.DIM, fg: colors.foreground, selectable: false });
2152
4587
  this.metricsRow.add(this.keyHints);
2153
- this.metricsStartActions = new BoxRenderable4(renderer2, { id: "omnius-metrics-start-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
4588
+ this.metricsStartActions = new BoxRenderable6(renderer2, { id: "omnius-metrics-start-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
2154
4589
  this.metricsRow.add(this.metricsStartActions);
2155
- this.metrics = new TextRenderable4(renderer2, { id: "omnius-metrics", content: "", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, flexShrink: 1, flexGrow: 1, minWidth: 0 });
4590
+ this.metrics = new TextRenderable5(renderer2, { id: "omnius-metrics", content: "", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, flexShrink: 1, flexGrow: 1, minWidth: 0 });
2156
4591
  this.metricsRow.add(this.metrics);
2157
- this.metricsEndActions = new BoxRenderable4(renderer2, { id: "omnius-metrics-end-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
4592
+ this.metricsEndActions = new BoxRenderable6(renderer2, { id: "omnius-metrics-end-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
2158
4593
  this.metricsRow.add(this.metricsEndActions);
2159
4594
  this.root.add(this.metricsRow);
2160
4595
  this.modals = new NativeModalStack(this.context, () => this.restoreFocus());
2161
4596
  renderer2.keyInput.on("keypress", this.onKey);
2162
4597
  renderer2.keyInput.on("paste", this.onPaste);
2163
- renderer2.on(CliRenderEvents2.FRAME, this.onFrame);
2164
- renderer2.on(CliRenderEvents2.RESIZE, this.onResize);
4598
+ renderer2.on(CliRenderEvents4.FRAME, this.onFrame);
4599
+ renderer2.on(CliRenderEvents4.RESIZE, this.onResize);
2165
4600
  this.applyTheme(initialTheme);
2166
4601
  this.editor.focus();
2167
4602
  }
@@ -2170,9 +4605,11 @@ var NativeFrontend = class {
2170
4605
  root;
2171
4606
  scroll;
2172
4607
  editor;
4608
+ vimComposer;
2173
4609
  content;
2174
4610
  modals;
2175
4611
  context;
4612
+ selectionMenu;
2176
4613
  header;
2177
4614
  identity;
2178
4615
  headerActions;
@@ -2183,6 +4620,7 @@ var NativeFrontend = class {
2183
4620
  tasks;
2184
4621
  body;
2185
4622
  instruments;
4623
+ instrumentDivider;
2186
4624
  detailButton;
2187
4625
  embeddedPane;
2188
4626
  liveButton;
@@ -2190,6 +4628,7 @@ var NativeFrontend = class {
2190
4628
  suggestions;
2191
4629
  footer;
2192
4630
  prompt;
4631
+ vimMode;
2193
4632
  enhance;
2194
4633
  metrics;
2195
4634
  keyHints;
@@ -2202,6 +4641,8 @@ var NativeFrontend = class {
2202
4641
  mediaText;
2203
4642
  micTrack;
2204
4643
  clipboard;
4644
+ copyFeedback;
4645
+ copyFeedbackTimer;
2205
4646
  state;
2206
4647
  themeSignature = "";
2207
4648
  headerSignature = "";
@@ -2219,6 +4660,9 @@ var NativeFrontend = class {
2219
4660
  disposeSelectionContrast;
2220
4661
  actionSignatures = /* @__PURE__ */ new WeakMap();
2221
4662
  detailsOpen;
4663
+ narrowDetailsOpen = false;
4664
+ sidebarWidth;
4665
+ sidebarDragWidth;
2222
4666
  headerPanelCollapsed = false;
2223
4667
  inputEventPending = false;
2224
4668
  viewportSignature = "";
@@ -2317,8 +4761,8 @@ var NativeFrontend = class {
2317
4761
  for (const child of [...parent.getChildren()]) child.destroy();
2318
4762
  for (const action of actions) {
2319
4763
  const colors = this.context.colors;
2320
- const attributes = action.disabled ? TextAttributes6.DIM : action.active ? TextAttributes6.BOLD | TextAttributes6.UNDERLINE : 0;
2321
- const button = new TextRenderable4(this.renderer, {
4764
+ const attributes = action.disabled ? TextAttributes7.DIM : action.active ? TextAttributes7.BOLD | TextAttributes7.UNDERLINE : 0;
4765
+ const button = new TextRenderable5(this.renderer, {
2322
4766
  id: `${prefix}:${action.id}`,
2323
4767
  content: styledText(action.label),
2324
4768
  flexShrink: 0,
@@ -2341,16 +4785,16 @@ var NativeFrontend = class {
2341
4785
  button.focusable = !action.disabled;
2342
4786
  if (!action.disabled) {
2343
4787
  button.on("focused", () => {
2344
- button.attributes = attributes | TextAttributes6.INVERSE;
4788
+ button.attributes = attributes | TextAttributes7.INVERSE;
2345
4789
  });
2346
4790
  button.on("blurred", () => {
2347
4791
  button.attributes = attributes;
2348
4792
  });
2349
4793
  button.onMouseOver = () => {
2350
- button.attributes = attributes | TextAttributes6.UNDERLINE | (button.focused ? TextAttributes6.INVERSE : 0);
4794
+ button.attributes = attributes | TextAttributes7.UNDERLINE | (button.focused ? TextAttributes7.INVERSE : 0);
2351
4795
  };
2352
4796
  button.onMouseOut = () => {
2353
- button.attributes = attributes | (button.focused ? TextAttributes6.INVERSE : 0);
4797
+ button.attributes = attributes | (button.focused ? TextAttributes7.INVERSE : 0);
2354
4798
  };
2355
4799
  }
2356
4800
  parent.add(button);
@@ -2366,14 +4810,15 @@ var NativeFrontend = class {
2366
4810
  renderActivity() {
2367
4811
  const activity = this.state?.activity;
2368
4812
  const stage = activity?.stage === "idle" ? void 0 : activity?.stage;
2369
- this.activity.visible = Boolean(activity?.processing || activity?.label || stage);
2370
- this.activity.content = `${activity?.processing ? "RUN · " : ""}${activity?.label || stage || ""}`;
2371
- this.status.visible = this.activity.visible || this.media.visible;
4813
+ const reading = activity?.label || stage || this.state?.instruments?.runtimeStatus || "";
4814
+ this.activity.visible = Boolean(activity?.processing || reading);
4815
+ this.activity.content = `${activity?.processing ? "RUN · " : ""}${reading}`;
4816
+ this.status.visible = this.activity.visible || this.media.visible || this.copyFeedback.visible;
2372
4817
  }
2373
4818
  renderMedia() {
2374
4819
  const media = this.state?.media;
2375
4820
  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;
4821
+ this.status.visible = this.activity.visible || this.media.visible || this.copyFeedback.visible;
2377
4822
  if (!media) return;
2378
4823
  this.mediaText.content = [
2379
4824
  media.recording ? "● REC" : void 0,
@@ -2397,25 +4842,7 @@ var NativeFrontend = class {
2397
4842
  this.headerSignature = "";
2398
4843
  this.metricsActionsSignature = "";
2399
4844
  const colors = this.context.colors = palette(theme);
2400
- for (const [name, style] of Object.entries({
2401
- default: { fg: colors.foreground },
2402
- conceal: { fg: colors.foreground },
2403
- "markup.heading": { fg: colors.accent, bold: true },
2404
- "markup.strong": { fg: colors.foreground, bold: true },
2405
- "markup.italic": { fg: colors.foreground, italic: true },
2406
- "markup.link": { fg: colors.accent, underline: true },
2407
- "markup.link.url": { fg: colors.dim },
2408
- "markup.raw": { fg: colors.accent },
2409
- "markup.list": { fg: colors.accent },
2410
- "markup.quote": { fg: colors.dim },
2411
- comment: { fg: colors.dim, italic: true },
2412
- keyword: { fg: colors.accent, bold: true },
2413
- string: { fg: colors.success },
2414
- number: { fg: colors.warning },
2415
- function: { fg: colors.foreground },
2416
- type: { fg: colors.accent },
2417
- punctuation: { fg: colors.dim }
2418
- })) this.context.syntax.registerStyle(name, style);
4845
+ installNativeSyntax(this.context.syntax, colors);
2419
4846
  this.root.backgroundColor = colors.background;
2420
4847
  this.header.backgroundColor = this.footer.backgroundColor = colors.background;
2421
4848
  this.header.borderColor = this.footer.borderColor = this.embeddedPane.borderColor = colors.border;
@@ -2423,6 +4850,7 @@ var NativeFrontend = class {
2423
4850
  this.headerPanel.fg = colors.foreground;
2424
4851
  this.routeWarning.fg = colors.warning;
2425
4852
  this.prompt.fg = this.activity.fg = this.liveButton.fg = colors.accent;
4853
+ this.vimMode.fg = colors.foreground;
2426
4854
  this.micTrack.fg = colors.foreground;
2427
4855
  this.queue.fg = this.metrics.fg = colors.dim;
2428
4856
  this.editor.backgroundColor = this.editor.focusedBackgroundColor = colors.background;
@@ -2439,6 +4867,10 @@ var NativeFrontend = class {
2439
4867
  styleNativeScrollbars(this.headerActions, colors);
2440
4868
  styleNativeScrollbars(this.scroll, colors);
2441
4869
  this.instruments.applyTheme(colors);
4870
+ this.instrumentDivider.fg = colors.foreground;
4871
+ this.instrumentDivider.bg = colors.background;
4872
+ this.copyFeedback.fg = colors.foreground;
4873
+ this.selectionMenu.applyTheme(colors);
2442
4874
  this.content.applyTheme();
2443
4875
  this.tasks.applyTheme();
2444
4876
  this.modals?.applyTheme();
@@ -2450,23 +4882,47 @@ var NativeFrontend = class {
2450
4882
  this.update(this.state);
2451
4883
  }
2452
4884
  toggleDetails() {
2453
- this.detailsOpen = !(this.detailsOpen ?? this.renderer.terminalWidth >= 110);
4885
+ if (this.renderer.terminalWidth >= 110) this.detailsOpen = !(this.detailsOpen ?? true);
4886
+ else this.narrowDetailsOpen = !this.narrowDetailsOpen;
4887
+ this.layoutInstruments();
4888
+ this.renderer.requestRender();
4889
+ }
4890
+ resizeInstruments(column) {
4891
+ if (this.renderer.terminalWidth < 110 || this.embeddedPane.visible) return;
4892
+ const width = this.renderer.terminalWidth;
4893
+ const requested = Math.max(0, this.body.x + width - Math.round(column) - 1);
4894
+ if (requested <= 4) {
4895
+ this.detailsOpen = false;
4896
+ if (this.sidebarDragWidth !== void 0) this.sidebarWidth = this.sidebarDragWidth;
4897
+ } else {
4898
+ this.detailsOpen = true;
4899
+ this.sidebarWidth = Math.max(24, Math.min(width - 41, requested));
4900
+ }
2454
4901
  this.layoutInstruments();
2455
4902
  this.renderer.requestRender();
2456
4903
  }
2457
4904
  layoutInstruments() {
2458
4905
  const width = this.renderer.terminalWidth;
4906
+ const focusedBeforeLayout = this.renderer.currentFocusedRenderable;
2459
4907
  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";
4908
+ const shown = !this.embeddedPane.visible && (wide ? this.detailsOpen ?? true : this.narrowDetailsOpen);
4909
+ const horizontal = wide || this.embeddedPane.visible && width >= 80;
4910
+ this.body.flexDirection = horizontal ? "row" : "column";
4911
+ this.scroll.height = horizontal ? "100%" : "auto";
4912
+ this.scroll.flexBasis = horizontal ? "auto" : 0;
2462
4913
  this.instruments.visible = shown;
2463
- const railWidth = Math.floor(width * 0.38);
4914
+ this.instrumentDivider.visible = wide && !this.embeddedPane.visible;
4915
+ this.instrumentDivider.setCollapsed(!shown);
4916
+ const railWidth = Math.max(24, Math.min(width - 41, this.sidebarWidth ?? Math.floor(width * 0.38)));
2464
4917
  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)));
4918
+ this.scroll.width = wide && this.instrumentDivider.visible ? width - (shown ? railWidth : 0) - 1 : this.embeddedPane.visible && width >= 80 ? "50%" : "100%";
4919
+ const inspectorHeight = Math.max(4, Math.min(9, Math.floor(this.renderer.terminalHeight / 3)));
4920
+ this.instruments.height = wide ? "100%" : inspectorHeight;
4921
+ this.instruments.maxHeight = wide ? "100%" : inspectorHeight;
4922
+ this.instruments.minHeight = wide ? 1 : inspectorHeight;
2467
4923
  this.instruments.flexGrow = 0;
2468
- this.instruments.flexShrink = wide ? 0 : 1;
2469
- this.instruments.border = wide ? ["left"] : ["top"];
4924
+ this.instruments.flexShrink = 0;
4925
+ this.instruments.border = wide ? [] : ["top"];
2470
4926
  this.instruments.borderStyle = "single";
2471
4927
  this.instruments.borderColor = this.context.colors.border;
2472
4928
  this.instruments.paddingLeft = wide ? 1 : 0;
@@ -2484,8 +4940,8 @@ var NativeFrontend = class {
2484
4940
  this.detailButton.width = detailWidth;
2485
4941
  this.keyHints.content = width < 60 ? "F1 HELP" : "F1 HELP TAB FOCUS";
2486
4942
  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) {
4943
+ for (let focused = focusedBeforeLayout; focused; focused = focused.parent) {
4944
+ if (!shown && focused === this.instruments || !this.instrumentDivider.visible && focused === this.instrumentDivider || !this.headerActions.visible && focused === this.headerActions) {
2489
4945
  this.restoreFocus();
2490
4946
  break;
2491
4947
  }
@@ -2553,8 +5009,11 @@ var NativeFrontend = class {
2553
5009
  }
2554
5010
  submit() {
2555
5011
  if (this.paused || this.state?.input.enabled === false) return;
2556
- if (this.suggestions.visible && this.editor.plainText.startsWith("/") && this.acceptSuggestion()) return;
2557
5012
  const text = this.editor.plainText;
5013
+ const command2 = /^\/([^\s]+)/.exec(text.trimStart())?.[1];
5014
+ const suggestion = String(this.suggestions.getSelectedOption()?.value ?? "");
5015
+ if (this.suggestions.visible && command2 && !findCommand(command2) && suggestion.startsWith(text.trimEnd()) && this.acceptSuggestion()) return;
5016
+ this.suggestions.visible = false;
2558
5017
  if (text.trim() && this.history[0] !== text) {
2559
5018
  this.history.unshift(text);
2560
5019
  this.history.length = Math.min(this.history.length, 1e3);
@@ -2599,6 +5058,10 @@ var NativeFrontend = class {
2599
5058
  this.emit({ type: "action", action, ...id === void 0 ? {} : { id } });
2600
5059
  }
2601
5060
  onKey = (key) => {
5061
+ if (this.selectionMenu.active) {
5062
+ this.selectionMenu.handleKey(key);
5063
+ return;
5064
+ }
2602
5065
  if (this.modals.active) {
2603
5066
  this.modals.active.handleKey(key);
2604
5067
  return;
@@ -2675,6 +5138,7 @@ var NativeFrontend = class {
2675
5138
  this.shortcut("ctrl-u", key);
2676
5139
  return;
2677
5140
  }
5141
+ if (this.renderer.currentFocusedRenderable === this.editor && !this.paused && this.state?.input.enabled !== false && this.vimComposer.handle(key)) return;
2678
5142
  if (key.name === "escape") {
2679
5143
  if (this.renderer.currentFocusedRenderable !== this.editor) {
2680
5144
  this.restoreFocus();
@@ -2769,6 +5233,10 @@ var NativeFrontend = class {
2769
5233
  }
2770
5234
  }
2771
5235
  onPaste = (event) => {
5236
+ if (this.selectionMenu.active) {
5237
+ event.preventDefault();
5238
+ return;
5239
+ }
2772
5240
  if (this.modals.active) {
2773
5241
  this.modals.active.handlePaste(event);
2774
5242
  return;
@@ -2785,8 +5253,27 @@ var NativeFrontend = class {
2785
5253
  else if (!this.paused && this.state?.input.enabled !== false) this.editor.focus();
2786
5254
  }
2787
5255
  async copy(text) {
2788
- const value = text ?? this.renderer.getSelection()?.getSelectedText() ?? this.editor.getSelectedText();
2789
- if (value) await this.clipboard.writeText(value, { destination: "best-available" });
5256
+ const value = text ?? (this.renderer.getSelection()?.getSelectedText() || this.editor.getSelectedText());
5257
+ if (!value) return;
5258
+ let message;
5259
+ try {
5260
+ const result = await this.clipboard.writeText(value, { destination: "best-available" });
5261
+ message = result.host.status === "written" ? "COPIED" : result.terminal.status === "attempted" ? "COPY SENT TO TERMINAL" : "COPY UNAVAILABLE";
5262
+ } catch {
5263
+ message = "COPY FAILED";
5264
+ }
5265
+ if (this.destroyed) return;
5266
+ if (this.copyFeedbackTimer) clearTimeout(this.copyFeedbackTimer);
5267
+ this.copyFeedback.content = message;
5268
+ this.copyFeedback.visible = true;
5269
+ this.status.visible = true;
5270
+ this.renderer.requestRender();
5271
+ this.copyFeedbackTimer = setTimeout(() => {
5272
+ this.copyFeedbackTimer = void 0;
5273
+ this.copyFeedback.visible = false;
5274
+ this.status.visible = this.activity.visible || this.media.visible;
5275
+ this.renderer.requestRender();
5276
+ }, 3e3);
2790
5277
  }
2791
5278
  async paste() {
2792
5279
  const result = await this.clipboard.read({ preferredTypes: ["text/plain;charset=utf-8", "text/plain"] });
@@ -2816,6 +5303,10 @@ var NativeFrontend = class {
2816
5303
  case "input-action":
2817
5304
  this.inputAction(command2.action, command2.value);
2818
5305
  break;
5306
+ case "composer-vim":
5307
+ this.vimComposer.setEnabled(command2.enabled);
5308
+ this.restoreFocus();
5309
+ break;
2819
5310
  case "mouse":
2820
5311
  this.renderer.useMouse = command2.enabled;
2821
5312
  break;
@@ -2826,6 +5317,7 @@ var NativeFrontend = class {
2826
5317
  this.renderer.setTerminalTitle(command2.text);
2827
5318
  break;
2828
5319
  case "modal-open":
5320
+ this.selectionMenu.close();
2829
5321
  this.modals.open(command2.modal);
2830
5322
  break;
2831
5323
  case "modal-update":
@@ -2894,6 +5386,7 @@ var NativeFrontend = class {
2894
5386
  this.embeddedPane.add(terminal);
2895
5387
  }
2896
5388
  terminal.focus();
5389
+ this.layoutInstruments();
2897
5390
  }
2898
5391
  closeEmbedded(id) {
2899
5392
  this.context.terminals.get(id)?.destroy();
@@ -2901,17 +5394,20 @@ var NativeFrontend = class {
2901
5394
  if (this.embeddedId === id) {
2902
5395
  this.embeddedId = void 0;
2903
5396
  this.embeddedPane.visible = false;
5397
+ this.layoutInstruments();
2904
5398
  this.restoreFocus();
2905
5399
  }
2906
5400
  }
2907
5401
  async dispose() {
2908
5402
  if (this.destroyed) return;
2909
5403
  this.destroyed = true;
5404
+ if (this.copyFeedbackTimer) clearTimeout(this.copyFeedbackTimer);
5405
+ this.selectionMenu.destroy();
2910
5406
  this.disposeSelectionContrast();
2911
5407
  this.renderer.keyInput.off("keypress", this.onKey);
2912
5408
  this.renderer.keyInput.off("paste", this.onPaste);
2913
- this.renderer.off(CliRenderEvents2.FRAME, this.onFrame);
2914
- this.renderer.off(CliRenderEvents2.RESIZE, this.onResize);
5409
+ this.renderer.off(CliRenderEvents4.FRAME, this.onFrame);
5410
+ this.renderer.off(CliRenderEvents4.RESIZE, this.onResize);
2915
5411
  this.modals.destroy();
2916
5412
  this.content.destroy();
2917
5413
  this.tasks.destroy();
@@ -2922,7 +5418,7 @@ var NativeFrontend = class {
2922
5418
  };
2923
5419
 
2924
5420
  // packages/cli/src/tui/native/components/cursor.ts
2925
- import { CliRenderEvents as CliRenderEvents3 } from "@opentui/core";
5421
+ import { CliRenderEvents as CliRenderEvents5 } from "@opentui/core";
2926
5422
  function installTerminalCursorDefaults(renderer2, session) {
2927
5423
  const reset = new TextEncoder().encode("\x1B]112\x07");
2928
5424
  let disposed = false;
@@ -2972,11 +5468,11 @@ function installTerminalCursorDefaults(renderer2, session) {
2972
5468
  const dispose = () => {
2973
5469
  if (disposed) return;
2974
5470
  disposed = true;
2975
- renderer2.off(CliRenderEvents3.FRAME, afterFrame);
2976
- renderer2.off(CliRenderEvents3.DESTROY, dispose);
5471
+ renderer2.off(CliRenderEvents5.FRAME, afterFrame);
5472
+ renderer2.off(CliRenderEvents5.DESTROY, dispose);
2977
5473
  };
2978
- renderer2.on(CliRenderEvents3.FRAME, afterFrame);
2979
- renderer2.once(CliRenderEvents3.DESTROY, dispose);
5474
+ renderer2.on(CliRenderEvents5.FRAME, afterFrame);
5475
+ renderer2.once(CliRenderEvents5.DESTROY, dispose);
2980
5476
  return { restore, dispose };
2981
5477
  }
2982
5478
 
@@ -3052,10 +5548,10 @@ try {
3052
5548
  maxFps: 60,
3053
5549
  clearOnShutdown: false
3054
5550
  });
3055
- renderer.on(CliRenderEvents4.RENDER_ERROR, (event) => {
5551
+ renderer.on(CliRenderEvents6.RENDER_ERROR, (event) => {
3056
5552
  void fail(event.error);
3057
5553
  });
3058
- renderer.on(CliRenderEvents4.HANDLER_ERROR, (event) => {
5554
+ renderer.on(CliRenderEvents6.HANDLER_ERROR, (event) => {
3059
5555
  void fail(event.error);
3060
5556
  });
3061
5557
  renderer.on("error", (event) => {