omnius 1.0.864 → 1.0.865

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/index.js +2 -2
  2. package/dist/tui/native/worker.js +397 -65
  3. package/dist/update-worker.js +21 -0
  4. package/docs/DISCOVERY.json +604 -0
  5. package/docs/DISCOVERY.md +17 -0
  6. package/docs/work-orders/2026-10-16-browser-instrument-dashboard.md +74 -0
  7. package/docs/work-orders/2026-10-16-interface-delivery.md +47 -0
  8. package/docs/work-orders/desktop-projects.md +9 -3
  9. package/docs/work-orders/evidence/desktop-indicator-registration-20261016.json +11 -6
  10. package/docs/work-orders/evidence/instrument-100x30.png +0 -0
  11. package/docs/work-orders/evidence/instrument-120x36.png +0 -0
  12. package/docs/work-orders/evidence/instrument-160x48.png +0 -0
  13. package/docs/work-orders/evidence/instrument-60x20.png +0 -0
  14. package/docs/work-orders/evidence/instrument-80x24.png +0 -0
  15. package/docs/work-orders/evidence/instrument-clean-transcript-120x36.png +0 -0
  16. package/docs/work-orders/evidence/instrument-collapse-120x36.png +0 -0
  17. package/docs/work-orders/evidence/instrument-devices-expanded-120x36.png +0 -0
  18. package/docs/work-orders/evidence/instrument-divider-collapsed-160x48.png +0 -0
  19. package/docs/work-orders/evidence/instrument-divider-resized-160x48.png +0 -0
  20. package/docs/work-orders/evidence/instrument-help-scroll-120x36.png +0 -0
  21. package/docs/work-orders/evidence/instrument-package-install-20261016-updated.json +110 -0
  22. package/docs/work-orders/evidence/instrument-popup-api-120x36.png +0 -0
  23. package/docs/work-orders/evidence/instrument-popup-api-input-120x36.png +0 -0
  24. package/docs/work-orders/evidence/instrument-popup-config-120x36.png +0 -0
  25. package/docs/work-orders/evidence/instrument-popup-dark-120x36.png +0 -0
  26. package/docs/work-orders/evidence/instrument-popup-dark-60x20.png +0 -0
  27. package/docs/work-orders/evidence/instrument-popup-light-120x36.png +0 -0
  28. package/docs/work-orders/evidence/instrument-popup-model-120x36.png +0 -0
  29. package/docs/work-orders/evidence/instrument-popup-network-120x36.png +0 -0
  30. package/docs/work-orders/evidence/instrument-production-terminal-live.json +3521 -2398
  31. package/docs/work-orders/evidence/instrument-report-broker-120x36.png +0 -0
  32. package/docs/work-orders/evidence/instrument-report-gpu-120x36.png +0 -0
  33. package/docs/work-orders/evidence/instrument-report-stats-120x36.png +0 -0
  34. package/docs/work-orders/evidence/instrument-selection-copied-120x36.png +0 -0
  35. package/docs/work-orders/evidence/instrument-selection-copy-menu-120x36.png +0 -0
  36. package/docs/work-orders/evidence/instrument-terminal-light-120x36.png +0 -0
  37. package/docs/work-orders/evidence/instrument-transcript-selection-120x36.png +0 -0
  38. package/docs/work-orders/evidence/instrument-vim-composer-120x36.png +0 -0
  39. package/docs/work-orders/evidence/interface-daemon-deployment-20261016.json +56 -0
  40. package/docs/work-orders/evidence/interface-targeted-verification-20261016-final.json +463 -0
  41. package/docs/work-orders/evidence/tray-checked-menu-final.png +0 -0
  42. package/docs/work-orders/evidence/tray-project-3.png +0 -0
  43. package/docs/work-orders/evidence/tray-project-4.png +0 -0
  44. package/docs/work-orders/evidence/tray-project-5.png +0 -0
  45. package/docs/work-orders/evidence/tray-project-7.png +0 -0
  46. package/docs/work-orders/evidence/tray-projects-live.json +307 -47
  47. package/docs/work-orders/evidence/tray-projects-long-menu.png +0 -0
  48. package/docs/work-orders/evidence/tray-projects-menu.png +0 -0
  49. package/docs/work-orders/evidence/web-instrument-dark-desktop-20261016.png +0 -0
  50. package/docs/work-orders/evidence/web-instrument-dark-phone-20261016.png +0 -0
  51. package/docs/work-orders/evidence/web-instrument-dashboard-live-20261016.json +110 -0
  52. package/docs/work-orders/evidence/web-instrument-light-desktop-20261016.png +0 -0
  53. package/docs/work-orders/evidence/web-instrument-light-phone-20261016.png +0 -0
  54. package/docs/work-orders/evidence/web-media-runtime-verification-20261016.json +75 -0
  55. package/docs/work-orders/instrument-tui.md +36 -4
  56. package/npm-shrinkwrap.json +2 -2
  57. package/package.json +1 -1
@@ -2,19 +2,19 @@
2
2
  import { createWriteStream } from "node:fs";
3
3
  import { createInterface } from "node:readline";
4
4
  import { PassThrough } from "node:stream";
5
- import { CliRenderEvents as CliRenderEvents5, NativeSession, RGBA as RGBA3, createCliRenderer } from "@opentui/core";
5
+ import { CliRenderEvents as CliRenderEvents6, NativeSession, RGBA as RGBA3, createCliRenderer } from "@opentui/core";
6
6
 
7
7
  // packages/cli/src/tui/native/components/frontend.ts
