omnius 1.0.862 → 1.0.864

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 (62) 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 +3569 -315
  13. package/docs/DISCOVERY.json +2080 -451
  14. package/docs/DISCOVERY.md +48 -2
  15. package/docs/work-orders/desktop-projects.md +42 -0
  16. package/docs/work-orders/evidence/desktop-indicator-registration-20261016.json +23 -0
  17. package/docs/work-orders/evidence/inference-device-policy-readonly-20261016.json +56 -0
  18. package/docs/work-orders/evidence/instrument-100x30.png +0 -0
  19. package/docs/work-orders/evidence/instrument-120x36.png +0 -0
  20. package/docs/work-orders/evidence/instrument-160x48.png +0 -0
  21. package/docs/work-orders/evidence/instrument-60x20.png +0 -0
  22. package/docs/work-orders/evidence/instrument-80x24.png +0 -0
  23. package/docs/work-orders/evidence/instrument-clean-transcript-120x36.png +0 -0
  24. package/docs/work-orders/evidence/instrument-collapse-120x36.png +0 -0
  25. package/docs/work-orders/evidence/instrument-devices-expanded-120x36.png +0 -0
  26. package/docs/work-orders/evidence/instrument-divider-collapsed-160x48.png +0 -0
  27. package/docs/work-orders/evidence/instrument-divider-resized-160x48.png +0 -0
  28. package/docs/work-orders/evidence/instrument-help-scroll-120x36.png +0 -0
  29. package/docs/work-orders/evidence/instrument-package-artifact-20261016.json +18 -0
  30. package/docs/work-orders/evidence/instrument-package-install-20261016.json +69 -0
  31. package/docs/work-orders/evidence/instrument-popup-api-120x36.png +0 -0
  32. package/docs/work-orders/evidence/instrument-popup-api-input-120x36.png +0 -0
  33. package/docs/work-orders/evidence/instrument-popup-config-120x36.png +0 -0
  34. package/docs/work-orders/evidence/instrument-popup-dark-120x36.png +0 -0
  35. package/docs/work-orders/evidence/instrument-popup-dark-60x20.png +0 -0
  36. package/docs/work-orders/evidence/instrument-popup-light-120x36.png +0 -0
  37. package/docs/work-orders/evidence/instrument-popup-model-120x36.png +0 -0
  38. package/docs/work-orders/evidence/instrument-popup-network-120x36.png +0 -0
  39. package/docs/work-orders/evidence/instrument-production-terminal-live.json +11818 -0
  40. package/docs/work-orders/evidence/instrument-report-broker-120x36.png +0 -0
  41. package/docs/work-orders/evidence/instrument-report-gpu-120x36.png +0 -0
  42. package/docs/work-orders/evidence/instrument-report-stats-120x36.png +0 -0
  43. package/docs/work-orders/evidence/instrument-semantic-components-20261016-updated.json +80 -0
  44. package/docs/work-orders/evidence/instrument-semantic-components-20261016.json +76 -0
  45. package/docs/work-orders/evidence/instrument-targeted-verification-20261016-updated.json +430 -0
  46. package/docs/work-orders/evidence/instrument-targeted-verification-20261016.json +408 -0
  47. package/docs/work-orders/evidence/instrument-terminal-light-120x36.png +0 -0
  48. package/docs/work-orders/evidence/instrument-vim-composer-120x36.png +0 -0
  49. package/docs/work-orders/evidence/tray-native-host-checking.png +0 -0
  50. package/docs/work-orders/evidence/tray-native-host-dark.png +0 -0
  51. package/docs/work-orders/evidence/tray-native-host-offline.png +0 -0
  52. package/docs/work-orders/evidence/tray-native-host.png +0 -0
  53. package/docs/work-orders/evidence/tray-project-3.png +0 -0
  54. package/docs/work-orders/evidence/tray-project-4.png +0 -0
  55. package/docs/work-orders/evidence/tray-project-5.png +0 -0
  56. package/docs/work-orders/evidence/tray-projects-live.json +338 -0
  57. package/docs/work-orders/evidence/tray-projects-menu.png +0 -0
  58. package/docs/work-orders/instrument-command-surface-audit.md +304 -0
  59. package/docs/work-orders/instrument-tui.md +156 -0
  60. package/docs/work-orders/native-tui-renderer.md +8 -6
  61. package/npm-shrinkwrap.json +6 -6
  62. package/package.json +1 -1
@@ -2,18 +2,19 @@
2
2
  import { createWriteStream } from "node:fs";
3
3
  import { createInterface } from "node:readline";
4
4
  import { PassThrough } from "node:stream";
5
- import { CliRenderEvents as CliRenderEvents2, NativeSession, RGBA as RGBA2, createCliRenderer } from "@opentui/core";
5
+ import { CliRenderEvents as CliRenderEvents5, NativeSession, RGBA as RGBA3, createCliRenderer } from "@opentui/core";
6
6
 
7
7
  // packages/cli/src/tui/native/components/frontend.ts
8
8
  import {
9
- BoxRenderable as BoxRenderable3,
10
- CliRenderEvents,
9
+ BoxRenderable as BoxRenderable5,
10
+ CliRenderEvents as CliRenderEvents3,
11
11
  EmbeddedTerminalRenderable as EmbeddedTerminalRenderable2,
12
- ScrollBoxRenderable as ScrollBoxRenderable3,
12
+ ScrollBoxRenderable as ScrollBoxRenderable4,
13
13
  SyntaxStyle as SyntaxStyle2,
14
14
  TabSelectRenderable,
15
15
  TextareaRenderable as TextareaRenderable2,
16
- TextRenderable as TextRenderable3,
16
+ TextRenderable as TextRenderable4,
17
+ TextAttributes as TextAttributes6,
17
18
  createClipboard,
18
19
  createHostClipboard,
19
20
  createRendererClipboardAdapter
@@ -26,22 +27,23 @@ import {
26
27
  EmbeddedTerminalRenderable,
27
28
  ImageRenderable,
28
29
  MarkdownRenderable,
30
+ RenderableEvents,
29
31
  ScrollBoxRenderable,
30
32
  TextRenderable
31
33
  } from "@opentui/core";
32
34
 
33
35
  // packages/cli/src/tui/native/components/text.ts
34
- import { RGBA, StyledText } from "@opentui/core";
36
+ import { CliRenderEvents, RGBA, StyledText, TextAttributes } from "@opentui/core";
35
37
 
36
38
  // packages/cli/src/tui/native/text.ts
37
39
  function stripTextControls(value) {
38
- return value.replace(/\x1b\][\s\S]*?(?:\x07|\x1b\\)/g, "").replace(/\x1b[P_^X][\s\S]*?\x1b\\/g, "").replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\x1b[\s\S]/g, "").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "");
40
+ return value.replace(/\x1b\][\s\S]*?(?:\x07|\x1b\\)/g, "").replace(/\x1b[P_^X][\s\S]*?\x1b\\/g, "").replace(/\x1b\[[-0-?]*[ -/]*[@-~]/g, "").replace(/\x1b[\s\S]/g, "").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "");
39
41
  }