8
8
  import {
9
- BoxRenderable as BoxRenderable5,
10
- CliRenderEvents as CliRenderEvents3,
9
+ BoxRenderable as BoxRenderable6,
10
+ CliRenderEvents as CliRenderEvents4,
11
11
  EmbeddedTerminalRenderable as EmbeddedTerminalRenderable2,
12
12
  ScrollBoxRenderable as ScrollBoxRenderable4,
13
13
  SyntaxStyle as SyntaxStyle2,
14
14
  TabSelectRenderable,
15
15
  TextareaRenderable as TextareaRenderable2,
16
- TextRenderable as TextRenderable4,
17
- TextAttributes as TextAttributes6,
16
+ TextRenderable as TextRenderable5,
17
+ TextAttributes as TextAttributes7,
18
18
  createClipboard,
19
19
  createHostClipboard,
20
20
  createRendererClipboardAdapter
@@ -1650,6 +1650,58 @@ import {
1650
1650
  TextRenderable as TextRenderable3
1651
1651
  } from "@opentui/core";
1652
1652
 
1653
+ // packages/cli/src/tui/native/components/device-history.ts
1654
+ var DeviceHistory = class {
1655
+ samples = [];
1656
+ append(sample) {
1657
+ if (!Number.isFinite(sample.at) || sample.at <= (this.samples.at(-1)?.at ?? 0)) return;
1658
+ this.samples.push(sample);
1659
+ if (this.samples.length > 120) this.samples.splice(0, this.samples.length - 120);
1660
+ }
1661
+ };
1662
+ function deviceHistorySparkline(samples, field, width) {
1663
+ const columns = Math.max(0, Math.floor(width));
1664
+ const latest = samples.at(-1)?.at;
1665
+ const values = Array(columns).fill(void 0);
1666
+ if (latest !== void 0) for (const sample of samples) {
1667
+ const x = columns - 1 - Math.round((latest - sample.at) / 2e3);
1668
+ if (x >= 0 && x < columns) values[x] = sample[field];
1669
+ }
1670
+ return values.map((value) => value === void 0 || !Number.isFinite(value) || value < 0 ? " " : "▁▂▃▄▅▆▇█"[Math.round(Math.min(100, value) * 7 / 100)]).join("");
1671
+ }
1672
+ function deviceHistoryGraph(samples, field, width) {
1673
+ const columns = Math.max(0, Math.floor(width) - 5), pixels = columns * 2;
1674
+ if (columns < 2) return "";
1675
+ const cells = Array.from({ length: 3 }, () => Array(columns).fill(0));
1676
+ const latest = samples.at(-1)?.at;
1677
+ const dots = [[1, 8], [2, 16], [4, 32], [64, 128]];
1678
+ const point = (x, y) => {
1679
+ if (x < 0 || x >= pixels || y < 0 || y >= 12) return;
1680
+ cells[Math.floor(y / 4)][Math.floor(x / 2)] |= dots[y % 4][x % 2];
1681
+ };
1682
+ let previous;
1683
+ if (latest !== void 0) for (const sample of samples) {
1684
+ const value = sample[field];
1685
+ const x = pixels - 1 - Math.round((latest - sample.at) / 1e3);
1686
+ if (x < 0) {
1687
+ previous = void 0;
1688
+ continue;
1689
+ }
1690
+ if (value === void 0 || !Number.isFinite(value) || value < 0) {
1691
+ previous = void 0;
1692
+ continue;
1693
+ }
1694
+ const y = Math.round(11 * (1 - Math.min(100, value) / 100));
1695
+ if (previous && sample.at - previous.at <= 5e3) {
1696
+ const distance = Math.max(1, Math.abs(x - previous.x), Math.abs(y - previous.y));
1697
+ for (let step = 0; step <= distance; step++) point(Math.round(previous.x + (x - previous.x) * step / distance), Math.round(previous.y + (y - previous.y) * step / distance));
1698
+ } else point(x, y);
1699
+ previous = { x, y, at: sample.at };
1700
+ }
1701
+ const labels = ["100%│", " 50%│", " 0%└"];
1702
+ return cells.map((row, y) => labels[y] + row.map((bits, x) => bits ? String.fromCodePoint(10240 + bits) : x % 4 === 0 ? "·" : " ").join("")).join("\n");
1703
+ }
1704
+
1653
1705
  // packages/cli/src/tui/terminal-cells.ts
1654
1706
  var graphemes = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1655
1707
  var extendedPictographic = /\p{Extended_Pictographic}/u;
@@ -1729,6 +1781,13 @@ function terminalCellWidth(value) {
1729
1781
 
1730
1782
  // packages/cli/src/tui/native/components/instrument-format.ts
1731
1783
  var graphemes2 = new Intl.Segmenter(void 0, { granularity: "grapheme" });
1784
+ function formatSignalDivider(width) {
1785
+ const cells = Math.max(0, Math.floor(width));
1786
+ if (cells < 32) return "─".repeat(cells);
1787
+ const plateau = Math.min(12, Math.max(6, Math.floor(cells / 8)));
1788
+ const left = Math.floor((cells - plateau - 2) / 2);
1789
+ return "▁".repeat(left) + "╱" + "▔".repeat(plateau) + "╲" + "▁".repeat(cells - left - plateau - 2);
1790
+ }
1732
1791
  function fitInstrumentText(value, cells, marker = "~") {
1733
1792
  const text = stripTerminalSequences(normalizeTerminalLine(value));
1734
1793
  const width = Math.max(0, Math.floor(cells));
@@ -1989,13 +2048,17 @@ var InstrumentDivider = class extends TextRenderable3 {
1989
2048
  };
1990
2049
  var DeviceMonitor = class extends BoxRenderable4 {
1991
2050
  constructor(renderer2, action) {
1992
- super(renderer2, { id: "omnius-devices", width: "100%", height: 3, minHeight: 3, maxHeight: "60%", flexShrink: 1, flexDirection: "column", visible: false });
2051
+ super(renderer2, { id: "omnius-devices", width: "100%", height: 5, minHeight: 5, maxHeight: "60%", flexShrink: 1, flexDirection: "column", visible: false });
1993
2052
  this.action = action;
1994
2053
  this.heading = new SectionBanner(renderer2, "DEVICES", "omnius-devices:heading", () => this.toggle());
1995
2054
  this.heading.update("DEVICES", false);
1996
2055
  this.add(this.heading);
1997
2056
  this.summary = new TextRenderable3(renderer2, { id: "omnius-devices:summary", content: "--", width: "100%", height: 1, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true });
1998
2057
  this.add(this.summary);
2058
+ this.compactLoad = new TextRenderable3(renderer2, { id: "omnius-devices:load-history", content: "", width: "100%", height: 1, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true });
2059
+ this.compactMemory = new TextRenderable3(renderer2, { id: "omnius-devices:memory-history", content: "", width: "100%", height: 1, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true });
2060
+ this.add(this.compactLoad);
2061
+ this.add(this.compactMemory);
1999
2062
  this.scroll = new ScrollBoxRenderable3(renderer2, {
2000
2063
  id: "omnius-devices:scroll",
2001
2064
  width: "100%",
@@ -2036,11 +2099,15 @@ var DeviceMonitor = class extends BoxRenderable4 {
2036
2099
  heading;
2037
2100
  scroll;
2038
2101
  summary;
2102
+ compactLoad;
2103
+ compactMemory;
2039
2104
  policy;
2040
2105
  status;
2041
2106
  notes;
2042
2107
  expanded = false;
2108
+ availableRows = 100;
2043
2109
  snapshot;
2110
+ historySource = "";
2044
2111
  rows = /* @__PURE__ */ new Map();
2045
2112
  button(renderer2, id, label, activate) {
2046
2113
  const button = new TextRenderable3(renderer2, {
@@ -2086,7 +2153,12 @@ var DeviceMonitor = class extends BoxRenderable4 {
2086
2153
  }
2087
2154
  update(snapshot) {
2088
2155
  this.snapshot = snapshot;
2089
- this.visible = Boolean(snapshot);
2156
+ this.visible = Boolean(snapshot) && this.availableRows >= 3;
2157
+ const historySource = `${snapshot?.source}:${snapshot?.local}`;
2158
+ if (this.historySource !== historySource) {
2159
+ for (const row of this.rows.values()) row.history.samples.splice(0);
2160
+ this.historySource = historySource;
2161
+ }
2090
2162
  const allowed = this.canSelect();
2091
2163
  if (this.policy.focused && !allowed) this.heading.focus();
2092
2164
  this.policy.focusable = allowed;
@@ -2108,7 +2180,7 @@ var DeviceMonitor = class extends BoxRenderable4 {
2108
2180
  flexShrink: 0,
2109
2181
  onSizeChange: () => {
2110
2182
  if (!row) return;
2111
- for (const child of [row.title, row.load, row.memory, row.reason]) child.width = row.root.width;
2183
+ for (const child of [row.title, row.load, row.memory, row.reason, row.loadGraph, row.memoryGraph]) child.width = row.root.width;
2112
2184
  this.refresh();
2113
2185
  }
2114
2186
  });
@@ -2120,6 +2192,8 @@ var DeviceMonitor = class extends BoxRenderable4 {
2120
2192
  title.on(RenderableEvents3.FOCUSED, () => this.scroll.scrollChildIntoView(root.id));
2121
2193
  const load = new TelemetryRow(this._ctx, "LOAD", `${root.id}:load`, 16);
2122
2194
  const memory = new TelemetryRow(this._ctx, "VRAM", `${root.id}:memory`, 16);
2195
+ const plot = (name) => new TextRenderable3(this._ctx, { id: `${root.id}:${name}-history`, content: "", width: "100%", height: 3, flexShrink: 0, fg: foreground, bg: background, wrapMode: "none", truncate: true, selectable: false });
2196
+ const loadGraph = plot("load"), memoryGraph = plot("memory"), history = new DeviceHistory();
2123
2197
  const reason = new TextRenderable3(this._ctx, {
2124
2198
  id: `${root.id}:reason`,
2125
2199
  content: "",
@@ -2134,12 +2208,19 @@ var DeviceMonitor = class extends BoxRenderable4 {
2134
2208
  });
2135
2209
  root.add(title);
2136
2210
  root.add(load);
2211
+ root.add(loadGraph);
2137
2212
  root.add(memory);
2213
+ root.add(memoryGraph);
2138
2214
  root.add(reason);
2139
2215
  this.scroll.add(root);
2140
- row = { root, title, load, memory, reason };
2216
+ row = { root, title, load, memory, reason, history, loadGraph, memoryGraph };
2141
2217
  this.rows.set(device.uuid, row);
2142
2218
  }
2219
+ if (snapshot?.sampledAt !== void 0) row.history.append({
2220
+ at: snapshot.sampledAt,
2221
+ load: device.utilization,
2222
+ memory: device.memoryUsedMB !== void 0 && device.memoryUsedMB >= 0 && device.memoryTotalMB !== void 0 && device.memoryTotalMB > 0 ? device.memoryUsedMB * 100 / device.memoryTotalMB : void 0
2223
+ });
2143
2224
  const selectable = this.canSelectDevice(device);
2144
2225
  if (row.title.focused && !selectable) this.heading.focus();
2145
2226
  row.title.focusable = selectable;
@@ -2156,11 +2237,20 @@ var DeviceMonitor = class extends BoxRenderable4 {
2156
2237
  toggle() {
2157
2238
  this.expanded = !this.expanded;
2158
2239
  this.scroll.visible = this.expanded;
2159
- this.minHeight = this.expanded ? 4 : 3;
2160
2240
  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;
2241
+ this.fitHeight(this.availableRows + 2);
2162
2242
  this.refresh();
2163
2243
  }
2244
+ fitHeight(railRows) {
2245
+ this.availableRows = Math.max(0, railRows - 2);
2246
+ this.visible = Boolean(this.snapshot) && this.availableRows >= 3;
2247
+ this.minHeight = Math.min(5, this.availableRows);
2248
+ this.maxHeight = this.availableRows;
2249
+ this.summary.visible = this.availableRows >= 5;
2250
+ this.compactLoad.visible = !this.expanded;
2251
+ this.compactMemory.visible = !this.expanded && this.availableRows >= 4;
2252
+ this.height = Math.min(this.availableRows, this.expanded ? Math.min(18, Math.max(6, Math.floor(railRows * 0.6))) : 5);
2253
+ }
2164
2254
  onResize(width, height) {
2165
2255
  super.onResize(width, height);
2166
2256
  this.refresh();
@@ -2176,13 +2266,28 @@ var DeviceMonitor = class extends BoxRenderable4 {
2176
2266
  this.notes.content = [
2177
2267
  data?.local === false ? "REMOTE" : void 0,
2178
2268
  data ? `${data.source.toUpperCase()} · ${data.policy.toUpperCase()} · ${data.broker.toUpperCase()}` : void 0,
2269
+ "2s/col",
2179
2270
  data?.warnings.length ? `${data.warnings.length} notices` : void 0
2180
2271
  ].filter(Boolean).join(" · ");
2272
+ const primary = devices.find((device) => device.preferred) ?? devices.find((device) => device.enabled) ?? devices[0];
2273
+ const compactHistory = primary ? this.rows.get(primary.uuid)?.history.samples ?? [] : [];
2274
+ for (const [element, field, label] of [[this.compactLoad, "load", "UTIL"], [this.compactMemory, "memory", primary?.unifiedMemory ? "UMA" : "VRAM"]]) {
2275
+ const current = primary ? field === "load" ? primary.utilization : primary.memoryUsedMB !== void 0 && primary.memoryTotalMB ? primary.memoryUsedMB / primary.memoryTotalMB * 100 : void 0 : void 0;
2276
+ const value = instrumentNumber(current, 0, "%");
2277
+ const caption = `${label}${primary?.index ?? "-"}`;
2278
+ const plotWidth = Math.max(0, this.width - caption.length - value.length - 2);
2279
+ const next = `${caption} ${deviceHistorySparkline(compactHistory, field, plotWidth)} ${value}`;
2280
+ if (element.plainText !== next) element.content = next;
2281
+ }
2181
2282
  for (const device of devices) {
2182
2283
  const row = this.rows.get(device.uuid);
2183
2284
  if (!row) continue;
2184
2285
  const state = device.enabled === void 0 ? "--" : device.enabled ? "ON" : "OFF";
2185
2286
  row.title.content = formatTelemetryRow(`GPU ${device.index} ${device.name}`, `${state}${device.preferred ? " *" : ""}`, row.root.width, 6);
2287
+ for (const [plot, field] of [[row.loadGraph, "load"], [row.memoryGraph, "memory"]]) {
2288
+ const next = deviceHistoryGraph(row.history.samples, field, row.root.width);
2289
+ if (plot.plainText !== next) plot.content = next;
2290
+ }
2186
2291
  }
2187
2292
  }
2188
2293
  focusTargets() {
@@ -2305,6 +2410,10 @@ var InstrumentRail = class extends BoxRenderable4 {
2305
2410
  contextMeter;
2306
2411
  contextHeader;
2307
2412
  contextDetails;
2413
+ onResize(width, height) {
2414
+ super.onResize(width, height);
2415
+ this.devices?.fitHeight(height);
2416
+ }
2308
2417
  constructor(renderer2, options = {}) {
2309
2418
  const id = options.id ?? "omnius-instruments";
2310
2419
  super(renderer2, {
@@ -3964,17 +4073,153 @@ var VimComposer = class {
3964
4073
  }
3965
4074
  };
3966
4075
 
4076
+ // packages/cli/src/tui/native/components/selection-menu.ts
4077
+ import { BoxRenderable as BoxRenderable5, CliRenderEvents as CliRenderEvents3, TextAttributes as TextAttributes6, TextRenderable as TextRenderable4 } from "@opentui/core";
4078
+ var NativeSelectionMenu = class {
4079
+ constructor(renderer2, colors, copy, restoreFocus) {
4080
+ this.renderer = renderer2;
4081
+ this.colors = colors;
4082
+ this.copy = copy;
4083
+ this.restoreFocus = restoreFocus;
4084
+ renderer2.on(CliRenderEvents3.RESIZE, this.position);
4085
+ }
4086
+ renderer;
4087
+ colors;
4088
+ copy;
4089
+ restoreFocus;
4090
+ overlay;
4091
+ panel;
4092
+ frame;
4093
+ previousFocus;
4094
+ anchor = { x: 0, y: 0 };
4095
+ selectedText = "";
4096
+ get active() {
4097
+ return Boolean(this.overlay);
4098
+ }
4099
+ open(x, y, text) {
4100
+ if (!text) return;
4101
+ this.close();
4102
+ this.selectedText = text;
4103
+ this.anchor = { x, y };
4104
+ this.previousFocus = this.renderer.currentFocusedRenderable;
4105
+ this.overlay = new BoxRenderable5(this.renderer, {
4106
+ id: "omnius-selection-menu",
4107
+ position: "absolute",
4108
+ left: 0,
4109
+ top: 0,
4110
+ width: "100%",
4111
+ height: "100%",
4112
+ backgroundColor: "transparent",
4113
+ shouldFill: false,
4114
+ zIndex: 120,
4115
+ onMouseDown: (event) => {
4116
+ this.close();
4117
+ event.preventDefault();
4118
+ event.stopPropagation();
4119
+ }
4120
+ });
4121
+ this.panel = new BoxRenderable5(this.renderer, {
4122
+ id: "omnius-selection-menu-panel",
4123
+ position: "absolute",
4124
+ width: 14,
4125
+ height: 3,
4126
+ flexDirection: "column",
4127
+ paddingX: 2,
4128
+ paddingY: 1,
4129
+ backgroundColor: this.colors.background,
4130
+ onMouseDown: (event) => {
4131
+ event.preventDefault();
4132
+ event.stopPropagation();
4133
+ }
4134
+ });
4135
+ const action = new TextRenderable4(this.renderer, {
4136
+ id: "omnius-selection-copy",
4137
+ content: "COPY",
4138
+ height: 1,
4139
+ width: "100%",
4140
+ selectable: false,
4141
+ fg: this.colors.foreground,
4142
+ bg: this.colors.background,
4143
+ attributes: TextAttributes6.BOLD | TextAttributes6.INVERSE,
4144
+ onMouseDown: (event) => {
4145
+ if (event.button === 0) this.confirm();
4146
+ event.preventDefault();
4147
+ event.stopPropagation();
4148
+ }
4149
+ });
4150
+ action.focusable = true;
4151
+ this.panel.add(action);
4152
+ this.overlay.add(this.panel);
4153
+ this.frame = new NativeCornerFrame(this.panel, this.colors, this.renderer, this.overlay);
4154
+ this.renderer.root.add(this.overlay);
4155
+ this.position();
4156
+ action.focus();
4157
+ this.renderer.requestRender();
4158
+ }
4159
+ position = () => {
4160
+ if (!this.panel) return;
4161
+ const width = Math.min(14, this.renderer.terminalWidth);
4162
+ this.panel.width = width;
4163
+ this.panel.left = Math.max(0, Math.min(this.anchor.x, this.renderer.terminalWidth - width));
4164
+ this.panel.top = Math.max(0, Math.min(this.anchor.y + 1, this.renderer.terminalHeight - 3));
4165
+ };
4166
+ handleKey(key) {
4167
+ if (key.eventType !== "release") {
4168
+ if (key.name === "return" || key.sequence === " " || key.ctrl && key.shift && key.name.toLowerCase() === "c") this.confirm();
4169
+ else if (key.name === "escape" || key.name === "tab") this.close();
4170
+ }
4171
+ key.preventDefault();
4172
+ key.stopPropagation();
4173
+ }
4174
+ confirm() {
4175
+ const text = this.selectedText;
4176
+ this.close();
4177
+ this.copy(text);
4178
+ }
4179
+ applyTheme(colors) {
4180
+ this.colors = colors;
4181
+ if (!this.panel) return;
4182
+ this.panel.backgroundColor = colors.background;
4183
+ this.frame?.setColors(colors);
4184
+ for (const child of this.panel.getChildren()) if (child instanceof TextRenderable4) {
4185
+ child.fg = colors.foreground;
4186
+ child.bg = colors.background;
4187
+ }
4188
+ }
4189
+ close() {
4190
+ if (!this.overlay) return;
4191
+ const previous = this.previousFocus;
4192
+ this.previousFocus = void 0;
4193
+ this.frame?.destroy();
4194
+ this.frame = void 0;
4195
+ this.overlay.destroyRecursively();
4196
+ this.overlay = void 0;
4197
+ this.panel = void 0;
4198
+ this.selectedText = "";
4199
+ if (previous && !previous.isDestroyed && previous.visible && previous.focusable) previous.focus();
4200
+ else this.restoreFocus();
4201
+ this.renderer.requestRender();
4202
+ }
4203
+ destroy() {
4204
+ this.close();
4205
+ this.renderer.off(CliRenderEvents3.RESIZE, this.position);
4206
+ }
4207
+ };
4208
+
3967
4209
  // packages/cli/src/tui/native/components/frontend.ts
3968
4210
  var initialTheme = { mode: "system", bg: -1, accent: -1, textPrimary: -1, textDim: -1, boxColor: -1 };
3969
4211
  var NativeFrontend = class {
3970
- constructor(renderer2, emit) {
4212
+ constructor(renderer2, emit, options = {}) {
3971
4213
  this.renderer = renderer2;
3972
4214
  this.emit = emit;
3973
4215
  const colors = palette(initialTheme);
3974
4216
  this.context = { renderer: renderer2, colors, syntax: SyntaxStyle2.fromStyles({}, renderer2.nativeScene), emit, terminals: /* @__PURE__ */ new Map() };
3975
4217
  this.disposeSelectionContrast = installDefaultSelectionContrast(renderer2);
3976
- this.clipboard = createClipboard({ host: createHostClipboard({ timeoutMs: 1500 }), terminal: createRendererClipboardAdapter(renderer2) });
3977
- this.root = new BoxRenderable5(renderer2, {
4218
+ this.clipboard = options.clipboard ?? createClipboard({ host: createHostClipboard({ timeoutMs: 1500 }), terminal: createRendererClipboardAdapter(renderer2) });
4219
+ this.selectionMenu = new NativeSelectionMenu(renderer2, colors, (text) => {
4220
+ void this.copy(text);
4221
+ }, () => this.restoreFocus());
4222
+ this.root = new BoxRenderable6(renderer2, {
3978
4223
  id: "omnius-native",
3979
4224
  width: "100%",
3980
4225
  height: "100%",
@@ -3982,7 +4227,7 @@ var NativeFrontend = class {
3982
4227
  backgroundColor: colors.background
3983
4228
  });
3984
4229
  renderer2.root.add(this.root);
3985
- this.header = new BoxRenderable5(renderer2, {
4230
+ this.header = new BoxRenderable6(renderer2, {
3986
4231
  id: "omnius-header",
3987
4232
  width: "100%",
3988
4233
  minWidth: 0,
@@ -3993,9 +4238,19 @@ var NativeFrontend = class {
3993
4238
  borderColor: colors.border
3994
4239
  });
3995
4240
  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 });
4241
+ this.header.renderAfter = (buffer) => {
4242
+ if (this.header.height > 0) buffer.drawText(
4243
+ formatSignalDivider(this.header.width),
4244
+ this.header.x,
4245
+ this.header.y + this.header.height - 1,
4246
+ this.context.colors.foreground,
4247
+ this.context.colors.background,
4248
+ TextAttributes7.DIM
4249
+ );
4250
+ };
4251
+ const headerRow = new BoxRenderable6(renderer2, { id: "omnius-header-row", width: "100%", minWidth: 0, minHeight: 1, flexDirection: "row", gap: 1 });
3997
4252
  this.header.add(headerRow);
3998
- this.identity = new TextRenderable4(renderer2, {
4253
+ this.identity = new TextRenderable5(renderer2, {
3999
4254
  id: "omnius-identity",
4000
4255
  content: "Omnius",
4001
4256
  fg: colors.foreground,
@@ -4005,7 +4260,7 @@ var NativeFrontend = class {
4005
4260
  truncate: true,
4006
4261
  minWidth: 18,
4007
4262
  selectable: false,
4008
- attributes: TextAttributes6.BOLD,
4263
+ attributes: TextAttributes7.BOLD,
4009
4264
  onMouseDown: (event) => {
4010
4265
  if (event.button === 0) {
4011
4266
  this.toggleHeaderPanel();
@@ -4033,16 +4288,16 @@ var NativeFrontend = class {
4033
4288
  verticalScrollbarOptions: { visible: false }
4034
4289
  });
4035
4290
  headerRow.add(this.headerActions);
4036
- this.headerPanel = new TextRenderable4(renderer2, { id: "omnius-header-panel", content: "", width: "100%", wrapMode: "word", visible: false });
4291
+ this.headerPanel = new TextRenderable5(renderer2, { id: "omnius-header-panel", content: "", width: "100%", wrapMode: "word", visible: false });
4037
4292
  this.header.add(this.headerPanel);
4038
- this.detailButton = new TextRenderable4(renderer2, {
4293
+ this.detailButton = new TextRenderable5(renderer2, {
4039
4294
  id: "omnius-details-toggle",
4040
4295
  content: "+ DETAILS",
4041
4296
  height: 1,
4042
4297
  width: 9,
4043
4298
  flexShrink: 0,
4044
4299
  fg: colors.foreground,
4045
- attributes: TextAttributes6.DIM,
4300
+ attributes: TextAttributes7.DIM,
4046
4301
  selectable: false,
4047
4302
  onMouseDown: (event) => {
4048
4303
  if (event.button === 0) {
@@ -4062,13 +4317,13 @@ var NativeFrontend = class {
4062
4317
  for (const control of [this.identity, this.detailButton]) {
4063
4318
  const attributes = control.attributes;
4064
4319
  control.on("focused", () => {
4065
- control.attributes = attributes | TextAttributes6.INVERSE;
4320
+ control.attributes = attributes | TextAttributes7.INVERSE;
4066
4321
  });
4067
4322
  control.on("blurred", () => {
4068
4323
  control.attributes = attributes;
4069
4324
  });
4070
4325
  }
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 });
4326
+ this.routeWarning = new TextRenderable5(renderer2, { id: "omnius-route-warning", content: "", width: "100%", wrapMode: "word", fg: colors.warning, attributes: TextAttributes7.BOLD | TextAttributes7.UNDERLINE, visible: false, flexShrink: 0 });
4072
4327
  this.root.add(this.routeWarning);
4073
4328
  this.tabs = new TabSelectRenderable(renderer2, {
4074
4329
  id: "omnius-agent-tabs",
@@ -4107,13 +4362,13 @@ var NativeFrontend = class {
4107
4362
  const selected = this.tabs.getSelectedIndex();
4108
4363
  const start = Math.max(0, Math.min(selected - Math.floor(count / 2), this.tabs.options.length - count));
4109
4364
  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);
4365
+ buffer.drawText("─".repeat(Math.max(0, tabWidth)), (selected - start) * tabWidth, 1, colors2.foreground, colors2.background, TextAttributes7.DIM);
4111
4366
  };
4112
4367
  this.root.add(this.tabs);
4113
- this.taskPane = new BoxRenderable5(renderer2, { id: "omnius-tasks", width: "100%", flexDirection: "column", maxHeight: 6, flexShrink: 0, overflow: "hidden", visible: false });
4368
+ this.taskPane = new BoxRenderable6(renderer2, { id: "omnius-tasks", width: "100%", flexDirection: "column", maxHeight: 6, flexShrink: 0, overflow: "hidden", visible: false });
4114
4369
  this.tasks = new NativeBlockList(this.context, this.taskPane);
4115
4370
  this.root.add(this.taskPane);
4116
- this.body = new BoxRenderable5(renderer2, { id: "omnius-body", width: "100%", flexDirection: "row", flexGrow: 1, minHeight: 1 });
4371
+ this.body = new BoxRenderable6(renderer2, { id: "omnius-body", width: "100%", flexDirection: "row", flexGrow: 1, minHeight: 1 });
4117
4372
  this.root.add(this.body);
4118
4373
  this.scroll = new ScrollBoxRenderable4(renderer2, {
4119
4374
  id: "omnius-conversation",
@@ -4128,6 +4383,15 @@ var NativeFrontend = class {
4128
4383
  viewportCulling: true,
4129
4384
  verticalScrollbarOptions: { showArrows: false, trackOptions: { foregroundColor: colors.dim, backgroundColor: colors.background } },
4130
4385
  contentOptions: { flexDirection: "column", onMouseDown: (event) => {
4386
+ if (event.button === 2) {
4387
+ const selected = this.renderer.getSelection()?.getSelectedText();
4388
+ if (selected) {
4389
+ this.selectionMenu.open(event.x, event.y, selected);
4390
+ event.preventDefault();
4391
+ event.stopPropagation();
4392
+ }
4393
+ return;
4394
+ }
4131
4395
  if (!this.selectionArmed || event.button !== 0 || !event.target) return;
4132
4396
  this.selectionArmed = false;
4133
4397
  let block = event.target;
@@ -4147,6 +4411,18 @@ var NativeFrontend = class {
4147
4411
  }
4148
4412
  event.preventDefault();
4149
4413
  event.stopPropagation();
4414
+ }, onMouseDrag: (event) => {
4415
+ if (!event.isDragging || !event.target || !this.renderer.getSelection()?.isDragging) return;
4416
+ let container = this.renderer.getSelectionContainer();
4417
+ while (container) {
4418
+ let current = event.target;
4419
+ while (current && current !== container) current = current.parent;
4420
+ if (current) break;
4421
+ this.renderer.updateSelection(event.target, event.x, event.y);
4422
+ const next = this.renderer.getSelectionContainer();
4423
+ if (next === container) break;
4424
+ container = next;
4425
+ }
4150
4426
  } }
4151
4427
  });
4152
4428
  this.body.add(this.scroll);
@@ -4167,7 +4443,7 @@ var NativeFrontend = class {
4167
4443
  this.root.onMouseUp = (event) => this.instrumentDivider.handleRelease(event);
4168
4444
  this.instruments = new InstrumentRail(renderer2, { colors, onInspectAgent: (id) => this.emitAction("view", id), onAction: (command2) => this.emitAction(command2) });
4169
4445
  this.body.add(this.instruments);
4170
- this.embeddedPane = new BoxRenderable5(renderer2, {
4446
+ this.embeddedPane = new BoxRenderable6(renderer2, {
4171
4447
  id: "omnius-editor-pane",
4172
4448
  width: "50%",
4173
4449
  flexShrink: 0,
@@ -4178,7 +4454,7 @@ var NativeFrontend = class {
4178
4454
  visible: false
4179
4455
  });
4180
4456
  this.body.add(this.embeddedPane);
4181
- this.liveButton = new TextRenderable4(renderer2, {
4457
+ this.liveButton = new TextRenderable5(renderer2, {
4182
4458
  id: "omnius-live-button",
4183
4459
  content: "↓ Live",
4184
4460
  width: "100%",
@@ -4201,7 +4477,7 @@ var NativeFrontend = class {
4201
4477
  });
4202
4478
  this.liveButton.focusable = true;
4203
4479
  this.root.add(this.liveButton);
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 });
4480
+ this.queue = new TextRenderable5(renderer2, { id: "omnius-steering-queue", content: "", width: "100%", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, visible: false, flexShrink: 0 });
4205
4481
  this.root.add(this.queue);
4206
4482
  this.suggestions = new NativeSelectRenderable(renderer2, {
4207
4483
  id: "omnius-suggestions",
@@ -4220,9 +4496,9 @@ var NativeFrontend = class {
4220
4496
  this.suggestions.onActivate = () => this.acceptSuggestion();
4221
4497
  this.suggestions.on("itemSelected", () => this.acceptSuggestion());
4222
4498
  this.root.add(this.suggestions);
4223
- this.status = new BoxRenderable5(renderer2, { id: "omnius-live-status", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 2, visible: false });
4499
+ this.status = new BoxRenderable6(renderer2, { id: "omnius-live-status", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 2, visible: false });
4224
4500
  this.root.add(this.status);
4225
- this.footer = new BoxRenderable5(renderer2, {
4501
+ this.footer = new BoxRenderable6(renderer2, {
4226
4502
  id: "omnius-input-box",
4227
4503
  width: "100%",
4228
4504
  flexShrink: 0,
@@ -4232,9 +4508,19 @@ var NativeFrontend = class {
4232
4508
  flexDirection: "column"
4233
4509
  });
4234
4510
  this.root.add(this.footer);
4235
- const inputRow = new BoxRenderable5(renderer2, { id: "omnius-input-row", width: "100%", flexDirection: "row", gap: 1 });
4511
+ this.footer.renderAfter = (buffer) => {
4512
+ if (this.footer.height > 0) buffer.drawText(
4513
+ formatSignalDivider(this.footer.width),
4514
+ this.footer.x,
4515
+ this.footer.y,
4516
+ this.context.colors.foreground,
4517
+ this.context.colors.background,
4518
+ TextAttributes7.DIM
4519
+ );
4520
+ };
4521
+ const inputRow = new BoxRenderable6(renderer2, { id: "omnius-input-row", width: "100%", flexDirection: "row", gap: 1 });
4236
4522
  this.footer.add(inputRow);
4237
- this.prompt = new TextRenderable4(renderer2, { id: "omnius-prompt", content: "❯", fg: colors.accent, wrapMode: "none", selectable: false, flexShrink: 0 });
4523
+ this.prompt = new TextRenderable5(renderer2, { id: "omnius-prompt", content: "❯", fg: colors.accent, wrapMode: "none", selectable: false, flexShrink: 0 });
4238
4524
  inputRow.add(this.prompt);
4239
4525
  this.editor = new TextareaRenderable2(renderer2, {
4240
4526
  id: "omnius-input",
@@ -4254,14 +4540,14 @@ var NativeFrontend = class {
4254
4540
  onCursorChange: () => this.editorChanged(),
4255
4541
  onSubmit: () => this.submit()
4256
4542
  });
4257
- this.vimMode = new TextRenderable4(renderer2, {
4543
+ this.vimMode = new TextRenderable5(renderer2, {
4258
4544
  id: "omnius-vim-mode",
4259
4545
  content: "INSERT",
4260
4546
  height: 1,
4261
4547
  maxWidth: 12,
4262
4548
  flexShrink: 0,
4263
4549
  fg: colors.foreground,
4264
- attributes: TextAttributes6.DIM,
4550
+ attributes: TextAttributes7.DIM,
4265
4551
  wrapMode: "none",
4266
4552
  truncate: true,
4267
4553
  visible: false,
@@ -4275,31 +4561,42 @@ var NativeFrontend = class {
4275
4561
  this.renderer.requestRender();
4276
4562
  });
4277
4563
  inputRow.add(this.editor);
4278
- this.enhance = new BoxRenderable5(renderer2, { id: "omnius-enhance", flexDirection: "row", gap: 1, flexShrink: 0, visible: false });
4564
+ this.enhance = new BoxRenderable6(renderer2, { id: "omnius-enhance", flexDirection: "row", gap: 1, flexShrink: 0, visible: false });
4279
4565
  inputRow.add(this.enhance);
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 });
4566
+ this.activity = new TextRenderable5(renderer2, { id: "omnius-activity", content: "", flexGrow: 1, minWidth: 0, height: 1, wrapMode: "none", truncate: true, visible: false, fg: colors.accent });
4281
4567
  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 });
4568
+ this.media = new BoxRenderable6(renderer2, { id: "omnius-media", height: 1, minWidth: 0, flexShrink: 1, flexDirection: "row", gap: 1, visible: false });
4569
+ this.mediaText = new TextRenderable5(renderer2, { id: "omnius-media-text", content: "", flexGrow: 1, minWidth: 0, wrapMode: "none", truncate: true, fg: colors.accent });
4284
4570
  this.media.add(this.mediaText);
4285
- this.micTrack = new TextRenderable4(renderer2, { id: "omnius-mic-meter", width: 12, height: 1, flexShrink: 0, fg: colors.foreground, visible: false });
4571
+ this.micTrack = new TextRenderable5(renderer2, { id: "omnius-mic-meter", width: 12, height: 1, flexShrink: 0, fg: colors.foreground, visible: false });
4286
4572
  this.media.add(this.micTrack);
4287
4573
  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 });
4574
+ this.copyFeedback = new TextRenderable5(renderer2, {
4575
+ id: "omnius-copy-feedback",
4576
+ content: "",
4577
+ height: 1,
4578
+ flexShrink: 0,
4579
+ fg: colors.foreground,
4580
+ attributes: TextAttributes7.DIM,
4581
+ selectable: false,
4582
+ visible: false
4583
+ });
4584
+ this.status.add(this.copyFeedback);
4585
+ this.metricsRow = new BoxRenderable6(renderer2, { id: "omnius-metrics-row", width: "100%", height: 1, flexShrink: 0, flexDirection: "row", gap: 1 });
4586
+ this.keyHints = new TextRenderable5(renderer2, { id: "omnius-key-hints", content: "F1 HELP TAB FOCUS", height: 1, flexShrink: 0, attributes: TextAttributes7.DIM, fg: colors.foreground, selectable: false });
4290
4587
  this.metricsRow.add(this.keyHints);
4291
- this.metricsStartActions = new BoxRenderable5(renderer2, { id: "omnius-metrics-start-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
4588
+ this.metricsStartActions = new BoxRenderable6(renderer2, { id: "omnius-metrics-start-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
4292
4589
  this.metricsRow.add(this.metricsStartActions);
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 });
4590
+ this.metrics = new TextRenderable5(renderer2, { id: "omnius-metrics", content: "", height: 1, wrapMode: "none", truncate: true, fg: colors.dim, flexShrink: 1, flexGrow: 1, minWidth: 0 });
4294
4591
  this.metricsRow.add(this.metrics);
4295
- this.metricsEndActions = new BoxRenderable5(renderer2, { id: "omnius-metrics-end-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
4592
+ this.metricsEndActions = new BoxRenderable6(renderer2, { id: "omnius-metrics-end-actions", flexDirection: "row", gap: 1, flexShrink: 0 });
4296
4593
  this.metricsRow.add(this.metricsEndActions);
4297
4594
  this.root.add(this.metricsRow);
4298
4595
  this.modals = new NativeModalStack(this.context, () => this.restoreFocus());
4299
4596
  renderer2.keyInput.on("keypress", this.onKey);
4300
4597
  renderer2.keyInput.on("paste", this.onPaste);
4301
- renderer2.on(CliRenderEvents3.FRAME, this.onFrame);
4302
- renderer2.on(CliRenderEvents3.RESIZE, this.onResize);
4598
+ renderer2.on(CliRenderEvents4.FRAME, this.onFrame);
4599
+ renderer2.on(CliRenderEvents4.RESIZE, this.onResize);
4303
4600
  this.applyTheme(initialTheme);
4304
4601
  this.editor.focus();
4305
4602
  }
@@ -4312,6 +4609,7 @@ var NativeFrontend = class {
4312
4609
  content;
4313
4610
  modals;
4314
4611
  context;
4612
+ selectionMenu;
4315
4613
  header;
4316
4614
  identity;
4317
4615
  headerActions;
@@ -4343,6 +4641,8 @@ var NativeFrontend = class {
4343
4641
  mediaText;
4344
4642
  micTrack;
4345
4643
  clipboard;
4644
+ copyFeedback;
4645
+ copyFeedbackTimer;
4346
4646
  state;
4347
4647
  themeSignature = "";
4348
4648
  headerSignature = "";
@@ -4461,8 +4761,8 @@ var NativeFrontend = class {
4461
4761
  for (const child of [...parent.getChildren()]) child.destroy();
4462
4762
  for (const action of actions) {
4463
4763
  const colors = this.context.colors;
4464
- const attributes = action.disabled ? TextAttributes6.DIM : action.active ? TextAttributes6.BOLD | TextAttributes6.UNDERLINE : 0;
4465
- const button = new TextRenderable4(this.renderer, {
4764
+ const attributes = action.disabled ? TextAttributes7.DIM : action.active ? TextAttributes7.BOLD | TextAttributes7.UNDERLINE : 0;
4765
+ const button = new TextRenderable5(this.renderer, {
4466
4766
  id: `${prefix}:${action.id}`,
4467
4767
  content: styledText(action.label),
4468
4768
  flexShrink: 0,
@@ -4485,16 +4785,16 @@ var NativeFrontend = class {
4485
4785
  button.focusable = !action.disabled;
4486
4786
  if (!action.disabled) {
4487
4787
  button.on("focused", () => {
4488
- button.attributes = attributes | TextAttributes6.INVERSE;
4788
+ button.attributes = attributes | TextAttributes7.INVERSE;
4489
4789
  });
4490
4790
  button.on("blurred", () => {
4491
4791
  button.attributes = attributes;
4492
4792
  });
4493
4793
  button.onMouseOver = () => {
4494
- button.attributes = attributes | TextAttributes6.UNDERLINE | (button.focused ? TextAttributes6.INVERSE : 0);
4794
+ button.attributes = attributes | TextAttributes7.UNDERLINE | (button.focused ? TextAttributes7.INVERSE : 0);
4495
4795
  };
4496
4796
  button.onMouseOut = () => {
4497
- button.attributes = attributes | (button.focused ? TextAttributes6.INVERSE : 0);
4797
+ button.attributes = attributes | (button.focused ? TextAttributes7.INVERSE : 0);
4498
4798
  };
4499
4799
  }
4500
4800
  parent.add(button);
@@ -4513,12 +4813,12 @@ var NativeFrontend = class {
4513
4813
  const reading = activity?.label || stage || this.state?.instruments?.runtimeStatus || "";
4514
4814
  this.activity.visible = Boolean(activity?.processing || reading);
4515
4815
  this.activity.content = `${activity?.processing ? "RUN · " : ""}${reading}`;
4516
- this.status.visible = this.activity.visible || this.media.visible;
4816
+ this.status.visible = this.activity.visible || this.media.visible || this.copyFeedback.visible;
4517
4817
  }
4518
4818
  renderMedia() {
4519
4819
  const media = this.state?.media;
4520
4820
  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;
4821
+ this.status.visible = this.activity.visible || this.media.visible || this.copyFeedback.visible;
4522
4822
  if (!media) return;
4523
4823
  this.mediaText.content = [
4524
4824
  media.recording ? "● REC" : void 0,
@@ -4569,6 +4869,8 @@ var NativeFrontend = class {
4569
4869
  this.instruments.applyTheme(colors);
4570
4870
  this.instrumentDivider.fg = colors.foreground;
4571
4871
  this.instrumentDivider.bg = colors.background;
4872
+ this.copyFeedback.fg = colors.foreground;
4873
+ this.selectionMenu.applyTheme(colors);
4572
4874
  this.content.applyTheme();
4573
4875
  this.tasks.applyTheme();
4574
4876
  this.modals?.applyTheme();
@@ -4756,6 +5058,10 @@ var NativeFrontend = class {
4756
5058
  this.emit({ type: "action", action, ...id === void 0 ? {} : { id } });
4757
5059
  }
4758
5060
  onKey = (key) => {
5061
+ if (this.selectionMenu.active) {
5062
+ this.selectionMenu.handleKey(key);
5063
+ return;
5064
+ }
4759
5065
  if (this.modals.active) {
4760
5066
  this.modals.active.handleKey(key);
4761
5067
  return;
@@ -4927,6 +5233,10 @@ var NativeFrontend = class {
4927
5233
  }
4928
5234
  }
4929
5235
  onPaste = (event) => {
5236
+ if (this.selectionMenu.active) {
5237
+ event.preventDefault();
5238
+ return;
5239
+ }
4930
5240
  if (this.modals.active) {
4931
5241
  this.modals.active.handlePaste(event);
4932
5242
  return;
@@ -4943,8 +5253,27 @@ var NativeFrontend = class {
4943
5253
  else if (!this.paused && this.state?.input.enabled !== false) this.editor.focus();
4944
5254
  }
4945
5255
  async copy(text) {
4946
- const value = text ?? this.renderer.getSelection()?.getSelectedText() ?? this.editor.getSelectedText();
4947
- if (value) await this.clipboard.writeText(value, { destination: "best-available" });
5256
+ const value = text ?? (this.renderer.getSelection()?.getSelectedText() || this.editor.getSelectedText());
5257
+ if (!value) return;
5258
+ let message;
5259
+ try {
5260
+ const result = await this.clipboard.writeText(value, { destination: "best-available" });
5261
+ message = result.host.status === "written" ? "COPIED" : result.terminal.status === "attempted" ? "COPY SENT TO TERMINAL" : "COPY UNAVAILABLE";
5262
+ } catch {
5263
+ message = "COPY FAILED";
5264
+ }
5265
+ if (this.destroyed) return;
5266
+ if (this.copyFeedbackTimer) clearTimeout(this.copyFeedbackTimer);
5267
+ this.copyFeedback.content = message;
5268
+ this.copyFeedback.visible = true;
5269
+ this.status.visible = true;
5270
+ this.renderer.requestRender();
5271
+ this.copyFeedbackTimer = setTimeout(() => {
5272
+ this.copyFeedbackTimer = void 0;
5273
+ this.copyFeedback.visible = false;
5274
+ this.status.visible = this.activity.visible || this.media.visible;
5275
+ this.renderer.requestRender();
5276
+ }, 3e3);
4948
5277
  }
4949
5278
  async paste() {
4950
5279
  const result = await this.clipboard.read({ preferredTypes: ["text/plain;charset=utf-8", "text/plain"] });
@@ -4988,6 +5317,7 @@ var NativeFrontend = class {
4988
5317
  this.renderer.setTerminalTitle(command2.text);
4989
5318
  break;
4990
5319
  case "modal-open":
5320
+ this.selectionMenu.close();
4991
5321
  this.modals.open(command2.modal);
4992
5322
  break;
4993
5323
  case "modal-update":
@@ -5071,11 +5401,13 @@ var NativeFrontend = class {
5071
5401
  async dispose() {
5072
5402
  if (this.destroyed) return;
5073
5403
  this.destroyed = true;
5404
+ if (this.copyFeedbackTimer) clearTimeout(this.copyFeedbackTimer);
5405
+ this.selectionMenu.destroy();
5074
5406
  this.disposeSelectionContrast();
5075
5407
  this.renderer.keyInput.off("keypress", this.onKey);
5076
5408
  this.renderer.keyInput.off("paste", this.onPaste);
5077
- this.renderer.off(CliRenderEvents3.FRAME, this.onFrame);
5078
- this.renderer.off(CliRenderEvents3.RESIZE, this.onResize);
5409
+ this.renderer.off(CliRenderEvents4.FRAME, this.onFrame);
5410
+ this.renderer.off(CliRenderEvents4.RESIZE, this.onResize);
5079
5411
  this.modals.destroy();
5080
5412
  this.content.destroy();
5081
5413
  this.tasks.destroy();
@@ -5086,7 +5418,7 @@ var NativeFrontend = class {
5086
5418
  };
5087
5419
 
5088
5420
  // packages/cli/src/tui/native/components/cursor.ts
5089
- import { CliRenderEvents as CliRenderEvents4 } from "@opentui/core";
5421
+ import { CliRenderEvents as CliRenderEvents5 } from "@opentui/core";
5090
5422
  function installTerminalCursorDefaults(renderer2, session) {
5091
5423
  const reset = new TextEncoder().encode("\x1B]112\x07");
5092
5424
  let disposed = false;
@@ -5136,11 +5468,11 @@ function installTerminalCursorDefaults(renderer2, session) {
5136
5468
  const dispose = () => {
5137
5469
  if (disposed) return;
5138
5470
  disposed = true;
5139
- renderer2.off(CliRenderEvents4.FRAME, afterFrame);
5140
- renderer2.off(CliRenderEvents4.DESTROY, dispose);
5471
+ renderer2.off(CliRenderEvents5.FRAME, afterFrame);
5472
+ renderer2.off(CliRenderEvents5.DESTROY, dispose);
5141
5473
  };
5142
- renderer2.on(CliRenderEvents4.FRAME, afterFrame);
5143
- renderer2.once(CliRenderEvents4.DESTROY, dispose);
5474
+ renderer2.on(CliRenderEvents5.FRAME, afterFrame);
5475
+ renderer2.once(CliRenderEvents5.DESTROY, dispose);
5144
5476
  return { restore, dispose };
5145
5477
  }
5146
5478
 
@@ -5216,10 +5548,10 @@ try {
5216
5548
  maxFps: 60,
5217
5549
  clearOnShutdown: false
5218
5550
  });
5219
- renderer.on(CliRenderEvents5.RENDER_ERROR, (event) => {
5551
+ renderer.on(CliRenderEvents6.RENDER_ERROR, (event) => {
5220
5552
  void fail(event.error);
5221
5553
  });
5222
- renderer.on(CliRenderEvents5.HANDLER_ERROR, (event) => {
5554
+ renderer.on(CliRenderEvents6.HANDLER_ERROR, (event) => {
5223
5555
  void fail(event.error);
5224
5556
  });
5225
5557
  renderer.on("error", (event) => {