40
42
  function nativeTextFromAnsi(value) {
41
43
  const result = [];
42
44
  let style = { attributes: 0 };
43
45
  let previous = 0;
44
- const pattern = /\x1b(?:\[[0-?]*[ -/]*[@-~]|\][\s\S]*?(?:\x07|\x1b\\)|[P_^X][\s\S]*?\x1b\\|[\s\S])/g;
46
+ const pattern = /\x1b(?:\[[-0-?]*[ -/]*[@-~]|\][\s\S]*?(?:\x07|\x1b\\)|[P_^X][\s\S]*?\x1b\\|[\s\S])/g;
45
47
  const append = (text) => {
46
48
  const clean = stripTextControls(text);
47
49
  if (!clean) return;
@@ -60,7 +62,7 @@ function nativeTextFromAnsi(value) {
60
62
  else delete style.link;
61
63
  continue;
62
64
  }
63
- if (!/^\x1b\[[\d;:]*m$/.test(seq)) continue;
65
+ if (!/^\x1b\[[-\d;:]*m$/.test(seq)) continue;
64
66
  const codes = seq.slice(2, -1).replace(/:/g, ";").split(";").map(Number);
65
67
  for (let i = 0; i < codes.length; i++) {
66
68
  const code = codes[i];
@@ -89,7 +91,10 @@ function nativeTextFromAnsi(value) {
89
91
  else if (code >= 40 && code <= 47) style.bg = `index:${code - 40}`;
90
92
  else if (code >= 100 && code <= 107) style.bg = `index:${code - 100 + 8}`;
91
93
  else if ((code === 38 || code === 48) && codes[i + 1] === 5) {
92
- style[code === 38 ? "fg" : "bg"] = `index:${Math.max(0, Math.min(255, codes[i + 2] ?? 0))}`;
94
+ const role = code === 38 ? "fg" : "bg";
95
+ const index = codes[i + 2] ?? 0;
96
+ if (index < 0) delete style[role];
97
+ else style[role] = `index:${Math.min(255, index)}`;
93
98
  i += 2;
94
99
  } else if ((code === 38 || code === 48) && codes[i + 1] === 2) {
95
100
  const rgb = codes.slice(i + 2, i + 5).map(
@@ -106,20 +111,6 @@ function nativeTextFromAnsi(value) {
106
111
  }
107
112
 
108
113
  // packages/cli/src/tui/native/components/text.ts
109
- var colorCache = /* @__PURE__ */ new Map();
110
- function nativeColor(value, background = false) {
111
- if (value === void 0 || value === -1 || value === "default")
112
- return background ? RGBA.defaultBackground() : RGBA.defaultForeground();
113
- const key = String(value);
114
- if (key === "index:-1") return background ? RGBA.defaultBackground() : RGBA.defaultForeground();
115
- let color = colorCache.get(key);
116
- if (!color) {
117
- color = typeof value === "number" || /^\d+$/.test(key) ? RGBA.fromIndex(Number(value)) : key.startsWith("index:") ? RGBA.fromIndex(Number(key.slice(6))) : RGBA.fromHex(key);
118
- if (colorCache.size >= 1024) colorCache.delete(colorCache.keys().next().value);
119
- colorCache.set(key, color);
120
- }
121
- return color;
122
- }
123
114
  function plainText(value) {
124
115
  return typeof value === "string" ? stripTextControls(value) : (value ?? []).map((span) => stripTextControls(span.text)).join("");
125
116
  }
@@ -130,27 +121,280 @@ function styledText(value) {
130
121
  return new StyledText(textSpans(value).map((span) => ({
131
122
  __isChunk: true,
132
123
  text: span.text,
133
- ...span.fg === void 0 ? {} : { fg: nativeColor(span.fg) },
134
- ...span.bg === void 0 ? {} : { bg: nativeColor(span.bg, true) },
135
124
  ...span.attributes === void 0 ? {} : { attributes: span.attributes },
136
125
  ...span.link ? { link: { url: span.link } } : {}
137
126
  })));
138
127
  }
139
- function palette(theme) {
128
+ function palette(_theme) {
129
+ const background2 = RGBA.defaultBackground();
130
+ const foreground2 = RGBA.defaultForeground();
140
131
  return {
141
- background: nativeColor(theme.bg, true),
142
- foreground: nativeColor(theme.textPrimary),
143
- accent: nativeColor(theme.accent),
144
- dim: nativeColor(theme.textDim),
145
- border: nativeColor(theme.boxColor),
146
- warning: nativeColor(theme.mode === "branding" ? 180 : 214),
147
- error: nativeColor(theme.mode === "branding" ? 173 : 198),
148
- success: nativeColor(theme.mode === "branding" ? 114 : 78),
149
- selection: nativeColor(theme.mode === "branding" ? 65 : theme.accent < 0 ? 240 : theme.accent)
132
+ background: background2,
133
+ foreground: foreground2,
134
+ accent: foreground2,
135
+ dim: foreground2,
136
+ border: foreground2,
137
+ warning: foreground2,
138
+ error: foreground2,
139
+ success: foreground2,
140
+ selection: background2
141
+ };
142
+ }
143
+ function installDefaultSelectionContrast(renderer2) {
144
+ const foreground2 = RGBA.defaultForeground();
145
+ const background2 = RGBA.defaultBackground();
146
+ const paintSelection = (buffer) => {
147
+ const lines = buffer.getSpanLines();
148
+ buffer.withBuffers((cells) => {
149
+ for (let y = 0; y < lines.length; y++) {
150
+ let x = 0;
151
+ for (const span of lines[y].spans) {
152
+ if (span.fg.intent === "default" && span.bg.intent === "default" && !(span.attributes & TextAttributes.INVERSE) && span.fg.equals(background2) && span.bg.equals(foreground2)) {
153
+ for (let column = x; column < x + span.width; column++)
154
+ cells.attributes[y * cells.width + column] |= TextAttributes.INVERSE;
155
+ }
156
+ x += span.width;
157
+ }
158
+ }
159
+ });
160
+ };
161
+ renderer2.addPostProcessFn(paintSelection);
162
+ let disposed = false;
163
+ const dispose = () => {
164
+ if (disposed) return;
165
+ disposed = true;
166
+ renderer2.removePostProcessFn(paintSelection);
167
+ renderer2.off(CliRenderEvents.DESTROY, dispose);
168
+ };
169
+ renderer2.once(CliRenderEvents.DESTROY, dispose);
170
+ renderer2.requestRender();
171
+ return dispose;
172
+ }
173
+
174
+ // packages/cli/src/tui/native/components/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 = [];
150
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;
151
381
  }
152
382
 
153
383
  // packages/cli/src/tui/native/components/blocks.ts
384
+ function nativeProgressText(value, working = false) {
385
+ if (value === void 0 || !Number.isFinite(value)) return `[············] ${working ? "working" : "pending"}`;
386
+ const ratio = Math.max(0, Math.min(1, value));
387
+ const filled = Math.round(ratio * 12);
388
+ return `[${"•".repeat(filled)}${"·".repeat(12 - filled)}] ${Math.round(ratio * 100)}%`;
389
+ }
390
+ function nativeControlFocus(control, baseAttributes = 0) {
391
+ const update = () => {
392
+ if (!control.isDestroyed) control.attributes = baseAttributes | (control.focused ? 32 : 0);
393
+ };
394
+ control.on(RenderableEvents.FOCUSED, update);
395
+ control.on(RenderableEvents.BLURRED, update);
396
+ update();
397
+ }
154
398
  var NativeBlockView = class {
155
399
  constructor(context, spec, inline = false) {
156
400
  this.context = context;
@@ -177,14 +421,15 @@ var NativeBlockView = class {
177
421
  text;
178
422
  body;
179
423
  children;
424
+ markdownSections;
180
425
  actions;
181
- progressBar;
182
- progressValue;
426
+ progress;
183
427
  image;
184
428
  terminal;
185
429
  collapsed = false;
186
430
  previousModelCollapsed;
187
431
  signature = "";
432
+ actionsSignature = "";
188
433
  pixel;
189
434
  get block() {
190
435
  return this.spec;
@@ -192,16 +437,18 @@ var NativeBlockView = class {
192
437
  build() {
193
438
  const { renderer: renderer2, colors, syntax } = this.context;
194
439
  const spec = this.spec;
195
- if (spec.kind === "card") this.root.borderStyle = "rounded";
196
- this.root.border = spec.kind === "card";
197
- this.root.paddingX = spec.kind === "card" ? 1 : 0;
198
- this.root.marginBottom = spec.kind === "card" ? 1 : 0;
440
+ if (spec.kind === "card") this.root.borderStyle = "single";
441
+ this.root.border = spec.kind === "card" ? ["left", "bottom"] : false;
442
+ this.root.paddingLeft = spec.kind === "card" ? 1 : 0;
443
+ this.root.paddingRight = 0;
444
+ this.root.marginBottom = 0;
199
445
  this.root.flexDirection = spec.kind === "row" ? "row" : "column";
200
446
  if (spec.title) {
201
447
  this.title = new TextRenderable(renderer2, {
202
448
  id: `block-title:${spec.id}`,
203
449
  content: "",
204
- fg: colors.accent,
450
+ fg: colors.foreground,
451
+ attributes: 1,
205
452
  flexShrink: 0,
206
453
  width: "100%",
207
454
  wrapMode: "word",
@@ -210,8 +457,15 @@ var NativeBlockView = class {
210
457
  if (event.button !== 0 || !this.spec.collapsible) return;
211
458
  this.toggle();
212
459
  event.stopPropagation();
460
+ },
461
+ onKeyDown: (key) => {
462
+ if (key.eventType === "release" || !this.spec.collapsible || key.name !== "return" && key.sequence !== " ") return;
463
+ this.toggle();
464
+ key.preventDefault();
465
+ key.stopPropagation();
213
466
  }
214
467
  });
468
+ nativeControlFocus(this.title, 1);
215
469
  this.root.add(this.title);
216
470
  }
217
471
  this.body = new BoxRenderable(renderer2, {
@@ -270,14 +524,31 @@ var NativeBlockView = class {
270
524
  flexShrink: 0,
271
525
  internalBlockMode: "top-level",
272
526
  conceal: true,
273
- 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
+ }
274
545
  });
275
546
  this.body.add(this.text);
276
547
  } else if (spec.kind === "code") {
277
548
  this.text = new CodeRenderable(renderer2, {
278
549
  id: `code:${spec.id}`,
279
550
  content: "",
280
- filetype: spec.language,
551
+ filetype: spec.language ?? "text",
281
552
  syntaxStyle: syntax,
282
553
  fg: colors.foreground,
283
554
  bg: colors.background,
@@ -286,7 +557,8 @@ var NativeBlockView = class {
286
557
  streaming: spec.streaming ?? false,
287
558
  selectable: true,
288
559
  wrapMode: "word",
289
- drawUnstyledText: true
560
+ drawUnstyledText: true,
561
+ onHighlight: (highlights, context) => highlights.length ? void 0 : nativeCodeHighlights(context.content)
290
562
  });
291
563
  this.body.add(this.text);
292
564
  } else if (spec.text !== void 0) {
@@ -303,36 +575,37 @@ var NativeBlockView = class {
303
575
  this.body.add(this.text);
304
576
  }
305
577
  if (spec.kind === "progress") {
306
- this.progressBar = new BoxRenderable(renderer2, {
578
+ this.progress = new TextRenderable(renderer2, {
307
579
  id: `progress-track:${spec.id}`,
308
580
  width: "100%",
309
581
  height: 1,
310
- backgroundColor: colors.dim
311
- });
312
- this.progressValue = new BoxRenderable(renderer2, {
313
- id: `progress-value:${spec.id}`,
314
- height: 1,
315
- width: "0%",
316
- backgroundColor: colors.accent
582
+ content: "",
583
+ fg: colors.foreground,
584
+ bg: colors.background,
585
+ attributes: 2,
586
+ wrapMode: "none",
587
+ selectable: false
317
588
  });
318
- this.progressBar.add(this.progressValue);
319
- this.body.add(this.progressBar);
589
+ this.body.add(this.progress);
320
590
  }
321
591
  this.children = new NativeBlockList(this.context, this.body);
322
592
  this.actions = new BoxRenderable(renderer2, {
323
593
  id: `block-actions:${spec.id}`,
324
594
  flexDirection: "row",
325
- gap: 2,
595
+ gap: 1,
326
596
  flexShrink: 0
327
597
  });
328
598
  this.root.add(this.actions);
329
599
  }
330
600
  reset() {
331
601
  this.children?.destroy();
602
+ this.markdownSections?.destroy();
603
+ this.markdownSections = void 0;
332
604
  if (this.terminal) this.context.terminals.delete(this.spec.id);
333
605
  for (const child of [...this.root.getChildren()]) child.destroyRecursively();
334
- this.title = this.text = this.body = this.actions = this.progressBar = this.progressValue = this.image = this.terminal = void 0;
606
+ this.title = this.text = this.body = this.actions = this.progress = this.image = this.terminal = void 0;
335
607
  this.children = void 0;
608
+ this.actionsSignature = "";
336
609
  this.build();
337
610
  }
338
611
  update(spec) {
@@ -346,27 +619,43 @@ var NativeBlockView = class {
346
619
  this.collapsed = spec.collapsed ?? false;
347
620
  this.previousModelCollapsed = spec.collapsed;
348
621
  }
349
- if (this.title) this.title.content = styledText(`${spec.collapsible ? this.collapsed ? "▸ " : "▾ " : ""}${spec.title ?? ""}`);
622
+ if (this.title) {
623
+ this.title.content = `${spec.collapsible ? this.collapsed ? "▸ " : "· " : ""}${plainText(spec.title)}`;
624
+ this.title.focusable = Boolean(spec.collapsible);
625
+ }
350
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
+ }
351
636
  if (this.text instanceof TextRenderable) this.text.content = styledText(spec.kind === "image" ? spec.text ?? "Image unavailable" : spec.text);
352
637
  else if (this.text instanceof MarkdownRenderable || this.text instanceof CodeRenderable) {
353
- 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);
354
640
  this.text.streaming = spec.streaming ?? false;
355
- if (this.text instanceof CodeRenderable) this.text.filetype = spec.language;
641
+ if (this.text instanceof CodeRenderable) this.text.filetype = code?.language ?? "text";
356
642
  }
357
643
  if (this.image && spec.imagePath && this.image.source !== spec.imagePath) this.image.source = spec.imagePath;
358
644
  if (this.terminal) this.terminal.height = spec.height ?? 14;
359
645
  if (this.image) this.image.height = spec.height ?? 12;
360
- if (this.progressValue) this.progressValue.width = `${Math.round(Math.min(1, Math.max(0, spec.progress ?? 0)) * 100)}%`;
646
+ if (this.progress) this.progress.content = nativeProgressText(spec.progress, spec.streaming);
361
647
  this.children?.replace(spec.children ?? []);
362
- if (this.actions) {
648
+ const actionsSignature = JSON.stringify(spec.actions ?? []);
649
+ if (this.actions && actionsSignature !== this.actionsSignature) {
650
+ this.actionsSignature = actionsSignature;
363
651
  for (const child of [...this.actions.getChildren()]) child.destroy();
364
652
  for (const action of spec.actions ?? []) {
365
653
  const button = new TextRenderable(this.context.renderer, {
366
654
  id: `block-action:${spec.id}:${action.id}`,
367
- content: styledText(action.label),
368
- fg: action.disabled ? this.context.colors.dim : spec.color ? nativeColor(spec.color) : this.context.colors.accent,
369
- attributes: action.active ? 33 : 0,
655
+ content: plainText(action.label),
656
+ fg: this.context.colors.foreground,
657
+ bg: this.context.colors.background,
658
+ attributes: action.disabled ? 2 : action.active ? 1 : 0,
370
659
  flexShrink: 0,
371
660
  selectable: false,
372
661
  onMouseDown: (event) => {
@@ -375,12 +664,13 @@ var NativeBlockView = class {
375
664
  event.stopPropagation();
376
665
  },
377
666
  onKeyDown: (key) => {
378
- if (key.name !== "return" || action.disabled) return;
667
+ if (key.eventType === "release" || key.name !== "return" && key.sequence !== " " || action.disabled) return;
379
668
  this.context.emit({ type: "action", action: action.command ?? action.id, id: this.spec.id });
380
669
  key.preventDefault();
381
670
  }
382
671
  });
383
672
  button.focusable = !action.disabled;
673
+ nativeControlFocus(button, action.disabled ? 2 : action.active ? 1 : 0);
384
674
  if (action.id.startsWith("pixel:") && !action.disabled) {
385
675
  button.onMouseOver = () => this.context.emit({ type: "action", action: `hover:${action.id}`, id: this.spec.id });
386
676
  button.onMouseOut = () => this.context.emit({ type: "action", action: `hover-end:${action.id}`, id: this.spec.id });
@@ -394,29 +684,37 @@ var NativeBlockView = class {
394
684
  toggle() {
395
685
  this.collapsed = !this.collapsed;
396
686
  if (this.body) this.body.visible = !this.collapsed;
397
- if (this.title) this.title.content = styledText(`${this.collapsed ? "▸ " : "▾ "}${this.spec.title ?? ""}`);
687
+ if (this.title) this.title.content = `${this.collapsed ? "▸ " : "· "}${plainText(this.spec.title)}`;
398
688
  this.context.emit({ type: "action", action: "toggle-block", id: this.spec.id });
399
689
  }
400
690
  applyTheme() {
401
691
  const colors = this.context.colors;
402
692
  this.root.backgroundColor = colors.background;
403
- if (this.spec.kind === "card") this.root.borderColor = this.spec.color ? nativeColor(this.spec.color) : colors.border;
693
+ if (this.spec.kind === "card") this.root.borderColor = colors.border;
404
694
  else this.root.border = false;
405
695
  if (this.title) {
406
- this.title.fg = this.spec.color ? nativeColor(this.spec.color) : colors.accent;
696
+ this.title.fg = colors.foreground;
407
697
  this.title.bg = colors.background;
408
698
  }
409
699
  if (this.body) this.body.backgroundColor = colors.background;
410
700
  if (this.text) {
411
- this.text.fg = this.spec.color ? nativeColor(this.spec.color) : colors.foreground;
701
+ this.text.fg = colors.foreground;
412
702
  this.text.bg = colors.background;
413
703
  }
414
- if (this.progressBar) this.progressBar.backgroundColor = colors.dim;
415
- if (this.progressValue) this.progressValue.backgroundColor = colors.accent;
704
+ if (this.progress) {
705
+ this.progress.fg = colors.foreground;
706
+ this.progress.bg = colors.background;
707
+ }
708
+ for (const action of this.actions?.getChildren() ?? []) if (action instanceof TextRenderable) {
709
+ action.fg = colors.foreground;
710
+ action.bg = colors.background;
711
+ }
416
712
  this.children?.applyTheme();
713
+ this.markdownSections?.applyTheme();
417
714
  }
418
715
  destroy() {
419
716
  this.children?.destroy();
717
+ this.markdownSections?.destroy();
420
718
  if (this.terminal) this.context.terminals.delete(this.spec.id);
421
719
  this.root.destroyRecursively();
422
720
  }
@@ -476,19 +774,104 @@ var NativeBlockList = class {
476
774
 
477
775
  // packages/cli/src/tui/native/components/modals.ts
478
776
  import {
479
- BoxRenderable as BoxRenderable2,
777
+ BoxRenderable as BoxRenderable3,
480
778
  InputRenderable,
481
779
  ScrollBoxRenderable as ScrollBoxRenderable2,
482
780
  TextareaRenderable,
781
+ TextAttributes as TextAttributes4,
483
782
  TextRenderable as TextRenderable2
484
783
  } from "@opentui/core";
485
784
 
486
785
  // packages/cli/src/tui/native/components/select.ts
487
786
  import { SelectRenderable } from "@opentui/core";
787
+
788
+ // packages/cli/src/tui/native/components/scrollbar.ts
789
+ import { TextAttributes as TextAttributes2 } from "@opentui/core";
790
+ function drawDotRail(buffer, x, y, length, horizontal, progress, colors) {
791
+ length = Math.max(0, Math.trunc(length));
792
+ if (!length) return;
793
+ buffer.fillRect(x, y, horizontal ? length : 1, horizontal ? 1 : length, colors.background);
794
+ if (horizontal) buffer.drawText("─".repeat(length), x, y, colors.foreground, colors.background, TextAttributes2.DIM);
795
+ else for (let row = 0; row < length; row++) buffer.drawText("│", x, y + row, colors.foreground, colors.background, TextAttributes2.DIM);
796
+ const position = Math.round(Math.max(0, Math.min(1, progress)) * (length - 1));
797
+ buffer.drawText("•", x + (horizontal ? position : 0), y + (horizontal ? 0 : position), colors.foreground, colors.background, TextAttributes2.BOLD);
798
+ }
799
+ function styleNativeScrollbars(scroll, colors) {
800
+ for (const bar of [scroll.horizontalScrollBar, scroll.verticalScrollBar]) {
801
+ bar.showArrows = false;
802
+ const slider = bar.slider;
803
+ slider.foregroundColor = colors.foreground;
804
+ slider.backgroundColor = colors.background;
805
+ slider.renderAfter = (buffer) => {
806
+ const horizontal = slider.orientation === "horizontal";
807
+ drawDotRail(
808
+ buffer,
809
+ slider.x,
810
+ slider.y,
811
+ horizontal ? slider.width : slider.height,
812
+ horizontal,
813
+ slider.max > slider.min ? (slider.value - slider.min) / (slider.max - slider.min) : 0,
814
+ colors
815
+ );
816
+ };
817
+ let dragging = false;
818
+ const seek = (event) => {
819
+ const horizontal = slider.orientation === "horizontal";
820
+ const length = horizontal ? slider.width : slider.height;
821
+ const offset = horizontal ? event.x - slider.x : event.y - slider.y;
822
+ const progress = length > 1 ? Math.max(0, Math.min(1, offset / (length - 1))) : 0;
823
+ slider.value = slider.min + progress * (slider.max - slider.min);
824
+ event.preventDefault();
825
+ event.stopPropagation();
826
+ };
827
+ slider.onMouseDown = (event) => {
828
+ if (event.button === 0) {
829
+ dragging = true;
830
+ seek(event);
831
+ }
832
+ };
833
+ slider.onMouseDrag = (event) => {
834
+ if (dragging) seek(event);
835
+ };
836
+ slider.onMouseUp = (event) => {
837
+ if (dragging) seek(event);
838
+ dragging = false;
839
+ };
840
+ slider.onMouseDragEnd = () => {
841
+ dragging = false;
842
+ };
843
+ }
844
+ }
845
+
846
+ // packages/cli/src/tui/native/components/select.ts
488
847
  var NativeSelectRenderable = class extends SelectRenderable {
489
848
  onActivate;
849
+ draggingRail = false;
490
850
  constructor(renderer2, options) {
491
851
  super(renderer2, options);
852
+ const colors = palette({ mode: "system", bg: -1, accent: -1, textPrimary: -1, textDim: -1, boxColor: -1 });
853
+ this.renderAfter = (buffer) => {
854
+ const count = this.visibleCount();
855
+ if (!this.showScrollIndicator || this.options.length <= count) return;
856
+ const offset = Math.max(0, Math.min(this.getSelectedIndex() - Math.floor(count / 2), this.options.length - count));
857
+ drawDotRail(buffer, this.width - 1, 0, this.height, false, offset / (this.options.length - count), colors);
858
+ };
859
+ }
860
+ visibleCount() {
861
+ return Math.max(1, Math.floor(this.height / (this.showDescription ? 2 : 1)));
862
+ }
863
+ seekRail(event) {
864
+ const progress = this.height > 1 ? Math.max(0, Math.min(1, (event.y - this.y) / (this.height - 1))) : 0;
865
+ const target = Math.round(progress * Math.max(0, this.options.length - 1));
866
+ for (let distance = 0; distance < this.options.length; distance++) {
867
+ const index = [target + distance, target - distance].find((candidate) => candidate >= 0 && candidate < this.options.length && !this.disabled(candidate));
868
+ if (index !== void 0) {
869
+ this.setSelectedIndex(index);
870
+ break;
871
+ }
872
+ }
873
+ event.preventDefault();
874
+ event.stopPropagation();
492
875
  }
493
876
  disabled(index) {
494
877
  const option = this.options[index];
@@ -517,6 +900,11 @@ var NativeSelectRenderable = class extends SelectRenderable {
517
900
  if (!this.disabled(this.getSelectedIndex())) super.selectCurrent();
518
901
  }
519
902
  onMouseEvent(event) {
903
+ if ((event.type === "drag" || event.type === "up") && this.draggingRail) {
904
+ this.seekRail(event);
905
+ if (event.type === "up") this.draggingRail = false;
906
+ return;
907
+ }
520
908
  if (event.type === "scroll") {
521
909
  if (event.scroll?.direction === "up") this.moveUp(1);
522
910
  else if (event.scroll?.direction === "down") this.moveDown(1);
@@ -524,6 +912,11 @@ var NativeSelectRenderable = class extends SelectRenderable {
524
912
  return;
525
913
  }
526
914
  if (event.type !== "down" || event.button !== 0) return;
915
+ if (this.showScrollIndicator && this.options.length > this.visibleCount() && event.x === this.x + this.width - 1) {
916
+ this.draggingRail = true;
917
+ this.seekRail(event);
918
+ return;
919
+ }
527
920
  const lines = this.showDescription ? 2 : 1;
528
921
  const count = Math.max(1, Math.floor(this.height / lines));
529
922
  const start = Math.max(0, Math.min(this.getSelectedIndex() - Math.floor(count / 2), this.options.length - count));
@@ -536,6 +929,169 @@ var NativeSelectRenderable = class extends SelectRenderable {
536
929
  }
537
930
  };
538
931
 
932
+ // packages/cli/src/tui/native/components/corner-frame.ts
933
+ import { BoxRenderable as BoxRenderable2, CliRenderEvents as CliRenderEvents2, RenderableEvents as RenderableEvents2, TextAttributes as TextAttributes3 } from "@opentui/core";
934
+ function drawCornerFrame(buffer, panel, colors, progress = 1) {
935
+ const { x, y, width, height } = panel;
936
+ if (width <= 0 || height <= 0) return;
937
+ const draw = (text, column, row) => buffer.drawText(text, column, row, colors.foreground, colors.background, TextAttributes3.DIM);
938
+ draw(" ".repeat(width), x, y);
939
+ if (height > 1) draw(" ".repeat(width), x, y + height - 1);
940
+ for (let row = 1; row < height - 1; row++) {
941
+ draw(" ", x, y + row);
942
+ if (width > 1) draw(" ", x + width - 1, y + row);
943
+ }
944
+ if (height === 1) {
945
+ draw("─".repeat(width), x, y);
946
+ return;
947
+ }
948
+ if (width === 1) {
949
+ for (let row = 0; row < height; row++) draw("│", x, y + row);
950
+ return;
951
+ }
952
+ const fraction = Math.max(0, Math.min(1, progress));
953
+ const horizontal = Math.round(Math.min(4, Math.max(0, Math.floor((width - 3) / 2))) * fraction);
954
+ const vertical = Math.round(Math.min(2, Math.max(0, Math.floor((height - 3) / 2))) * fraction);
955
+ draw(`┌${"─".repeat(horizontal)}`, x, y);
956
+ draw(`${"─".repeat(horizontal)}┐`, x + width - horizontal - 1, y);
957
+ draw(`└${"─".repeat(horizontal)}`, x, y + height - 1);
958
+ draw(`${"─".repeat(horizontal)}┘`, x + width - horizontal - 1, y + height - 1);
959
+ for (let arm = 1; arm <= vertical; arm++) {
960
+ for (const column of [x, x + width - 1]) {
961
+ draw("│", column, y + arm);
962
+ draw("│", column, y + height - arm - 1);
963
+ }
964
+ }
965
+ }
966
+ var NativeCornerFrame = class {
967
+ constructor(panel, colors, renderer2, container = panel.parent) {
968
+ this.panel = panel;
969
+ this.colors = colors;
970
+ this.renderer = renderer2;
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;
989
+ };
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;
995
+ panel.once(RenderableEvents2.DESTROYED, this.destroy);
996
+ if (this.reduced) {
997
+ this.progress = 1;
998
+ this.phase = "open";
999
+ }
1000
+ renderer2.on(CliRenderEvents2.FRAME, this.onFrame);
1001
+ }
1002
+ panel;
1003
+ colors;
1004
+ renderer;
1005
+ container;
1006
+ chrome;
1007
+ progress = 0;
1008
+ timer;
1009
+ disposed = false;
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";
1015
+ get animating() {
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;
1023
+ }
1024
+ setColors(colors) {
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);
1083
+ }
1084
+ destroy = () => {
1085
+ if (this.disposed) return;
1086
+ this.disposed = true;
1087
+ if (this.timer !== void 0) clearTimeout(this.timer);
1088
+ this.timer = void 0;
1089
+ this.panel.off(RenderableEvents2.DESTROYED, this.destroy);
1090
+ this.renderer.off(CliRenderEvents2.FRAME, this.onFrame);
1091
+ this.chrome.destroyRecursively();
1092
+ };
1093
+ };
1094
+
539
1095
  // packages/cli/src/tui/native/components/modals.ts
540
1096
  var NativeModalView = class {
541
1097
  constructor(context, spec, close2) {
@@ -543,7 +1099,7 @@ var NativeModalView = class {
543
1099
  this.close = close2;
544
1100
  this.spec = spec;
545
1101
  const { renderer: renderer2, colors } = context;
546
- this.root = new BoxRenderable2(renderer2, {
1102
+ this.root = new BoxRenderable3(renderer2, {
547
1103
  id: `modal:${spec.id}`,
548
1104
  position: "absolute",
549
1105
  top: 0,
@@ -554,22 +1110,32 @@ var NativeModalView = class {
554
1110
  justifyContent: "center",
555
1111
  alignItems: "center",
556
1112
  zIndex: 100,
557
- backgroundColor: colors.background
1113
+ backgroundColor: "transparent",
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
+ }
558
1123
  });
559
- this.panel = new BoxRenderable2(renderer2, {
1124
+ this.panel = new BoxRenderable3(renderer2, {
560
1125
  id: `modal-panel:${spec.id}`,
561
1126
  flexDirection: "column",
562
1127
  width: "94%",
563
1128
  maxHeight: "94%",
564
- border: true,
565
- borderStyle: "rounded",
566
- borderColor: colors.border,
1129
+ border: false,
567
1130
  backgroundColor: colors.background,
568
- paddingX: 1,
1131
+ shouldFill: false,
1132
+ paddingX: 2,
569
1133
  paddingY: 1,
1134
+ focusable: true,
570
1135
  onMouseDown: () => this.focus()
571
1136
  });
572
1137
  this.root.add(this.panel);
1138
+ this.frame = new NativeCornerFrame(this.panel, colors, renderer2, this.root);
573
1139
  this.breadcrumb = this.addText(`modal-breadcrumb:${spec.id}`);
574
1140
  this.heading = this.addText(`modal-title:${spec.id}`);
575
1141
  this.description = this.addText(`modal-description:${spec.id}`);
@@ -585,20 +1151,29 @@ var NativeModalView = class {
585
1151
  viewportCulling: true,
586
1152
  contentOptions: { flexDirection: "column" }
587
1153
  });
1154
+ styleNativeScrollbars(this.document, colors);
588
1155
  this.blocks = new NativeBlockList(this.modalContext(), this.document);
589
1156
  this.panel.add(this.document);
590
1157
  } else {
591
- this.progressTrack = new BoxRenderable2(renderer2, { id: `modal-progress:${spec.id}`, width: "100%", height: 1, backgroundColor: colors.dim });
592
- this.progressValue = new BoxRenderable2(renderer2, { id: `modal-progress-value:${spec.id}`, width: "0%", height: 1, backgroundColor: colors.accent });
593
- this.progressTrack.add(this.progressValue);
594
- this.panel.add(this.progressTrack);
595
- const progressBlocks = new BoxRenderable2(renderer2, { id: `modal-progress-blocks:${spec.id}`, flexDirection: "column", width: "100%" });
1158
+ this.progress = new TextRenderable2(renderer2, {
1159
+ id: `modal-progress:${spec.id}`,
1160
+ width: "100%",
1161
+ height: 1,
1162
+ content: "",
1163
+ fg: colors.foreground,
1164
+ bg: colors.background,
1165
+ attributes: 2,
1166
+ wrapMode: "none",
1167
+ selectable: false
1168
+ });
1169
+ this.panel.add(this.progress);
1170
+ const progressBlocks = new BoxRenderable3(renderer2, { id: `modal-progress-blocks:${spec.id}`, flexDirection: "column", width: "100%" });
596
1171
  this.panel.add(progressBlocks);
597
1172
  this.blocks = new NativeBlockList(this.modalContext(), progressBlocks);
598
1173
  }
599
1174
  this.error = this.addText(`modal-error:${spec.id}`);
600
1175
  this.hints = this.addText(`modal-hints:${spec.id}`);
601
- this.actions = new BoxRenderable2(renderer2, { id: `modal-actions:${spec.id}`, flexDirection: "row", gap: 2, width: "100%", marginTop: 1 });
1176
+ this.actions = new BoxRenderable3(renderer2, { id: `modal-actions:${spec.id}`, flexDirection: "row", gap: 1, width: "100%", marginTop: 0 });
602
1177
  this.panel.add(this.actions);
603
1178
  this.update(spec);
604
1179
  }
@@ -606,6 +1181,7 @@ var NativeModalView = class {
606
1181
  close;
607
1182
  root;
608
1183
  panel;
1184
+ frame;
609
1185
  spec;
610
1186
  heading;
611
1187
  description;
@@ -619,11 +1195,11 @@ var NativeModalView = class {
619
1195
  select;
620
1196
  document;
621
1197
  blocks;
622
- progressTrack;
623
- progressValue;
1198
+ progress;
624
1199
  filtered = [];
625
1200
  filter = "";
626
1201
  changing = false;
1202
+ actionsSignature = "";
627
1203
  addText(id) {
628
1204
  const text = new TextRenderable2(this.context.renderer, { id, content: "", width: "100%", wrapMode: "word", flexShrink: 0, fg: this.context.colors.foreground, selectable: true });
629
1205
  this.panel.add(text);
@@ -636,6 +1212,7 @@ var NativeModalView = class {
636
1212
  width: "100%",
637
1213
  value: this.spec.filter ?? "",
638
1214
  placeholder: "Filter…",
1215
+ placeholderColor: colors.foreground,
639
1216
  textColor: colors.foreground,
640
1217
  backgroundColor: colors.background,
641
1218
  focusedTextColor: colors.foreground,
@@ -682,8 +1259,8 @@ var NativeModalView = class {
682
1259
  focusedTextColor: colors.foreground,
683
1260
  focusedBackgroundColor: colors.background,
684
1261
  placeholder: this.spec.placeholder ?? "",
685
- selectionBg: colors.selection,
686
- cursorColor: colors.accent,
1262
+ placeholderColor: colors.foreground,
1263
+ cursorColor: colors.foreground,
687
1264
  onContentChange: () => {
688
1265
  if (!this.editor || this.changing) return;
689
1266
  this.refreshMask();
@@ -713,14 +1290,14 @@ var NativeModalView = class {
713
1290
  const spec = this.spec;
714
1291
  this.changing = true;
715
1292
  try {
716
- this.heading.content = styledText(spec.title);
1293
+ this.heading.content = plainText(spec.title);
717
1294
  this.description.content = styledText(spec.description);
718
1295
  this.description.visible = plainText(spec.description).length > 0;
719
1296
  this.breadcrumb.content = (spec.breadcrumbs ?? []).join(" › ");
720
1297
  this.breadcrumb.visible = (spec.breadcrumbs?.length ?? 0) > 0;
721
1298
  this.error.content = spec.validationError ?? "";
722
1299
  this.error.visible = Boolean(spec.validationError);
723
- this.hints.content = spec.hints ?? (spec.kind === "select" ? "↑ ↓ select · Enter confirm · Esc return" : spec.multiline ? "Ctrl+Enter confirm · Esc return" : "Enter confirm · Esc return");
1300
+ this.hints.content = spec.hints ?? `${spec.deferSelection || spec.captureKeys ? "" : "Tab controls · "}${spec.kind === "select" ? "↑ ↓ select · Enter confirm · Esc return" : spec.multiline ? "Ctrl+Enter confirm · Esc return" : "Enter confirm · Esc return"}`;
724
1301
  if (this.filterInput && patch.filter !== void 0 && patch.filter !== old.filter) {
725
1302
  this.filter = patch.filter;
726
1303
  this.filterInput.setText(patch.filter);
@@ -729,29 +1306,36 @@ var NativeModalView = class {
729
1306
  this.refreshMask();
730
1307
  this.refreshOptions(patch.selectedIndex);
731
1308
  this.blocks?.replace(spec.blocks ?? []);
732
- if (this.progressValue) this.progressValue.width = `${Math.round(Math.max(0, Math.min(1, spec.progress ?? 0)) * 100)}%`;
733
- for (const child of [...this.actions.getChildren()]) child.destroy();
734
- for (const action of spec.actions ?? []) {
735
- const button = new TextRenderable2(this.context.renderer, {
736
- id: `modal-action:${spec.id}:${action.id}`,
737
- content: styledText(action.label),
738
- fg: action.disabled ? this.context.colors.dim : this.context.colors.accent,
739
- selectable: false,
740
- onMouseDown: (event) => {
741
- if (event.button !== 0 || action.disabled) return;
742
- this.emitAction(action.command ?? action.id, this.editor?.plainText);
743
- event.stopPropagation();
744
- },
745
- onKeyDown: (key) => {
746
- if (key.name !== "return" || action.disabled) return;
747
- this.emitAction(action.command ?? action.id, this.editor?.plainText);
748
- key.preventDefault();
749
- }
750
- });
751
- button.focusable = !action.disabled;
752
- this.actions.add(button);
1309
+ if (this.progress) this.progress.content = nativeProgressText(spec.progress, true);
1310
+ const actionsSignature = JSON.stringify(spec.actions ?? []);
1311
+ if (actionsSignature !== this.actionsSignature) {
1312
+ this.actionsSignature = actionsSignature;
1313
+ for (const child of [...this.actions.getChildren()]) child.destroy();
1314
+ for (const action of spec.actions ?? []) {
1315
+ const button = new TextRenderable2(this.context.renderer, {
1316
+ id: `modal-action:${spec.id}:${action.id}`,
1317
+ content: plainText(action.label),
1318
+ fg: this.context.colors.foreground,
1319
+ bg: this.context.colors.background,
1320
+ attributes: action.disabled ? 2 : action.active ? 1 : 0,
1321
+ selectable: false,
1322
+ onMouseDown: (event) => {
1323
+ if (event.button !== 0 || action.disabled) return;
1324
+ this.emitAction(action.command ?? action.id, this.editor?.plainText);
1325
+ event.stopPropagation();
1326
+ },
1327
+ onKeyDown: (key) => {
1328
+ if (key.eventType === "release" || key.name !== "return" && key.sequence !== " " || action.disabled) return;
1329
+ this.emitAction(action.command ?? action.id, this.editor?.plainText);
1330
+ key.preventDefault();
1331
+ }
1332
+ });
1333
+ button.focusable = !action.disabled;
1334
+ nativeControlFocus(button, action.disabled ? 2 : action.active ? 1 : 0);
1335
+ this.actions.add(button);
1336
+ }
1337
+ this.actions.visible = (spec.actions?.length ?? 0) > 0;
753
1338
  }
754
- this.actions.visible = (spec.actions?.length ?? 0) > 0;
755
1339
  } finally {
756
1340
  this.changing = false;
757
1341
  }
@@ -790,6 +1374,7 @@ var NativeModalView = class {
790
1374
  this.context.emit({ type: "modal-action", id: this.spec.id, action, key: current?.item.key, index: current?.index, value });
791
1375
  }
792
1376
  confirm() {
1377
+ if (this.frame.closing) return;
793
1378
  if (this.spec.kind === "progress") {
794
1379
  this.emitAction("enter");
795
1380
  return;
@@ -804,6 +1389,7 @@ var NativeModalView = class {
804
1389
  this.close(this.spec.id, { confirmed: true, key: current?.item.key ?? null, index: current?.index ?? -1, ...value === void 0 ? {} : { value } });
805
1390
  }
806
1391
  cancel(action = "cancel") {
1392
+ if (this.frame.closing) return;
807
1393
  if (this.spec.deferSelection) {
808
1394
  this.emitAction(action);
809
1395
  return;
@@ -811,25 +1397,49 @@ var NativeModalView = class {
811
1397
  this.close(this.spec.id, { confirmed: false, key: this.spec.breadcrumbs?.length ? "__back__" : null, index: this.current()?.index ?? -1 });
812
1398
  }
813
1399
  focus() {
1400
+ if (this.frame.closing) return;
814
1401
  if (this.filterInput) this.filterInput.focus();
815
1402
  else if (this.editor && !this.spec.secret) this.editor.focus();
816
1403
  else if (this.document) this.document.focus();
817
1404
  else this.panel.focus();
818
1405
  }
1406
+ nextControl(direction) {
1407
+ const controls = [];
1408
+ const visit = (node) => {
1409
+ if (!node.visible || node.isDestroyed) return;
1410
+ if (node !== this.panel && node.focusable) controls.push(node);
1411
+ for (const child of node.getChildren()) visit(child);
1412
+ };
1413
+ visit(this.panel);
1414
+ if (!controls.length) {
1415
+ this.panel.focus();
1416
+ return;
1417
+ }
1418
+ const focused = this.context.renderer.currentFocusedRenderable;
1419
+ const current = focused ? controls.indexOf(focused) : -1;
1420
+ const next = controls[current < 0 ? direction > 0 ? 0 : controls.length - 1 : (current + direction + controls.length) % controls.length];
1421
+ next.focus();
1422
+ this.document?.scrollChildIntoView(next.id);
1423
+ }
819
1424
  handleKey(key) {
820
1425
  const consume = () => {
821
1426
  key.preventDefault();
822
1427
  key.stopPropagation();
823
1428
  return true;
824
1429
  };
1430
+ if (this.frame.closing) return consume();
1431
+ if (key.eventType === "release") return false;
825
1432
  if (key.name === "escape" || key.ctrl && key.name === "c") {
826
1433
  this.cancel();
827
1434
  return consume();
828
1435
  }
829
1436
  if (key.name === "tab") {
830
- this.emitAction(key.shift ? "shift-tab" : "tab");
1437
+ if (this.spec.deferSelection || this.spec.captureKeys) this.emitAction(key.shift ? "shift-tab" : "tab");
1438
+ else this.nextControl(key.shift ? -1 : 1);
831
1439
  return consume();
832
1440
  }
1441
+ const focusedId = this.context.renderer.currentFocusedRenderable?.id ?? "";
1442
+ if ((key.name === "return" || key.sequence === " ") && /^(?:block-title:|block-action:|modal-action:)/.test(focusedId)) return false;
833
1443
  if (this.spec.kind === "select") {
834
1444
  if (key.name === "up" || key.name === "down" || key.name === "pageup" || key.name === "pagedown") {
835
1445
  const amount = key.name.startsWith("page") ? Math.max(1, Math.floor((this.select?.height ?? 5) / (this.select?.showDescription ? 2 : 1))) : 1;
@@ -886,6 +1496,11 @@ var NativeModalView = class {
886
1496
  return false;
887
1497
  }
888
1498
  handlePaste(event) {
1499
+ if (this.frame.closing) {
1500
+ event.preventDefault();
1501
+ event.stopPropagation();
1502
+ return true;
1503
+ }
889
1504
  const text = new TextDecoder().decode(event.bytes);
890
1505
  if (this.overBudget(text, this.filterInput?.plainText ?? this.editor?.plainText ?? "")) {
891
1506
  event.preventDefault();
@@ -910,14 +1525,22 @@ var NativeModalView = class {
910
1525
  }
911
1526
  applyTheme() {
912
1527
  const colors = this.context.colors;
913
- this.root.backgroundColor = colors.background;
914
1528
  this.panel.backgroundColor = colors.background;
915
- this.panel.borderColor = colors.border;
916
- this.heading.fg = colors.accent;
1529
+ this.frame.setColors(colors);
1530
+ this.heading.fg = colors.foreground;
1531
+ this.heading.attributes = 1;
917
1532
  this.description.fg = colors.foreground;
918
1533
  this.breadcrumb.fg = colors.dim;
919
- this.error.fg = colors.error;
1534
+ this.breadcrumb.attributes = 2;
1535
+ this.error.fg = colors.foreground;
1536
+ this.error.attributes = 1;
920
1537
  this.hints.fg = colors.dim;
1538
+ this.hints.attributes = 2;
1539
+ if (this.progress) {
1540
+ this.progress.fg = colors.foreground;
1541
+ this.progress.bg = colors.background;
1542
+ }
1543
+ if (this.document) styleNativeScrollbars(this.document, colors);
921
1544
  if (this.select) {
922
1545
  this.select.backgroundColor = this.select.focusedBackgroundColor = colors.background;
923
1546
  this.select.textColor = this.select.focusedTextColor = colors.foreground;
@@ -929,13 +1552,20 @@ var NativeModalView = class {
929
1552
  for (const editor of [this.editor, this.filterInput]) if (editor) {
930
1553
  editor.backgroundColor = editor.focusedBackgroundColor = colors.background;
931
1554
  editor.textColor = editor.focusedTextColor = colors.foreground;
932
- editor.selectionBg = colors.selection;
933
- editor.cursorColor = colors.accent;
1555
+ editor.selectionBg = void 0;
1556
+ editor.selectionFg = void 0;
1557
+ editor.cursorColor = colors.foreground;
1558
+ editor.placeholderColor = colors.foreground;
934
1559
  }
935
1560
  if (this.secretMask) this.secretMask.fg = colors.foreground;
1561
+ for (const action of this.actions.getChildren()) if (action instanceof TextRenderable2) {
1562
+ action.fg = colors.foreground;
1563
+ action.bg = colors.background;
1564
+ }
936
1565
  this.blocks?.applyTheme();
937
1566
  }
938
1567
  destroy() {
1568
+ this.frame.destroy();
939
1569
  this.blocks?.destroy();
940
1570
  if (this.spec.secret) this.editor?.destroy();
941
1571
  this.root.destroyRecursively();
@@ -945,11 +1575,23 @@ var NativeModalStack = class {
945
1575
  constructor(context, restoreFocus) {
946
1576
  this.context = context;
947
1577
  this.restoreFocus = restoreFocus;
1578
+ context.renderer.addPostProcessFn(this.dimUnderlay);
948
1579
  }
949
1580
  context;
950
1581
  restoreFocus;
951
1582
  views = [];
952
1583
  byId = /* @__PURE__ */ new Map();
1584
+ dimUnderlay = (buffer) => {
1585
+ const panel = this.active?.panel;
1586
+ if (!panel || panel.isDestroyed || !panel.visible) return;
1587
+ const { x, y, width, height } = this.active.frame.bounds;
1588
+ buffer.withBuffers((cells) => {
1589
+ for (let row = 0; row < cells.height; row++) for (let column = 0; column < cells.width; column++) {
1590
+ if (column < x || column >= x + width || row < y || row >= y + height)
1591
+ cells.attributes[row * cells.width + column] |= TextAttributes4.DIM;
1592
+ }
1593
+ });
1594
+ };
953
1595
  get active() {
954
1596
  return this.views.at(-1);
955
1597
  }
@@ -973,96 +1615,2468 @@ var NativeModalStack = class {
973
1615
  close(id, result, notify = false) {
974
1616
  const view = this.byId.get(id);
975
1617
  if (!view) return;
976
- const wasActive = view === this.active;
977
- view.destroy();
978
- this.byId.delete(id);
979
- this.views = this.views.filter((candidate) => candidate !== view);
980
- if (wasActive) {
981
- if (this.active) {
982
- this.active.root.visible = true;
983
- this.active.focus();
984
- } else this.restoreFocus();
985
- }
986
- if (notify) this.context.emit({ type: "modal-result", id, result });
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
+ });
987
1631
  }
988
1632
  applyTheme() {
989
1633
  for (const view of this.views) view.applyTheme();
990
1634
  }
991
1635
  destroy() {
1636
+ this.context.renderer.removePostProcessFn(this.dimUnderlay);
992
1637
  for (const view of this.views) view.destroy();
993
1638
  this.views = [];
994
1639
  this.byId.clear();
995
1640
  }
996
1641
  };
997
1642
 
998
- // packages/cli/src/tui/native/components/frontend.ts
999
- var initialTheme = { mode: "branding", bg: -1, accent: 108, textPrimary: 254, textDim: 145, boxColor: 108 };
1000
- var spinner = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
1001
- var NativeFrontend = class {
1002
- constructor(renderer2, emit) {
1003
- this.renderer = renderer2;
1004
- this.emit = emit;
1005
- const colors = palette(initialTheme);
1006
- this.context = { renderer: renderer2, colors, syntax: SyntaxStyle2.fromStyles({}, renderer2.nativeScene), emit, terminals: /* @__PURE__ */ new Map() };
1007
- this.clipboard = createClipboard({ host: createHostClipboard({ timeoutMs: 1500 }), terminal: createRendererClipboardAdapter(renderer2) });
1008
- this.root = new BoxRenderable3(renderer2, {
1009
- id: "omnius-native",
1010
- width: "100%",
1011
- height: "100%",
1012
- flexDirection: "column",
1013
- backgroundColor: colors.background
1014
- });
1015
- renderer2.root.add(this.root);
1016
- this.header = new BoxRenderable3(renderer2, {
1017
- id: "omnius-header",
1643
+ // packages/cli/src/tui/native/components/instruments.ts
1644
+ import {
1645
+ BoxRenderable as BoxRenderable4,
1646
+ RGBA as RGBA2,
1647
+ RenderableEvents as RenderableEvents3,
1648
+ ScrollBoxRenderable as ScrollBoxRenderable3,
1649
+ TextAttributes as TextAttributes5,
1650
+ TextRenderable as TextRenderable3
1651
+ } from "@opentui/core";
1652
+
1653
+ // packages/cli/src/tui/terminal-cells.ts
1654
+ var graphemes = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1655
+ var extendedPictographic = /\p{Extended_Pictographic}/u;
1656
+ var emojiPresentation = /\p{Emoji_Presentation}/u;
1657
+ var zeroWidthGrapheme = /^[\p{Mark}\p{Cf}\u200c\u200d\ufe0e\ufe0f]+$/u;
1658
+ function escapeEnd(value, start) {
1659
+ if (value[start] !== "\x1B") return start + 1;
1660
+ const kind = value[start + 1];
1661
+ if (kind === "[") {
1662
+ for (let i = start + 2; i < value.length; i++) {
1663
+ const code = value.charCodeAt(i);
1664
+ if (code >= 64 && code <= 126) return i + 1;
1665
+ }
1666
+ return value.length;
1667
+ }
1668
+ if (kind === "]") {
1669
+ for (let i = start + 2; i < value.length; i++) {
1670
+ if (value[i] === "\x07") return i + 1;
1671
+ if (value[i] === "\x1B" && value[i + 1] === "\\") return i + 2;
1672
+ }
1673
+ return value.length;
1674
+ }
1675
+ if (kind === "P" || kind === "X" || kind === "^" || kind === "_") {
1676
+ for (let i = start + 2; i < value.length; i++) {
1677
+ if (value[i] === "\x1B" && value[i + 1] === "\\") return i + 2;
1678
+ }
1679
+ return value.length;
1680
+ }
1681
+ return Math.min(value.length, start + 2);
1682
+ }
1683
+ function stripTerminalSequences(value) {
1684
+ let out = "";
1685
+ for (let i = 0; i < value.length; ) {
1686
+ if (value[i] === "\x1B") {
1687
+ i = escapeEnd(value, i);
1688
+ continue;
1689
+ }
1690
+ const code = value.charCodeAt(i);
1691
+ if (code >= 0 && code < 32 || code === 127) {
1692
+ i++;
1693
+ continue;
1694
+ }
1695
+ const cp = value.codePointAt(i);
1696
+ out += String.fromCodePoint(cp);
1697
+ i += cp > 65535 ? 2 : 1;
1698
+ }
1699
+ return out;
1700
+ }
1701
+ function normalizeTerminalLine(value) {
1702
+ return value.replace(/\r\n|\r|\n/g, " ").replace(/\t/g, " ");
1703
+ }
1704
+ function isWideCodePoint(cp) {
1705
+ return cp >= 4352 && cp <= 4447 || cp === 9001 || cp === 9002 || cp >= 11904 && cp <= 12350 || cp >= 12352 && cp <= 42191 || cp >= 44032 && cp <= 55203 || cp >= 63744 && cp <= 64255 || cp >= 65040 && cp <= 65049 || cp >= 65072 && cp <= 65135 || cp >= 65280 && cp <= 65376 || cp >= 65504 && cp <= 65510 || cp >= 110592 && cp <= 111359 || cp >= 131072 && cp <= 262141;
1706
+ }
1707
+ function graphemeCellWidth(value) {
1708
+ if (!value || zeroWidthGrapheme.test(value)) return 0;
1709
+ const cp = value.codePointAt(0);
1710
+ if (cp < 32 || cp >= 127 && cp < 160) return 0;
1711
+ if (emojiPresentation.test(value) || value.includes("️") || value.includes("‍") && extendedPictographic.test(value) || cp >= 127462 && cp <= 127487) {
1712
+ return 2;
1713
+ }
1714
+ return isWideCodePoint(cp) ? 2 : 1;
1715
+ }
1716
+ function plainGraphemes(value) {
1717
+ return Array.from(
1718
+ graphemes.segment(stripTerminalSequences(value)),
1719
+ (part) => part.segment
1720
+ );
1721
+ }
1722
+ function terminalCellWidth(value) {
1723
+ let width = 0;
1724
+ for (const grapheme of plainGraphemes(normalizeTerminalLine(value))) {
1725
+ width += graphemeCellWidth(grapheme);
1726
+ }
1727
+ return width;
1728
+ }
1729
+
1730
+ // packages/cli/src/tui/native/components/instrument-format.ts
1731
+ var graphemes2 = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1732
+ function fitInstrumentText(value, cells, marker = "~") {
1733
+ const text = stripTerminalSequences(normalizeTerminalLine(value));
1734
+ const width = Math.max(0, Math.floor(cells));
1735
+ if (terminalCellWidth(text) <= width) return text;
1736
+ const suffix = width > 0 ? marker : "";
1737
+ const budget = Math.max(0, width - terminalCellWidth(suffix));
1738
+ let used = 0, result = "";
1739
+ for (const { segment } of graphemes2.segment(text)) {
1740
+ const size = graphemeCellWidth(segment);
1741
+ if (used + size > budget) break;
1742
+ used += size;
1743
+ result += segment;
1744
+ }
1745
+ return result + suffix;
1746
+ }
1747
+ function formatTelemetryRow(label, value, width, reserved = 12) {
1748
+ const cells = Math.max(0, Math.floor(width));
1749
+ if (!cells) return "";
1750
+ const reading = stripTerminalSequences(normalizeTerminalLine(value));
1751
+ const readingCells = terminalCellWidth(reading);
1752
+ if (readingCells >= cells) return fitInstrumentText(reading, cells);
1753
+ const field = Math.min(cells, Math.max(readingCells, Math.floor(reserved)));
1754
+ const labelCells = Math.max(0, cells - field - 1);
1755
+ const left = fitInstrumentText(label.toUpperCase(), labelCells);
1756
+ const dots = ".".repeat(Math.max(0, cells - field - terminalCellWidth(left)));
1757
+ return left + dots + " ".repeat(Math.max(0, field - readingCells)) + reading;
1758
+ }
1759
+ function instrumentNumber(value, precision = 0, unit = "") {
1760
+ return value === void 0 || !Number.isFinite(value) || value < 0 ? "--" : `${value.toFixed(Math.max(0, Math.min(6, precision)))}${unit}`;
1761
+ }
1762
+ function formatSignalMeter(value, maximum, width = 8) {
1763
+ const cells = Math.max(1, Math.floor(width));
1764
+ if (value === void 0 || maximum === void 0 || !Number.isFinite(value) || !Number.isFinite(maximum) || value < 0 || maximum <= 0)
1765
+ return `[${".".repeat(cells)}] --`;
1766
+ const ratio = Math.max(0, Math.min(1, value / maximum));
1767
+ const filled = Math.round(ratio * cells);
1768
+ return `[${"●".repeat(filled)}${"○".repeat(cells - filled)}] ${Math.round(ratio * 100)}%`;
1769
+ }
1770
+ function formatSectionBanner(title, width, expanded = true) {
1771
+ const cells = Math.max(0, Math.floor(width));
1772
+ const label = fitInstrumentText(
1773
+ `${expanded ? "-" : "+"} ${title.toUpperCase()} `,
1774
+ cells
1775
+ );
1776
+ return label + "─".repeat(Math.max(0, cells - terminalCellWidth(label)));
1777
+ }
1778
+ function instrumentStatus(value) {
1779
+ const status = (value || "unknown").toLowerCase();
1780
+ if (status === "running") return "ACTIVE";
1781
+ if (status === "completed" || status === "succeeded") return "DONE";
1782
+ if (status === "failed" || status === "error") return "ERR";
1783
+ if (status === "waiting") return "STDBY";
1784
+ if (status === "paused") return "PAUSED";
1785
+ if (status === "stopped") return "STOPPED";
1786
+ return status.toUpperCase();
1787
+ }
1788
+
1789
+ // packages/cli/src/tui/native/components/instruments.ts
1790
+ var foreground = RGBA2.defaultForeground();
1791
+ var background = RGBA2.defaultBackground();
1792
+ var SectionBanner = class extends TextRenderable3 {
1793
+ constructor(renderer2, title, id, activate) {
1794
+ super(renderer2, {
1795
+ id,
1796
+ content: `- ${title.toUpperCase()}`,
1018
1797
  width: "100%",
1019
- flexDirection: "column",
1798
+ height: 1,
1020
1799
  flexShrink: 0,
1021
- border: true,
1022
- borderStyle: "rounded",
1023
- borderColor: colors.border,
1024
- paddingX: 1
1025
- });
1026
- this.root.add(this.header);
1027
- const headerRow = new BoxRenderable3(renderer2, { id: "omnius-header-row", width: "100%", height: 1, flexDirection: "row", gap: 2 });
1028
- this.header.add(headerRow);
1029
- this.identity = new TextRenderable3(renderer2, {
1030
- id: "omnius-identity",
1031
- content: "Omnius",
1032
- fg: colors.foreground,
1033
- flexShrink: 1,
1034
- flexGrow: 1,
1800
+ fg: foreground,
1801
+ bg: background,
1802
+ attributes: TextAttributes5.BOLD,
1803
+ selectable: false,
1035
1804
  wrapMode: "none",
1036
1805
  truncate: true,
1037
- minWidth: 8,
1038
- selectable: false
1806
+ onMouseDown: (event) => {
1807
+ if (event.button === 0) {
1808
+ this.focus();
1809
+ this.activate();
1810
+ event.stopPropagation();
1811
+ }
1812
+ },
1813
+ onKeyDown: (key) => {
1814
+ if (key.eventType !== "release" && (key.name === "return" || key.sequence === " ")) {
1815
+ this.activate();
1816
+ key.preventDefault();
1817
+ }
1818
+ }
1039
1819
  });
1040
- headerRow.add(this.identity);
1041
- this.headerActions = new ScrollBoxRenderable3(renderer2, {
1042
- id: "omnius-header-actions",
1043
- width: "50%",
1044
- flexGrow: 0,
1045
- flexShrink: 0,
1046
- minWidth: 0,
1047
- height: 1,
1048
- scrollX: true,
1049
- scrollY: false,
1050
- contentOptions: { flexDirection: "row", gap: 2 },
1051
- verticalScrollbarOptions: { visible: false },
1052
- horizontalScrollbarOptions: { visible: false }
1820
+ this.activate = activate;
1821
+ this.heading = title;
1822
+ this.focusable = true;
1823
+ this.on(RenderableEvents3.FOCUSED, () => {
1824
+ this.attributes = TextAttributes5.BOLD | TextAttributes5.INVERSE;
1053
1825
  });
1054
- headerRow.add(this.headerActions);
1055
- this.headerPanel = new TextRenderable3(renderer2, { id: "omnius-header-panel", content: "", width: "100%", wrapMode: "word", visible: false });
1056
- this.header.add(this.headerPanel);
1057
- this.routeWarning = new TextRenderable3(renderer2, { id: "omnius-route-warning", content: "", width: "100%", wrapMode: "word", fg: colors.warning, visible: false, flexShrink: 0 });
1058
- this.root.add(this.routeWarning);
1059
- this.tabs = new TabSelectRenderable(renderer2, {
1060
- id: "omnius-agent-tabs",
1061
- width: "100%",
1062
- height: 1,
1826
+ this.on(RenderableEvents3.BLURRED, () => {
1827
+ this.attributes = TextAttributes5.BOLD;
1828
+ });
1829
+ }
1830
+ activate;
1831
+ expanded = true;
1832
+ heading;
1833
+ onResize(width, height) {
1834
+ super.onResize(width, height);
1835
+ this.refresh();
1836
+ }
1837
+ update(title, expanded = this.expanded) {
1838
+ this.heading = title;
1839
+ this.expanded = expanded;
1840
+ this.refresh();
1841
+ }
1842
+ refresh() {
1843
+ const next = formatSectionBanner(this.heading, this.width, this.expanded);
1844
+ if (this.plainText !== next) this.content = next;
1845
+ }
1846
+ };
1847
+ var TelemetryRow = class extends TextRenderable3 {
1848
+ constructor(renderer2, label, id, reserved = 12) {
1849
+ super(renderer2, {
1850
+ id,
1851
+ content: "",
1852
+ width: "100%",
1853
+ height: 1,
1854
+ flexShrink: 0,
1855
+ fg: foreground,
1856
+ bg: background,
1857
+ wrapMode: "none",
1858
+ truncate: true
1859
+ });
1860
+ this.reserved = reserved;
1861
+ this.label = label;
1862
+ }
1863
+ reserved;
1864
+ label;
1865
+ value = "--";
1866
+ onResize(width, height) {
1867
+ super.onResize(width, height);
1868
+ this.refresh();
1869
+ }
1870
+ update(value, label = this.label) {
1871
+ this.label = label;
1872
+ this.value = value;
1873
+ this.refresh();
1874
+ }
1875
+ refresh() {
1876
+ const next = formatTelemetryRow(
1877
+ this.label,
1878
+ this.value,
1879
+ this.width,
1880
+ this.reserved
1881
+ );
1882
+ if (this.plainText !== next) this.content = next;
1883
+ }
1884
+ };
1885
+ var SignalMeter = class extends TelemetryRow {
1886
+ constructor(renderer2, label, id) {
1887
+ super(renderer2, label, id, 18);
1888
+ }
1889
+ updateReading(value, maximum) {
1890
+ this.update(formatSignalMeter(value, maximum));
1891
+ }
1892
+ };
1893
+ var InstrumentDivider = class extends TextRenderable3 {
1894
+ constructor(renderer2, actions) {
1895
+ super(renderer2, {
1896
+ id: "omnius-instrument-divider",
1897
+ width: 1,
1898
+ minWidth: 1,
1899
+ maxWidth: 1,
1900
+ height: "100%",
1901
+ flexGrow: 0,
1902
+ flexShrink: 0,
1903
+ fg: foreground,
1904
+ bg: background,
1905
+ selectable: false,
1906
+ wrapMode: "none",
1907
+ attributes: TextAttributes5.DIM,
1908
+ onMouseDown: (event) => {
1909
+ if (event.button !== 0) return;
1910
+ this.held = true;
1911
+ this.moved = false;
1912
+ this.startX = event.x;
1913
+ this.actions.start?.();
1914
+ this.attributes = TextAttributes5.BOLD;
1915
+ event.preventDefault();
1916
+ event.stopPropagation();
1917
+ },
1918
+ onMouseDrag: (event) => {
1919
+ this.handleDrag(event);
1920
+ },
1921
+ onMouseUp: (event) => {
1922
+ this.handleRelease(event);
1923
+ },
1924
+ onMouseDragEnd: (event) => {
1925
+ this.handleRelease(event);
1926
+ },
1927
+ onMouseOver: () => {
1928
+ this.attributes = TextAttributes5.BOLD;
1929
+ },
1930
+ onMouseOut: () => {
1931
+ if (!this.held) this.attributes = this.focused ? TextAttributes5.INVERSE : TextAttributes5.DIM;
1932
+ },
1933
+ onKeyDown: (key) => {
1934
+ if (key.eventType === "release") return;
1935
+ if (key.name === "return" || key.sequence === " ") this.actions.toggle();
1936
+ else if (key.name === "left" || key.name === "right") this.actions.resize(this.x + (key.name === "left" ? -1 : 1) * (key.shift ? 8 : 2));
1937
+ else return;
1938
+ key.preventDefault();
1939
+ key.stopPropagation();
1940
+ }
1941
+ });
1942
+ this.actions = actions;
1943
+ this.focusable = true;
1944
+ this.on(RenderableEvents3.FOCUSED, () => {
1945
+ this.attributes = TextAttributes5.INVERSE;
1946
+ });
1947
+ this.on(RenderableEvents3.BLURRED, () => {
1948
+ this.attributes = TextAttributes5.DIM;
1949
+ });
1950
+ }
1951
+ actions;
1952
+ collapsed = false;
1953
+ held = false;
1954
+ moved = false;
1955
+ startX = 0;
1956
+ setCollapsed(collapsed) {
1957
+ this.collapsed = collapsed;
1958
+ this.refresh();
1959
+ }
1960
+ handleDrag(event) {
1961
+ if (!this.held) return;
1962
+ if (event.x !== this.startX) this.moved = true;
1963
+ if (this.moved) this.actions.resize(event.x);
1964
+ event.preventDefault();
1965
+ event.stopPropagation();
1966
+ }
1967
+ handleRelease(event) {
1968
+ if (!this.held) return;
1969
+ this.release(event.x);
1970
+ event.preventDefault();
1971
+ event.stopPropagation();
1972
+ }
1973
+ onResize(width, height) {
1974
+ super.onResize(width, height);
1975
+ this.refresh();
1976
+ }
1977
+ refresh() {
1978
+ const rows = Array.from({ length: Math.max(0, this.height) }, (_, row) => row === Math.floor(this.height / 2) ? this.collapsed ? "‹" : "›" : "│");
1979
+ const text = rows.join("\n");
1980
+ if (this.plainText !== text) this.content = text;
1981
+ }
1982
+ release(column) {
1983
+ if (this.moved) this.actions.resize(column);
1984
+ else this.actions.toggle();
1985
+ this.held = false;
1986
+ this.actions.finish?.();
1987
+ this.attributes = this.focused ? TextAttributes5.INVERSE : TextAttributes5.DIM;
1988
+ }
1989
+ };
1990
+ var DeviceMonitor = class extends BoxRenderable4 {
1991
+ constructor(renderer2, action) {
1992
+ super(renderer2, { id: "omnius-devices", width: "100%", height: 3, minHeight: 3, maxHeight: "60%", flexShrink: 1, flexDirection: "column", visible: false });
1993
+ this.action = action;
1994
+ this.heading = new SectionBanner(renderer2, "DEVICES", "omnius-devices:heading", () => this.toggle());
1995
+ this.heading.update("DEVICES", false);
1996
+ this.add(this.heading);
1997
+ this.summary = new TextRenderable3(renderer2, { id: "omnius-devices:summary", content: "--", width: "100%", height: 1, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true });
1998
+ this.add(this.summary);
1999
+ this.scroll = new ScrollBoxRenderable3(renderer2, {
2000
+ id: "omnius-devices:scroll",
2001
+ width: "100%",
2002
+ flexGrow: 1,
2003
+ flexShrink: 1,
2004
+ minHeight: 0,
2005
+ visible: false,
2006
+ scrollY: true,
2007
+ scrollX: false,
2008
+ viewportCulling: true,
2009
+ horizontalScrollbarOptions: { visible: false },
2010
+ verticalScrollbarOptions: { showArrows: false },
2011
+ contentOptions: { flexDirection: "column" }
2012
+ });
2013
+ this.add(this.scroll);
2014
+ this.notes = new TextRenderable3(renderer2, {
2015
+ id: "omnius-devices:notes",
2016
+ content: "",
2017
+ width: "100%",
2018
+ height: 1,
2019
+ flexShrink: 0,
2020
+ fg: foreground,
2021
+ bg: background,
2022
+ wrapMode: "none",
2023
+ truncate: true
2024
+ });
2025
+ this.scroll.add(this.notes);
2026
+ const controls = new BoxRenderable4(renderer2, { id: "omnius-devices:controls", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 2 });
2027
+ this.add(controls);
2028
+ this.policy = this.button(renderer2, "omnius-devices:policy", "GPU POLICY", () => {
2029
+ if (this.canSelect()) this.action?.("/gpu");
2030
+ });
2031
+ this.status = this.button(renderer2, "omnius-devices:status", "STATUS", () => this.action?.("/gpu status"));
2032
+ controls.add(this.policy);
2033
+ controls.add(this.status);
2034
+ }
2035
+ action;
2036
+ heading;
2037
+ scroll;
2038
+ summary;
2039
+ policy;
2040
+ status;
2041
+ notes;
2042
+ expanded = false;
2043
+ snapshot;
2044
+ rows = /* @__PURE__ */ new Map();
2045
+ button(renderer2, id, label, activate) {
2046
+ const button = new TextRenderable3(renderer2, {
2047
+ id,
2048
+ content: label,
2049
+ height: 1,
2050
+ flexShrink: 0,
2051
+ fg: foreground,
2052
+ bg: background,
2053
+ selectable: false,
2054
+ wrapMode: "none",
2055
+ truncate: true,
2056
+ onMouseDown: (event) => {
2057
+ if (event.button === 0 && button.focusable) {
2058
+ button.focus();
2059
+ activate();
2060
+ event.preventDefault();
2061
+ event.stopPropagation();
2062
+ }
2063
+ },
2064
+ onKeyDown: (key) => {
2065
+ if (key.eventType !== "release" && (key.name === "return" || key.sequence === " ") && button.focusable) {
2066
+ activate();
2067
+ key.preventDefault();
2068
+ key.stopPropagation();
2069
+ }
2070
+ }
2071
+ });
2072
+ button.focusable = Boolean(this.action);
2073
+ button.on(RenderableEvents3.FOCUSED, () => {
2074
+ button.attributes = TextAttributes5.INVERSE;
2075
+ });
2076
+ button.on(RenderableEvents3.BLURRED, () => {
2077
+ button.attributes = button.focusable ? 0 : TextAttributes5.DIM;
2078
+ });
2079
+ return button;
2080
+ }
2081
+ canSelect() {
2082
+ return Boolean(this.action && this.snapshot?.selectionAvailable && !this.snapshot.selectionLocked);
2083
+ }
2084
+ canSelectDevice(device) {
2085
+ return this.canSelect() && device.eligible === true && !device.unifiedMemory && /^GPU-[\w-]+$/.test(device.uuid);
2086
+ }
2087
+ update(snapshot) {
2088
+ this.snapshot = snapshot;
2089
+ this.visible = Boolean(snapshot);
2090
+ const allowed = this.canSelect();
2091
+ if (this.policy.focused && !allowed) this.heading.focus();
2092
+ this.policy.focusable = allowed;
2093
+ this.policy.content = snapshot?.selectionLocked ? "POLICY LOCKED" : "GPU POLICY";
2094
+ this.policy.attributes = allowed ? this.policy.focused ? TextAttributes5.INVERSE : 0 : TextAttributes5.DIM;
2095
+ const keep = new Set(snapshot?.devices.map((device) => device.uuid) ?? []);
2096
+ for (const [uuid, row] of this.rows) if (!keep.has(uuid)) {
2097
+ if (row.title.focused) this.heading.focus();
2098
+ row.root.destroyRecursively();
2099
+ this.rows.delete(uuid);
2100
+ }
2101
+ for (const device of snapshot?.devices ?? []) {
2102
+ let row = this.rows.get(device.uuid);
2103
+ if (!row) {
2104
+ const root = new BoxRenderable4(this._ctx, {
2105
+ id: `omnius-device:${device.uuid}`,
2106
+ width: "100%",
2107
+ flexDirection: "column",
2108
+ flexShrink: 0,
2109
+ onSizeChange: () => {
2110
+ if (!row) return;
2111
+ for (const child of [row.title, row.load, row.memory, row.reason]) child.width = row.root.width;
2112
+ this.refresh();
2113
+ }
2114
+ });
2115
+ const title = this.button(this._ctx, `${root.id}:select`, "", () => {
2116
+ const current = this.snapshot?.devices.find((item) => item.uuid === device.uuid);
2117
+ if (current && this.canSelectDevice(current)) this.action?.(`/gpu select ${current.uuid}`);
2118
+ });
2119
+ title.width = "100%";
2120
+ title.on(RenderableEvents3.FOCUSED, () => this.scroll.scrollChildIntoView(root.id));
2121
+ const load = new TelemetryRow(this._ctx, "LOAD", `${root.id}:load`, 16);
2122
+ const memory = new TelemetryRow(this._ctx, "VRAM", `${root.id}:memory`, 16);
2123
+ const reason = new TextRenderable3(this._ctx, {
2124
+ id: `${root.id}:reason`,
2125
+ content: "",
2126
+ width: "100%",
2127
+ height: 1,
2128
+ fg: foreground,
2129
+ bg: background,
2130
+ attributes: TextAttributes5.DIM,
2131
+ wrapMode: "none",
2132
+ truncate: true,
2133
+ visible: false
2134
+ });
2135
+ root.add(title);
2136
+ root.add(load);
2137
+ root.add(memory);
2138
+ root.add(reason);
2139
+ this.scroll.add(root);
2140
+ row = { root, title, load, memory, reason };
2141
+ this.rows.set(device.uuid, row);
2142
+ }
2143
+ const selectable = this.canSelectDevice(device);
2144
+ if (row.title.focused && !selectable) this.heading.focus();
2145
+ row.title.focusable = selectable;
2146
+ row.title.attributes = row.title.focused ? TextAttributes5.INVERSE : device.preferred ? TextAttributes5.BOLD : selectable ? 0 : TextAttributes5.DIM;
2147
+ row.load.update(formatSignalMeter(device.utilization, 100, 6));
2148
+ const used = device.memoryUsedMB === void 0 ? "--" : instrumentNumber(device.memoryUsedMB / 1024, 1);
2149
+ const total = device.memoryTotalMB === void 0 || device.memoryTotalMB <= 0 ? "--" : instrumentNumber(device.memoryTotalMB / 1024, 1);
2150
+ row.memory.update(`${used}/${total} GiB`, device.unifiedMemory ? "UMA" : "VRAM");
2151
+ row.reason.content = device.policyReason ?? "";
2152
+ row.reason.visible = Boolean(device.policyReason);
2153
+ }
2154
+ this.refresh();
2155
+ }
2156
+ toggle() {
2157
+ this.expanded = !this.expanded;
2158
+ this.scroll.visible = this.expanded;
2159
+ this.minHeight = this.expanded ? 4 : 3;
2160
+ this.scroll.minHeight = this.expanded ? 1 : 0;
2161
+ this.height = this.expanded ? Math.min(12, Math.max(5, Math.floor(this._ctx.terminalHeight / 3))) : 3;
2162
+ this.refresh();
2163
+ }
2164
+ onResize(width, height) {
2165
+ super.onResize(width, height);
2166
+ this.refresh();
2167
+ }
2168
+ refresh() {
2169
+ if (!this.heading) return;
2170
+ const data = this.snapshot, devices = data?.devices ?? [];
2171
+ this.heading.update(data?.source === "jtop" ? "DEVICES / JETSON" : "DEVICES", this.expanded);
2172
+ const measured = devices.map((device) => device.utilization).filter((value) => value !== void 0 && Number.isFinite(value) && value >= 0);
2173
+ const on = devices.filter((device) => device.enabled === true).length;
2174
+ const policyKnown = devices.every((device) => device.enabled !== void 0);
2175
+ 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`;
2176
+ this.notes.content = [
2177
+ data?.local === false ? "REMOTE" : void 0,
2178
+ data ? `${data.source.toUpperCase()} · ${data.policy.toUpperCase()} · ${data.broker.toUpperCase()}` : void 0,
2179
+ data?.warnings.length ? `${data.warnings.length} notices` : void 0
2180
+ ].filter(Boolean).join(" · ");
2181
+ for (const device of devices) {
2182
+ const row = this.rows.get(device.uuid);
2183
+ if (!row) continue;
2184
+ const state = device.enabled === void 0 ? "--" : device.enabled ? "ON" : "OFF";
2185
+ row.title.content = formatTelemetryRow(`GPU ${device.index} ${device.name}`, `${state}${device.preferred ? " *" : ""}`, row.root.width, 6);
2186
+ }
2187
+ }
2188
+ focusTargets() {
2189
+ return !this.visible ? [] : [
2190
+ this.heading,
2191
+ ...this.policy.focusable ? [this.policy] : [],
2192
+ ...this.status.focusable ? [this.status] : [],
2193
+ ...this.expanded ? [...this.rows.values()].map((row) => row.title).filter((title) => title.focusable) : []
2194
+ ];
2195
+ }
2196
+ };
2197
+ var ChannelMap = class extends BoxRenderable4 {
2198
+ constructor(renderer2, id, inspect, reveal) {
2199
+ super(renderer2, {
2200
+ id,
2201
+ width: "100%",
2202
+ flexDirection: "column",
2203
+ flexShrink: 0
2204
+ });
2205
+ this.inspect = inspect;
2206
+ this.reveal = reveal;
2207
+ }
2208
+ inspect;
2209
+ reveal;
2210
+ rows = /* @__PURE__ */ new Map();
2211
+ channels = /* @__PURE__ */ new Map();
2212
+ agents = [];
2213
+ onResize(width, height) {
2214
+ super.onResize(width, height);
2215
+ this.refresh();
2216
+ }
2217
+ update(agents) {
2218
+ this.agents = agents;
2219
+ const keep = new Set(agents.map((agent) => agent.id));
2220
+ for (const [id, row] of this.rows)
2221
+ if (!keep.has(id)) {
2222
+ row.destroyRecursively();
2223
+ this.rows.delete(id);
2224
+ }
2225
+ for (const agent of agents) {
2226
+ if (!this.channels.has(agent.id))
2227
+ this.channels.set(agent.id, this.channels.size + 1);
2228
+ if (!this.rows.has(agent.id)) {
2229
+ const row = new TextRenderable3(this._ctx, {
2230
+ id: `${this.id}:channel:${agent.id}`,
2231
+ content: "",
2232
+ width: "100%",
2233
+ height: 1,
2234
+ flexShrink: 0,
2235
+ fg: foreground,
2236
+ bg: background,
2237
+ wrapMode: "none",
2238
+ truncate: true,
2239
+ selectable: false,
2240
+ onMouseDown: (event) => {
2241
+ if (event.button === 0) {
2242
+ row.focus();
2243
+ this.inspect?.(agent.id);
2244
+ event.stopPropagation();
2245
+ }
2246
+ },
2247
+ onKeyDown: (key) => {
2248
+ if (key.eventType === "release") return;
2249
+ if (key.name === "return" || key.sequence === " ") {
2250
+ this.inspect?.(agent.id);
2251
+ key.preventDefault();
2252
+ } else if (key.name === "down" || key.name === "up") {
2253
+ const targets = this.focusTargets();
2254
+ const index = targets.indexOf(row), delta = key.name === "down" ? 1 : -1;
2255
+ targets[(index + delta + targets.length) % targets.length]?.focus();
2256
+ key.preventDefault();
2257
+ }
2258
+ }
2259
+ });
2260
+ row.focusable = !!this.inspect;
2261
+ row.on(RenderableEvents3.FOCUSED, () => {
2262
+ row.attributes = TextAttributes5.INVERSE;
2263
+ this.reveal?.(row);
2264
+ });
2265
+ row.on(RenderableEvents3.BLURRED, () => {
2266
+ row.attributes = 0;
2267
+ });
2268
+ this.rows.set(agent.id, row);
2269
+ this.add(row);
2270
+ }
2271
+ }
2272
+ this.refresh();
2273
+ }
2274
+ focusTargets() {
2275
+ return this.agents.flatMap((agent) => {
2276
+ const row = this.rows.get(agent.id);
2277
+ return row?.focusable ? [row] : [];
2278
+ });
2279
+ }
2280
+ refresh() {
2281
+ for (const agent of this.agents) {
2282
+ const row = this.rows.get(agent.id);
2283
+ if (!row) continue;
2284
+ const channel = String(this.channels.get(agent.id)).padStart(2, "0");
2285
+ const next = formatTelemetryRow(
2286
+ `${agent.selected ? ">" : " "}${channel} ${agent.label}`,
2287
+ instrumentStatus(agent.status),
2288
+ this.width,
2289
+ 7
2290
+ );
2291
+ if (row.plainText !== next) row.content = next;
2292
+ }
2293
+ }
2294
+ };
2295
+ var InstrumentRail = class extends BoxRenderable4 {
2296
+ scroll;
2297
+ inferenceHeader;
2298
+ sessionHeader;
2299
+ channels;
2300
+ devices;
2301
+ inference;
2302
+ session;
2303
+ model;
2304
+ rows = /* @__PURE__ */ new Map();
2305
+ contextMeter;
2306
+ contextHeader;
2307
+ contextDetails;
2308
+ constructor(renderer2, options = {}) {
2309
+ const id = options.id ?? "omnius-instruments";
2310
+ super(renderer2, {
2311
+ id,
2312
+ width: "100%",
2313
+ height: "100%",
2314
+ minWidth: 0,
2315
+ minHeight: 1,
2316
+ flexDirection: "column",
2317
+ flexGrow: 1,
2318
+ flexShrink: 1
2319
+ });
2320
+ this.scroll = new ScrollBoxRenderable3(renderer2, {
2321
+ id: `${id}:scroll`,
2322
+ width: "100%",
2323
+ flexGrow: 1,
2324
+ minHeight: 1,
2325
+ minWidth: 0,
2326
+ scrollY: true,
2327
+ scrollX: false,
2328
+ viewportCulling: true,
2329
+ verticalScrollbarOptions: { showArrows: false },
2330
+ horizontalScrollbarOptions: { visible: false },
2331
+ contentOptions: { flexDirection: "column", paddingRight: 1 }
2332
+ });
2333
+ this.add(this.scroll);
2334
+ this.inferenceHeader = new SectionBanner(
2335
+ renderer2,
2336
+ "INFERENCE",
2337
+ `${id}:inference`,
2338
+ () => this.toggleSection("inference")
2339
+ );
2340
+ this.inferenceHeader.on(
2341
+ RenderableEvents3.FOCUSED,
2342
+ () => this.reveal(this.inferenceHeader)
2343
+ );
2344
+ this.scroll.add(this.inferenceHeader);
2345
+ this.inference = new BoxRenderable4(renderer2, {
2346
+ id: `${id}:inference-body`,
2347
+ width: "100%",
2348
+ flexDirection: "column",
2349
+ flexShrink: 0
2350
+ });
2351
+ this.scroll.add(this.inference);
2352
+ const addRow = (parent, key, label, reserved = 12) => {
2353
+ const row = new TelemetryRow(renderer2, label, `${id}:${key}`, reserved);
2354
+ this.rows.set(key, row);
2355
+ parent.add(row);
2356
+ return row;
2357
+ };
2358
+ addRow(this.inference, "route", "ROUTE");
2359
+ addRow(this.inference, "backend", "BACKEND");
2360
+ this.model = new TextRenderable3(renderer2, {
2361
+ id: `${id}:model`,
2362
+ content: "MODEL --",
2363
+ width: "100%",
2364
+ height: 1,
2365
+ fg: foreground,
2366
+ bg: background,
2367
+ wrapMode: "none",
2368
+ truncate: true,
2369
+ flexShrink: 0
2370
+ });
2371
+ this.inference.add(this.model);
2372
+ addRow(this.inference, "input", "IN TOK");
2373
+ addRow(this.inference, "output", "OUT TOK");
2374
+ addRow(this.inference, "speed", "RATE");
2375
+ addRow(this.inference, "context", "CTX TOK", 20);
2376
+ this.contextMeter = new SignalMeter(
2377
+ renderer2,
2378
+ "CTX LOAD",
2379
+ `${id}:context-load`
2380
+ );
2381
+ this.inference.add(this.contextMeter);
2382
+ addRow(this.inference, "state", "STATE");
2383
+ this.contextHeader = new SectionBanner(renderer2, "CONTEXT DETAILS", `${id}:context-details`, () => {
2384
+ this.contextDetails.visible = !this.contextDetails.visible;
2385
+ this.contextHeader.update("CONTEXT DETAILS", this.contextDetails.visible);
2386
+ });
2387
+ this.contextHeader.on(RenderableEvents3.FOCUSED, () => this.reveal(this.contextHeader));
2388
+ this.inference.add(this.contextHeader);
2389
+ this.contextDetails = new BoxRenderable4(renderer2, { id: `${id}:context-detail-body`, width: "100%", flexDirection: "column", flexShrink: 0, visible: false });
2390
+ this.inference.add(this.contextDetails);
2391
+ this.contextHeader.update("CONTEXT DETAILS", false);
2392
+ addRow(this.contextDetails, "messages", "MESSAGES");
2393
+ addRow(this.contextDetails, "headroom", "HEADROOM");
2394
+ addRow(this.contextDetails, "context-tools", "TOOL CALLS");
2395
+ addRow(this.contextDetails, "adversary", "ADVERSARY");
2396
+ addRow(this.contextDetails, "duration", "LAST GEN");
2397
+ this.sessionHeader = new SectionBanner(
2398
+ renderer2,
2399
+ "SESSION / AGENTS",
2400
+ `${id}:session`,
2401
+ () => this.toggleSection("session")
2402
+ );
2403
+ this.sessionHeader.on(
2404
+ RenderableEvents3.FOCUSED,
2405
+ () => this.reveal(this.sessionHeader)
2406
+ );
2407
+ this.scroll.add(this.sessionHeader);
2408
+ this.session = new BoxRenderable4(renderer2, {
2409
+ id: `${id}:session-body`,
2410
+ width: "100%",
2411
+ flexDirection: "column",
2412
+ flexShrink: 0
2413
+ });
2414
+ this.scroll.add(this.session);
2415
+ addRow(this.session, "selected", "VIEW");
2416
+ addRow(this.session, "elapsed", "ELAPSED");
2417
+ addRow(this.session, "turns", "TURNS");
2418
+ addRow(this.session, "tools", "TOOLS");
2419
+ addRow(this.session, "toolResults", "TOOL OK/ERR");
2420
+ addRow(this.session, "queued", "QUEUED");
2421
+ this.channels = new ChannelMap(
2422
+ renderer2,
2423
+ `${id}:channels`,
2424
+ options.onInspectAgent,
2425
+ (row) => this.reveal(row)
2426
+ );
2427
+ this.session.add(this.channels);
2428
+ this.session.visible = false;
2429
+ this.sessionHeader.update("SESSION / AGENTS", false);
2430
+ this.devices = new DeviceMonitor(renderer2, options.onAction);
2431
+ this.add(this.devices);
2432
+ if (options.colors) this.applyTheme(options.colors);
2433
+ }
2434
+ update(state) {
2435
+ const data = state.instruments;
2436
+ const inference = data?.inference;
2437
+ const session = data?.session;
2438
+ const set = (id, value) => this.rows.get(id).update(value);
2439
+ set("messages", instrumentNumber(inference?.contextMessages));
2440
+ set("headroom", inference?.contextHeadroom !== void 0 && Number.isFinite(inference.contextHeadroom) ? String(Math.round(inference.contextHeadroom)) : "--");
2441
+ set("context-tools", instrumentNumber(inference?.contextToolCalls));
2442
+ set("adversary", instrumentNumber(inference?.contextAdversaryOutcomes));
2443
+ set("duration", inference?.generationDurationMs === void 0 ? "--" : instrumentNumber(inference.generationDurationMs / 1e3, 1, "s"));
2444
+ this.contextHeader.visible = [inference?.contextMessages, inference?.contextHeadroom, inference?.contextToolCalls, inference?.contextAdversaryOutcomes, inference?.generationDurationMs].some((value) => value !== void 0);
2445
+ this.contextDetails.visible = this.contextHeader.visible && this.contextHeader.expanded;
2446
+ this.devices.update(data?.devices);
2447
+ set("route", (inference?.route || "UNKNOWN").toUpperCase());
2448
+ set("backend", (inference?.backend || "--").toUpperCase());
2449
+ const model = `MODEL ${inference?.model || "--"}`;
2450
+ if (this.model.plainText !== model) this.model.content = model;
2451
+ set("input", instrumentNumber(inference?.inputTokens));
2452
+ set(
2453
+ "output",
2454
+ `${inference?.outputEstimated ? "~" : ""}${instrumentNumber(inference?.outputTokens)}`
2455
+ );
2456
+ set("speed", instrumentNumber(inference?.tokensPerSecond, 1, " TOK/S"));
2457
+ const used = instrumentNumber(inference?.contextUsedTokens), limit = instrumentNumber(inference?.contextLimitTokens);
2458
+ set("context", `${inference?.contextEstimated ? "~" : ""}${used}/${limit}`);
2459
+ this.contextMeter.updateReading(
2460
+ inference?.contextUsedTokens,
2461
+ inference?.contextLimitTokens
2462
+ );
2463
+ set(
2464
+ "state",
2465
+ inference?.streaming ? "STREAMING" : instrumentStatus(session?.status)
2466
+ );
2467
+ set(
2468
+ "selected",
2469
+ session?.selectedLabel || state.tabs.find((tab) => tab.id === state.activeViewId)?.label || state.activeViewId
2470
+ );
2471
+ set("elapsed", session?.elapsed ?? "--");
2472
+ set("turns", instrumentNumber(session?.turns));
2473
+ set("tools", instrumentNumber(session?.tools));
2474
+ set(
2475
+ "toolResults",
2476
+ `${instrumentNumber(session?.successfulTools)}/${instrumentNumber(session?.failedTools)}`
2477
+ );
2478
+ set("queued", instrumentNumber(session?.queued ?? state.queued.length));
2479
+ this.channels.update(
2480
+ session?.agents ?? state.tabs.map((tab) => ({
2481
+ id: tab.id,
2482
+ label: tab.label,
2483
+ status: "unknown",
2484
+ selected: tab.id === state.activeViewId
2485
+ }))
2486
+ );
2487
+ }
2488
+ applyTheme(colors) {
2489
+ this.backgroundColor = colors.background;
2490
+ const visit = (node) => {
2491
+ if (node instanceof TextRenderable3) {
2492
+ node.fg = colors.foreground;
2493
+ node.bg = colors.background;
2494
+ }
2495
+ for (const child of node.getChildren()) visit(child);
2496
+ };
2497
+ visit(this);
2498
+ styleNativeScrollbars(this.scroll, colors);
2499
+ styleNativeScrollbars(this.devices.scroll, colors);
2500
+ }
2501
+ toggleSection(id) {
2502
+ const header = id === "inference" ? this.inferenceHeader : this.sessionHeader;
2503
+ const body = id === "inference" ? this.inference : this.session;
2504
+ body.visible = !body.visible;
2505
+ header.update(
2506
+ id === "inference" ? "INFERENCE" : "SESSION / AGENTS",
2507
+ body.visible
2508
+ );
2509
+ }
2510
+ focusTargets() {
2511
+ if (!this.visible) return [];
2512
+ return [
2513
+ this.inferenceHeader,
2514
+ ...this.inference.visible && this.contextHeader.visible ? [this.contextHeader] : [],
2515
+ this.sessionHeader,
2516
+ ...this.session.visible ? this.channels.focusTargets() : [],
2517
+ ...this.devices.focusTargets()
2518
+ ];
2519
+ }
2520
+ focusSection(index = 0) {
2521
+ const target = [this.inferenceHeader, this.sessionHeader][index];
2522
+ if (!target || !this.visible) return false;
2523
+ target.focus();
2524
+ return true;
2525
+ }
2526
+ reveal(target) {
2527
+ const viewport = this.scroll.viewport;
2528
+ if (target.y < viewport.y) this.scroll.scrollBy(target.y - viewport.y);
2529
+ else if (target.y + target.height > viewport.y + viewport.height)
2530
+ this.scroll.scrollBy(
2531
+ target.y + target.height - viewport.y - viewport.height
2532
+ );
2533
+ }
2534
+ };
2535
+
2536
+ // packages/cli/src/tui/command-registry.ts
2537
+ var COMMAND_SIGNATURES = [
2538
+ ["/model", "Select from available models"],
2539
+ ["/model <name>", "Switch to a specific model"],
2540
+ ["/models", "List all available models"],
2541
+ ["/endpoint", "Show current endpoint"],
2542
+ ["/endpoint <url>", "Set backend URL (auto-detects type)"],
2543
+ ["/endpoint <url> --auth <t>", "Set endpoint with Bearer auth"],
2544
+ ["/fallback", "Show remote inference fallback mode"],
2545
+ ["/fallback status", "Show remote inference fallback mode"],
2546
+ ["/fallback auto", "Automatically use remote inference after local failure (default)"],
2547
+ ["/fallback manual", "Ask before remote inference after each local failure"],
2548
+ ["/config", "Show current configuration"],
2549
+ ["/color", "Use terminal colors and thin section rules"],
2550
+ ["/color status", "Show terminal appearance preferences"],
2551
+ ["/color frill on|off|toggle|status", "Legacy text ornament preference"],
2552
+ [
2553
+ "/color flow on|off|toggle|status",
2554
+ "Legacy border animation preference"
2555
+ ],
2556
+ ["/theme", "Alias for /color"],
2557
+ ["/setup", "Open guided cloud setup; /setup local opens local model setup"],
2558
+ ["/wizard", "Alias for /setup"],
2559
+ ["/parallel", "Show current Ollama parallel inference slots"],
2560
+ ["/parallel <1-15>", "Set parallel slots (restarts Ollama, max 15)"],
2561
+ ["/gpu", "Select enabled GPUs with arrow keys and Space"],
2562
+ ["/gpu status|reset", "Inspect or reset the host-local GPU policy"],
2563
+ ["/gpu select <UUID>", "Open the GPU policy selector focused on a device"],
2564
+ [
2565
+ "/broker",
2566
+ "Show resource broker status — loaded models, in-flight loads, RAM/VRAM headroom"
2567
+ ],
2568
+ [
2569
+ "/broker evict <host>:<name>",
2570
+ "Force eviction of a tracked model (e.g. /broker evict ollama:moondream)"
2571
+ ],
2572
+ [
2573
+ "/broker threshold <ram|vram|idle> <v>",
2574
+ "Tune broker thresholds (RAM/VRAM in MB, idle in seconds)"
2575
+ ],
2576
+ ["/ollama cleanup", "Dry-run stale Ollama pool process cleanup"],
2577
+ ["/ollama cleanup --execute", "Terminate guarded stale Ollama pool runners"],
2578
+ ["/mcp", "Show MCP server/tool status and controls"],
2579
+ ["/mcp reload", "Reload MCP servers and tools"],
2580
+ ["/indicator", "Start the native system indicator and report readiness"],
2581
+ ["/indicator start", "Start the native system indicator and report readiness"],
2582
+ ["/indicator status", "Report native system indicator readiness and daemon health"],
2583
+ ["/tray", "Alias for /indicator"],
2584
+ ["/tray start", "Alias for /indicator start"],
2585
+ ["/tray status", "Alias for /indicator status"],
2586
+ ["/update", "Check for updates and auto-install"],
2587
+ ["/upgrade", "Alias for /update"],
2588
+ ["/update auto", "Auto-install updates, restart, and resume work (default)"],
2589
+ ["/update manual", "Disable auto-update (only update on /update)"],
2590
+ ["/agents", "Open the agent command center"],
2591
+ [
2592
+ "/update check",
2593
+ "Check for Omnius and Ollama updates without opening the menu"
2594
+ ],
2595
+ ["/update quick", "Install the latest Omnius package non-interactively"],
2596
+ [
2597
+ "/update full",
2598
+ "Run full non-interactive update: package, deps, rebuild, Python, cloudflared"
2599
+ ],
2600
+ ["/voice", "Toggle TTS voice feedback"],
2601
+ [
2602
+ "/voice <model>",
2603
+ "Set voice: glados, overwatch, luxtts:announcer-testchamber03, kokoro, luxtts, misotts, supertonic, or a selected voicebox_* model"
2604
+ ],
2605
+ ["/voice clone <file>", "Set voice clone reference audio (wav/mp3/ogg/flac)"],
2606
+ ["/voice asr", "Open ASR system and model selection"],
2607
+ ["/voice asr status", "Show selected ASR engine/model and activation state"],
2608
+ ["/voice asr list", "List ASR engines, models, and live/file capabilities"],
2609
+ ["/voice asr use <engine> [model]", "Select and activate an installed ASR engine/model"],
2610
+ ["/voice asr setup <engine> [model] [device=N]", "Install pinned ASR runtime/weights and activate on an exact CUDA device"],
2611
+ [
2612
+ "/voice clone glados",
2613
+ "Generate clone ref from GLaDOS for LuxTTS/MisoTTS voice cloning"
2614
+ ],
2615
+ [
2616
+ "/voice clone overwatch",
2617
+ "Generate clone ref from Overwatch for LuxTTS/MisoTTS voice cloning"
2618
+ ],
2619
+ ["/think", "Toggle thinking/reasoning mode (Qwen3, DeepSeek-R1, etc.)"],
2620
+ ["/stream", "Toggle real-time token streaming (earth syntax highlighting)"],
2621
+ ["/tree", "Switch between the action waterfall and an explorable action tree"],
2622
+ ["/tree on|off|status", "Show, hide, or inspect the action-tree presentation"],
2623
+ ["/realtime", "Toggle short spoken-dialogue mode for ASR/TTS-backed chat"],
2624
+ ["/realtime on|off|status", "Set or inspect realtime conversation mode"],
2625
+ ["/mouse", "Toggle terminal mouse tracking"],
2626
+ ["/neovim", "Toggle embedded Neovim editor (Ctrl+N to switch focus)"],
2627
+ ["/neovim <file>", "Open file in embedded Neovim editor"],
2628
+ ["/nvim <file>", "Alias for /neovim <file>"],
2629
+ ["/vim", "Toggle Vim composer editing (Esc normal; i insert)"],
2630
+ ["/vim on|off|status", "Set or inspect Vim composer editing"],
2631
+ [
2632
+ "/dream",
2633
+ "Start dream mode - creative idle exploration (writes to .omnius/dreams/)"
2634
+ ],
2635
+ [
2636
+ "/dream deep",
2637
+ "Deep dream - multi-cycle exploration with sleep architecture"
2638
+ ],
2639
+ [
2640
+ "/dream lucid",
2641
+ "Lucid dream - implements proposals, tests, evaluates in cycles"
2642
+ ],
2643
+ ["/dream stop", "Wake up - stop dreaming"],
2644
+ ["/listen", "Toggle live microphone transcription (Whisper)"],
2645
+ ["/mic", "Alias for /listen"],
2646
+ ["/listen <model>", "Set model: tiny, base, small, medium, large"],
2647
+ ["/listen manual", "Manual mode - press Enter to submit (turns off auto)"],
2648
+ ["/listen auto", "Auto-submit after 3s silence (blinking indicator)"],
2649
+ ["/listen stop", "Stop listening"],
2650
+ ["/live", "Open live sensor stream menu for video/audio context"],
2651
+ ["/live status", "Show live sensor devices, selected streams, and latest snapshot"],
2652
+ ["/live run [agent]", "Start low-latency video/audio feedback; agent enables background PFC review"],
2653
+ ["/live chat", "Start low-latency voicechat with live audio/video context and async agent review"],
2654
+ ["/livechat", "Alias for /live chat, used by the top live button"],
2655
+ ["/live camera <device>", "Select a camera and render an ASCII preview"],
2656
+ ["/live resolution [camera] 720p|1080p|1440p|4k|native", "Set per-camera live stream resolution (default 1080p)"],
2657
+ ["/live fps [camera] 1|2|4|8|12|15|24|30", "Set per-camera live stream FPS (default 8)"],
2658
+ ["/live camera-on|camera-off [camera]", "Enable or disable an individual live camera stream"],
2659
+ ["/live rotate auto|cw|ccw|180|none [camera]", "Detect or set persistent camera orientation correction"],
2660
+ ["/live infer [now|on|off]", "Toggle or run camera object/location inference"],
2661
+ ["/live clip [on|off]", "Toggle CLIP visual-memory recognition on live frames"],
2662
+ ["/live audio|asr|sounds on|off", "Toggle live audio context streams"],
2663
+ ["/live stop", "Disable all live sensor streams"],
2664
+ ["/camera", "Open compact camera preset menu shared with /live"],
2665
+ ["/camera status", "Show selected camera and per-device presets"],
2666
+ ["/camera select|preview|resolution|fps|rotate", "Manage camera source, preview, resolution, FPS, and orientation"],
2667
+ ["/paste", "Attach clipboard image content to the current or next prompt"],
2668
+ ["/image", "Open image-generation model/setup menu"],
2669
+ [
2670
+ "/image <prompt>",
2671
+ "Generate an image from a prompt and show an ASCII preview"
2672
+ ],
2673
+ ["/image --model <model> <prompt>", "Generate with an explicit image model"],
2674
+ [
2675
+ "/image setup <ollama|diffusers|sdcpp>",
2676
+ "Show setup commands for an image-generation backend"
2677
+ ],
2678
+ [
2679
+ "/image list",
2680
+ "List image models by category, quality, size, and hardware fit"
2681
+ ],
2682
+ ["/sound", "Open sound-effect model/setup menu"],
2683
+ ["/sound <prompt>", "Generate a sound effect or ambience clip from a prompt"],
2684
+ [
2685
+ "/sound --model <model> <prompt>",
2686
+ "Generate sound with an explicit audio model"
2687
+ ],
2688
+ [
2689
+ "/sound setup <diffusers|audiocraft|stable-audio|tangoflux>",
2690
+ "Show setup commands for a sound-generation backend"
2691
+ ],
2692
+ [
2693
+ "/sound list",
2694
+ "List sound models by category, quality, size, and hardware fit"
2695
+ ],
2696
+ ["/music", "Open music-generation model/setup menu"],
2697
+ ["/music <prompt>", "Generate a music clip from a prompt"],
2698
+ [
2699
+ "/music --model <model> <prompt>",
2700
+ "Generate music with an explicit music model"
2701
+ ],
2702
+ [
2703
+ "/music setup <transformers|audiocraft|stable-audio|diffusers>",
2704
+ "Show setup commands for a music-generation backend"
2705
+ ],
2706
+ [
2707
+ "/music list",
2708
+ "List music models by category, quality, size, and hardware fit"
2709
+ ],
2710
+ ["/video", "Open video-generation model/setup menu"],
2711
+ [
2712
+ "/video <prompt>",
2713
+ "Generate a short video from a prompt and show an ASCII thumbnail"
2714
+ ],
2715
+ ["/video --model <model> <prompt>", "Generate video with an explicit model"],
2716
+ [
2717
+ "/video --image <path|url> <prompt>",
2718
+ "Image-to-video animation from a still"
2719
+ ],
2720
+ [
2721
+ "/video setup <diffusers|comfyui>",
2722
+ "Show setup commands for a video-generation backend"
2723
+ ],
2724
+ [
2725
+ "/video list",
2726
+ "List video models by category, quality, size, and hardware fit"
2727
+ ],
2728
+ ["/video prewarm", "Download and warm up the selected video model"],
2729
+ ["/pointcloud", "Open point-cloud generation model/setup menu"],
2730
+ [
2731
+ "/pointcloud --image <path>",
2732
+ "Generate a 3D point cloud PLY from an image using depth estimation"
2733
+ ],
2734
+ [
2735
+ "/pointcloud --image <path> --model <model>",
2736
+ "Generate with an explicit depth model (depth-anything-v3-* or mda-*)"
2737
+ ],
2738
+ [
2739
+ "/pointcloud --image <path1,path2> --backend mda",
2740
+ "Multi-view depth fusion from comma-separated image paths"
2741
+ ],
2742
+ [
2743
+ "/pointcloud setup <depth-anything|mda>",
2744
+ "Show setup commands for a point-cloud backend"
2745
+ ],
2746
+ [
2747
+ "/pointcloud list",
2748
+ "List point-cloud models by backend, quality, and VRAM fit"
2749
+ ],
2750
+ ["/pointcloud prewarm", "Download and warm up the selected depth model"],
2751
+ ["/points <args>", "Alias for /pointcloud"],
2752
+ ["/pcl <args>", "Alias for /pointcloud"],
2753
+ ["/call", "Start voice call session (cloudflared tunnel + ASR/TTS)"],
2754
+ ["/hangup", "End active call session"],
2755
+ [
2756
+ "/queue <prompt>",
2757
+ "Queue a prompt for the next turn without interrupting the current run"
2758
+ ],
2759
+ ["/q <prompt>", "Alias for /queue <prompt>"],
2760
+ ["/retry", "Retry the last submitted request"],
2761
+ ["/undo", "Undo the last queued or saved exchange when available"],
2762
+ ["/title [name]", "Show or set the current session title"],
2763
+ [
2764
+ "/rollback [checkpoint]",
2765
+ "List checkpoints or restore an explicit checkpoint when policy allows"
2766
+ ],
2767
+ ["/background <prompt>", "Run a prompt in an independent background session"],
2768
+ ["/bg <prompt>", "Alias for /background <prompt>"],
2769
+ ["/cost", "Show session token cost breakdown"],
2770
+ ["/usage", "Show current session token count and cost"],
2771
+ ["/insights", "Show historical session insights"],
2772
+ ["/evaluate", "Evaluate last completed task (LLM quality scoring)"],
2773
+ [
2774
+ "/score",
2775
+ "Show inference capability scorecard (memory, compute, speed, models)"
2776
+ ],
2777
+ [
2778
+ "/task-type",
2779
+ "Set task type (code, document, analysis, plan, general, auto)"
2780
+ ],
2781
+ ["/stats", "Show session dashboard (metrics, tool usage, task history)"],
2782
+ [
2783
+ "/metrics",
2784
+ "Show runtime metrics: tokens, context window, throughput, and tool usage"
2785
+ ],
2786
+ ["/sessions", "Browse saved sessions"],
2787
+ ["/new", "Start a fresh chat in the current project"],
2788
+ [
2789
+ "/pause",
2790
+ "Pause after current turn finishes (gentle halt, /resume to continue)"
2791
+ ],
2792
+ [
2793
+ "/stop",
2794
+ "Kill current inference immediately and save state (/resume to continue)"
2795
+ ],
2796
+ [
2797
+ "/resume",
2798
+ "Resume a paused task or choose a saved project chat"
2799
+ ],
2800
+ ["/resume <session>", "Resume a saved project chat by id or title"],
2801
+ [
2802
+ "/destroy processes",
2803
+ "Find and kill orphaned Omnius child processes (this project)"
2804
+ ],
2805
+ [
2806
+ "/destroy processes --global",
2807
+ "Kill ALL orphaned Omnius processes system-wide"
2808
+ ],
2809
+ [
2810
+ "/destroy project",
2811
+ "Remove .omnius folder, kill all tasks, clear console, and exit"
2812
+ ],
2813
+ ["/context save", "Force-save session context to .omnius/context/"],
2814
+ ["/context restore", "Restore context from previous sessions into next task"],
2815
+ ["/context show", "Show saved session context status"],
2816
+ ["/compact", "Force context compaction now (default strategy)"],
2817
+ ["/compacted", "Review applied, held, rejected, and failed compaction attempts"],
2818
+ [
2819
+ "/compact <strategy>",
2820
+ "Compact with strategy: aggressive, decisions, errors, summary, structured"
2821
+ ],
2822
+ ["/bruteforce", "Toggle brute-force mode (auto re-engage on turn limit)"],
2823
+ [
2824
+ "/deep",
2825
+ "Toggle deep context - relaxes compaction so large models use 85% of context"
2826
+ ],
2827
+ [
2828
+ "/flow",
2829
+ "Toggle FlowState protocol - immediate action, tight loops, goal clarity"
2830
+ ],
2831
+ ["/emojis", "Toggle emoji rendering in TUI messages"],
2832
+ ["/tools", "List agent-created custom tools"],
2833
+ ["/skills", "List available AIWG skills"],
2834
+ ["/skill", "Alias for /skills"],
2835
+ ["/skills <keyword>", "Filter skills by name or trigger"],
2836
+ ["/skillify [request]", "Extract a reusable skill from the current session"],
2837
+ ["/<skill-name> [args]", "Invoke an AIWG skill directly"],
2838
+ [
2839
+ "/full-send-bless",
2840
+ "Infinite warm loop - DMN self-reflection chains tasks autonomously"
2841
+ ],
2842
+ ["/bless stop", "End blessed mode + DMN"],
2843
+ [
2844
+ "/telegram --key <token>",
2845
+ "Save Telegram bot token (persisted to settings)"
2846
+ ],
2847
+ [
2848
+ "/telegram --admin <userid>",
2849
+ "Set admin user filter (only this user can interact)"
2850
+ ],
2851
+ ["/telegram", "Toggle Telegram bridge on/off (uses saved key)"],
2852
+ ["/telegram status", "Show Telegram bridge status"],
2853
+ ["/telegram stop", "Disconnect Telegram bridge"],
2854
+ ["/telegram disable", "Persistently disable Telegram long polling"],
2855
+ ["/telegram enable", "Re-enable Telegram long polling"],
2856
+ [
2857
+ "/telegram mode auto|chat|action",
2858
+ "Set Telegram interaction routing: auto, fast chat, or action sub-agent"
2859
+ ],
2860
+ [
2861
+ "/telegram subagents <1-5>",
2862
+ "Set global Telegram work-slot limit across all chats/groups"
2863
+ ],
2864
+ [
2865
+ "/telegram model",
2866
+ "Pick the Telegram-isolated model (model list from the Telegram endpoint)"
2867
+ ],
2868
+ [
2869
+ "/telegram model <name|reset>",
2870
+ "Set or clear the Telegram-isolated model (separate from main agent)"
2871
+ ],
2872
+ [
2873
+ "/telegram endpoint",
2874
+ "Pick the Telegram-isolated backend endpoint (same menu as /endpoint, bot-scoped)"
2875
+ ],
2876
+ [
2877
+ "/telegram endpoint <url|reset> [--auth <t>]",
2878
+ "Set or clear the Telegram-isolated endpoint"
2879
+ ],
2880
+ [
2881
+ "/telegram auth",
2882
+ "Show a TUI-only one-time code for Telegram admin authentication"
2883
+ ],
2884
+ [
2885
+ "/telegram auth cancel",
2886
+ "Cancel the pending Telegram admin authentication code"
2887
+ ],
2888
+ [
2889
+ "/telegram bot <username> <text>",
2890
+ "Send a Bot API bot-to-bot message by @username"
2891
+ ],
2892
+ [
2893
+ "/telegram poll <chat> <question> | <option> [| <option>]",
2894
+ "Send a Telegram poll"
2895
+ ],
2896
+ [
2897
+ "/telegram live-photo <chat> <live_photo> <photo> [caption]",
2898
+ "Send a Telegram live photo"
2899
+ ],
2900
+ [
2901
+ "/telegram draft <chat_id> <draft_id> [text]",
2902
+ "Set or clear a Telegram message draft"
2903
+ ],
2904
+ [
2905
+ "/telegram admins <chat> [--bots]",
2906
+ "List chat administrators, optionally including bots"
2907
+ ],
2908
+ [
2909
+ "/telegram personal <user_id> <limit>",
2910
+ "Fetch last messages from a user's profile personal chat"
2911
+ ],
2912
+ [
2913
+ "/telegram access get <managed_bot_user_id>",
2914
+ "Show managed-bot access restriction settings"
2915
+ ],
2916
+ [
2917
+ "/telegram access set <managed_bot_user_id> <restricted|open> [added_user_ids]",
2918
+ "Set managed-bot access restrictions"
2919
+ ],
2920
+ [
2921
+ "/telegram tools [group <chat>|panel <chat> [policy_chat]]",
2922
+ "Show unified Telegram tool policy or send inline admin toggle buttons"
2923
+ ],
2924
+ [
2925
+ "/telegram delete-message <chat> <message> [reason]",
2926
+ "Delete one Telegram message after policy and Bot API rights checks"
2927
+ ],
2928
+ [
2929
+ "/telegram delete-messages <chat> <message,message> [reason]",
2930
+ "Delete multiple Telegram messages after policy and Bot API rights checks"
2931
+ ],
2932
+ [
2933
+ "/telegram delete-reaction <chat> <message> [--user <id>|--actor <chat>]",
2934
+ "Delete a Telegram message reaction"
2935
+ ],
2936
+ [
2937
+ "/telegram delete-reactions <chat> [--user <id>|--actor <chat>]",
2938
+ "Delete recent Telegram message reactions by user or chat"
2939
+ ],
2940
+ ["/platforms", "Show gateway platform state"],
2941
+ ["/platforms status", "Show gateway platform state"],
2942
+ ["/platforms menu", "Open interactive platform connectivity menu"],
2943
+ ["/platforms setup", "Open platform onboarding menu"],
2944
+ ["/platforms onboard", "Open platform onboarding menu"],
2945
+ ["/platforms config", "Show redacted platform adapter configuration"],
2946
+ ["/platforms enable <id>", "Enable a platform adapter"],
2947
+ ["/platforms disable <id>", "Disable a platform adapter"],
2948
+ [
2949
+ "/platforms set <id> token-env <ENV>",
2950
+ "Set adapter token environment variable"
2951
+ ],
2952
+ ["/platforms set <id> base-url <url>", "Set adapter base URL or webhook URL"],
2953
+ [
2954
+ "/platforms set <id> target <target>",
2955
+ "Set adapter default channel, room, or target"
2956
+ ],
2957
+ [
2958
+ "/platforms set <id> mode <polling|webhook>",
2959
+ "Set adapter polling or webhook mode"
2960
+ ],
2961
+ [
2962
+ "/platforms clear <id> <key>",
2963
+ "Clear saved adapter token, token-env, base-url, or target"
2964
+ ],
2965
+ ["/gateway", "Alias for /platforms"],
2966
+ ["/gateway menu", "Alias for /platforms menu"],
2967
+ [
2968
+ "/cohere",
2969
+ "Toggle COHERE cognitive commons - join distributed memory/identity mesh"
2970
+ ],
2971
+ [
2972
+ "/cohere stats",
2973
+ "Show network stats - queries in/out, model usage, peer transparency"
2974
+ ],
2975
+ ["/cohere models", "List downloaded models with COHERE exposure status"],
2976
+ ["/cohere allow <model>", "Allow a model for remote COHERE queries"],
2977
+ ["/cohere deny <model>", "Hide a model from remote COHERE queries"],
2978
+ ["/nexus", "Show nexus P2P network status"],
2979
+ ["/nexus connect", "Connect to the agent mesh network"],
2980
+ [
2981
+ "/nexus name <name>",
2982
+ "Set custom agent name (persists globally across all Omnius instances)"
2983
+ ],
2984
+ ["/nexus name", "Show current agent name"],
2985
+ ["/nexus restart", "Kill daemon and reconnect (picks up new version)"],
2986
+ ["/nexus restart --clean", "Restart + clear metering data for fresh stats"],
2987
+ ["/nexus disconnect", "Kill daemon without reconnecting"],
2988
+ ["/nexus wallet", "Show wallet address and x402 payment status"],
2989
+ ["/ipfs", "Show IPFS/Helia status"],
2990
+ ["/ipfs pin <cid>", "Pin an IPFS CID"],
2991
+ ["/ipfs publish", "Publish identity snapshot to IPFS"],
2992
+ ["/ipfs share <tool|skill> <name>", "Share a tool or skill through IPFS"],
2993
+ ["/ipfs import <cid>", "Import a shared IPFS tool or skill reference"],
2994
+ ["/sponsor", "Sponsor inference - onboarding wizard or dashboard"],
2995
+ ["/sponsor status", "Show sponsor dashboard with usage metrics"],
2996
+ ["/sponsor image <prompt>", "Generate an image on a sponsored peer"],
2997
+ ["/sponsor video <prompt>", "Generate a video on a sponsored peer"],
2998
+ ["/sponsor sound <prompt>", "Generate a sound effect on a sponsored peer"],
2999
+ ["/sponsor music <prompt>", "Generate music on a sponsored peer"],
3000
+ ["/sponsor pause", "Pause active sponsorship"],
3001
+ ["/sponsor remove", "Remove sponsorship entirely"],
3002
+ [
3003
+ "/endpoint sponsor",
3004
+ "Browse and connect to sponsored inference from the nexus mesh"
3005
+ ],
3006
+ ["/expose <backend>", "Expose local inference via libp2p (default)"],
3007
+ ["/expose <backend> --tunnel", "Expose via cloudflared tunnel"],
3008
+ ["/expose passthrough", "Forward configured /endpoint through libp2p P2P"],
3009
+ [
3010
+ "/expose forward --loadbalance",
3011
+ "Passthrough with distributed rate-limit budget"
3012
+ ],
3013
+ ["/expose config", "Interactive expose configuration menu"],
3014
+ ["/expose status", "Show gateway stats (requests, models, budget)"],
3015
+ ["/expose stop", "Stop all expose gateways"],
3016
+ ["/expose stop --tunnel", "Stop tunnel gateway only"],
3017
+ ["/expose stop --libp2p", "Stop libp2p gateway only"],
3018
+ ["/access", "Show access policy and host binding"],
3019
+ [
3020
+ "/access <loopback|lan|any>",
3021
+ "Set access policy (OMNIUS_ACCESS) and restart daemon"
3022
+ ],
3023
+ ["/k <any|lan|loopback|config>", "Quick network access helper"],
3024
+ ["/scheduler", "Scheduled tasks control panel (list/kill/toggle)"],
3025
+ ["/cron", "Cron/scheduled job management"],
3026
+ ["/cron list", "List cron jobs"],
3027
+ ["/cron add <schedule> <prompt>", "Create a new cron job"],
3028
+ ["/cron edit <id> <field> <val>", "Update a cron job field"],
3029
+ ["/cron pause <id>", "Pause a cron job"],
3030
+ ["/cron resume <id>", "Resume a paused cron job"],
3031
+ ["/cron run <id>", "Execute a cron job immediately"],
3032
+ ["/cron remove <id>", "Delete a cron job"],
3033
+ ["/cron outputs <id>", "Show saved output files for a job"],
3034
+ ["/cron tick", "Run scheduler tick"],
3035
+ ["/scheduler menu", "Interactive scheduler menu (toggle/kill)"],
3036
+ ["/scheduler list", "List all scheduled tasks and timers"],
3037
+ [
3038
+ "/scheduler kill",
3039
+ "Kill schedulers + active runs (with escalation if needed)"
3040
+ ],
3041
+ ["/reminder [list|all]", "List scoped TUI reminders"],
3042
+ [
3043
+ "/remind <when> <message>",
3044
+ "Set a scoped person notification; a due time is required"
3045
+ ],
3046
+ [
3047
+ "/remind in 30m <message>",
3048
+ "Set a scoped person notification with a relative due time"
3049
+ ],
3050
+ ["/reminders", "Alias for /reminder"],
3051
+ ["/daemon", "Show or manage the local API daemon"],
3052
+ ["/daemon <start|stop|restart|takeover>", "Control the local API daemon"],
3053
+ ["/apikey", "Show current API key (for pasting into Web UI / clients)"],
3054
+ ["/apikey copy", "Copy the API key to clipboard"],
3055
+ ["/apikey new", "Rotate to a new API key (regenerate)"],
3056
+ ["/secrets", "List registered secrets"],
3057
+ ["/secrets set <name> <value>", "Register a secret for redacted tool use"],
3058
+ ["/secrets delete <name>", "Remove a registered secret"],
3059
+ ["/key", "Alias for /apikey"],
3060
+ ["/codegraph", "Code-graph snapshot: stats, top files, recent activity"],
3061
+ [
3062
+ "/codegraph watch [N]",
3063
+ "Subscribe to live code-graph events for N seconds (default 30)"
3064
+ ],
3065
+ ["/cg", "Alias for /codegraph"],
3066
+ ["/storage", "Show .omnius storage usage and safety summary"],
3067
+ ["/files", "Show searchable working-directory file overview"],
3068
+ ["/files <query>", "Filter the working-directory file overview"],
3069
+ ["/files menu", "Open searchable workspace file selector"],
3070
+ ["/workspace", "Alias for /files"],
3071
+ ["/workspace <query>", "Alias for /files <query>"],
3072
+ ["/workspace menu", "Alias for /files menu"],
3073
+ ["/ingest <file>", "Ingest an audio, PDF, or text file into memory"],
3074
+ ["/transcribe <file>", "Alias for /ingest <file>"],
3075
+ ["/fortemi", "Manage Fortemi React bridge integration"],
3076
+ [
3077
+ "/host <host[:port]>",
3078
+ "Set bind host:port (OMNIUS_HOST) and restart daemon"
3079
+ ],
3080
+ ["/network config", "Interactive network access menu"],
3081
+ ["/p2p start", "Join the P2P agent mesh network"],
3082
+ ["/p2p status", "Show mesh peers and rooms"],
3083
+ ["/p2p stop", "Leave the mesh network"],
3084
+ ["/style", "Show current response style"],
3085
+ ["/style <preset>", "Set style: concise, balanced, verbose, pedagogical"],
3086
+ [
3087
+ "/personality <preset>",
3088
+ "Alias family for response style/personality controls"
3089
+ ],
3090
+ ["/commands auto", "Allow agent to invoke slash commands as tools"],
3091
+ ["/cmds auto", "Alias for /commands auto"],
3092
+ ["/commands manual", "Slash commands are user-only (default)"],
3093
+ [
3094
+ "/selfmodify",
3095
+ "Toggle agent self-modify mode for natural-language runtime commands"
3096
+ ],
3097
+ [
3098
+ "/selfmodify on",
3099
+ "Allow the agent to decide when to invoke self-modifying slash commands"
3100
+ ],
3101
+ [
3102
+ "/selfmodify off",
3103
+ "Disable agent self-modifying slash-command access (default)"
3104
+ ],
3105
+ ["/selfmodify status", "Show current self-modify mode"],
3106
+ [
3107
+ "/selfimprove status",
3108
+ "Show durable review/proposal backlog, leases, retries, and last error"
3109
+ ],
3110
+ [
3111
+ "/selfimprove start",
3112
+ "Enable review mode and drain eligible work with the parent model"
3113
+ ],
3114
+ ["/selfimprove stop", "Disable processing while retaining the durable backlog"],
3115
+ ["/selfimprove disable", "Alias for /selfimprove stop"],
3116
+ [
3117
+ "/selfimprove review",
3118
+ "Run evidence review and candidate generation without active promotion"
3119
+ ],
3120
+ [
3121
+ "/selfimprove autonomous",
3122
+ "Opt in to downstream candidate experiment and promotion orchestration"
3123
+ ],
3124
+ [
3125
+ "/selfimprove rollback <promotion-id> [reason]",
3126
+ "Restore the exact pre-promotion baseline and verify the rollback"
3127
+ ],
3128
+ ["/debug", "Toggle debug mode — show/hide trust_tier wrappers and REG fires"],
3129
+ ["/debug on", "Show trust_tier wrappers and REG fires in terminal"],
3130
+ ["/debug off", "Hide trust_tier wrappers and REG fires (default)"],
3131
+ ["/debug", "Toggle debug mode — show/hide trust_tier wrappers and REG fires"],
3132
+ ["/debug on", "Show trust_tier wrappers and REG fires in terminal"],
3133
+ ["/debug off", "Hide trust_tier wrappers and REG fires (default)"],
3134
+ ["/hf", "Show HF token status (set / not set)"],
3135
+ [
3136
+ "/hf <token>",
3137
+ "Save Hugging Face access token to ~/.omnius/hf_token and unblock the active gated download"
3138
+ ],
3139
+ ["/hf clear", "Remove the persisted HF token"],
3140
+ [
3141
+ "/hf cancel",
3142
+ "Cancel any pending HF token prompt and fall back immediately"
3143
+ ],
3144
+ [
3145
+ "/models",
3146
+ "Show unified ~/.omnius model store: disk usage, cached repos, last-used"
3147
+ ],
3148
+ ["/models 3d", "Select CAD/3D generation and reconstruction model adapters"],
3149
+ ["/models cad", "Select text-to-CAD model adapters such as cad0"],
3150
+ [
3151
+ "/models viewer [file]",
3152
+ "Start a temporary standalone CAD/3D viewer endpoint"
3153
+ ],
3154
+ [
3155
+ "/models hf list --modality 3d",
3156
+ "List curated and installed Hugging Face CAD/3D adapters"
3157
+ ],
3158
+ [
3159
+ "/models evict <repo>",
3160
+ "Remove a cached HF repo from the unified store to reclaim disk"
3161
+ ],
3162
+ [
3163
+ "/models evict --free <GB>",
3164
+ "LRU-evict cached models until <GB> of free disk is available"
3165
+ ],
3166
+ [
3167
+ "/models cleanup",
3168
+ "Migrate legacy per-repo .omnius/{image,video,audio}-gen caches into the unified store and delete the originals"
3169
+ ],
3170
+ [
3171
+ "/models cleanup --dry-run",
3172
+ "Preview /models cleanup without touching disk"
3173
+ ],
3174
+ ["/voicechat", "Start voice chat session (async voice conversation)"],
3175
+ ["/voicechat stop", "Stop voice chat session"],
3176
+ ["/memory", "Open the interactive memory pixel map and provenance trace"],
3177
+ ["/memory legacy", "Open the legacy list-based memory browser"],
3178
+ [
3179
+ "/metabolize [--dry-run] [--verbose] [--min-score <n>]",
3180
+ "Run memory maintenance: import deferred memory, prune episodes, compact graph stores"
3181
+ ],
3182
+ [
3183
+ "/prune [now|dry|infer|aggressive|stats] [--target <n>]",
3184
+ "Shrink knowledge.db in the background: evict low-signal graph nodes (archived), then VACUUM. 'infer' = model-guided sleep consolidation. No arg shows stats + usage"
3185
+ ],
3186
+ ["/verbose", "Toggle verbose mode"],
3187
+ ["/clear", "Clear the screen"],
3188
+ ["/help", "Show this help"],
3189
+ ["/quit", "Exit omnius"]
3190
+ ].map(([signature, description]) => ({ signature, description }));
3191
+ var PLANNED_SIGNATURES = [
3192
+ ["/provider", "Show or select provider profile"],
3193
+ ["/prompt", "Show or edit the active prompt template"],
3194
+ ["/reasoning", "Alias family for thinking/reasoning controls"],
3195
+ ["/statusbar", "Configure the status bar"],
3196
+ ["/sb", "Alias for /statusbar"],
3197
+ ["/skin", "Configure the visual skin/theme bundle"],
3198
+ ["/toolsets", "Show or select tool exposure sets"],
3199
+ ["/browser connect|disconnect|status", "Manage browser-agent connectivity"],
3200
+ ["/reload-mcp", "Reload MCP servers and tools"],
3201
+ ["/plugins", "List or manage plugins"],
3202
+ ["/status", "Show overall runtime status"],
3203
+ ["/sethome", "Set the project home for gateway/session use"],
3204
+ ["/approve", "Approve a pending gateway/admin action"],
3205
+ ["/deny", "Deny a pending gateway/admin action"]
3206
+ ].map(([signature, description]) => ({
3207
+ signature,
3208
+ description,
3209
+ implementationStatus: "planned"
3210
+ }));
3211
+ var CATEGORY_OVERRIDES = {
3212
+ model: "model",
3213
+ models: "model",
3214
+ endpoint: "network",
3215
+ fallback: "network",
3216
+ provider: "network",
3217
+ parallel: "runtime",
3218
+ gpu: "runtime",
3219
+ broker: "runtime",
3220
+ setup: "runtime",
3221
+ wizard: "runtime",
3222
+ mcp: "tools",
3223
+ mcps: "tools",
3224
+ update: "admin",
3225
+ upgrade: "admin",
3226
+ voice: "media",
3227
+ voicechat: "media",
3228
+ listen: "media",
3229
+ mic: "media",
3230
+ paste: "media",
3231
+ image: "media",
3232
+ video: "media",
3233
+ pointcloud: "media",
3234
+ points: "media",
3235
+ pcl: "media",
3236
+ ingest: "media",
3237
+ transcribe: "media",
3238
+ call: "media",
3239
+ hangup: "media",
3240
+ queue: "session",
3241
+ q: "session",
3242
+ retry: "session",
3243
+ undo: "session",
3244
+ title: "session",
3245
+ rollback: "session",
3246
+ background: "session",
3247
+ bg: "session",
3248
+ pause: "session",
3249
+ stop: "session",
3250
+ resume: "session",
3251
+ new: "session",
3252
+ context: "session",
3253
+ compact: "session",
3254
+ compacted: "session",
3255
+ gc: "session",
3256
+ sessions: "session",
3257
+ session: "session",
3258
+ dream: "runtime",
3259
+ "full-send-bless": "runtime",
3260
+ bless: "runtime",
3261
+ telegram: "gateway",
3262
+ cohere: "network",
3263
+ nexus: "network",
3264
+ ipfs: "network",
3265
+ storage: "admin",
3266
+ files: "tools",
3267
+ workspace: "tools",
3268
+ "task-type": "runtime",
3269
+ tasktype: "runtime",
3270
+ sponsor: "network",
3271
+ expose: "gateway",
3272
+ access: "network",
3273
+ host: "network",
3274
+ network: "network",
3275
+ k: "network",
3276
+ scheduler: "runtime",
3277
+ reminder: "runtime",
3278
+ daemon: "runtime",
3279
+ indicator: "ui",
3280
+ apikey: "secret",
3281
+ key: "secret",
3282
+ secrets: "secret",
3283
+ secret: "secret",
3284
+ vault: "secret",
3285
+ codegraph: "tools",
3286
+ cg: "tools",
3287
+ p2p: "network",
3288
+ tools: "tools",
3289
+ skills: "tools",
3290
+ skill: "tools",
3291
+ skillify: "tools",
3292
+ memory: "memory",
3293
+ mem: "memory",
3294
+ metabolize: "memory",
3295
+ prune: "memory",
3296
+ mouse: "ui",
3297
+ color: "ui",
3298
+ colors: "ui",
3299
+ theme: "ui",
3300
+ neovim: "ui",
3301
+ nvim: "ui",
3302
+ vim: "ui",
3303
+ emojis: "ui",
3304
+ emoji: "ui",
3305
+ statusbar: "ui",
3306
+ sb: "ui",
3307
+ skin: "ui",
3308
+ prompt: "ui",
3309
+ personality: "ui",
3310
+ reasoning: "ui",
3311
+ selfmodify: "runtime",
3312
+ selfimprove: "runtime",
3313
+ "self-improve": "runtime",
3314
+ debug: "runtime",
3315
+ selfmod: "runtime",
3316
+ "self-modify": "runtime",
3317
+ stats: "session",
3318
+ metrics: "session"
3319
+ };
3320
+ var ALIASES = {
3321
+ help: ["h", "?"],
3322
+ quit: ["exit"],
3323
+ queue: ["q"],
3324
+ background: ["bg"],
3325
+ clear: ["cls"],
3326
+ verbose: ["v"],
3327
+ config: ["cfg"],
3328
+ color: ["colors", "theme"],
3329
+ setup: ["wizard"],
3330
+ mcp: ["mcps"],
3331
+ update: ["upgrade"],
3332
+ indicator: ["tray"],
3333
+ listen: ["mic"],
3334
+ pointcloud: ["points", "pcl", "point"],
3335
+ neovim: ["nvim"],
3336
+ apikey: ["key"],
3337
+ secrets: ["secret", "vault"],
3338
+ sessions: ["session"],
3339
+ cost: ["costs"],
3340
+ evaluate: ["eval"],
3341
+ score: ["inference"],
3342
+ "task-type": ["tasktype", "tt"],
3343
+ stats: ["dashboard"],
3344
+ memory: ["mem"],
3345
+ metabolize: ["metab"],
3346
+ files: ["workspace"],
3347
+ skills: ["skill"],
3348
+ commands: ["cmds"],
3349
+ selfmodify: ["selfmod", "self-modify"],
3350
+ selfimprove: ["self-improve"],
3351
+ codegraph: ["cg"],
3352
+ endpoint: ["ep"],
3353
+ telegram: ["tg"],
3354
+ bruteforce: ["brute"],
3355
+ deep: ["deep-context"],
3356
+ flow: ["flowstate"],
3357
+ emojis: ["emoji"],
3358
+ compact: ["gc"],
3359
+ network: ["k"],
3360
+ scheduler: [],
3361
+ reminder: ["remind", "reminders"],
3362
+ statusbar: ["sb"],
3363
+ platforms: ["gateway"]
3364
+ };
3365
+ var USER_ONLY = /* @__PURE__ */ new Set([
3366
+ "fallback",
3367
+ "quit",
3368
+ "exit",
3369
+ "clear",
3370
+ "cls",
3371
+ "q",
3372
+ "queue",
3373
+ "model",
3374
+ "endpoint",
3375
+ "parallel",
3376
+ "gpu",
3377
+ "memory",
3378
+ "broker",
3379
+ "commands",
3380
+ "cmds",
3381
+ "selfmodify",
3382
+ "selfmod",
3383
+ "self-modify",
3384
+ "selfimprove",
3385
+ "self-improve",
3386
+ "debug",
3387
+ "dbg",
3388
+ "hf",
3389
+ "models",
3390
+ "mcp",
3391
+ "mcps",
3392
+ "update",
3393
+ "upgrade",
3394
+ "setup",
3395
+ "wizard",
3396
+ "telegram",
3397
+ "call",
3398
+ "hangup",
3399
+ "listen",
3400
+ "mic",
3401
+ "paste",
3402
+ "image",
3403
+ "video",
3404
+ "pointcloud",
3405
+ "points",
3406
+ "pcl",
3407
+ "ingest",
3408
+ "transcribe",
3409
+ "expose",
3410
+ "p2p",
3411
+ "ipfs",
3412
+ "storage",
3413
+ "files",
3414
+ "workspace",
3415
+ "secrets",
3416
+ "secret",
3417
+ "vault",
3418
+ "skillify",
3419
+ "dream",
3420
+ "bless",
3421
+ "full-send-bless",
3422
+ "pause",
3423
+ "stop",
3424
+ "resume",
3425
+ "new",
3426
+ "retry",
3427
+ "undo",
3428
+ "rollback",
3429
+ "background",
3430
+ "bg",
3431
+ "apikey",
3432
+ "key",
3433
+ "access",
3434
+ "host",
3435
+ "network",
3436
+ "k",
3437
+ "scheduler",
3438
+ "cron",
3439
+ "daemon",
3440
+ "indicator",
3441
+ "compacted",
3442
+ "fortemi",
3443
+ "mouse",
3444
+ "approve",
3445
+ "deny",
3446
+ "sethome"
3447
+ ]);
3448
+ var DESTRUCTIVE = /* @__PURE__ */ new Set([
3449
+ "destroy",
3450
+ "stop",
3451
+ "rollback",
3452
+ "update",
3453
+ "upgrade",
3454
+ "deny",
3455
+ "daemon",
3456
+ "ollama"
3457
+ ]);
3458
+ var NETWORKED = /* @__PURE__ */ new Set([
3459
+ "endpoint",
3460
+ "update",
3461
+ "upgrade",
3462
+ "telegram",
3463
+ "call",
3464
+ "listen",
3465
+ "mic",
3466
+ "image",
3467
+ "video",
3468
+ "pointcloud",
3469
+ "points",
3470
+ "pcl",
3471
+ "cohere",
3472
+ "nexus",
3473
+ "ipfs",
3474
+ "sponsor",
3475
+ "expose",
3476
+ "access",
3477
+ "host",
3478
+ "network",
3479
+ "k",
3480
+ "p2p",
3481
+ "daemon",
3482
+ "fortemi",
3483
+ "mcp",
3484
+ "mcps",
3485
+ "browser",
3486
+ "cron",
3487
+ "reload-mcp",
3488
+ "plugins",
3489
+ "platforms",
3490
+ "gateway"
3491
+ ]);
3492
+ var SECRET_BEARING = /* @__PURE__ */ new Set([
3493
+ "endpoint",
3494
+ "apikey",
3495
+ "key",
3496
+ "telegram",
3497
+ "secrets",
3498
+ "secret",
3499
+ "vault",
3500
+ "hf"
3501
+ ]);
3502
+ var PROFILE_GATED = /* @__PURE__ */ new Set([
3503
+ "destroy",
3504
+ "update",
3505
+ "upgrade",
3506
+ "telegram",
3507
+ "call",
3508
+ "listen",
3509
+ "mic",
3510
+ "expose",
3511
+ "access",
3512
+ "host",
3513
+ "network",
3514
+ "k",
3515
+ "p2p",
3516
+ "daemon",
3517
+ "fortemi",
3518
+ "mcp",
3519
+ "mcps",
3520
+ "ipfs",
3521
+ "approve",
3522
+ "deny",
3523
+ "sethome"
3524
+ ]);
3525
+ var REST_BLOCKED = /* @__PURE__ */ new Set([
3526
+ "pause",
3527
+ "resume",
3528
+ "new",
3529
+ "retry",
3530
+ "undo",
3531
+ "rollback",
3532
+ "background",
3533
+ "bg",
3534
+ "paste",
3535
+ "indicator"
3536
+ ]);
3537
+ var LOCAL_TUI_ONLY = /* @__PURE__ */ new Set(["indicator", "compacted", "gpu", "memory"]);
3538
+ var CANONICAL_BY_ALIAS = /* @__PURE__ */ new Map();
3539
+ for (const [canonical, aliases2] of Object.entries(ALIASES)) {
3540
+ for (const alias of aliases2) CANONICAL_BY_ALIAS.set(alias, canonical);
3541
+ }
3542
+ var DYNAMIC_COMMANDS = [];
3543
+ function getSlashHelpEntries(opts = {}) {
3544
+ const includePlanned = opts.includePlanned === true;
3545
+ const seen = /* @__PURE__ */ new Set();
3546
+ const out = [];
3547
+ for (const item of [
3548
+ ...DYNAMIC_COMMANDS,
3549
+ ...COMMAND_SIGNATURES,
3550
+ ...PLANNED_SIGNATURES
3551
+ ]) {
3552
+ if (!includePlanned && item.implementationStatus === "planned") continue;
3553
+ if (seen.has(item.signature)) continue;
3554
+ seen.add(item.signature);
3555
+ out.push([item.signature, item.description]);
3556
+ }
3557
+ return out;
3558
+ }
3559
+ var SLASH_COMMANDS = getSlashHelpEntries();
3560
+ function listCommandRegistry(opts = {}) {
3561
+ const includePlanned = opts.includePlanned !== false;
3562
+ const grouped = /* @__PURE__ */ new Map();
3563
+ for (const sig of [
3564
+ ...COMMAND_SIGNATURES,
3565
+ ...PLANNED_SIGNATURES,
3566
+ ...DYNAMIC_COMMANDS
3567
+ ]) {
3568
+ if (!includePlanned && sig.implementationStatus === "planned") continue;
3569
+ const name = commandNameFromSignature(sig.signature);
3570
+ if (!name || name.startsWith("<")) continue;
3571
+ if (!grouped.has(name)) grouped.set(name, []);
3572
+ grouped.get(name).push(sig);
3573
+ }
3574
+ return Array.from(grouped.entries()).map(
3575
+ ([name, signatures]) => buildCommandDef(name, signatures)
3576
+ );
3577
+ }
3578
+ function findCommand(name) {
3579
+ const normalized = normalizeCommandName(name);
3580
+ const all = listCommandRegistry({ includePlanned: true });
3581
+ return all.find(
3582
+ (cmd) => cmd.name === normalized || cmd.aliases.includes(normalized)
3583
+ );
3584
+ }
3585
+ function buildCommandDef(name, signatures) {
3586
+ const status = signatures.every(
3587
+ (sig) => sig.implementationStatus === "planned"
3588
+ ) ? "planned" : "implemented";
3589
+ const safety = {
3590
+ userOnly: USER_ONLY.has(name),
3591
+ destructive: DESTRUCTIVE.has(name),
3592
+ networked: NETWORKED.has(name),
3593
+ secretBearing: SECRET_BEARING.has(name),
3594
+ profileGated: PROFILE_GATED.has(name)
3595
+ };
3596
+ const aliases2 = Array.from(new Set(ALIASES[name] ?? []));
3597
+ const category = CATEGORY_OVERRIDES[name] ?? (status === "planned" ? "planned" : "core");
3598
+ const subcommands = Array.from(
3599
+ new Set(
3600
+ signatures.map((sig) => subcommandFromSignature(sig.signature)).filter((sub) => !!sub)
3601
+ )
3602
+ );
3603
+ const argsHint = inferArgsHint(signatures[0]?.signature ?? `/${name}`);
3604
+ const surfaces = {
3605
+ tui: true,
3606
+ rest: status === "implemented" && !["quit", "exit"].includes(name) && !safety.destructive && !safety.secretBearing && !REST_BLOCKED.has(name) && !LOCAL_TUI_ONLY.has(name),
3607
+ gateway: status === "implemented" && !safety.destructive && !safety.secretBearing && !LOCAL_TUI_ONLY.has(name),
3608
+ agentTool: status === "implemented" && !safety.userOnly && !safety.destructive && !safety.secretBearing && !safety.profileGated && !LOCAL_TUI_ONLY.has(name)
3609
+ };
3610
+ return {
3611
+ name,
3612
+ aliases: aliases2,
3613
+ description: signatures[0]?.description ?? "",
3614
+ category,
3615
+ argsHint,
3616
+ subcommands,
3617
+ signatures,
3618
+ surfaces,
3619
+ safety,
3620
+ implementationStatus: status
3621
+ };
3622
+ }
3623
+ function commandNameFromSignature(signature) {
3624
+ const raw = normalizeCommandName(
3625
+ (signature.trim().split(/\s+/)[0] ?? "").replace(/^\//, "")
3626
+ );
3627
+ return CANONICAL_BY_ALIAS.get(raw) ?? raw;
3628
+ }
3629
+ function subcommandFromSignature(signature) {
3630
+ const parts = signature.trim().split(/\s+/);
3631
+ if (parts.length < 2) return null;
3632
+ const sub = parts[1] ?? "";
3633
+ if (!sub || sub.startsWith("<") || sub.startsWith("[") || sub.includes("|"))
3634
+ return null;
3635
+ return sub.replace(/[|].*$/, "");
3636
+ }
3637
+ function inferArgsHint(signature) {
3638
+ return signature.trim().split(/\s+/).slice(1).join(" ");
3639
+ }
3640
+ function normalizeCommandName(name) {
3641
+ return name.replace(/^\//, "").trim().toLowerCase();
3642
+ }
3643
+
3644
+ // packages/cli/src/tui/native/components/vim-composer.ts
3645
+ var VimComposer = class {
3646
+ constructor(editor, changed) {
3647
+ this.editor = editor;
3648
+ this.changed = changed;
3649
+ }
3650
+ editor;
3651
+ changed;
3652
+ enabled = false;
3653
+ mode = "INSERT";
3654
+ count = "";
3655
+ operator = "";
3656
+ operatorCount = 1;
3657
+ anchor = 0;
3658
+ register = "";
3659
+ linewise = false;
3660
+ find = "";
3661
+ search;
3662
+ lastSearch;
3663
+ setEnabled(enabled) {
3664
+ if (enabled === this.enabled) return;
3665
+ this.enabled = enabled;
3666
+ this.mode = "INSERT";
3667
+ this.reset();
3668
+ this.editor.clearSelection();
3669
+ this.changed();
3670
+ }
3671
+ get label() {
3672
+ return this.search ? `${this.search.backward ? "?" : "/"}${this.search.value}` : this.mode;
3673
+ }
3674
+ reset() {
3675
+ this.count = "";
3676
+ this.operator = "";
3677
+ this.operatorCount = 1;
3678
+ this.find = "";
3679
+ this.search = void 0;
3680
+ }
3681
+ normal() {
3682
+ this.mode = "NORMAL";
3683
+ this.editor.clearSelection();
3684
+ this.reset();
3685
+ this.changed();
3686
+ }
3687
+ end() {
3688
+ return this.editor.editBuffer.measureText(this.editor.plainText);
3689
+ }
3690
+ takeCount() {
3691
+ const n = Math.max(1, Math.min(1e4, Number(this.count) || 1));
3692
+ this.count = "";
3693
+ return n;
3694
+ }
3695
+ select(start, end) {
3696
+ this.editor.setSelection(Math.min(start, end), Math.max(start, end));
3697
+ }
3698
+ operate(start, end, operation, linewise = false) {
3699
+ this.select(start, end);
3700
+ this.register = this.editor.getSelectedText();
3701
+ this.linewise = linewise;
3702
+ if (linewise && this.register && !this.register.endsWith("\n")) this.register += "\n";
3703
+ if (operation !== "y") this.editor.deleteSelection();
3704
+ else {
3705
+ this.editor.clearSelection();
3706
+ this.editor.cursorOffset = Math.min(start, end);
3707
+ }
3708
+ this.reset();
3709
+ this.mode = operation === "c" ? "INSERT" : "NORMAL";
3710
+ this.changed();
3711
+ }
3712
+ lineRange(count) {
3713
+ const row = this.editor.logicalCursor.row;
3714
+ const start = this.editor.editBuffer.getLineStartOffset(row);
3715
+ const next = row + count;
3716
+ let end = next < this.editor.lineCount ? this.editor.editBuffer.getLineStartOffset(next) : this.end();
3717
+ if (end === start && row > 0) return [start - 1, end];
3718
+ return [start, end];
3719
+ }
3720
+ motion(key, count) {
3721
+ const editor = this.editor;
3722
+ if (key === "0") editor.gotoLineHome();
3723
+ else if (key === "$") {
3724
+ if (count > 1) editor.gotoLine(Math.min(editor.lineCount - 1, editor.logicalCursor.row + count - 1));
3725
+ editor.gotoLineEnd();
3726
+ } else if (key === "^") {
3727
+ editor.gotoLineHome();
3728
+ const line = editor.getTextRange(editor.cursorOffset, this.end()).split("\n")[0];
3729
+ for (const char of line) {
3730
+ if (!/\s/.test(char)) break;
3731
+ editor.moveCursorRight();
3732
+ }
3733
+ } else if (key === "G") {
3734
+ editor.gotoLine(count > 1 ? Math.min(editor.lineCount - 1, count - 1) : editor.lineCount - 1);
3735
+ editor.gotoLineHome();
3736
+ } else if (key === "gg") {
3737
+ editor.gotoLine(Math.max(0, count - 1));
3738
+ editor.gotoLineHome();
3739
+ } else if (key === "e") {
3740
+ for (let i = 0; i < count; i++) {
3741
+ const start = editor.cursorOffset;
3742
+ const endWord = () => {
3743
+ editor.moveWordForward();
3744
+ const nextWord2 = editor.cursorOffset;
3745
+ editor.moveCursorLeft();
3746
+ while (editor.cursorOffset > start && /\s/u.test(editor.getTextRange(editor.cursorOffset, nextWord2).slice(0, 1))) editor.moveCursorLeft();
3747
+ return nextWord2;
3748
+ };
3749
+ const nextWord = endWord();
3750
+ if (editor.cursorOffset <= start && nextWord < this.end()) {
3751
+ editor.cursorOffset = nextWord;
3752
+ endWord();
3753
+ }
3754
+ }
3755
+ } else if (["h", "l", "j", "k", "w", "b", "left", "right", "up", "down"].includes(key)) {
3756
+ for (let i = 0; i < count; i++) {
3757
+ if (key === "h" || key === "left") editor.moveCursorLeft();
3758
+ else if (key === "l" || key === "right") editor.moveCursorRight();
3759
+ else if (key === "j" || key === "down") editor.moveCursorDown();
3760
+ else if (key === "k" || key === "up") editor.moveCursorUp();
3761
+ else if (key === "w") editor.moveWordForward();
3762
+ else editor.moveWordBackward();
3763
+ }
3764
+ } else return false;
3765
+ return true;
3766
+ }
3767
+ searchFor(value, backward) {
3768
+ if (!value) return;
3769
+ const text = this.editor.plainText;
3770
+ const character = this.editor.getTextRange(0, this.editor.cursorOffset).length;
3771
+ const index = backward ? character > 0 ? text.lastIndexOf(value, character - 1) : -1 : text.indexOf(value, character + 1);
3772
+ const found = index >= 0 ? index : backward ? text.lastIndexOf(value) : text.indexOf(value);
3773
+ if (found >= 0) this.editor.cursorOffset = this.editor.editBuffer.measureText(text.slice(0, found));
3774
+ }
3775
+ handle(key) {
3776
+ if (!this.enabled || key.eventType === "release") return false;
3777
+ const consume = () => {
3778
+ key.preventDefault();
3779
+ key.stopPropagation();
3780
+ return true;
3781
+ };
3782
+ if (this.mode === "INSERT") {
3783
+ if (key.name !== "escape") return false;
3784
+ if (this.editor.logicalCursor.col > 0) this.editor.moveCursorLeft();
3785
+ this.normal();
3786
+ return consume();
3787
+ }
3788
+ if (key.name === "escape") {
3789
+ this.normal();
3790
+ return consume();
3791
+ }
3792
+ if (key.ctrl && key.name === "r") {
3793
+ this.editor.redo();
3794
+ return consume();
3795
+ }
3796
+ if (key.ctrl || key.meta || key.name === "return" && !this.search || key.name === "tab") return false;
3797
+ const char = key.sequence.length === 1 ? key.sequence : key.name;
3798
+ if (this.search) {
3799
+ if (key.name === "return") {
3800
+ this.lastSearch = this.search;
3801
+ this.searchFor(this.search.value, this.search.backward);
3802
+ this.search = void 0;
3803
+ } else if (key.name === "backspace") this.search.value = [...this.search.value].slice(0, -1).join("");
3804
+ else if (key.sequence.length === 1) this.search.value += key.sequence;
3805
+ this.changed();
3806
+ return consume();
3807
+ }
3808
+ if (this.find) {
3809
+ if (this.find === "r") {
3810
+ const start2 = this.editor.cursorOffset;
3811
+ this.editor.moveCursorRight();
3812
+ if (this.editor.cursorOffset !== start2) {
3813
+ this.select(start2, this.editor.cursorOffset);
3814
+ this.editor.insertText(char);
3815
+ this.editor.cursorOffset = start2;
3816
+ }
3817
+ this.find = "";
3818
+ return consume();
3819
+ }
3820
+ const forward = this.find === "f" || this.find === "t", till = this.find === "t" || this.find === "T";
3821
+ const start = this.editor.cursorOffset, row = this.editor.logicalCursor.row;
3822
+ for (let i = 0; i < this.editor.plainText.length; i++) {
3823
+ const previous = this.editor.cursorOffset;
3824
+ if (forward) this.editor.moveCursorRight();
3825
+ else this.editor.moveCursorLeft();
3826
+ const current = this.editor.cursorOffset;
3827
+ if (current === previous || this.editor.logicalCursor.row !== row) {
3828
+ this.editor.cursorOffset = start;
3829
+ break;
3830
+ }
3831
+ const end = current;
3832
+ this.editor.moveCursorRight();
3833
+ const next = this.editor.cursorOffset;
3834
+ this.editor.cursorOffset = end;
3835
+ if (this.editor.getTextRange(end, next) === char) {
3836
+ if (till) {
3837
+ if (forward) this.editor.moveCursorLeft();
3838
+ else this.editor.moveCursorRight();
3839
+ }
3840
+ break;
3841
+ }
3842
+ }
3843
+ this.find = "";
3844
+ this.changed();
3845
+ return consume();
3846
+ }
3847
+ if (/^[1-9]$/.test(char) || char === "0" && this.count) {
3848
+ this.count = (this.count + char).slice(0, 5);
3849
+ return consume();
3850
+ }
3851
+ const count = this.takeCount();
3852
+ if (this.operator === "g") {
3853
+ this.operator = "";
3854
+ if (char === "g") {
3855
+ this.motion("gg", this.operatorCount);
3856
+ return consume();
3857
+ }
3858
+ }
3859
+ if (this.operator && this.operator !== "g") {
3860
+ const op = this.operator, repeat = count * this.operatorCount, start = this.editor.cursorOffset;
3861
+ if (char === op) {
3862
+ const range = this.lineRange(repeat);
3863
+ this.operate(...range, op, true);
3864
+ return consume();
3865
+ }
3866
+ if (this.motion(char, Math.min(1e4, repeat))) this.operate(start, this.editor.cursorOffset, op);
3867
+ else this.reset();
3868
+ return consume();
3869
+ }
3870
+ if (this.mode === "VISUAL" && ["d", "x", "c", "y"].includes(char)) {
3871
+ const selection = this.editor.getSelection();
3872
+ if (selection) this.operate(selection.start, selection.end, char === "x" ? "d" : char);
3873
+ return consume();
3874
+ }
3875
+ if (this.motion(char, count)) {
3876
+ if (this.mode === "VISUAL") this.select(this.anchor, this.editor.cursorOffset);
3877
+ return consume();
3878
+ }
3879
+ if (char === "g") {
3880
+ this.operator = "g";
3881
+ this.operatorCount = count;
3882
+ return consume();
3883
+ }
3884
+ if (["d", "c", "y"].includes(char)) {
3885
+ this.operator = char;
3886
+ this.operatorCount = count;
3887
+ return consume();
3888
+ }
3889
+ if (char === "i" || char === "a" || char === "I" || char === "A") {
3890
+ if (char === "a" && this.editor.cursorOffset < this.end()) this.editor.moveCursorRight();
3891
+ if (char === "I") this.motion("^", 1);
3892
+ if (char === "A") this.editor.gotoLineEnd();
3893
+ this.mode = "INSERT";
3894
+ this.reset();
3895
+ this.changed();
3896
+ return consume();
3897
+ }
3898
+ if (char === "o" || char === "O") {
3899
+ if (char === "o") {
3900
+ this.editor.gotoLineEnd();
3901
+ this.editor.newLine();
3902
+ } else {
3903
+ this.editor.gotoLineHome();
3904
+ this.editor.insertText("\n");
3905
+ this.editor.moveCursorLeft();
3906
+ }
3907
+ this.mode = "INSERT";
3908
+ this.changed();
3909
+ return consume();
3910
+ }
3911
+ if (char === "v") {
3912
+ if (this.mode === "VISUAL") this.normal();
3913
+ else {
3914
+ this.mode = "VISUAL";
3915
+ this.anchor = this.editor.cursorOffset;
3916
+ this.changed();
3917
+ }
3918
+ return consume();
3919
+ }
3920
+ if (char === "x" || char === "X") {
3921
+ const start = this.editor.cursorOffset;
3922
+ for (let i = 0; i < count; i++) char === "x" ? this.editor.moveCursorRight() : this.editor.moveCursorLeft();
3923
+ this.operate(start, this.editor.cursorOffset, "d");
3924
+ return consume();
3925
+ }
3926
+ if (char === "D" || char === "C") {
3927
+ const start = this.editor.cursorOffset;
3928
+ this.editor.gotoLineEnd();
3929
+ this.operate(start, this.editor.cursorOffset, char === "C" ? "c" : "d");
3930
+ return consume();
3931
+ }
3932
+ if (char === "p" || char === "P") {
3933
+ if (this.register) {
3934
+ if (this.linewise) {
3935
+ if (char === "P") this.editor.gotoLineHome();
3936
+ else {
3937
+ this.editor.gotoLineEnd();
3938
+ if (this.editor.cursorOffset < this.end()) this.editor.moveCursorRight();
3939
+ else this.editor.insertText("\n");
3940
+ }
3941
+ } else if (char === "p") this.editor.moveCursorRight();
3942
+ this.editor.insertText(this.register.repeat(Math.min(1e3, count)));
3943
+ }
3944
+ return consume();
3945
+ }
3946
+ if (char === "u") {
3947
+ for (let i = 0; i < count; i++) this.editor.undo();
3948
+ return consume();
3949
+ }
3950
+ if (["f", "F", "t", "T", "r"].includes(char)) {
3951
+ this.find = char;
3952
+ return consume();
3953
+ }
3954
+ if (char === "/" || char === "?") {
3955
+ this.search = { value: "", backward: char === "?" };
3956
+ this.changed();
3957
+ return consume();
3958
+ }
3959
+ if ((char === "n" || char === "N") && this.lastSearch) {
3960
+ this.searchFor(this.lastSearch.value, this.lastSearch.backward !== (char === "N"));
3961
+ return consume();
3962
+ }
3963
+ return consume();
3964
+ }
3965
+ };
3966
+
3967
+ // packages/cli/src/tui/native/components/frontend.ts
3968
+ var initialTheme = { mode: "system", bg: -1, accent: -1, textPrimary: -1, textDim: -1, boxColor: -1 };
3969
+ var NativeFrontend = class {
3970
+ constructor(renderer2, emit) {
3971
+ this.renderer = renderer2;
3972
+ this.emit = emit;
3973
+ const colors = palette(initialTheme);
3974
+ this.context = { renderer: renderer2, colors, syntax: SyntaxStyle2.fromStyles({}, renderer2.nativeScene), emit, terminals: /* @__PURE__ */ new Map() };
3975
+ this.disposeSelectionContrast = installDefaultSelectionContrast(renderer2);
3976
+ this.clipboard = createClipboard({ host: createHostClipboard({ timeoutMs: 1500 }), terminal: createRendererClipboardAdapter(renderer2) });
3977
+ this.root = new BoxRenderable5(renderer2, {
3978
+ id: "omnius-native",
3979
+ width: "100%",
3980
+ height: "100%",
3981
+ flexDirection: "column",
3982
+ backgroundColor: colors.background
3983
+ });
3984
+ renderer2.root.add(this.root);
3985
+ this.header = new BoxRenderable5(renderer2, {
3986
+ id: "omnius-header",
3987
+ width: "100%",
3988
+ minWidth: 0,
3989
+ flexDirection: "column",
3990
+ flexShrink: 0,
3991
+ border: ["bottom"],
3992
+ borderStyle: "single",
3993
+ borderColor: colors.border
3994
+ });
3995
+ this.root.add(this.header);
3996
+ const headerRow = new BoxRenderable5(renderer2, { id: "omnius-header-row", width: "100%", minWidth: 0, minHeight: 1, flexDirection: "row", gap: 1 });
3997
+ this.header.add(headerRow);
3998
+ this.identity = new TextRenderable4(renderer2, {
3999
+ id: "omnius-identity",
4000
+ content: "Omnius",
4001
+ fg: colors.foreground,
4002
+ flexShrink: 0,
4003
+ flexGrow: 0,
4004
+ wrapMode: "none",
4005
+ truncate: true,
4006
+ minWidth: 18,
4007
+ selectable: false,
4008
+ attributes: TextAttributes6.BOLD,
4009
+ onMouseDown: (event) => {
4010
+ if (event.button === 0) {
4011
+ this.toggleHeaderPanel();
4012
+ event.stopPropagation();
4013
+ }
4014
+ },
4015
+ onKeyDown: (key) => {
4016
+ if (key.eventType !== "release" && (key.name === "return" || key.sequence === " ")) {
4017
+ this.toggleHeaderPanel();
4018
+ key.preventDefault();
4019
+ }
4020
+ }
4021
+ });
4022
+ headerRow.add(this.identity);
4023
+ this.headerActions = new ScrollBoxRenderable4(renderer2, {
4024
+ id: "omnius-header-actions",
4025
+ flexGrow: 0,
4026
+ flexShrink: 1,
4027
+ minWidth: 0,
4028
+ minHeight: 1,
4029
+ scrollX: true,
4030
+ scrollY: false,
4031
+ viewportOptions: { height: 1, minHeight: 1, maxHeight: 1, flexGrow: 0, flexShrink: 0 },
4032
+ contentOptions: { flexDirection: "row", gap: 1, justifyContent: "flex-end" },
4033
+ verticalScrollbarOptions: { visible: false }
4034
+ });
4035
+ headerRow.add(this.headerActions);
4036
+ this.headerPanel = new TextRenderable4(renderer2, { id: "omnius-header-panel", content: "", width: "100%", wrapMode: "word", visible: false });
4037
+ this.header.add(this.headerPanel);
4038
+ this.detailButton = new TextRenderable4(renderer2, {
4039
+ id: "omnius-details-toggle",
4040
+ content: "+ DETAILS",
4041
+ height: 1,
4042
+ width: 9,
4043
+ flexShrink: 0,
4044
+ fg: colors.foreground,
4045
+ attributes: TextAttributes6.DIM,
4046
+ selectable: false,
4047
+ onMouseDown: (event) => {
4048
+ if (event.button === 0) {
4049
+ this.toggleDetails();
4050
+ event.stopPropagation();
4051
+ }
4052
+ },
4053
+ onKeyDown: (key) => {
4054
+ if (key.eventType !== "release" && (key.name === "return" || key.sequence === " ")) {
4055
+ this.toggleDetails();
4056
+ key.preventDefault();
4057
+ }
4058
+ }
4059
+ });
4060
+ this.detailButton.focusable = true;
4061
+ headerRow.add(this.detailButton, 1);
4062
+ for (const control of [this.identity, this.detailButton]) {
4063
+ const attributes = control.attributes;
4064
+ control.on("focused", () => {
4065
+ control.attributes = attributes | TextAttributes6.INVERSE;
4066
+ });
4067
+ control.on("blurred", () => {
4068
+ control.attributes = attributes;
4069
+ });
4070
+ }
4071
+ 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 });
4072
+ this.root.add(this.routeWarning);
4073
+ this.tabs = new TabSelectRenderable(renderer2, {
4074
+ id: "omnius-agent-tabs",
4075
+ width: "100%",
4076
+ height: 2,
1063
4077
  showDescription: false,
1064
- showUnderline: false,
1065
- showScrollArrows: true,
4078
+ showUnderline: true,
4079
+ showScrollArrows: false,
1066
4080
  tabWidth: 18,
1067
4081
  wrapSelection: true,
1068
4082
  visible: false,
@@ -1082,16 +4096,26 @@ var NativeFrontend = class {
1082
4096
  this.tabs.on("selectionChanged", () => {
1083
4097
  if (this.changing) return;
1084
4098
  const selected = this.tabs.getSelectedOption();
1085
- if (selected) this.emit({ type: "action", action: "view", id: String(selected.value) });
4099
+ if (selected) this.emitAction("view", String(selected.value));
1086
4100
  this.editor.focus();
1087
4101
  });
4102
+ this.tabs.renderAfter = (buffer) => {
4103
+ if (this.tabs.height < 2) return;
4104
+ const colors2 = this.context.colors;
4105
+ const tabWidth = Math.min(this.tabs.getTabWidth(), this.tabs.width);
4106
+ const count = Math.max(1, Math.floor(this.tabs.width / Math.max(1, tabWidth)));
4107
+ const selected = this.tabs.getSelectedIndex();
4108
+ const start = Math.max(0, Math.min(selected - Math.floor(count / 2), this.tabs.options.length - count));
4109
+ buffer.fillRect(0, 1, this.tabs.width, 1, colors2.background);
4110
+ buffer.drawText("─".repeat(Math.max(0, tabWidth)), (selected - start) * tabWidth, 1, colors2.foreground, colors2.background, TextAttributes6.DIM);
4111
+ };
1088
4112
  this.root.add(this.tabs);
1089
- this.taskPane = new BoxRenderable3(renderer2, { id: "omnius-tasks", width: "100%", flexDirection: "column", maxHeight: 6, flexShrink: 0, overflow: "hidden", visible: false });
4113
+ this.taskPane = new BoxRenderable5(renderer2, { id: "omnius-tasks", width: "100%", flexDirection: "column", maxHeight: 6, flexShrink: 0, overflow: "hidden", visible: false });
1090
4114
  this.tasks = new NativeBlockList(this.context, this.taskPane);
1091
4115
  this.root.add(this.taskPane);
1092
- this.body = new BoxRenderable3(renderer2, { id: "omnius-body", width: "100%", flexDirection: "row", flexGrow: 1, minHeight: 1 });
4116
+ this.body = new BoxRenderable5(renderer2, { id: "omnius-body", width: "100%", flexDirection: "row", flexGrow: 1, minHeight: 1 });
1093
4117
  this.root.add(this.body);
1094
- this.scroll = new ScrollBoxRenderable3(renderer2, {
4118
+ this.scroll = new ScrollBoxRenderable4(renderer2, {
1095
4119
  id: "omnius-conversation",
1096
4120
  flexGrow: 1,
1097
4121
  flexShrink: 1,
@@ -1127,18 +4151,34 @@ var NativeFrontend = class {
1127
4151
  });
1128
4152
  this.body.add(this.scroll);
1129
4153
  this.content = new NativeBlockList(this.context, this.scroll);
1130
- this.embeddedPane = new BoxRenderable3(renderer2, {
4154
+ this.instrumentDivider = new InstrumentDivider(renderer2, {
4155
+ resize: (column) => this.resizeInstruments(column),
4156
+ toggle: () => this.toggleDetails(),
4157
+ start: () => {
4158
+ this.sidebarDragWidth = this.sidebarWidth ?? Math.floor(this.renderer.terminalWidth * 0.38);
4159
+ },
4160
+ finish: () => {
4161
+ this.sidebarDragWidth = void 0;
4162
+ }
4163
+ });
4164
+ this.body.add(this.instrumentDivider);
4165
+ this.root.onMouseDrag = (event) => this.instrumentDivider.handleDrag(event);
4166
+ this.root.onMouseDragEnd = (event) => this.instrumentDivider.handleRelease(event);
4167
+ this.root.onMouseUp = (event) => this.instrumentDivider.handleRelease(event);
4168
+ this.instruments = new InstrumentRail(renderer2, { colors, onInspectAgent: (id) => this.emitAction("view", id), onAction: (command2) => this.emitAction(command2) });
4169
+ this.body.add(this.instruments);
4170
+ this.embeddedPane = new BoxRenderable5(renderer2, {
1131
4171
  id: "omnius-editor-pane",
1132
4172
  width: "50%",
1133
4173
  flexShrink: 0,
1134
4174
  flexDirection: "column",
1135
4175
  border: true,
1136
- borderStyle: "rounded",
4176
+ borderStyle: "single",
1137
4177
  borderColor: colors.border,
1138
4178
  visible: false
1139
4179
  });
1140
4180
  this.body.add(this.embeddedPane);
1141
- this.liveButton = new TextRenderable3(renderer2, {
4181
+ this.liveButton = new TextRenderable4(renderer2, {
1142
4182
  id: "omnius-live-button",
1143
4183
  content: "↓ Live",
1144
4184
  width: "100%",
@@ -1153,7 +4193,7 @@ var NativeFrontend = class {
1153
4193
  }
1154
4194
  },
1155
4195
  onKeyDown: (key) => {
1156
- if (key.name === "return") {
4196
+ if (key.eventType !== "release" && (key.name === "return" || key.sequence === " ")) {
1157
4197
  this.scrollToLive();
1158
4198
  key.preventDefault();
1159
4199
  }
@@ -1161,7 +4201,7 @@ var NativeFrontend = class {
1161
4201
  });
1162
4202
  this.liveButton.focusable = true;
1163
4203
  this.root.add(this.liveButton);
1164
- this.queue = new TextRenderable3(renderer2, { id: "omnius-steering-queue", content: "", width: "100%", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, visible: false, flexShrink: 0 });
4204
+ this.queue = new TextRenderable4(renderer2, { id: "omnius-steering-queue", content: "", width: "100%", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, visible: false, flexShrink: 0 });
1165
4205
  this.root.add(this.queue);
1166
4206
  this.suggestions = new NativeSelectRenderable(renderer2, {
1167
4207
  id: "omnius-suggestions",
@@ -1180,20 +4220,21 @@ var NativeFrontend = class {
1180
4220
  this.suggestions.onActivate = () => this.acceptSuggestion();
1181
4221
  this.suggestions.on("itemSelected", () => this.acceptSuggestion());
1182
4222
  this.root.add(this.suggestions);
1183
- this.footer = new BoxRenderable3(renderer2, {
4223
+ this.status = new BoxRenderable5(renderer2, { id: "omnius-live-status", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 2, visible: false });
4224
+ this.root.add(this.status);
4225
+ this.footer = new BoxRenderable5(renderer2, {
1184
4226
  id: "omnius-input-box",
1185
4227
  width: "100%",
1186
4228
  flexShrink: 0,
1187
- border: true,
1188
- borderStyle: "rounded",
4229
+ border: ["top"],
4230
+ borderStyle: "single",
1189
4231
  borderColor: colors.border,
1190
- flexDirection: "column",
1191
- paddingX: 1
4232
+ flexDirection: "column"
1192
4233
  });
1193
4234
  this.root.add(this.footer);
1194
- const inputRow = new BoxRenderable3(renderer2, { id: "omnius-input-row", width: "100%", flexDirection: "row", gap: 1 });
4235
+ const inputRow = new BoxRenderable5(renderer2, { id: "omnius-input-row", width: "100%", flexDirection: "row", gap: 1 });
1195
4236
  this.footer.add(inputRow);
1196
- this.prompt = new TextRenderable3(renderer2, { id: "omnius-prompt", content: "❯", fg: colors.accent, wrapMode: "none", selectable: false, flexShrink: 0 });
4237
+ this.prompt = new TextRenderable4(renderer2, { id: "omnius-prompt", content: "❯", fg: colors.accent, wrapMode: "none", selectable: false, flexShrink: 0 });
1197
4238
  inputRow.add(this.prompt);
1198
4239
  this.editor = new TextareaRenderable2(renderer2, {
1199
4240
  id: "omnius-input",
@@ -1206,40 +4247,59 @@ var NativeFrontend = class {
1206
4247
  backgroundColor: colors.background,
1207
4248
  focusedTextColor: colors.foreground,
1208
4249
  focusedBackgroundColor: colors.background,
1209
- selectionBg: colors.selection,
1210
- cursorColor: colors.accent,
4250
+ placeholderColor: colors.foreground,
1211
4251
  cursorStyle: { style: "block", blinking: true },
1212
4252
  keyBindings: [{ name: "return", action: "submit" }, { name: "return", shift: true, action: "newline" }],
1213
4253
  onContentChange: () => this.editorChanged(),
1214
4254
  onCursorChange: () => this.editorChanged(),
1215
4255
  onSubmit: () => this.submit()
1216
4256
  });
4257
+ this.vimMode = new TextRenderable4(renderer2, {
4258
+ id: "omnius-vim-mode",
4259
+ content: "INSERT",
4260
+ height: 1,
4261
+ maxWidth: 12,
4262
+ flexShrink: 0,
4263
+ fg: colors.foreground,
4264
+ attributes: TextAttributes6.DIM,
4265
+ wrapMode: "none",
4266
+ truncate: true,
4267
+ visible: false,
4268
+ selectable: false
4269
+ });
4270
+ inputRow.add(this.vimMode);
4271
+ this.vimComposer = new VimComposer(this.editor, () => {
4272
+ this.vimMode.visible = this.vimComposer.enabled;
4273
+ this.vimMode.content = this.vimComposer.label;
4274
+ this.editorChanged();
4275
+ this.renderer.requestRender();
4276
+ });
1217
4277
  inputRow.add(this.editor);
1218
- this.enhance = new BoxRenderable3(renderer2, { id: "omnius-enhance", flexDirection: "row", gap: 1, flexShrink: 0, visible: false });
4278
+ this.enhance = new BoxRenderable5(renderer2, { id: "omnius-enhance", flexDirection: "row", gap: 1, flexShrink: 0, visible: false });
1219
4279
  inputRow.add(this.enhance);
1220
- this.activity = new TextRenderable3(renderer2, { id: "omnius-activity", content: "", width: "100%", height: 1, wrapMode: "none", truncate: true, visible: false, fg: colors.accent });
1221
- this.footer.add(this.activity);
1222
- this.media = new BoxRenderable3(renderer2, { id: "omnius-media", width: "100%", height: 1, flexDirection: "row", gap: 1, visible: false });
1223
- this.mediaText = new TextRenderable3(renderer2, { id: "omnius-media-text", content: "", flexGrow: 1, minWidth: 0, wrapMode: "none", truncate: true, fg: colors.accent });
4280
+ this.activity = new TextRenderable4(renderer2, { id: "omnius-activity", content: "", flexGrow: 1, minWidth: 0, height: 1, wrapMode: "none", truncate: true, visible: false, fg: colors.accent });
4281
+ this.status.add(this.activity);
4282
+ this.media = new BoxRenderable5(renderer2, { id: "omnius-media", height: 1, minWidth: 0, flexShrink: 1, flexDirection: "row", gap: 1, visible: false });
4283
+ this.mediaText = new TextRenderable4(renderer2, { id: "omnius-media-text", content: "", flexGrow: 1, minWidth: 0, wrapMode: "none", truncate: true, fg: colors.accent });
1224
4284
  this.media.add(this.mediaText);
1225
- this.micTrack = new BoxRenderable3(renderer2, { id: "omnius-mic-meter", width: 12, height: 1, flexShrink: 0, backgroundColor: colors.dim, visible: false });
1226
- this.micLevel = new BoxRenderable3(renderer2, { id: "omnius-mic-level", width: "0%", height: 1, backgroundColor: colors.accent });
1227
- this.micTrack.add(this.micLevel);
4285
+ this.micTrack = new TextRenderable4(renderer2, { id: "omnius-mic-meter", width: 12, height: 1, flexShrink: 0, fg: colors.foreground, visible: false });
1228
4286
  this.media.add(this.micTrack);
1229
- this.footer.add(this.media);
1230
- this.metricsRow = new BoxRenderable3(renderer2, { id: "omnius-metrics-row", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 1 });
1231
- this.metricsStartActions = new BoxRenderable3(renderer2, { id: "omnius-metrics-start-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
4287
+ this.status.add(this.media);
4288
+ this.metricsRow = new BoxRenderable5(renderer2, { id: "omnius-metrics-row", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 1 });
4289
+ 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 });
4290
+ this.metricsRow.add(this.keyHints);
4291
+ this.metricsStartActions = new BoxRenderable5(renderer2, { id: "omnius-metrics-start-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
1232
4292
  this.metricsRow.add(this.metricsStartActions);
1233
- this.metrics = new TextRenderable3(renderer2, { id: "omnius-metrics", content: "", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, flexShrink: 1, flexGrow: 1, minWidth: 0 });
4293
+ this.metrics = new TextRenderable4(renderer2, { id: "omnius-metrics", content: "", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, flexShrink: 1, flexGrow: 1, minWidth: 0 });
1234
4294
  this.metricsRow.add(this.metrics);
1235
- this.metricsEndActions = new BoxRenderable3(renderer2, { id: "omnius-metrics-end-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
4295
+ this.metricsEndActions = new BoxRenderable5(renderer2, { id: "omnius-metrics-end-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
1236
4296
  this.metricsRow.add(this.metricsEndActions);
1237
4297
  this.root.add(this.metricsRow);
1238
4298
  this.modals = new NativeModalStack(this.context, () => this.restoreFocus());
1239
4299
  renderer2.keyInput.on("keypress", this.onKey);
1240
4300
  renderer2.keyInput.on("paste", this.onPaste);
1241
- renderer2.on(CliRenderEvents.FRAME, this.onFrame);
1242
- renderer2.on(CliRenderEvents.RESIZE, this.onResize);
4301
+ renderer2.on(CliRenderEvents3.FRAME, this.onFrame);
4302
+ renderer2.on(CliRenderEvents3.RESIZE, this.onResize);
1243
4303
  this.applyTheme(initialTheme);
1244
4304
  this.editor.focus();
1245
4305
  }
@@ -1248,6 +4308,7 @@ var NativeFrontend = class {
1248
4308
  root;
1249
4309
  scroll;
1250
4310
  editor;
4311
+ vimComposer;
1251
4312
  content;
1252
4313
  modals;
1253
4314
  context;
@@ -1260,22 +4321,27 @@ var NativeFrontend = class {
1260
4321
  taskPane;
1261
4322
  tasks;
1262
4323
  body;
4324
+ instruments;
4325
+ instrumentDivider;
4326
+ detailButton;
1263
4327
  embeddedPane;
1264
4328
  liveButton;
1265
4329
  queue;
1266
4330
  suggestions;
1267
4331
  footer;
1268
4332
  prompt;
4333
+ vimMode;
1269
4334
  enhance;
1270
4335
  metrics;
4336
+ keyHints;
1271
4337
  metricsRow;
1272
4338
  metricsStartActions;
1273
4339
  metricsEndActions;
1274
4340
  activity;
4341
+ status;
1275
4342
  media;
1276
4343
  mediaText;
1277
4344
  micTrack;
1278
- micLevel;
1279
4345
  clipboard;
1280
4346
  state;
1281
4347
  themeSignature = "";
@@ -1291,8 +4357,13 @@ var NativeFrontend = class {
1291
4357
  history = [];
1292
4358
  historyIndex = -1;
1293
4359
  savedInput = "";
1294
- animation;
1295
- animationIndex = 0;
4360
+ disposeSelectionContrast;
4361
+ actionSignatures = /* @__PURE__ */ new WeakMap();
4362
+ detailsOpen;
4363
+ narrowDetailsOpen = false;
4364
+ sidebarWidth;
4365
+ sidebarDragWidth;
4366
+ headerPanelCollapsed = false;
1296
4367
  inputEventPending = false;
1297
4368
  viewportSignature = "";
1298
4369
  newEntries = 0;
@@ -1319,20 +4390,21 @@ var NativeFrontend = class {
1319
4390
  if (headerSignature !== this.headerSignature) {
1320
4391
  this.headerSignature = headerSignature;
1321
4392
  this.identity.content = styledText([
1322
- { text: state.header.title, ...state.theme.accent < 0 ? {} : { fg: `index:${state.theme.accent}` } },
4393
+ { text: `${plainText(state.header.panel) ? this.headerPanelCollapsed ? "▸" : "▾" : "·"} ${state.header.title}` },
1323
4394
  ...state.header.detail ? [{ text: " " }, ...textSpans(state.header.detail)] : []
1324
4395
  ]);
1325
4396
  this.headerPanel.content = styledText(state.header.panel);
1326
- this.headerPanel.visible = plainText(state.header.panel).length > 0;
4397
+ this.headerPanel.visible = plainText(state.header.panel).length > 0 && !this.headerPanelCollapsed;
4398
+ this.identity.focusable = plainText(state.header.panel).length > 0;
1327
4399
  this.replaceActions(this.headerActions, state.header.actions, "header");
1328
4400
  this.headerActions.visible = state.header.actions.length > 0;
1329
4401
  }
1330
- this.routeWarning.content = state.routeWarning ?? "";
4402
+ this.routeWarning.content = state.routeWarning ? `! ${state.routeWarning}` : "";
1331
4403
  this.routeWarning.visible = Boolean(state.routeWarning);
1332
4404
  const tabsSignature = JSON.stringify(state.tabs);
1333
4405
  if (tabsSignature !== this.tabsSignature) {
1334
4406
  this.tabsSignature = tabsSignature;
1335
- this.tabs.setOptions(state.tabs.map((tab) => ({ name: plainText(tab.label), description: "", value: tab.id })));
4407
+ this.tabs.setOptions(state.tabs.map((tab) => ({ name: fitInstrumentText(plainText(tab.label), this.tabs.getTabWidth() - 2), description: "", value: tab.id })));
1336
4408
  }
1337
4409
  this.tabs.visible = state.tabs.length > 1;
1338
4410
  const activeTab = state.tabs.findIndex((tab) => tab.id === state.activeViewId);
@@ -1364,7 +4436,9 @@ var NativeFrontend = class {
1364
4436
  this.editor.focusable = state.input.enabled && !this.paused;
1365
4437
  if (state.input.enabled && !this.paused && !this.modals.active && (inputWasEnabled === false || !this.renderer.currentFocusedRenderable)) this.editor.focus();
1366
4438
  this.metrics.content = styledText(state.metrics);
1367
- this.metricsRow.visible = state.metricsVisible;
4439
+ this.metrics.visible = state.metricsVisible;
4440
+ this.instruments.update(state);
4441
+ this.layoutInstruments();
1368
4442
  const metricsActions = JSON.stringify(state.metricsActions ?? []);
1369
4443
  if (metricsActions !== this.metricsActionsSignature) {
1370
4444
  this.metricsActionsSignature = metricsActions;
@@ -1374,7 +4448,6 @@ var NativeFrontend = class {
1374
4448
  this.renderEnhance();
1375
4449
  this.renderActivity();
1376
4450
  this.renderMedia();
1377
- this.updateAnimation();
1378
4451
  } finally {
1379
4452
  this.changing = false;
1380
4453
  }
@@ -1382,43 +4455,46 @@ var NativeFrontend = class {
1382
4455
  this.renderer.requestRender();
1383
4456
  }
1384
4457
  replaceActions(parent, actions, prefix) {
4458
+ const signature = JSON.stringify(actions);
4459
+ if (this.actionSignatures.get(parent) === signature) return;
4460
+ this.actionSignatures.set(parent, signature);
1385
4461
  for (const child of [...parent.getChildren()]) child.destroy();
1386
4462
  for (const action of actions) {
1387
4463
  const colors = this.context.colors;
1388
- const filled = prefix === "enhance" || prefix === "header" && !action.id.startsWith("header-");
1389
- const inverse = filled && prefix === "header" && (this.state?.theme.mode === "system" || this.state?.theme.accent === -1);
1390
- const backing = prefix === "enhance" ? colors.warning : colors.accent;
1391
- const [r, g, b] = backing.toInts();
1392
- const foreground = filled && !inverse ? nativeColor(r * 0.299 + g * 0.587 + b * 0.114 > 140 ? 16 : 231) : colors.accent;
1393
- const button = new TextRenderable3(this.renderer, {
4464
+ const attributes = action.disabled ? TextAttributes6.DIM : action.active ? TextAttributes6.BOLD | TextAttributes6.UNDERLINE : 0;
4465
+ const button = new TextRenderable4(this.renderer, {
1394
4466
  id: `${prefix}:${action.id}`,
1395
- content: styledText(filled ? ` ${action.label} ` : action.label),
4467
+ content: styledText(action.label),
1396
4468
  flexShrink: 0,
1397
4469
  wrapMode: "none",
1398
4470
  selectable: false,
1399
- fg: action.disabled ? colors.dim : foreground,
1400
- bg: filled && !inverse ? backing : colors.background,
1401
- attributes: (action.active ? 1 : 0) | (inverse ? 32 : 0),
4471
+ fg: colors.foreground,
4472
+ bg: colors.background,
4473
+ attributes,
1402
4474
  onMouseDown: (event) => {
1403
4475
  if (event.button !== 0 || action.disabled) return;
1404
- this.emit({ type: "action", action: action.command ?? action.id, id: action.id });
4476
+ this.emitAction(action.command ?? action.id, action.id);
1405
4477
  event.stopPropagation();
1406
4478
  },
1407
4479
  onKeyDown: (key) => {
1408
- if (key.name !== "return" || action.disabled) return;
1409
- this.emit({ type: "action", action: action.command ?? action.id, id: action.id });
4480
+ if (key.eventType === "release" || key.name !== "return" && key.sequence !== " " || action.disabled) return;
4481
+ this.emitAction(action.command ?? action.id, action.id);
1410
4482
  key.preventDefault();
1411
4483
  }
1412
4484
  });
1413
4485
  button.focusable = !action.disabled;
1414
4486
  if (!action.disabled) {
4487
+ button.on("focused", () => {
4488
+ button.attributes = attributes | TextAttributes6.INVERSE;
4489
+ });
4490
+ button.on("blurred", () => {
4491
+ button.attributes = attributes;
4492
+ });
1415
4493
  button.onMouseOver = () => {
1416
- button.bg = colors.selection;
1417
- button.fg = colors.foreground;
4494
+ button.attributes = attributes | TextAttributes6.UNDERLINE | (button.focused ? TextAttributes6.INVERSE : 0);
1418
4495
  };
1419
4496
  button.onMouseOut = () => {
1420
- button.bg = filled && !inverse ? backing : colors.background;
1421
- button.fg = foreground;
4497
+ button.attributes = attributes | (button.focused ? TextAttributes6.INVERSE : 0);
1422
4498
  };
1423
4499
  }
1424
4500
  parent.add(button);
@@ -1426,23 +4502,26 @@ var NativeFrontend = class {
1426
4502
  }
1427
4503
  renderEnhance() {
1428
4504
  const state = this.state?.enhance;
1429
- this.enhance.visible = Boolean(state);
4505
+ this.enhance.visible = Boolean(state && (state.state !== "idle" || this.editor.plainText.trim()));
1430
4506
  if (!state) return;
1431
- const actions = state.state === "confirm" ? [{ id: "enhance-accept", label: "✓" }, { id: "enhance-reject", label: "×" }] : [{ id: "enhance", label: state.state === "busy" ? `${spinner[this.animationIndex % spinner.length]} Enhance` : state.label ?? "Enhance", disabled: state.state === "busy" }];
4507
+ const actions = state.state === "confirm" ? [{ id: "enhance-accept", label: "✓" }, { id: "enhance-reject", label: "×" }] : [{ id: "enhance", label: state.state === "busy" ? "WORK · ENHANCE" : state.label ?? "ENHANCE", disabled: state.state === "busy" }];
1432
4508
  this.replaceActions(this.enhance, actions, "enhance");
1433
4509
  }
1434
4510
  renderActivity() {
1435
4511
  const activity = this.state?.activity;
1436
4512
  const stage = activity?.stage === "idle" ? void 0 : activity?.stage;
1437
- this.activity.visible = Boolean(activity?.processing || activity?.label || stage);
1438
- this.activity.content = `${activity?.processing ? spinner[this.animationIndex % spinner.length] + " " : ""}${[activity?.label, stage].filter(Boolean).join(" · ")}`;
4513
+ const reading = activity?.label || stage || this.state?.instruments?.runtimeStatus || "";
4514
+ this.activity.visible = Boolean(activity?.processing || reading);
4515
+ this.activity.content = `${activity?.processing ? "RUN · " : ""}${reading}`;
4516
+ this.status.visible = this.activity.visible || this.media.visible;
1439
4517
  }
1440
4518
  renderMedia() {
1441
4519
  const media = this.state?.media;
1442
4520
  this.media.visible = Boolean(media && (media.audio || media.video || media.voiceEnabled || media.recording || media.countdown > 0 || media.label));
4521
+ this.status.visible = this.activity.visible || this.media.visible;
1443
4522
  if (!media) return;
1444
4523
  this.mediaText.content = [
1445
- media.recording ? `${this.animationIndex % 10 < 5 ? "●" : "○"} REC` : void 0,
4524
+ media.recording ? "● REC" : void 0,
1446
4525
  media.speech ? "speech" : void 0,
1447
4526
  media.audio ? "audio" : void 0,
1448
4527
  media.video ? "video" : void 0,
@@ -1453,23 +4532,8 @@ var NativeFrontend = class {
1453
4532
  ].filter(Boolean).join(" · ");
1454
4533
  this.mediaText.fg = media.recording ? this.context.colors.warning : this.context.colors.accent;
1455
4534
  this.micTrack.visible = media.recording && media.levelDb !== void 0;
1456
- this.micLevel.width = `${Math.round(Math.max(0, Math.min(1, ((media.levelDb ?? -60) + 60) / 60)) * 100)}%`;
1457
- this.micLevel.backgroundColor = media.speech ? this.context.colors.success : this.context.colors.accent;
1458
- }
1459
- updateAnimation() {
1460
- const active = this.state?.activity?.processing || this.state?.enhance?.state === "busy" || this.state?.media?.recording;
1461
- if (active && !this.animation) {
1462
- this.animation = setInterval(() => {
1463
- this.animationIndex++;
1464
- this.renderActivity();
1465
- this.renderMedia();
1466
- if (this.state?.enhance?.state === "busy") this.renderEnhance();
1467
- }, 100);
1468
- this.animation.unref();
1469
- } else if (!active && this.animation) {
1470
- clearInterval(this.animation);
1471
- this.animation = void 0;
1472
- }
4535
+ const level = Math.round(Math.max(0, Math.min(1, ((media.levelDb ?? -60) + 60) / 60)) * 8);
4536
+ this.micTrack.content = `[${"●".repeat(level)}${"·".repeat(8 - level)}]`;
1473
4537
  }
1474
4538
  applyTheme(theme) {
1475
4539
  const signature = JSON.stringify(theme);
@@ -1478,24 +4542,7 @@ var NativeFrontend = class {
1478
4542
  this.headerSignature = "";
1479
4543
  this.metricsActionsSignature = "";
1480
4544
  const colors = this.context.colors = palette(theme);
1481
- for (const [name, style] of Object.entries({
1482
- default: { fg: colors.foreground },
1483
- "markup.heading": { fg: colors.accent, bold: true },
1484
- "markup.strong": { fg: colors.foreground, bold: true },
1485
- "markup.italic": { fg: colors.foreground, italic: true },
1486
- "markup.link": { fg: colors.accent, underline: true },
1487
- "markup.link.url": { fg: colors.dim },
1488
- "markup.raw": { fg: colors.accent },
1489
- "markup.list": { fg: colors.accent },
1490
- "markup.quote": { fg: colors.dim },
1491
- comment: { fg: colors.dim, italic: true },
1492
- keyword: { fg: colors.accent, bold: true },
1493
- string: { fg: colors.success },
1494
- number: { fg: colors.warning },
1495
- function: { fg: colors.foreground },
1496
- type: { fg: colors.accent },
1497
- punctuation: { fg: colors.dim }
1498
- })) this.context.syntax.registerStyle(name, style);
4545
+ installNativeSyntax(this.context.syntax, colors);
1499
4546
  this.root.backgroundColor = colors.background;
1500
4547
  this.header.backgroundColor = this.footer.backgroundColor = colors.background;
1501
4548
  this.header.borderColor = this.footer.borderColor = this.embeddedPane.borderColor = colors.border;
@@ -1503,13 +4550,12 @@ var NativeFrontend = class {
1503
4550
  this.headerPanel.fg = colors.foreground;
1504
4551
  this.routeWarning.fg = colors.warning;
1505
4552
  this.prompt.fg = this.activity.fg = this.liveButton.fg = colors.accent;
1506
- this.micTrack.backgroundColor = colors.dim;
1507
- this.micLevel.backgroundColor = colors.accent;
4553
+ this.vimMode.fg = colors.foreground;
4554
+ this.micTrack.fg = colors.foreground;
1508
4555
  this.queue.fg = this.metrics.fg = colors.dim;
1509
4556
  this.editor.backgroundColor = this.editor.focusedBackgroundColor = colors.background;
1510
4557
  this.editor.textColor = this.editor.focusedTextColor = colors.foreground;
1511
- this.editor.cursorColor = colors.accent;
1512
- this.editor.selectionBg = colors.selection;
4558
+ this.editor.editorView.setSelectionColors(void 0, void 0);
1513
4559
  this.tabs.backgroundColor = this.tabs.focusedBackgroundColor = colors.background;
1514
4560
  this.tabs.textColor = colors.dim;
1515
4561
  this.tabs.selectedTextColor = colors.foreground;
@@ -1518,10 +4564,87 @@ var NativeFrontend = class {
1518
4564
  this.suggestions.textColor = colors.dim;
1519
4565
  this.suggestions.selectedTextColor = colors.foreground;
1520
4566
  this.suggestions.selectedBackgroundColor = colors.selection;
4567
+ styleNativeScrollbars(this.headerActions, colors);
4568
+ styleNativeScrollbars(this.scroll, colors);
4569
+ this.instruments.applyTheme(colors);
4570
+ this.instrumentDivider.fg = colors.foreground;
4571
+ this.instrumentDivider.bg = colors.background;
1521
4572
  this.content.applyTheme();
1522
4573
  this.tasks.applyTheme();
1523
4574
  this.modals?.applyTheme();
1524
4575
  }
4576
+ toggleHeaderPanel() {
4577
+ if (!this.state || !plainText(this.state.header.panel)) return;
4578
+ this.headerPanelCollapsed = !this.headerPanelCollapsed;
4579
+ this.headerSignature = "";
4580
+ this.update(this.state);
4581
+ }
4582
+ toggleDetails() {
4583
+ if (this.renderer.terminalWidth >= 110) this.detailsOpen = !(this.detailsOpen ?? true);
4584
+ else this.narrowDetailsOpen = !this.narrowDetailsOpen;
4585
+ this.layoutInstruments();
4586
+ this.renderer.requestRender();
4587
+ }
4588
+ resizeInstruments(column) {
4589
+ if (this.renderer.terminalWidth < 110 || this.embeddedPane.visible) return;
4590
+ const width = this.renderer.terminalWidth;
4591
+ const requested = Math.max(0, this.body.x + width - Math.round(column) - 1);
4592
+ if (requested <= 4) {
4593
+ this.detailsOpen = false;
4594
+ if (this.sidebarDragWidth !== void 0) this.sidebarWidth = this.sidebarDragWidth;
4595
+ } else {
4596
+ this.detailsOpen = true;
4597
+ this.sidebarWidth = Math.max(24, Math.min(width - 41, requested));
4598
+ }
4599
+ this.layoutInstruments();
4600
+ this.renderer.requestRender();
4601
+ }
4602
+ layoutInstruments() {
4603
+ const width = this.renderer.terminalWidth;
4604
+ const focusedBeforeLayout = this.renderer.currentFocusedRenderable;
4605
+ const wide = width >= 110;
4606
+ const shown = !this.embeddedPane.visible && (wide ? this.detailsOpen ?? true : this.narrowDetailsOpen);
4607
+ const horizontal = wide || this.embeddedPane.visible && width >= 80;
4608
+ this.body.flexDirection = horizontal ? "row" : "column";
4609
+ this.scroll.height = horizontal ? "100%" : "auto";
4610
+ this.scroll.flexBasis = horizontal ? "auto" : 0;
4611
+ this.instruments.visible = shown;
4612
+ this.instrumentDivider.visible = wide && !this.embeddedPane.visible;
4613
+ this.instrumentDivider.setCollapsed(!shown);
4614
+ const railWidth = Math.max(24, Math.min(width - 41, this.sidebarWidth ?? Math.floor(width * 0.38)));
4615
+ this.instruments.width = wide ? railWidth : "100%";
4616
+ this.scroll.width = wide && this.instrumentDivider.visible ? width - (shown ? railWidth : 0) - 1 : this.embeddedPane.visible && width >= 80 ? "50%" : "100%";
4617
+ const inspectorHeight = Math.max(4, Math.min(9, Math.floor(this.renderer.terminalHeight / 3)));
4618
+ this.instruments.height = wide ? "100%" : inspectorHeight;
4619
+ this.instruments.maxHeight = wide ? "100%" : inspectorHeight;
4620
+ this.instruments.minHeight = wide ? 1 : inspectorHeight;
4621
+ this.instruments.flexGrow = 0;
4622
+ this.instruments.flexShrink = 0;
4623
+ this.instruments.border = wide ? [] : ["top"];
4624
+ this.instruments.borderStyle = "single";
4625
+ this.instruments.borderColor = this.context.colors.border;
4626
+ this.instruments.paddingLeft = wide ? 1 : 0;
4627
+ this.embeddedPane.width = width >= 80 ? "50%" : "100%";
4628
+ this.embeddedPane.maxHeight = width >= 80 ? "100%" : "50%";
4629
+ this.headerActions.visible = width >= 60 && Boolean(this.state?.header.actions.length);
4630
+ const actions = this.state?.header.actions ?? [];
4631
+ const naturalWidth = actions.reduce((size, action) => size + terminalCellWidth(plainText(action.label)), 0) + Math.max(0, actions.length - 1);
4632
+ const detailWidth = width < 40 ? 1 : 9;
4633
+ const gapCount = this.headerActions.visible ? 2 : 1;
4634
+ const controlWidth = Math.min(naturalWidth, Math.max(1, width - 18 - detailWidth - gapCount));
4635
+ this.headerActions.width = controlWidth;
4636
+ this.identity.minWidth = Math.max(0, Math.min(18, width - detailWidth - gapCount));
4637
+ this.identity.width = Math.max(0, width - (this.headerActions.visible ? controlWidth : 0) - detailWidth - gapCount);
4638
+ this.detailButton.width = detailWidth;
4639
+ this.keyHints.content = width < 60 ? "F1 HELP" : "F1 HELP TAB FOCUS";
4640
+ this.detailButton.content = `${shown ? "-" : "+"}${detailWidth > 1 ? " DETAILS" : ""}`;
4641
+ for (let focused = focusedBeforeLayout; focused; focused = focused.parent) {
4642
+ if (!shown && focused === this.instruments || !this.instrumentDivider.visible && focused === this.instrumentDivider || !this.headerActions.visible && focused === this.headerActions) {
4643
+ this.restoreFocus();
4644
+ break;
4645
+ }
4646
+ }
4647
+ }
1525
4648
  get offset() {
1526
4649
  return Math.max(0, this.scroll.scrollHeight - this.scroll.viewport.height - this.scroll.scrollTop);
1527
4650
  }
@@ -1559,6 +4682,7 @@ var NativeFrontend = class {
1559
4682
  }
1560
4683
  };
1561
4684
  onResize = () => {
4685
+ this.layoutInstruments();
1562
4686
  this.emit({ type: "resize", columns: this.renderer.terminalWidth, rows: this.renderer.terminalHeight });
1563
4687
  };
1564
4688
  editorChanged() {
@@ -1578,12 +4702,16 @@ var NativeFrontend = class {
1578
4702
  if (text === this.lastInput && cursor === this.lastCursor) return;
1579
4703
  this.lastInput = text;
1580
4704
  this.lastCursor = cursor;
4705
+ this.renderEnhance();
1581
4706
  this.emit({ type: "input", text, cursor });
1582
4707
  }
1583
4708
  submit() {
1584
4709
  if (this.paused || this.state?.input.enabled === false) return;
1585
- if (this.suggestions.visible && this.editor.plainText.startsWith("/") && this.acceptSuggestion()) return;
1586
4710
  const text = this.editor.plainText;
4711
+ const command2 = /^\/([^\s]+)/.exec(text.trimStart())?.[1];
4712
+ const suggestion = String(this.suggestions.getSelectedOption()?.value ?? "");
4713
+ if (this.suggestions.visible && command2 && !findCommand(command2) && suggestion.startsWith(text.trimEnd()) && this.acceptSuggestion()) return;
4714
+ this.suggestions.visible = false;
1587
4715
  if (text.trim() && this.history[0] !== text) {
1588
4716
  this.history.unshift(text);
1589
4717
  this.history.length = Math.min(this.history.length, 1e3);
@@ -1608,7 +4736,7 @@ var NativeFrontend = class {
1608
4736
  const text = String(selected.value);
1609
4737
  this.setInput(text + (text.endsWith(" ") ? "" : " "));
1610
4738
  this.suggestions.visible = false;
1611
- this.emit({ type: "action", action: "completion", id: text });
4739
+ this.emitAction("completion", text);
1612
4740
  return true;
1613
4741
  }
1614
4742
  setInput(text, cursor = text.length) {
@@ -1623,6 +4751,10 @@ var NativeFrontend = class {
1623
4751
  key.preventDefault();
1624
4752
  key.stopPropagation();
1625
4753
  }
4754
+ emitAction(action, id) {
4755
+ this.flushLatestInput();
4756
+ this.emit({ type: "action", action, ...id === void 0 ? {} : { id } });
4757
+ }
1626
4758
  onKey = (key) => {
1627
4759
  if (this.modals.active) {
1628
4760
  this.modals.active.handleKey(key);
@@ -1634,10 +4766,22 @@ var NativeFrontend = class {
1634
4766
  return;
1635
4767
  }
1636
4768
  if (this.renderer.currentFocusedRenderable instanceof EmbeddedTerminalRenderable2) return;
4769
+ if (key.name === "f1") {
4770
+ this.emitAction("/help");
4771
+ key.preventDefault();
4772
+ key.stopPropagation();
4773
+ return;
4774
+ }
4775
+ if (key.ctrl && key.shift && key.name.toLowerCase() === "i") {
4776
+ this.toggleDetails();
4777
+ key.preventDefault();
4778
+ key.stopPropagation();
4779
+ return;
4780
+ }
1637
4781
  const headerButtons = (this.state?.header.actions ?? []).filter((action) => !action.disabled).map((action) => this.getRenderable(`header:${action.id}`)).filter((button) => Boolean(button));
1638
4782
  const headerIndex = headerButtons.indexOf(this.renderer.currentFocusedRenderable);
1639
4783
  if (headerIndex >= 0) {
1640
- const direction = key.name === "left" || key.name === "tab" && key.shift ? -1 : key.name === "right" || key.name === "tab" ? 1 : 0;
4784
+ const direction = key.name === "left" ? -1 : key.name === "right" ? 1 : 0;
1641
4785
  if (direction || key.name === "home" || key.name === "end") {
1642
4786
  const index = key.name === "home" ? 0 : key.name === "end" ? headerButtons.length - 1 : (headerIndex + direction + headerButtons.length) % headerButtons.length;
1643
4787
  const button = headerButtons[index];
@@ -1688,19 +4832,30 @@ var NativeFrontend = class {
1688
4832
  this.shortcut("ctrl-u", key);
1689
4833
  return;
1690
4834
  }
4835
+ if (this.renderer.currentFocusedRenderable === this.editor && !this.paused && this.state?.input.enabled !== false && this.vimComposer.handle(key)) return;
1691
4836
  if (key.name === "escape") {
4837
+ if (this.renderer.currentFocusedRenderable !== this.editor) {
4838
+ this.restoreFocus();
4839
+ key.preventDefault();
4840
+ key.stopPropagation();
4841
+ return;
4842
+ }
1692
4843
  if (this.suggestions.visible) this.suggestions.visible = false;
1693
4844
  this.shortcut("escape", key);
1694
4845
  return;
1695
4846
  }
1696
4847
  if (key.name === "tab") {
1697
4848
  if (key.ctrl) this.shortcut("ctrl-tab", key);
1698
- else if (this.suggestions.visible) {
4849
+ else if (!key.shift && this.renderer.currentFocusedRenderable === this.editor && this.suggestions.visible) {
1699
4850
  this.acceptSuggestion();
1700
4851
  key.preventDefault();
1701
- } else this.shortcut("tab", key);
4852
+ } else if (!key.shift && this.renderer.currentFocusedRenderable === this.editor && this.editor.plainText) this.shortcut("tab", key);
4853
+ else this.focusNext(key.shift ? -1 : 1);
4854
+ key.preventDefault();
4855
+ key.stopPropagation();
1702
4856
  return;
1703
4857
  }
4858
+ if (this.renderer.currentFocusedRenderable !== this.editor && this.renderer.currentFocusedRenderable !== this.scroll && !key.ctrl) return;
1704
4859
  if (key.ctrl && (key.name === "left" || key.name === "right")) {
1705
4860
  this.shortcut(`ctrl-${key.name}`, key);
1706
4861
  return;
@@ -1749,6 +4904,28 @@ var NativeFrontend = class {
1749
4904
  return;
1750
4905
  }
1751
4906
  };
4907
+ focusNext(direction) {
4908
+ const targets = [];
4909
+ const visit = (node) => {
4910
+ if (!node.visible) return;
4911
+ if (node.focusable && node !== this.scroll && !(node instanceof ScrollBoxRenderable4) && node.height > 0 && node.width > 0) targets.push(node);
4912
+ for (const child of node.getChildren()) visit(child);
4913
+ };
4914
+ visit(this.root);
4915
+ if (this.instruments.visible) {
4916
+ for (const target2 of this.instruments.focusTargets()) if (!targets.includes(target2)) targets.push(target2);
4917
+ }
4918
+ if (!targets.length) return;
4919
+ const current = targets.indexOf(this.renderer.currentFocusedRenderable);
4920
+ const target = targets[(current + direction + targets.length) % targets.length];
4921
+ target.focus();
4922
+ if (target.id.startsWith("header:")) this.headerActions.scrollChildIntoView(target.id);
4923
+ else if (target.id.startsWith("block-title:") || target.id.startsWith("block-action:")) {
4924
+ let block = target;
4925
+ while (block && !block.id.startsWith("block:")) block = block.parent;
4926
+ if (block) this.scroll.scrollChildIntoView(block.id);
4927
+ }
4928
+ }
1752
4929
  onPaste = (event) => {
1753
4930
  if (this.modals.active) {
1754
4931
  this.modals.active.handlePaste(event);
@@ -1797,6 +4974,10 @@ var NativeFrontend = class {
1797
4974
  case "input-action":
1798
4975
  this.inputAction(command2.action, command2.value);
1799
4976
  break;
4977
+ case "composer-vim":
4978
+ this.vimComposer.setEnabled(command2.enabled);
4979
+ this.restoreFocus();
4980
+ break;
1800
4981
  case "mouse":
1801
4982
  this.renderer.useMouse = command2.enabled;
1802
4983
  break;
@@ -1875,6 +5056,7 @@ var NativeFrontend = class {
1875
5056
  this.embeddedPane.add(terminal);
1876
5057
  }
1877
5058
  terminal.focus();
5059
+ this.layoutInstruments();
1878
5060
  }
1879
5061
  closeEmbedded(id) {
1880
5062
  this.context.terminals.get(id)?.destroy();
@@ -1882,17 +5064,18 @@ var NativeFrontend = class {
1882
5064
  if (this.embeddedId === id) {
1883
5065
  this.embeddedId = void 0;
1884
5066
  this.embeddedPane.visible = false;
5067
+ this.layoutInstruments();
1885
5068
  this.restoreFocus();
1886
5069
  }
1887
5070
  }
1888
5071
  async dispose() {
1889
5072
  if (this.destroyed) return;
1890
5073
  this.destroyed = true;
1891
- if (this.animation) clearInterval(this.animation);
5074
+ this.disposeSelectionContrast();
1892
5075
  this.renderer.keyInput.off("keypress", this.onKey);
1893
5076
  this.renderer.keyInput.off("paste", this.onPaste);
1894
- this.renderer.off(CliRenderEvents.FRAME, this.onFrame);
1895
- this.renderer.off(CliRenderEvents.RESIZE, this.onResize);
5077
+ this.renderer.off(CliRenderEvents3.FRAME, this.onFrame);
5078
+ this.renderer.off(CliRenderEvents3.RESIZE, this.onResize);
1896
5079
  this.modals.destroy();
1897
5080
  this.content.destroy();
1898
5081
  this.tasks.destroy();
@@ -1902,21 +5085,86 @@ var NativeFrontend = class {
1902
5085
  }
1903
5086
  };
1904
5087
 
5088
+ // packages/cli/src/tui/native/components/cursor.ts
5089
+ import { CliRenderEvents as CliRenderEvents4 } from "@opentui/core";
5090
+ function installTerminalCursorDefaults(renderer2, session) {
5091
+ const reset = new TextEncoder().encode("\x1B]112\x07");
5092
+ let disposed = false;
5093
+ let requested = 0;
5094
+ let restored = 0;
5095
+ let pending;
5096
+ let observedFrame;
5097
+ const restore = () => {
5098
+ if (disposed || session.disposed) return Promise.resolve();
5099
+ requested++;
5100
+ if (pending) return pending;
5101
+ let resolve;
5102
+ let reject;
5103
+ const completion = new Promise((done, fail2) => {
5104
+ resolve = done;
5105
+ reject = fail2;
5106
+ });
5107
+ pending = completion;
5108
+ void (async () => {
5109
+ try {
5110
+ while (!disposed && !session.disposed && restored < requested) {
5111
+ const generation = requested;
5112
+ while (!session.write(reset)) await session.idle();
5113
+ await session.idle();
5114
+ restored = generation;
5115
+ }
5116
+ pending = void 0;
5117
+ resolve();
5118
+ } catch (error) {
5119
+ pending = void 0;
5120
+ reject(error);
5121
+ }
5122
+ })();
5123
+ return completion;
5124
+ };
5125
+ const afterFrame = () => {
5126
+ const current = restore();
5127
+ if (observedFrame === current) return;
5128
+ observedFrame = current;
5129
+ void current.then(() => {
5130
+ if (observedFrame === current) observedFrame = void 0;
5131
+ }, (error) => {
5132
+ if (observedFrame === current) observedFrame = void 0;
5133
+ if (!disposed && !renderer2.isDestroyed) renderer2.emit("error", error);
5134
+ });
5135
+ };
5136
+ const dispose = () => {
5137
+ if (disposed) return;
5138
+ disposed = true;
5139
+ renderer2.off(CliRenderEvents4.FRAME, afterFrame);
5140
+ renderer2.off(CliRenderEvents4.DESTROY, dispose);
5141
+ };
5142
+ renderer2.on(CliRenderEvents4.FRAME, afterFrame);
5143
+ renderer2.once(CliRenderEvents4.DESTROY, dispose);
5144
+ return { restore, dispose };
5145
+ }
5146
+
1905
5147
  // packages/cli/src/tui/native/worker.ts
1906
5148
  var replies = createWriteStream("", { fd: 3, autoClose: false });
1907
5149
  var reply = (message) => replies.write(JSON.stringify(message) + "\n");
1908
5150
  var terminalInput = new PassThrough();
1909
5151
  var renderer;
1910
5152
  var frontend;
5153
+ var cursorDefaults;
1911
5154
  var stopping = false;
1912
5155
  var failed = false;
1913
5156
  async function close() {
1914
5157
  if (stopping) return;
1915
5158
  stopping = true;
1916
- await frontend?.dispose();
1917
- if (renderer) {
1918
- renderer.destroy();
1919
- await renderer.closed;
5159
+ try {
5160
+ await frontend?.dispose();
5161
+ await cursorDefaults?.restore();
5162
+ } finally {
5163
+ cursorDefaults?.dispose();
5164
+ if (renderer) {
5165
+ renderer.destroy();
5166
+ await renderer.closed;
5167
+ }
1920
5168
  }
1921
5169
  }
1922
5170
  async function fail(error) {
@@ -1936,18 +5184,22 @@ async function command(message) {
1936
5184
  frontend.update(message.snapshot, message.content);
1937
5185
  renderer.requestRender();
1938
5186
  await renderer.idle();
1939
- } else if (message.type === "suspend") await renderer.suspend();
1940
- else if (message.type === "resume") {
5187
+ } else if (message.type === "suspend") {
5188
+ await cursorDefaults.restore();
5189
+ await renderer.suspend();
5190
+ } else if (message.type === "resume") {
1941
5191
  await renderer.resume();
1942
5192
  renderer.requestRender();
1943
5193
  await renderer.idle();
5194
+ await cursorDefaults.restore();
1944
5195
  } else if (message.type === "stop") await close();
1945
5196
  reply({ type: "ack", id: message.id });
1946
5197
  if (message.type === "stop") replies.end(() => process.exit(0));
1947
5198
  }
1948
5199
  try {
5200
+ const session = new NativeSession(process.stdout);
1949
5201
  renderer = await createCliRenderer({
1950
- nativeSession: new NativeSession(process.stdout),
5202
+ nativeSession: session,
1951
5203
  stdin: terminalInput,
1952
5204
  stdout: process.stdout,
1953
5205
  exitOnCtrlC: false,
@@ -1958,21 +5210,22 @@ try {
1958
5210
  screenMode: "alternate-screen",
1959
5211
  consoleMode: "disabled",
1960
5212
  externalOutputMode: "passthrough",
1961
- backgroundColor: RGBA2.defaultBackground(),
5213
+ backgroundColor: RGBA3.defaultBackground(),
1962
5214
  openConsoleOnError: false,
1963
5215
  targetFps: 60,
1964
5216
  maxFps: 60,
1965
5217
  clearOnShutdown: false
1966
5218
  });
1967
- renderer.on(CliRenderEvents2.RENDER_ERROR, (event) => {
5219
+ renderer.on(CliRenderEvents5.RENDER_ERROR, (event) => {
1968
5220
  void fail(event.error);
1969
5221
  });
1970
- renderer.on(CliRenderEvents2.HANDLER_ERROR, (event) => {
5222
+ renderer.on(CliRenderEvents5.HANDLER_ERROR, (event) => {
1971
5223
  void fail(event.error);
1972
5224
  });
1973
5225
  renderer.on("error", (event) => {
1974
5226
  void fail(event);
1975
5227
  });
5228
+ cursorDefaults = installTerminalCursorDefaults(renderer, session);
1976
5229
  frontend = new NativeFrontend(renderer, (event) => reply({ type: "event", event }));
1977
5230
  const controls = createInterface({ input: process.stdin, crlfDelay: Infinity });
1978
5231
  let pending = Promise.resolve();
@@ -2004,6 +5257,7 @@ try {
2004
5257
  });
2005
5258
  renderer.requestRender();
2006
5259
  await renderer.idle();
5260
+ await cursorDefaults.restore();
2007
5261
  reply({ type: "ready" });
2008
5262
  } catch (error) {
2009
5263
  await fail(error);