@mrciphersmith/keryx 0.2.116 → 0.2.118

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 (2) hide show
  1. package/dist/cli.js +582 -279
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -50252,7 +50252,7 @@ import path47 from "path";
50252
50252
  // package.json
50253
50253
  var package_default = {
50254
50254
  name: "@mrciphersmith/keryx",
50255
- version: "0.2.116",
50255
+ version: "0.2.118",
50256
50256
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
50257
50257
  private: false,
50258
50258
  publishConfig: {
@@ -74138,6 +74138,15 @@ function parseGrokToml(file, text) {
74138
74138
  }
74139
74139
  continue;
74140
74140
  }
74141
+ const arrayHeader = /^\[\[([^[\]]+)\]\]$/.exec(line);
74142
+ if (arrayHeader !== null) {
74143
+ const first = arrayHeader[1].split(".")[0]?.trim().replace(/^(["'])(.*)\1$/, "$2");
74144
+ current = undefined;
74145
+ if (first === "mcp_servers") {
74146
+ problems.push({ file, message: `line ${lineNo}: "${line}" is not a table header this reader understands` });
74147
+ }
74148
+ continue;
74149
+ }
74141
74150
  if (line.startsWith("[")) {
74142
74151
  problems.push({ file, message: `line ${lineNo}: "${line}" is not a table header this reader understands` });
74143
74152
  current = undefined;
@@ -82502,8 +82511,6 @@ function realSpawner() {
82502
82511
  env,
82503
82512
  detached: true
82504
82513
  });
82505
- let dataCb;
82506
- let exitCb;
82507
82514
  let netClosed = false;
82508
82515
  const closeNetOnce = async () => {
82509
82516
  if (netClosed)
@@ -82511,43 +82518,81 @@ function realSpawner() {
82511
82518
  netClosed = true;
82512
82519
  await netClose();
82513
82520
  };
82514
- const outDecoder = new TextDecoder;
82515
- const errDecoder = new TextDecoder;
82516
- const pump = async (stream, kind, decoder) => {
82517
- if (stream === undefined)
82518
- return;
82519
- const reader = stream.getReader();
82520
- try {
82521
- for (;; ) {
82522
- const chunk = await reader.read();
82523
- if (chunk.done)
82524
- break;
82525
- if (chunk.value !== undefined)
82526
- dataCb?.(decoder.decode(chunk.value, { stream: true }), kind);
82527
- }
82528
- } catch {} finally {
82529
- reader.releaseLock();
82530
- }
82531
- };
82532
- Promise.all([pump(proc.stdout, "stdout", outDecoder), pump(proc.stderr, "stderr", errDecoder)]);
82533
- proc.exited.then(async (exitCode) => {
82534
- exitCb?.({ exitCode });
82535
- await closeNetOnce();
82536
- });
82537
- return {
82538
- pid: proc.pid,
82539
- onOutput: (cb) => {
82540
- dataCb = cb;
82541
- },
82542
- onExit: (cb) => {
82543
- exitCb = cb;
82544
- },
82521
+ return superviseSpawnedProcess(proc, {
82545
82522
  kill: (signal) => {
82546
82523
  try {
82547
82524
  process.kill(-proc.pid, signal);
82548
82525
  } catch {}
82526
+ },
82527
+ afterExit: closeNetOnce
82528
+ });
82529
+ };
82530
+ }
82531
+ var EXIT_OUTPUT_DRAIN_MS = 2000;
82532
+ function superviseSpawnedProcess(proc, opts) {
82533
+ const drainMs = opts.drainMs ?? EXIT_OUTPUT_DRAIN_MS;
82534
+ let dataCb;
82535
+ let exitCb;
82536
+ const pendingChunks = [];
82537
+ let pendingExit;
82538
+ const emitData = (chunk, stream) => {
82539
+ if (dataCb === undefined)
82540
+ pendingChunks.push({ chunk, stream });
82541
+ else
82542
+ dataCb(chunk, stream);
82543
+ };
82544
+ const outDecoder = new TextDecoder;
82545
+ const errDecoder = new TextDecoder;
82546
+ const pump = async (stream, kind, decoder) => {
82547
+ if (stream === undefined)
82548
+ return;
82549
+ const reader = stream.getReader();
82550
+ try {
82551
+ for (;; ) {
82552
+ const chunk = await reader.read();
82553
+ if (chunk.done)
82554
+ break;
82555
+ if (chunk.value !== undefined)
82556
+ emitData(decoder.decode(chunk.value, { stream: true }), kind);
82549
82557
  }
82550
- };
82558
+ } catch {} finally {
82559
+ reader.releaseLock();
82560
+ }
82561
+ };
82562
+ const drained = Promise.all([
82563
+ pump(proc.stdout, "stdout", outDecoder),
82564
+ pump(proc.stderr, "stderr", errDecoder)
82565
+ ]);
82566
+ proc.exited.then(async (exitCode) => {
82567
+ let timer;
82568
+ await Promise.race([
82569
+ drained,
82570
+ new Promise((resolve) => {
82571
+ timer = setTimeout(resolve, drainMs);
82572
+ timer.unref?.();
82573
+ })
82574
+ ]);
82575
+ if (timer !== undefined)
82576
+ clearTimeout(timer);
82577
+ if (exitCb === undefined)
82578
+ pendingExit = { exitCode };
82579
+ else
82580
+ exitCb({ exitCode });
82581
+ await opts.afterExit?.();
82582
+ });
82583
+ return {
82584
+ pid: proc.pid,
82585
+ onOutput: (cb) => {
82586
+ dataCb = cb;
82587
+ for (const { chunk, stream } of pendingChunks.splice(0))
82588
+ cb(chunk, stream);
82589
+ },
82590
+ onExit: (cb) => {
82591
+ exitCb = cb;
82592
+ if (pendingExit !== undefined)
82593
+ cb(pendingExit);
82594
+ },
82595
+ kill: opts.kill
82551
82596
  };
82552
82597
  }
82553
82598
  function createJobRegistry(options) {
@@ -89158,6 +89203,7 @@ function resolveInitialTab(tabs, initialTab) {
89158
89203
  }
89159
89204
  return first.id;
89160
89205
  }
89206
+ var MODAL_BODY_SCROLLBAR_COLS = 1;
89161
89207
  function innerWidthOf(state) {
89162
89208
  const panelWidth = state.panel.width;
89163
89209
  if (typeof panelWidth === "number" && panelWidth > MODAL_PANEL_CHROME_X) {
@@ -89210,7 +89256,7 @@ function mountTab(state, input, tabId) {
89210
89256
  const size = resolveModalPanelSize(availWidth, state.chrome.renderer.height, input.contentRows);
89211
89257
  const panelWidth = Math.min(availWidth, size.width);
89212
89258
  const cleanup = input.renderTab(tabId, state.body, {
89213
- width: resolveModalInnerWidth(panelWidth),
89259
+ width: resolveModalInnerWidth(panelWidth) - MODAL_BODY_SCROLLBAR_COLS,
89214
89260
  height: modalBodyRows(size.height)
89215
89261
  });
89216
89262
  state.tabCleanup = typeof cleanup === "function" ? cleanup : undefined;
@@ -89387,6 +89433,9 @@ function ensureHost(otui, chrome) {
89387
89433
  if (key.name === "right" && claimArrow("right")) {
89388
89434
  return;
89389
89435
  }
89436
+ if (state.tabs.length < 2 && (key.name === "left" || key.name === "right")) {
89437
+ return;
89438
+ }
89390
89439
  if (key.name === "left" || onStrip && key.name === "tab" && key.shift === true) {
89391
89440
  const prev = idx > 0 ? state.tabs[idx - 1] : undefined;
89392
89441
  if (prev !== undefined) {
@@ -89428,11 +89477,11 @@ function ensureHost(otui, chrome) {
89428
89477
  }
89429
89478
  function makeHandle(state, generation) {
89430
89479
  return {
89431
- close() {
89480
+ close(opts) {
89432
89481
  if (state.generation !== generation) {
89433
89482
  return;
89434
89483
  }
89435
- closeHost(state, { restoreFocus: true, runOnClose: true });
89484
+ closeHost(state, { restoreFocus: opts?.restoreFocus ?? true, runOnClose: true });
89436
89485
  },
89437
89486
  setTab(id) {
89438
89487
  if (state.generation !== generation || !state.open || state.input === undefined) {
@@ -89481,6 +89530,12 @@ function openModal(otui, chrome, input) {
89481
89530
  if (focused === null || !containsNode(state.scroll, focused)) {
89482
89531
  state.tabStrip.focus();
89483
89532
  }
89533
+ const r = chrome.renderer;
89534
+ setTimeout(() => {
89535
+ if (state.generation === generation && state.open) {
89536
+ r.requestRender();
89537
+ }
89538
+ }, 0);
89484
89539
  return makeHandle(state, generation);
89485
89540
  }
89486
89541
 
@@ -90475,7 +90530,7 @@ function classifyBusyDispatch(params) {
90475
90530
  // src/tui/boot-animation.ts
90476
90531
  var DEFAULT_BOOT_DURATION_MS = 350;
90477
90532
  var WORDMARK = ["========================", " K E R Y X ", "========================"];
90478
- var STEPS = ["Reading .metaproject index", "Connecting provider", "Warming graph"];
90533
+ var SPLASH_HINT = "type a message to start \xB7 / for commands";
90479
90534
  async function playBootAnimation(otui, r, opts) {
90480
90535
  if (process.env.KERYX_SKIP_BOOT === "1" || opts.skip === true) {
90481
90536
  return;
@@ -90500,29 +90555,14 @@ async function playBootAnimation(otui, r, opts) {
90500
90555
  for (const [index, line] of WORDMARK.entries()) {
90501
90556
  logoBox.add(new otui.TextRenderable(r, { id: `boot-logo-${index}`, content: otui.t`${otui.bold(line)}` }));
90502
90557
  }
90503
- const statusText = new otui.TextRenderable(r, {
90504
- id: "boot-status",
90505
- content: otui.t`${otui.dim(STEPS[0] ?? "")}`,
90506
- marginTop: 1
90507
- });
90508
- box.add(statusText);
90509
90558
  box.add(new otui.TextRenderable(r, { id: "boot-hint", content: otui.t`${otui.dim("any key to skip")}`, marginTop: 1 }));
90510
90559
  await new Promise((resolve) => {
90511
90560
  let settled = false;
90512
- let stepIndex = 0;
90513
- const stepTimer = setInterval(() => {
90514
- stepIndex = Math.min(stepIndex + 1, STEPS.length - 1);
90515
- const step = STEPS[stepIndex];
90516
- if (step !== undefined) {
90517
- statusText.content = otui.t`${otui.dim(step)}`;
90518
- }
90519
- }, Math.max(1, Math.floor(durationMs / STEPS.length)));
90520
90561
  const finish = () => {
90521
90562
  if (settled) {
90522
90563
  return;
90523
90564
  }
90524
90565
  settled = true;
90525
- clearInterval(stepTimer);
90526
90566
  clearTimeout(doneTimer);
90527
90567
  unsubscribe();
90528
90568
  r.root.remove(box);
@@ -90532,6 +90572,31 @@ async function playBootAnimation(otui, r, opts) {
90532
90572
  const unsubscribe = opts.onKeypress(() => finish());
90533
90573
  });
90534
90574
  }
90575
+ var SHELL_CHROME_ROWS = 8;
90576
+ function mountEmptyTranscriptSplash(otui, r, transcript) {
90577
+ const rows = WORDMARK.length + 2;
90578
+ const paneRows = Math.max(0, r.height - SHELL_CHROME_ROWS);
90579
+ const box = new otui.BoxRenderable(r, {
90580
+ id: "transcript-splash",
90581
+ width: "100%",
90582
+ flexDirection: "column",
90583
+ alignItems: "center",
90584
+ flexShrink: 0,
90585
+ marginTop: Math.max(1, Math.floor((paneRows - rows) / 2))
90586
+ });
90587
+ for (const [index, line] of WORDMARK.entries()) {
90588
+ box.add(new otui.TextRenderable(r, { id: `splash-logo-${index}`, content: otui.t`${otui.bold(line)}` }));
90589
+ }
90590
+ box.add(new otui.TextRenderable(r, { id: "splash-hint", content: otui.t`${otui.dim(SPLASH_HINT)}`, marginTop: 1 }));
90591
+ transcript.add(box);
90592
+ let removed = false;
90593
+ return () => {
90594
+ if (removed)
90595
+ return;
90596
+ removed = true;
90597
+ transcript.remove(box);
90598
+ };
90599
+ }
90535
90600
 
90536
90601
  // src/tui/workspace-inspector.ts
90537
90602
  var WORKSPACE_COMMAND = "/workspace";
@@ -91270,13 +91335,11 @@ async function declineProposalViaShell(run, workspaceId, proposalId) {
91270
91335
 
91271
91336
  // src/tui/mcp-inspector.ts
91272
91337
  var MCP_INSPECTOR_FOOTER = [
91273
- { key: "\u2191/\u2193", label: "select" },
91274
- { key: "click", label: "row: select/act" },
91275
- { key: "c/d", label: "connect/disconnect" },
91276
- { key: "y", label: "confirm" },
91338
+ { key: "\u2191/\u2193", label: "scroll/select" },
91277
91339
  { key: "\u2190/\u2192", label: "tabs" },
91278
91340
  { key: "esc", label: "close" }
91279
91341
  ];
91342
+ var MCP_TAB_KEYS = "keys: c/d connect/disconnect \xB7 y confirm \xB7 click a row to arm, again to confirm";
91280
91343
  var MCP_TOOLS_COMMAND = "/integrations";
91281
91344
  function isMcpToolsCommand(line) {
91282
91345
  return (line.trim().split(/\s+/)[0] ?? "") === MCP_TOOLS_COMMAND;
@@ -91294,10 +91357,87 @@ var RUNTIME_LABELS = {
91294
91357
  function runtimeLabel(id) {
91295
91358
  return RUNTIME_LABELS[id] ?? id;
91296
91359
  }
91297
- function formatToolRowLine(tool) {
91298
- const risk = (tool.risk ?? "read").padEnd(6);
91299
- const name = tool.name.padEnd(28);
91300
- return `${name} ${risk} ${tool.description ?? ""}`.trimEnd();
91360
+ var MIN_HANGING_BUDGET = 24;
91361
+ var FALLBACK_INDENT = 4;
91362
+ function splitLongWord(word, budget) {
91363
+ const pieces = [];
91364
+ let rest = word;
91365
+ while (rest.length > budget) {
91366
+ const slash = rest.lastIndexOf("/", budget);
91367
+ const cut = slash > 0 ? slash : budget;
91368
+ pieces.push(rest.slice(0, cut));
91369
+ rest = rest.slice(cut);
91370
+ }
91371
+ pieces.push(rest);
91372
+ return pieces;
91373
+ }
91374
+ function wrapHangingRow(head, text, width, indent = head.length) {
91375
+ if (width === undefined) {
91376
+ return [`${head}${text}`.trimEnd()];
91377
+ }
91378
+ const hang = width - indent >= MIN_HANGING_BUDGET ? indent : Math.min(indent, FALLBACK_INDENT);
91379
+ const pad = " ".repeat(hang);
91380
+ const budget = Math.max(1, width - hang);
91381
+ const out = [];
91382
+ let current = head;
91383
+ let fresh = true;
91384
+ const units = [];
91385
+ for (const match of text.matchAll(/(\S+)( *)/g)) {
91386
+ const word = match[1];
91387
+ const last = units.at(-1);
91388
+ if (last !== undefined && last.gap === " " && last.word.includes("/") && word.includes("/") && !/^[/~-]/.test(word)) {
91389
+ last.word += ` ${word}`;
91390
+ last.gap = match[2];
91391
+ continue;
91392
+ }
91393
+ units.push({ word, gap: match[2] });
91394
+ }
91395
+ let gap = "";
91396
+ for (const unit of units) {
91397
+ for (const [i, piece] of splitLongWord(unit.word, budget).entries()) {
91398
+ const sep = fresh || i > 0 ? "" : gap;
91399
+ if (current.length + sep.length + piece.length <= width || fresh && current === pad) {
91400
+ current += sep + piece;
91401
+ } else {
91402
+ out.push(current.trimEnd());
91403
+ current = pad + piece;
91404
+ }
91405
+ fresh = false;
91406
+ }
91407
+ gap = unit.gap;
91408
+ }
91409
+ out.push(current.trimEnd());
91410
+ return out;
91411
+ }
91412
+ var TOOL_NAME_COLUMN = 28;
91413
+ var TOOL_APPROVAL_COLUMN = 8;
91414
+ function approvalLabel(risk) {
91415
+ const value = risk ?? "read";
91416
+ return value === "read" ? "none" : value;
91417
+ }
91418
+ var TOOLS_COLUMN_HEADER = `${"tool".padEnd(TOOL_NAME_COLUMN)} ${"approval".padEnd(TOOL_APPROVAL_COLUMN)} description`;
91419
+ function formatToolRowLines(tool, width) {
91420
+ const head = `${tool.name.padEnd(TOOL_NAME_COLUMN)} ${approvalLabel(tool.risk).padEnd(TOOL_APPROVAL_COLUMN)} `;
91421
+ return wrapHangingRow(head, tool.description ?? "", width, TOOL_NAME_COLUMN + TOOL_APPROVAL_COLUMN + 2);
91422
+ }
91423
+ function fitRowsByLines(lineCounts, start, rows) {
91424
+ if (lineCounts.length === 0) {
91425
+ return { start: 0, end: 0 };
91426
+ }
91427
+ let maxStart = lineCounts.length - 1;
91428
+ let tail = lineCounts[maxStart] ?? 1;
91429
+ while (maxStart > 0 && tail + (lineCounts[maxStart - 1] ?? 1) <= rows) {
91430
+ maxStart -= 1;
91431
+ tail += lineCounts[maxStart] ?? 1;
91432
+ }
91433
+ const from = Math.min(maxStart, Math.max(0, start));
91434
+ let end = from + 1;
91435
+ let used = lineCounts[from] ?? 1;
91436
+ while (end < lineCounts.length && used + (lineCounts[end] ?? 1) <= rows) {
91437
+ used += lineCounts[end] ?? 1;
91438
+ end += 1;
91439
+ }
91440
+ return { start: from, end };
91301
91441
  }
91302
91442
  function isActionable(id) {
91303
91443
  return id !== "generic";
@@ -91312,7 +91452,7 @@ function formatOtherServers(otherServers) {
91312
91452
  const list = rest > 0 ? `${shown.join(", ")}, +${rest} more` : shown.join(", ");
91313
91453
  return ` \xB7 also has: ${list}`;
91314
91454
  }
91315
- function formatMcpRowLine(runtime, isSelected, status) {
91455
+ function formatMcpRowLine(runtime, isSelected, status, width) {
91316
91456
  const mark = isSelected ? ">" : " ";
91317
91457
  const label = runtimeLabel(runtime.id).padEnd(20);
91318
91458
  const statusText = runtime.connected ? "\u25CF keryx connected" : "\u25CB keryx not connected";
@@ -91328,7 +91468,8 @@ function formatMcpRowLine(runtime, isSelected, status) {
91328
91468
  } else {
91329
91469
  action = runtime.connected ? " [d] disconnect" : " [c] connect";
91330
91470
  }
91331
- return `${mark} ${label} ${statusText}${action}${formatOtherServers(runtime.otherServers)}`;
91471
+ return wrapHangingRow(`${mark} ${label} `, `${statusText}${action}${formatOtherServers(runtime.otherServers)}`, width).join(`
91472
+ `);
91332
91473
  }
91333
91474
  function asRowTarget(body) {
91334
91475
  const parent = body;
@@ -91352,6 +91493,7 @@ function presentMcpTools(openModal, otui, chrome, options) {
91352
91493
  let rowCtor;
91353
91494
  let activeRenderer;
91354
91495
  let unsubscribeKey;
91496
+ let bodyWidth;
91355
91497
  const rendererHint = options.renderer ?? chrome?.renderer;
91356
91498
  const bodyRows = options.visibleRows ?? (typeof rendererHint?.width === "number" && typeof rendererHint.height === "number" ? modalBodyRows(resolveModalPanelSize(rendererHint.width, rendererHint.height).height) : 13);
91357
91499
  const paintToolsRows = () => {
@@ -91364,11 +91506,23 @@ function presentMcpTools(openModal, otui, chrome, options) {
91364
91506
  toolsBody.add(new rowCtor(activeRenderer, { id: "mcp-tools-empty", content: "No tools available." }));
91365
91507
  return;
91366
91508
  }
91367
- const start = clampScroll3(toolsScroll, options.tools.length, bodyRows);
91368
- for (const [i, tool] of options.tools.slice(start, start + bodyRows).entries()) {
91369
- toolsBody.add(new rowCtor(activeRenderer, { id: `mcp-tool-row-${start + i}`, content: formatToolRowLine(tool) }));
91509
+ toolsBody.add(new rowCtor(activeRenderer, { id: "mcp-tools-columns", content: TOOLS_COLUMN_HEADER }));
91510
+ const rows = options.tools.map((tool) => formatToolRowLines(tool, bodyWidth));
91511
+ const window = toolsWindow(rows);
91512
+ toolsScroll = window.start;
91513
+ for (let index = window.start;index < window.end; index++) {
91514
+ toolsBody.add(new rowCtor(activeRenderer, {
91515
+ id: `mcp-tool-row-${index}`,
91516
+ content: (rows[index] ?? []).join(`
91517
+ `)
91518
+ }));
91370
91519
  }
91371
91520
  };
91521
+ const toolsRowBudget = () => {
91522
+ const captionLines = wrapHangingRow("", TOOLS_TAB_HEADER, bodyWidth, 0).length;
91523
+ return Math.max(1, bodyRows - captionLines - 1);
91524
+ };
91525
+ const toolsWindow = (rows) => fitRowsByLines(rows.map((lines) => lines.length), toolsScroll, toolsRowBudget());
91372
91526
  const paintMcpRows = () => {
91373
91527
  if (mcpBody === undefined || rowCtor === undefined) {
91374
91528
  return;
@@ -91380,18 +91534,19 @@ function presentMcpTools(openModal, otui, chrome, options) {
91380
91534
  mcpBody.add(new rowCtor(activeRenderer, { id: "mcp-mcp-empty", content: "No MCP client runtimes registered." }));
91381
91535
  return;
91382
91536
  }
91537
+ mcpBody.add(new rowCtor(activeRenderer, { id: "mcp-mcp-keys", content: MCP_TAB_KEYS }));
91383
91538
  const start = clampScroll3(mcpScroll, runtimes.length, bodyRows);
91384
91539
  for (const [i, runtime] of runtimes.slice(start, start + bodyRows).entries()) {
91385
91540
  const index = start + i;
91386
91541
  mcpBody.add(new rowCtor(activeRenderer, {
91387
91542
  id: `mcp-row-${runtime.id}`,
91388
- content: formatMcpRowLine(runtime, index === mcpSelected, status),
91543
+ content: formatMcpRowLine(runtime, index === mcpSelected, status, bodyWidth),
91389
91544
  onMouseDown: () => handleRowClick(runtime.id, index)
91390
91545
  }));
91391
91546
  }
91392
91547
  };
91393
91548
  const paint = () => {
91394
- toolsScroll = clampScroll3(toolsScroll, options.tools.length, bodyRows);
91549
+ toolsScroll = Math.max(0, toolsScroll);
91395
91550
  mcpScroll = scrollToReveal3(mcpSelected, mcpScroll, bodyRows);
91396
91551
  mcpScroll = clampScroll3(mcpScroll, runtimes.length, bodyRows);
91397
91552
  paintToolsRows();
@@ -91474,9 +91629,10 @@ function presentMcpTools(openModal, otui, chrome, options) {
91474
91629
  }
91475
91630
  rowCtor = ctor;
91476
91631
  activeRenderer = renderer;
91632
+ bodyWidth = ctx?.width;
91477
91633
  if (tabId === "tools") {
91478
91634
  toolsBody = target;
91479
- toolsScroll = clampScroll3(toolsScroll, options.tools.length, bodyRows);
91635
+ toolsScroll = Math.max(0, toolsScroll);
91480
91636
  paintToolsRows();
91481
91637
  return;
91482
91638
  }
@@ -91524,7 +91680,7 @@ function presentMcpTools(openModal, otui, chrome, options) {
91524
91680
  if (onMcp) {
91525
91681
  moveMcpSelection(mcpSelected - 1);
91526
91682
  } else {
91527
- toolsScroll = clampScroll3(toolsScroll - 1, options.tools.length, bodyRows);
91683
+ toolsScroll = Math.max(0, toolsScroll - 1);
91528
91684
  paint();
91529
91685
  }
91530
91686
  return;
@@ -91533,7 +91689,7 @@ function presentMcpTools(openModal, otui, chrome, options) {
91533
91689
  if (onMcp) {
91534
91690
  moveMcpSelection(mcpSelected + 1);
91535
91691
  } else {
91536
- toolsScroll = clampScroll3(toolsScroll + 1, options.tools.length, bodyRows);
91692
+ toolsScroll += 1;
91537
91693
  paint();
91538
91694
  }
91539
91695
  return;
@@ -91543,7 +91699,7 @@ function presentMcpTools(openModal, otui, chrome, options) {
91543
91699
  if (onMcp) {
91544
91700
  mcpScroll = clampScroll3(mcpScroll + step, runtimes.length, bodyRows);
91545
91701
  } else {
91546
- toolsScroll = clampScroll3(toolsScroll + step, options.tools.length, bodyRows);
91702
+ toolsScroll = Math.max(0, toolsScroll + step);
91547
91703
  }
91548
91704
  paint();
91549
91705
  }
@@ -91556,6 +91712,7 @@ function openMcpTools(otui, chrome, options) {
91556
91712
  }
91557
91713
 
91558
91714
  // src/tui/mcp-consumer.ts
91715
+ import { homedir as homedir9 } from "os";
91559
91716
  var MCP_CONSUMER_COMMAND = "/mcp";
91560
91717
  function isMcpConsumerCommand(line) {
91561
91718
  return (line.trim().split(/\s+/)[0] ?? "") === MCP_CONSUMER_COMMAND;
@@ -91584,12 +91741,21 @@ function credentialNames(server) {
91584
91741
  }
91585
91742
  return [...names].sort();
91586
91743
  }
91587
- function targetOf(server) {
91744
+ var MAX_TARGET_CHARS = 240;
91745
+ function collapseHome2(text, home) {
91746
+ const trimmed = home.replace(/\/+$/, "");
91747
+ if (trimmed === "")
91748
+ return text;
91749
+ const escaped = trimmed.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
91750
+ return text.replace(new RegExp(`(^|[\\s="'])${escaped}(?=/|$|[\\s"'])`, "g"), "$1~");
91751
+ }
91752
+ function targetOf(server, home) {
91588
91753
  const raw = server.raw;
91589
- const target = typeof raw.url === "string" && raw.url.length > 0 ? displayUrl(raw.url) : [raw.command, ...raw.args ?? []].filter(Boolean).join(" ");
91590
- return sanitiseIdentifier(target);
91754
+ const target = typeof raw.url === "string" && raw.url.length > 0 ? displayUrl(raw.url) : collapseHome2([raw.command, ...raw.args ?? []].filter(Boolean).join(" "), home);
91755
+ return sanitiseIdentifier(target, MAX_TARGET_CHARS);
91591
91756
  }
91592
91757
  function buildConsumerModel(input) {
91758
+ const home = input.home ?? homedir9();
91593
91759
  const stateByName = new Map(input.states.map((s) => [s.name, s]));
91594
91760
  const rows = input.configured.map((server) => {
91595
91761
  const state = stateByName.get(server.name);
@@ -91601,7 +91767,7 @@ function buildConsumerModel(input) {
91601
91767
  transport: transportOf(server),
91602
91768
  status,
91603
91769
  toolCount: state?.toolCount ?? 0,
91604
- target: targetOf(server),
91770
+ target: targetOf(server, home),
91605
91771
  credentials: credentialNames(server),
91606
91772
  detail,
91607
91773
  action: status === "needs-approval" ? `keryx mcp trust ${server.name}` : status === "needs_auth" ? "set the variable named above, then restart the session" : undefined
@@ -91609,10 +91775,10 @@ function buildConsumerModel(input) {
91609
91775
  });
91610
91776
  return {
91611
91777
  rows,
91612
- problems: input.problems.map((p) => `${p.file}: ${sanitiseForDisplay(p.message)}`),
91778
+ problems: input.problems.map((p) => `${collapseHome2(p.file, home)}: ${sanitiseForDisplay(p.message)}`),
91613
91779
  emptyHint: rows.length > 0 ? undefined : `No MCP servers configured.
91614
- user: ${input.userFile}
91615
- project: ${input.projectFile}`
91780
+ user: ${collapseHome2(input.userFile, home)}
91781
+ project: ${collapseHome2(input.projectFile, home)}`
91616
91782
  };
91617
91783
  }
91618
91784
  var MCP_CONSUMER_FOOTER = [
@@ -91641,7 +91807,7 @@ function consumerActionFor(status) {
91641
91807
  function statusGlyph(status) {
91642
91808
  return STATUS_GLYPH[status] ?? status;
91643
91809
  }
91644
- function formatConsumerModalRow(row, isSelected, status) {
91810
+ function formatConsumerModalRow(row, isSelected, status, width) {
91645
91811
  const mark = isSelected ? ">" : " ";
91646
91812
  const name = row.name.padEnd(18);
91647
91813
  const source = row.source.padEnd(10);
@@ -91666,7 +91832,8 @@ function formatConsumerModalRow(row, isSelected, status) {
91666
91832
  action = "";
91667
91833
  }
91668
91834
  const creds = row.credentials.length > 0 ? ` [reads ${row.credentials.join(", ")}]` : "";
91669
- return `${mark} ${name} ${source} ${transport} ${glyph}${tools}${action} ${row.target}${creds}`.trimEnd();
91835
+ return wrapHangingRow(`${mark} ${name} ${source} ${transport} ${glyph}`, `${tools}${action} ${row.target}${creds}`, width).join(`
91836
+ `);
91670
91837
  }
91671
91838
  function formatConsumerModalLines(model, selected, status) {
91672
91839
  if (model === undefined) {
@@ -91707,6 +91874,7 @@ function presentMcpConsumer(openModalFn, otui, chrome, options) {
91707
91874
  let rowCtor;
91708
91875
  let activeRenderer;
91709
91876
  let unsubscribeKey;
91877
+ let bodyWidth;
91710
91878
  const rendererHint = options.renderer ?? chrome?.renderer;
91711
91879
  const bodyRows = options.visibleRows ?? (typeof rendererHint?.width === "number" && typeof rendererHint.height === "number" ? modalBodyRows(resolveModalPanelSize(rendererHint.width, rendererHint.height).height) : 13);
91712
91880
  const rowsOf = () => options.snapshot()?.rows ?? [];
@@ -91739,7 +91907,7 @@ function presentMcpConsumer(openModalFn, otui, chrome, options) {
91739
91907
  const index = start + i;
91740
91908
  body.add(new rowCtor(activeRenderer, {
91741
91909
  id: `mcp-consumer-row-${row.name}`,
91742
- content: formatConsumerModalRow(row, index === selected, status),
91910
+ content: formatConsumerModalRow(row, index === selected, status, bodyWidth),
91743
91911
  onMouseDown: () => handleRowClick(row.name, index)
91744
91912
  }));
91745
91913
  }
@@ -91810,6 +91978,7 @@ function presentMcpConsumer(openModalFn, otui, chrome, options) {
91810
91978
  rowCtor = ctor;
91811
91979
  activeRenderer = renderer;
91812
91980
  body = target;
91981
+ bodyWidth = ctx?.width;
91813
91982
  paint();
91814
91983
  },
91815
91984
  onClose: () => {
@@ -97618,30 +97787,31 @@ var CUSTOM_ADD_ENTRY = {
97618
97787
  label: "\uFF0B Add custom provider (OpenAI-compatible)",
97619
97788
  note: "name \xB7 URL \xB7 key \xB7 models \u2192 saved to llm-providers.json"
97620
97789
  };
97621
- function promptCustomFieldStep(otui, r, opts) {
97790
+ function promptCustomFieldStep(otui, target, opts) {
97791
+ const r = stepRenderer(target);
97622
97792
  return new Promise((resolve) => {
97623
- const box = overlayBox(otui, r, "custom-field-picker");
97624
- r.root.add(box);
97625
- box.add(new otui.TextRenderable(r, { id: "cf-title", content: otui.t`${otui.bold(opts.title)} ${otui.dim("(Enter \xB7 Esc to go back)")}` }));
97793
+ const surface = openStepSurface(otui, target, {
97794
+ id: "custom-field-picker",
97795
+ title: opts.title,
97796
+ tab: "Custom provider",
97797
+ hint: "(Enter \xB7 Esc to go back)",
97798
+ footer: inputStepFooter("back"),
97799
+ contentRows: opts.note !== undefined ? 4 : 2,
97800
+ onEscape: () => {
97801
+ input.blur();
97802
+ resolve({ kind: "back" });
97803
+ }
97804
+ });
97626
97805
  if (opts.note !== undefined) {
97627
- box.add(new otui.TextRenderable(r, { id: "cf-note", content: otui.t`${otui.dim(opts.note)}`, marginTop: 1 }));
97806
+ surface.body.add(new otui.TextRenderable(r, { id: "cf-note", content: otui.t`${otui.dim(opts.note)}`, marginTop: 1 }));
97628
97807
  }
97629
97808
  const input = new otui.InputRenderable(r, { id: "cf-input", value: opts.value ?? "", marginTop: 1 });
97630
- box.add(input);
97809
+ surface.body.add(input);
97631
97810
  input.focus();
97632
97811
  const cleanup = () => {
97633
- unsub();
97634
97812
  input.blur();
97635
- r.root.remove(box);
97813
+ surface.close();
97636
97814
  };
97637
- const unsub = onKeypress4(r, (key) => {
97638
- if (key.name === "escape") {
97639
- cleanup();
97640
- resolve({ kind: "back" });
97641
- key.preventDefault();
97642
- key.stopPropagation();
97643
- }
97644
- });
97645
97815
  input.on(otui.InputRenderableEvents.ENTER, () => {
97646
97816
  const entered = input.value.trim();
97647
97817
  cleanup();
@@ -97661,9 +97831,9 @@ function parseOptionalPositiveWizardNumber(raw) {
97661
97831
  const parsed = parseOptionalWizardNumber(raw);
97662
97832
  return parsed !== undefined && parsed > 0 ? parsed : undefined;
97663
97833
  }
97664
- async function promptCustomProviderWizard(otui, r) {
97834
+ async function promptCustomProviderWizard(otui, target) {
97665
97835
  while (true) {
97666
- const nameStep = await promptCustomFieldStep(otui, r, {
97836
+ const nameStep = await promptCustomFieldStep(otui, target, {
97667
97837
  title: "Provider name",
97668
97838
  note: "unique id used in /provider (letters, digits, - _)"
97669
97839
  });
@@ -97675,7 +97845,7 @@ async function promptCustomProviderWizard(otui, r) {
97675
97845
  continue;
97676
97846
  }
97677
97847
  while (true) {
97678
- const urlStep = await promptCustomFieldStep(otui, r, {
97848
+ const urlStep = await promptCustomFieldStep(otui, target, {
97679
97849
  title: "Base URL",
97680
97850
  note: "before the chat path \u2014 /v1/chat/completions is appended, e.g. http://10.110.43.19:8080",
97681
97851
  value: "http://localhost:8000"
@@ -97697,7 +97867,7 @@ async function promptCustomProviderWizard(otui, r) {
97697
97867
  continue;
97698
97868
  }
97699
97869
  const baseUrl = baseUrlRaw.replace(/\/+$/, "");
97700
- const keyStep = await promptCustomFieldStep(otui, r, {
97870
+ const keyStep = await promptCustomFieldStep(otui, target, {
97701
97871
  title: "API key (optional)",
97702
97872
  note: "empty = no key \xB7 saved owner-only (0600) in your keryx config dir"
97703
97873
  });
@@ -97705,7 +97875,7 @@ async function promptCustomProviderWizard(otui, r) {
97705
97875
  continue;
97706
97876
  }
97707
97877
  const apiKey = keyStep.value.length > 0 ? keyStep.value : undefined;
97708
- const modelsStep = await promptCustomFieldStep(otui, r, {
97878
+ const modelsStep = await promptCustomFieldStep(otui, target, {
97709
97879
  title: "Models (optional)",
97710
97880
  note: "comma-separated ids \xB7 empty = fetch the live /v1/models list"
97711
97881
  });
@@ -97713,7 +97883,7 @@ async function promptCustomProviderWizard(otui, r) {
97713
97883
  continue;
97714
97884
  }
97715
97885
  const models = modelsStep.value.split(",").map((m) => m.trim()).filter((m) => m.length > 0);
97716
- const temperatureStep = await promptCustomFieldStep(otui, r, {
97886
+ const temperatureStep = await promptCustomFieldStep(otui, target, {
97717
97887
  title: "Temperature (optional)",
97718
97888
  note: "empty = provider default \xB7 e.g. 0.2"
97719
97889
  });
@@ -97721,7 +97891,7 @@ async function promptCustomProviderWizard(otui, r) {
97721
97891
  continue;
97722
97892
  }
97723
97893
  const temperature = parseOptionalWizardNumber(temperatureStep.value);
97724
- const maxOutputTokensStep = await promptCustomFieldStep(otui, r, {
97894
+ const maxOutputTokensStep = await promptCustomFieldStep(otui, target, {
97725
97895
  title: "Max output tokens (optional)",
97726
97896
  note: "empty = default 1024 \xB7 e.g. 4096"
97727
97897
  });
@@ -97729,7 +97899,7 @@ async function promptCustomProviderWizard(otui, r) {
97729
97899
  continue;
97730
97900
  }
97731
97901
  const maxOutputTokens = parseOptionalPositiveWizardNumber(maxOutputTokensStep.value);
97732
- const timeoutMsStep = await promptCustomFieldStep(otui, r, {
97902
+ const timeoutMsStep = await promptCustomFieldStep(otui, target, {
97733
97903
  title: "Request timeout ms (optional)",
97734
97904
  note: "empty = no engine-internal timeout \xB7 e.g. 180000"
97735
97905
  });
@@ -98084,6 +98254,78 @@ function onKeypress4(r, handler) {
98084
98254
  r._internalKeyInput.onInternal("keypress", handler);
98085
98255
  return () => r._internalKeyInput.offInternal("keypress", handler);
98086
98256
  }
98257
+ function isModalChrome(target) {
98258
+ return target !== null && typeof target === "object" && "renderer" in target && "focusComposer" in target;
98259
+ }
98260
+ function stepRenderer(target) {
98261
+ return isModalChrome(target) ? target.renderer : target;
98262
+ }
98263
+ function openStepSurface(otui, target, opts) {
98264
+ const r = stepRenderer(target);
98265
+ if (!isModalChrome(target)) {
98266
+ const box = overlayBox(otui, r, opts.id);
98267
+ r.root.add(box);
98268
+ box.add(new otui.TextRenderable(r, { id: `${opts.id}-title`, content: otui.t`${otui.bold(opts.title)} ${otui.dim(opts.hint)}` }));
98269
+ let closed = false;
98270
+ const close = () => {
98271
+ if (closed)
98272
+ return;
98273
+ closed = true;
98274
+ unsub();
98275
+ r.root.remove(box);
98276
+ };
98277
+ const unsub = onKeypress4(r, (key) => {
98278
+ if (key.name === "escape") {
98279
+ close();
98280
+ opts.onEscape();
98281
+ key.preventDefault();
98282
+ key.stopPropagation();
98283
+ }
98284
+ });
98285
+ return { body: box, modal: false, close };
98286
+ }
98287
+ let body;
98288
+ let closedByStep = false;
98289
+ const handle = openModal(otui, target, {
98290
+ title: opts.title,
98291
+ tabs: [{ id: opts.id, label: opts.tab }],
98292
+ footer: opts.footer,
98293
+ contentRows: opts.contentRows,
98294
+ renderTab: (_tabId, tabBody) => {
98295
+ body = tabBody;
98296
+ },
98297
+ onClose: () => {
98298
+ if (!closedByStep)
98299
+ opts.onEscape();
98300
+ }
98301
+ });
98302
+ if (handle === undefined || body === undefined) {
98303
+ return openStepSurface(otui, r, opts);
98304
+ }
98305
+ return {
98306
+ body,
98307
+ modal: true,
98308
+ close: () => {
98309
+ if (closedByStep)
98310
+ return;
98311
+ closedByStep = true;
98312
+ handle.close({ restoreFocus: false });
98313
+ }
98314
+ };
98315
+ }
98316
+ function selectStepFooter(escLabel) {
98317
+ return [
98318
+ { key: "\u2191/\u2193", label: "select" },
98319
+ { key: "Enter", label: "confirm" },
98320
+ { key: "esc", label: escLabel }
98321
+ ];
98322
+ }
98323
+ function inputStepFooter(escLabel) {
98324
+ return [
98325
+ { key: "Enter", label: "confirm" },
98326
+ { key: "esc", label: escLabel }
98327
+ ];
98328
+ }
98087
98329
  function parseSearchProviderArgs(line) {
98088
98330
  const parts = line.trim().length === 0 ? [] : line.trim().split(/\s+/);
98089
98331
  const [providerId, ...tail] = parts;
@@ -98130,27 +98372,28 @@ async function selectSearchProviderAndReport(controller, onSystem, providerId) {
98130
98372
  onSystem?.(`Search provider '${providerId}' selected.
98131
98373
  `);
98132
98374
  }
98133
- function promptSearchFieldStep(otui, r, field, value) {
98375
+ function promptSearchFieldStep(otui, target, field, value) {
98376
+ const r = stepRenderer(target);
98134
98377
  return new Promise((resolve) => {
98135
- const box = overlayBox(otui, r, "search-field-picker");
98136
- r.root.add(box);
98137
- box.add(new otui.TextRenderable(r, { id: "sf-title", content: otui.t`${otui.bold(field.label)} ${otui.dim(`${field.required ? "required" : "optional"} \xB7 Enter \xB7 Esc to go back`)}` }));
98378
+ const surface = openStepSurface(otui, target, {
98379
+ id: "search-field-picker",
98380
+ title: field.label,
98381
+ tab: field.required ? "Required" : "Optional",
98382
+ hint: `${field.required ? "required" : "optional"} \xB7 Enter \xB7 Esc to go back`,
98383
+ footer: inputStepFooter("back"),
98384
+ contentRows: 2,
98385
+ onEscape: () => {
98386
+ input.blur();
98387
+ resolve({ kind: "back" });
98388
+ }
98389
+ });
98138
98390
  const input = new otui.InputRenderable(r, { id: "sf-input", value, marginTop: 1 });
98139
- box.add(input);
98391
+ surface.body.add(input);
98140
98392
  input.focus();
98141
98393
  const cleanup = () => {
98142
- unsub();
98143
98394
  input.blur();
98144
- r.root.remove(box);
98395
+ surface.close();
98145
98396
  };
98146
- const unsub = onKeypress4(r, (key) => {
98147
- if (key.name === "escape") {
98148
- cleanup();
98149
- resolve({ kind: "back" });
98150
- key.preventDefault();
98151
- key.stopPropagation();
98152
- }
98153
- });
98154
98397
  input.on(otui.InputRenderableEvents.ENTER, () => {
98155
98398
  const entered = input.value.trim();
98156
98399
  cleanup();
@@ -98158,32 +98401,33 @@ function promptSearchFieldStep(otui, r, field, value) {
98158
98401
  });
98159
98402
  });
98160
98403
  }
98161
- function promptSearchCredentialStep(otui, r, opts) {
98404
+ function promptSearchCredentialStep(otui, target, opts) {
98405
+ const r = stepRenderer(target);
98162
98406
  return new Promise((resolve) => {
98163
- const box = overlayBox(otui, r, "search-credential-picker");
98164
- r.root.add(box);
98165
- box.add(new otui.TextRenderable(r, { id: "sc-title", content: otui.t`${otui.bold(`Paste your ${opts.label}`)} ${otui.dim("(Enter \xB7 Esc to go back)")}` }));
98166
- box.add(new otui.TextRenderable(r, {
98407
+ const surface = openStepSurface(otui, target, {
98408
+ id: "search-credential-picker",
98409
+ title: `Paste your ${opts.label}`,
98410
+ tab: "Credential",
98411
+ hint: "(Enter \xB7 Esc to go back)",
98412
+ footer: inputStepFooter("back"),
98413
+ contentRows: 4,
98414
+ onEscape: () => {
98415
+ keyInput.blur();
98416
+ resolve({ kind: "back" });
98417
+ }
98418
+ });
98419
+ surface.body.add(new otui.TextRenderable(r, {
98167
98420
  id: "sc-note",
98168
98421
  content: otui.t`${otui.dim("Saved to your keryx config dir (owner-only, 0600)")}`,
98169
98422
  marginTop: 1
98170
98423
  }));
98171
98424
  const keyInput = new otui.InputRenderable(r, { id: "sc-input", placeholder: "...", marginTop: 1 });
98172
- box.add(keyInput);
98425
+ surface.body.add(keyInput);
98173
98426
  keyInput.focus();
98174
98427
  const cleanup = () => {
98175
- unsub();
98176
98428
  keyInput.blur();
98177
- r.root.remove(box);
98429
+ surface.close();
98178
98430
  };
98179
- const unsub = onKeypress4(r, (key) => {
98180
- if (key.name === "escape") {
98181
- cleanup();
98182
- resolve({ kind: "back" });
98183
- key.preventDefault();
98184
- key.stopPropagation();
98185
- }
98186
- });
98187
98431
  keyInput.on(otui.InputRenderableEvents.ENTER, () => {
98188
98432
  const value = keyInput.value.trim();
98189
98433
  cleanup();
@@ -98191,14 +98435,24 @@ function promptSearchCredentialStep(otui, r, opts) {
98191
98435
  });
98192
98436
  });
98193
98437
  }
98194
- function promptSetActiveProviderStep(otui, r) {
98438
+ function promptSetActiveProviderStep(otui, target) {
98439
+ const r = stepRenderer(target);
98195
98440
  return new Promise((resolve) => {
98196
- const box = overlayBox(otui, r, "search-active-picker");
98197
- r.root.add(box);
98198
- box.add(new otui.TextRenderable(r, { id: "sa-title", content: otui.t`${otui.bold("Set as active provider after a successful test?")} ${otui.dim("(\u2191/\u2193, Enter \xB7 Esc to go back)")}` }));
98441
+ const surface = openStepSurface(otui, target, {
98442
+ id: "search-active-picker",
98443
+ title: "Set as active provider after a successful test?",
98444
+ tab: "Active",
98445
+ hint: "(\u2191/\u2193, Enter \xB7 Esc to go back)",
98446
+ footer: selectStepFooter("back"),
98447
+ contentRows: selectBoxHeight(2, true),
98448
+ onEscape: () => {
98449
+ select.blur();
98450
+ resolve(undefined);
98451
+ }
98452
+ });
98199
98453
  const select = new otui.SelectRenderable(r, {
98200
98454
  id: "sa-select",
98201
- width: 60,
98455
+ width: surface.modal ? "100%" : 60,
98202
98456
  height: selectBoxHeight(2, true),
98203
98457
  options: [
98204
98458
  { name: "Yes", description: "select it once the test passes" },
@@ -98206,21 +98460,12 @@ function promptSetActiveProviderStep(otui, r) {
98206
98460
  ],
98207
98461
  selectedTextColor: "#ffd166"
98208
98462
  });
98209
- box.add(select);
98463
+ surface.body.add(select);
98210
98464
  select.focus();
98211
98465
  const cleanup = () => {
98212
- unsub();
98213
98466
  select.blur();
98214
- r.root.remove(box);
98467
+ surface.close();
98215
98468
  };
98216
- const unsub = onKeypress4(r, (key) => {
98217
- if (key.name === "escape") {
98218
- cleanup();
98219
- resolve(undefined);
98220
- key.preventDefault();
98221
- key.stopPropagation();
98222
- }
98223
- });
98224
98469
  select.on(otui.SelectRenderableEvents.ITEM_SELECTED, () => {
98225
98470
  const chosen = select.getSelectedOption();
98226
98471
  cleanup();
@@ -98228,35 +98473,36 @@ function promptSetActiveProviderStep(otui, r) {
98228
98473
  });
98229
98474
  });
98230
98475
  }
98231
- function pickSearchProviderStep(otui, r, providers) {
98476
+ function pickSearchProviderStep(otui, target, providers) {
98477
+ const r = stepRenderer(target);
98232
98478
  return new Promise((resolve) => {
98233
- const box = overlayBox(otui, r, "search-provider-picker");
98234
- r.root.add(box);
98235
- box.add(new otui.TextRenderable(r, { id: "spp-title", content: otui.t`${otui.bold("Select a search provider")} ${otui.dim("(\u2191/\u2193, Enter \xB7 Esc to cancel)")}` }));
98479
+ const surface = openStepSurface(otui, target, {
98480
+ id: "search-provider-picker",
98481
+ title: "Select a search provider",
98482
+ tab: "Search providers",
98483
+ hint: "(\u2191/\u2193, Enter \xB7 Esc to cancel)",
98484
+ footer: selectStepFooter("cancel"),
98485
+ contentRows: selectBoxHeight(providers.length, true),
98486
+ onEscape: () => {
98487
+ select.blur();
98488
+ resolve(undefined);
98489
+ }
98490
+ });
98236
98491
  const labelOf = (p) => `${p.id} (${p.displayName})`;
98237
98492
  const select = new otui.SelectRenderable(r, {
98238
98493
  id: "spp-select",
98239
- width: 60,
98494
+ width: surface.modal ? "100%" : 60,
98240
98495
  height: selectBoxHeight(providers.length, true),
98241
98496
  showScrollIndicator: true,
98242
98497
  options: providers.map((p) => ({ name: labelOf(p), description: p.kind })),
98243
98498
  selectedTextColor: "#ffd166"
98244
98499
  });
98245
- box.add(select);
98500
+ surface.body.add(select);
98246
98501
  select.focus();
98247
98502
  const cleanup = () => {
98248
- unsub();
98249
98503
  select.blur();
98250
- r.root.remove(box);
98504
+ surface.close();
98251
98505
  };
98252
- const unsub = onKeypress4(r, (key) => {
98253
- if (key.name === "escape") {
98254
- cleanup();
98255
- resolve(undefined);
98256
- key.preventDefault();
98257
- key.stopPropagation();
98258
- }
98259
- });
98260
98506
  select.on(otui.SelectRenderableEvents.ITEM_SELECTED, () => {
98261
98507
  const chosen = select.getSelectedOption();
98262
98508
  cleanup();
@@ -98316,19 +98562,46 @@ async function runSearchProviderFieldsStep(otui, r, provider, seed) {
98316
98562
  }
98317
98563
  return { kind: "done", fields, credential, setActive };
98318
98564
  }
98319
- function runSearchProviderTestStep(otui, r, controller, provider, fields, credential, setActive) {
98565
+ function runSearchProviderTestStep(otui, target, controller, provider, fields, credential, setActive) {
98566
+ const r = stepRenderer(target);
98320
98567
  return new Promise((resolve) => {
98321
- const box = overlayBox(otui, r, "search-test-picker");
98322
- r.root.add(box);
98568
+ let settled;
98569
+ let left = false;
98570
+ const modal = isModalChrome(target);
98571
+ let surface;
98572
+ if (modal) {
98573
+ surface = openStepSurface(otui, target, {
98574
+ id: "search-test-picker",
98575
+ title: `Test '${provider.id}'`,
98576
+ tab: "Test",
98577
+ hint: "",
98578
+ footer: [
98579
+ { key: "Enter", label: "close after success" },
98580
+ { key: "esc", label: "back" }
98581
+ ],
98582
+ contentRows: 2,
98583
+ onEscape: () => {
98584
+ left = true;
98585
+ unsub();
98586
+ resolve(settled === "success" ? "done" : "retry");
98587
+ }
98588
+ });
98589
+ }
98590
+ const box = surface?.body ?? overlayBox(otui, r, "search-test-picker");
98591
+ if (surface === undefined)
98592
+ r.root.add(box);
98323
98593
  const status = new otui.TextRenderable(r, { id: "st-title", content: otui.t`${otui.bold(`Testing '${provider.id}'`)} ${otui.dim("...")}` });
98324
98594
  box.add(status);
98325
- let settled;
98326
98595
  const cleanup = () => {
98596
+ left = true;
98327
98597
  unsub();
98328
- r.root.remove(box);
98598
+ if (surface !== undefined)
98599
+ surface.close();
98600
+ else
98601
+ r.root.remove(box);
98329
98602
  };
98330
98603
  const unsub = onKeypress4(r, (key) => {
98331
- if (settled === "failure" && key.name === "escape") {
98604
+ if (!modal && settled === "failure" && key.name === "escape") {
98332
98605
  cleanup();
98333
98606
  resolve("retry");
98334
98607
  key.preventDefault();
@@ -98343,6 +98616,9 @@ function runSearchProviderTestStep(otui, r, controller, provider, fields, creden
98343
98616
  (async () => {
98344
98617
  controller.configure(provider.id, { ...provider.defaults, ...fields }, credential);
98345
98618
  const tested = await controller.test(provider.id);
98619
+ if (left) {
98620
+ return;
98621
+ }
98346
98622
  if (!tested.ok) {
98347
98623
  settled = "failure";
98348
98624
  const reason = tested.reason === "missing-credential" ? "missing credential" : "connection validation failed";
@@ -98355,6 +98631,9 @@ function runSearchProviderTestStep(otui, r, controller, provider, fields, creden
98355
98631
  return;
98356
98632
  }
98357
98633
  const selected = await controller.select(provider.id);
98634
+ if (left) {
98635
+ return;
98636
+ }
98358
98637
  status.content = selected.ok ? otui.t`${otui.green("\u2713")} ${otui.bold(`'${provider.id}' configured, tested, and set as active`)} ${otui.dim("(Enter to close)")}` : otui.t`${otui.green("\u2713")} ${otui.bold(`'${provider.id}' configured and tested`)} ${otui.dim(`but could not be set active (${selected.reason ?? "unknown"})`)} ${otui.dim("(Enter to close)")}`;
98359
98638
  })();
98360
98639
  });
@@ -98381,11 +98660,22 @@ async function searchProviderWizardInTui(otui, r, controller) {
98381
98660
  }
98382
98661
  }
98383
98662
  }
98384
- function promptBaseUrlStep(otui, r, label, baseUrl, notice) {
98663
+ function promptBaseUrlStep(otui, target, label, baseUrl, notice) {
98664
+ const r = stepRenderer(target);
98385
98665
  return new Promise((resolve) => {
98386
- const box = overlayBox(otui, r, "base-url-picker");
98387
- r.root.add(box);
98388
- box.add(new otui.TextRenderable(r, { id: "bp-title", content: otui.t`${otui.bold(`${label} endpoint URL`)} ${otui.dim("(Enter \xB7 Esc to go back)")}` }));
98666
+ const surface = openStepSurface(otui, target, {
98667
+ id: "base-url-picker",
98668
+ title: `${label} endpoint URL`,
98669
+ tab: "Endpoint",
98670
+ hint: "(Enter \xB7 Esc to go back)",
98671
+ footer: inputStepFooter("back"),
98672
+ contentRows: notice !== undefined ? 6 : 4,
98673
+ onEscape: () => {
98674
+ input.blur();
98675
+ resolve(undefined);
98676
+ }
98677
+ });
98678
+ const box = surface.body;
98389
98679
  if (notice !== undefined) {
98390
98680
  box.add(new otui.TextRenderable(r, { id: "bp-notice", content: otui.t`${otui.red("\u2717")} ${notice}`, marginTop: 1 }));
98391
98681
  }
@@ -98394,17 +98684,9 @@ function promptBaseUrlStep(otui, r, label, baseUrl, notice) {
98394
98684
  box.add(input);
98395
98685
  input.focus();
98396
98686
  const cleanup = () => {
98397
- unsub();
98398
- r.root.remove(box);
98687
+ input.blur();
98688
+ surface.close();
98399
98689
  };
98400
- const unsub = onKeypress4(r, (key) => {
98401
- if (key.name === "escape") {
98402
- cleanup();
98403
- resolve(undefined);
98404
- key.preventDefault();
98405
- key.stopPropagation();
98406
- }
98407
- });
98408
98690
  input.on(otui.InputRenderableEvents.ENTER, () => {
98409
98691
  const value = input.value.trim();
98410
98692
  cleanup();
@@ -98412,11 +98694,25 @@ function promptBaseUrlStep(otui, r, label, baseUrl, notice) {
98412
98694
  });
98413
98695
  });
98414
98696
  }
98415
- function promptApiKeyStep(otui, r, opts) {
98697
+ function promptApiKeyStep(otui, target, opts) {
98698
+ const r = stepRenderer(target);
98416
98699
  return new Promise((resolve) => {
98417
- const box = overlayBox(otui, r, "key-picker");
98418
- r.root.add(box);
98419
- box.add(new otui.TextRenderable(r, { id: "kp-title", content: otui.t`${otui.bold(`Paste your ${opts.label} API key`)} ${otui.dim("(Enter \xB7 Esc to go back)")}` }));
98700
+ const surface = openStepSurface(otui, target, {
98701
+ id: "key-picker",
98702
+ title: `Paste your ${opts.label} API key`,
98703
+ tab: "API key",
98704
+ hint: "(Enter \xB7 Esc to go back)",
98705
+ footer: [
98706
+ { key: "Enter", label: "save (empty = skip)" },
98707
+ { key: "esc", label: "back" }
98708
+ ],
98709
+ contentRows: opts.notice !== undefined ? 6 : 4,
98710
+ onEscape: () => {
98711
+ keyInput.blur();
98712
+ resolve({ kind: "back" });
98713
+ }
98714
+ });
98715
+ const box = surface.body;
98420
98716
  if (opts.notice !== undefined) {
98421
98717
  box.add(new otui.TextRenderable(r, { id: "kp-notice", content: otui.t`${otui.red("\u2717")} ${opts.notice}`, marginTop: 1 }));
98422
98718
  }
@@ -98428,18 +98724,9 @@ function promptApiKeyStep(otui, r, opts) {
98428
98724
  const keyInput = new otui.InputRenderable(r, { id: "kp-input", placeholder: opts.placeholder ?? "sk-...", marginTop: 1 });
98429
98725
  box.add(keyInput);
98430
98726
  keyInput.focus();
98431
- const onKey = (key) => {
98432
- if (key.name === "escape") {
98433
- cleanup();
98434
- resolve({ kind: "back" });
98435
- key.preventDefault();
98436
- key.stopPropagation();
98437
- }
98438
- };
98439
- const unsub = onKeypress4(r, onKey);
98440
98727
  const cleanup = () => {
98441
- unsub();
98442
- r.root.remove(box);
98728
+ keyInput.blur();
98729
+ surface.close();
98443
98730
  };
98444
98731
  keyInput.on(otui.InputRenderableEvents.ENTER, () => {
98445
98732
  const value = keyInput.value.trim();
@@ -98448,38 +98735,39 @@ function promptApiKeyStep(otui, r, opts) {
98448
98735
  });
98449
98736
  });
98450
98737
  }
98451
- function pickAuthMethodStep(otui, r, providerLabel, methods) {
98738
+ function pickAuthMethodStep(otui, target, providerLabel, methods) {
98739
+ const r = stepRenderer(target);
98452
98740
  return new Promise((resolve) => {
98453
- const box = overlayBox(otui, r, "auth-method-picker");
98454
- r.root.add(box);
98455
- box.add(new otui.TextRenderable(r, { id: "amp-title", content: otui.t`${otui.bold(`How to connect ${providerLabel}`)} ${otui.dim("(\u2191/\u2193, Enter \xB7 Esc to go back)")}` }));
98741
+ const surface = openStepSurface(otui, target, {
98742
+ id: "auth-method-picker",
98743
+ title: `How to connect ${providerLabel}`,
98744
+ tab: "Sign-in",
98745
+ hint: "(\u2191/\u2193, Enter \xB7 Esc to go back)",
98746
+ footer: selectStepFooter("back"),
98747
+ contentRows: selectBoxHeight(methods.length, true),
98748
+ onEscape: () => {
98749
+ select.blur();
98750
+ resolve(undefined);
98751
+ }
98752
+ });
98456
98753
  const descriptions = {
98457
98754
  "device-code": deviceCodeMethodLabel(providerLabel === "GitHub Copilot" ? "github-copilot" : providerLabel === "OpenAI" ? "openai" : "grok"),
98458
98755
  "api-key": "Manually enter API Key"
98459
98756
  };
98460
98757
  const select = new otui.SelectRenderable(r, {
98461
98758
  id: "amp-select",
98462
- width: 60,
98759
+ width: surface.modal ? "100%" : 60,
98463
98760
  height: selectBoxHeight(methods.length, true),
98464
98761
  showScrollIndicator: true,
98465
98762
  options: methods.map((m) => ({ name: m, description: descriptions[m] })),
98466
98763
  selectedTextColor: "#ffd166"
98467
98764
  });
98468
- box.add(select);
98765
+ surface.body.add(select);
98469
98766
  select.focus();
98470
98767
  const cleanup = () => {
98471
- unsub();
98472
98768
  select.blur();
98473
- r.root.remove(box);
98769
+ surface.close();
98474
98770
  };
98475
- const unsub = onKeypress4(r, (key) => {
98476
- if (key.name === "escape") {
98477
- cleanup();
98478
- resolve(undefined);
98479
- key.preventDefault();
98480
- key.stopPropagation();
98481
- }
98482
- });
98483
98771
  select.on(otui.SelectRenderableEvents.ITEM_SELECTED, () => {
98484
98772
  const chosen = select.getSelectedOption();
98485
98773
  cleanup();
@@ -98487,30 +98775,27 @@ function pickAuthMethodStep(otui, r, providerLabel, methods) {
98487
98775
  });
98488
98776
  });
98489
98777
  }
98490
- function runDeviceLoginInTui(otui, r, provider, dir) {
98778
+ function runDeviceLoginInTui(otui, target, provider, dir) {
98779
+ const r = stepRenderer(target);
98491
98780
  return new Promise((resolve) => {
98492
- const box = overlayBox(otui, r, "device-login");
98493
- r.root.add(box);
98494
- box.add(new otui.TextRenderable(r, {
98495
- id: "dl-title",
98496
- content: otui.t`${otui.bold(deviceCodeMethodLabel(provider))} ${otui.dim("(Esc to cancel)")}`
98497
- }));
98498
- const status = new otui.TextRenderable(r, { id: "dl-status", content: otui.t`${otui.dim("Requesting a device code\u2026")}`, marginTop: 1 });
98499
- box.add(status);
98500
98781
  const controller = new AbortController;
98501
- const cleanup = () => {
98502
- unsub();
98503
- r.root.remove(box);
98504
- };
98505
- const unsub = onKeypress4(r, (key) => {
98506
- if (key.name === "escape") {
98782
+ const surface = openStepSurface(otui, target, {
98783
+ id: "device-login",
98784
+ title: deviceCodeMethodLabel(provider),
98785
+ tab: "Device login",
98786
+ hint: "(Esc to cancel)",
98787
+ footer: [{ key: "esc", label: "cancel" }],
98788
+ contentRows: 4,
98789
+ onEscape: () => {
98507
98790
  controller.abort();
98508
- cleanup();
98509
98791
  resolve(false);
98510
- key.preventDefault();
98511
- key.stopPropagation();
98512
98792
  }
98513
98793
  });
98794
+ const status = new otui.TextRenderable(r, { id: "dl-status", content: otui.t`${otui.dim("Requesting a device code\u2026")}`, marginTop: 1 });
98795
+ surface.body.add(status);
98796
+ const cleanup = () => {
98797
+ surface.close();
98798
+ };
98514
98799
  loginDeviceCode({
98515
98800
  provider,
98516
98801
  fetch: (input, init) => globalThis.fetch(input, init),
@@ -98552,15 +98837,26 @@ function modelPickerNotice(label, result) {
98552
98837
  }
98553
98838
  return modelsFailureLine(label, result.failure);
98554
98839
  }
98555
- function pickProviderStep(otui, r, detected) {
98840
+ function pickProviderStep(otui, target, detected) {
98841
+ const r = stepRenderer(target);
98556
98842
  return new Promise((resolve) => {
98557
- const box = overlayBox(otui, r, "picker");
98558
- r.root.add(box);
98559
- box.add(new otui.TextRenderable(r, { id: "picker-title", content: otui.t`${otui.bold("Select a provider")} ${otui.dim("(\u2191/\u2193, Enter \xB7 Esc to cancel)")}` }));
98843
+ const surface = openStepSurface(otui, target, {
98844
+ id: "picker",
98845
+ title: "Select a provider",
98846
+ tab: "Providers",
98847
+ hint: "(\u2191/\u2193, Enter \xB7 Esc to cancel)",
98848
+ footer: selectStepFooter("cancel"),
98849
+ contentRows: selectBoxHeight(detected.length, true),
98850
+ onEscape: () => {
98851
+ provSelect.blur();
98852
+ resolve(undefined);
98853
+ }
98854
+ });
98855
+ const box = surface.body;
98560
98856
  const labelOf = (d) => d.label ?? d.name;
98561
98857
  const provSelect = new otui.SelectRenderable(r, {
98562
98858
  id: "picker-provider",
98563
- width: 60,
98859
+ width: surface.modal ? "100%" : 60,
98564
98860
  height: selectBoxHeight(detected.length, true),
98565
98861
  showScrollIndicator: true,
98566
98862
  options: detected.map((d) => ({ name: labelOf(d), description: d.note ?? `${d.models.length} model(s)` })),
@@ -98568,18 +98864,9 @@ function pickProviderStep(otui, r, detected) {
98568
98864
  });
98569
98865
  box.add(provSelect);
98570
98866
  provSelect.focus();
98571
- const onKey = (key) => {
98572
- if (key.name === "escape") {
98573
- cleanup();
98574
- resolve(undefined);
98575
- key.preventDefault();
98576
- key.stopPropagation();
98577
- }
98578
- };
98579
- const unsub = onKeypress4(r, onKey);
98580
98867
  const cleanup = () => {
98581
- unsub();
98582
- r.root.remove(box);
98868
+ provSelect.blur();
98869
+ surface.close();
98583
98870
  };
98584
98871
  provSelect.on(otui.SelectRenderableEvents.ITEM_SELECTED, () => {
98585
98872
  const chosen = provSelect.getSelectedOption();
@@ -98588,12 +98875,7 @@ function pickProviderStep(otui, r, detected) {
98588
98875
  });
98589
98876
  });
98590
98877
  }
98591
- function isModalChrome(target) {
98592
- return target !== null && typeof target === "object" && "renderer" in target && "focusComposer" in target;
98593
- }
98594
98878
  function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
98595
- const chrome = isModalChrome(rOrChrome) ? rOrChrome : undefined;
98596
- const r = chrome !== undefined ? chrome.renderer : rOrChrome;
98597
98879
  return new Promise((resolve) => {
98598
98880
  if (detected.length === 0) {
98599
98881
  resolve(undefined);
@@ -98611,13 +98893,13 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
98611
98893
  }
98612
98894
  const allCandidates = options.onlyConnected ? candidates : [...candidates, CUSTOM_ADD_ENTRY];
98613
98895
  while (true) {
98614
- const prov = await pickProviderStep(otui, r, allCandidates);
98896
+ const prov = await pickProviderStep(otui, rOrChrome, allCandidates);
98615
98897
  if (prov === undefined) {
98616
98898
  resolve(undefined);
98617
98899
  return;
98618
98900
  }
98619
98901
  if (prov.name === CUSTOM_PROVIDER_ADD_ID) {
98620
- const created = await promptCustomProviderWizard(otui, r);
98902
+ const created = await promptCustomProviderWizard(otui, rOrChrome);
98621
98903
  if (created !== undefined) {
98622
98904
  saveCustomCompatProvider({
98623
98905
  name: created.name,
@@ -98641,7 +98923,7 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
98641
98923
  continue;
98642
98924
  }
98643
98925
  const lockDiscoveredHost = prov.name === "github-copilot";
98644
- let selectedBaseUrl = options.onlyConnected || prov.baseUrl === undefined || lockDiscoveredHost ? prov.baseUrl : await promptBaseUrlStep(otui, r, prov.label ?? prov.name, prov.baseUrl);
98926
+ let selectedBaseUrl = options.onlyConnected || prov.baseUrl === undefined || lockDiscoveredHost ? prov.baseUrl : await promptBaseUrlStep(otui, rOrChrome, prov.label ?? prov.name, prov.baseUrl);
98645
98927
  if (!options.onlyConnected && !lockDiscoveredHost && prov.baseUrl !== undefined && selectedBaseUrl === undefined) {
98646
98928
  continue;
98647
98929
  }
@@ -98669,13 +98951,13 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
98669
98951
  offered.push("api-key");
98670
98952
  let method = offered.length === 1 ? offered[0] : undefined;
98671
98953
  if (offered.length > 1) {
98672
- method = await pickAuthMethodStep(otui, r, label, offered);
98954
+ method = await pickAuthMethodStep(otui, rOrChrome, label, offered);
98673
98955
  if (method === undefined) {
98674
98956
  return "back";
98675
98957
  }
98676
98958
  }
98677
98959
  if (method === "device-code") {
98678
- if (!await runDeviceLoginInTui(otui, r, prov.name, options.configDir)) {
98960
+ if (!await runDeviceLoginInTui(otui, rOrChrome, prov.name, options.configDir)) {
98679
98961
  return "back";
98680
98962
  }
98681
98963
  const saved = loadShellConfig(options.configDir).baseUrls?.[prov.name];
@@ -98685,7 +98967,7 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
98685
98967
  }
98686
98968
  return "ok";
98687
98969
  }
98688
- const kr = await promptApiKeyStep(otui, r, {
98970
+ const kr = await promptApiKeyStep(otui, rOrChrome, {
98689
98971
  label,
98690
98972
  envKey,
98691
98973
  ...notice === undefined ? {} : { notice }
@@ -98715,7 +98997,7 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
98715
98997
  models = await modelsForPicker(selectedProvider, modelDeps);
98716
98998
  }
98717
98999
  if (!options.onlyConnected && selectedProvider.baseUrl !== undefined && endpointMayBeAtFault(models.failure)) {
98718
- const corrected = await promptBaseUrlStep(otui, r, label, selectedProvider.baseUrl, modelsFailureLine(label, models.failure));
99000
+ const corrected = await promptBaseUrlStep(otui, rOrChrome, label, selectedProvider.baseUrl, modelsFailureLine(label, models.failure));
98719
99001
  if (corrected === undefined) {
98720
99002
  continue;
98721
99003
  }
@@ -98726,7 +99008,7 @@ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
98726
99008
  models = await modelsForPicker(selectedProvider, modelDeps);
98727
99009
  }
98728
99010
  }
98729
- const model = await pickModelInTui(otui, chrome ?? r, models.models, modelPickerNotice(label, models), {
99011
+ const model = await pickModelInTui(otui, rOrChrome, models.models, modelPickerNotice(label, models), {
98730
99012
  escLabel: "back"
98731
99013
  });
98732
99014
  if (model === undefined) {
@@ -98962,6 +99244,9 @@ function pickSessionInTui(otui, rOrChrome, sessions) {
98962
99244
  });
98963
99245
  });
98964
99246
  }
99247
+ function describeModeRow(mode, readOnly) {
99248
+ return readOnly ? { mode, readOnly: "\xB7 read-only" } : { mode };
99249
+ }
98965
99250
  var MODE_PICKER_DESCRIPTIONS = {
98966
99251
  ask: "Every mutating action asks first (today's default)",
98967
99252
  trust: "Auto-approves safe actions; still asks for destructive/credential ones",
@@ -99073,6 +99358,8 @@ async function launchTuiAgentShell(opts) {
99073
99358
  sidebar.add(new otui.TextRenderable(r, { id: "sb-model-k", content: otui.t`${otui.dim("Model")}`, marginTop: 1 }));
99074
99359
  const sbModelV = new otui.TextRenderable(r, { id: "sb-model-v", content: otui.t`${otui.dim(`${sel.provider}/${sel.model}`)}` });
99075
99360
  sidebar.add(sbModelV);
99361
+ const sbModeV = new otui.TextRenderable(r, { id: "sb-mode-v", content: "" });
99362
+ sidebar.add(sbModeV);
99076
99363
  sidebar.add(new otui.TextRenderable(r, { id: "sb-usage-k", content: otui.t`${otui.dim("Usage")}`, marginTop: 1 }));
99077
99364
  const sbUsageV = new otui.TextRenderable(r, { id: "sb-usage-v", content: otui.t`${otui.dim("\u21910 \u21930")}` });
99078
99365
  sidebar.add(sbUsageV);
@@ -99689,6 +99976,11 @@ async function launchTuiAgentShell(opts) {
99689
99976
  io.permissionMode = () => permissionMode;
99690
99977
  let readOnly = false;
99691
99978
  io.readOnly = () => readOnly;
99979
+ const paintModeRow = () => {
99980
+ const row = describeModeRow(permissionMode, readOnly);
99981
+ sbModeV.content = row.readOnly === undefined ? otui.t`${otui.dim(`mode ${row.mode}`)}` : otui.t`${otui.dim(`mode ${row.mode}`)} ${otui.yellow(row.readOnly)}`;
99982
+ };
99983
+ paintModeRow();
99692
99984
  io.onAutoApproved = (tool, input, meta) => {
99693
99985
  const preview = tool === "shell_exec" ? parseShellExecCommand(input) : tool;
99694
99986
  const label = meta.destructive ? `\u25C7 auto-approved (${permissionMode}) [destructive]` : `\u25C7 auto-approved (${permissionMode})`;
@@ -99716,7 +100008,12 @@ async function launchTuiAgentShell(opts) {
99716
100008
  };
99717
100009
  deps = { ...deps, onContextCompaction };
99718
100010
  liveDeps = deps;
100011
+ let removeSplash;
99719
100012
  const applyOpened = (opened, previewHistory) => {
100013
+ if (opened.history.length > 0) {
100014
+ removeSplash?.();
100015
+ removeSplash = undefined;
100016
+ }
99720
100017
  liveSession = opened.handle;
99721
100018
  history = previewHistory === true ? opened.history.slice(-SESSION_PREVIEW_MESSAGE_COUNT) : opened.history;
99722
100019
  archive = opened.archive.length > 0 ? [...opened.archive] : [...opened.history];
@@ -99832,6 +100129,9 @@ async function launchTuiAgentShell(opts) {
99832
100129
  }));
99833
100130
  }
99834
100131
  slateSession = { dir: liveSession.dir, cwd: sessionCwd, opened: false };
100132
+ if (history.length === 0) {
100133
+ removeSplash = mountEmptyTranscriptSplash(otui, r, transcript);
100134
+ }
99835
100135
  refreshWorkspaceSidebar();
99836
100136
  refreshReviewSidebar();
99837
100137
  const paintSessionHeader = () => {
@@ -100128,6 +100428,7 @@ Staying in the current session.
100128
100428
  }
100129
100429
  }
100130
100430
  permissionMode = next;
100431
+ paintModeRow();
100131
100432
  chrome.showToast(`Permission mode: ${next}`);
100132
100433
  if (saveFlag) {
100133
100434
  const saved = setProjectPermissionMode(sessionCwd, next);
@@ -100185,11 +100486,13 @@ Staying in the current session.
100185
100486
  }
100186
100487
  if (wanted === "on") {
100187
100488
  readOnly = true;
100489
+ paintModeRow();
100188
100490
  chrome.showToast("Read-only mode: on");
100189
100491
  return;
100190
100492
  }
100191
100493
  if (wanted === "off") {
100192
100494
  readOnly = false;
100495
+ paintModeRow();
100193
100496
  chrome.showToast(`Read-only mode: off (permission mode stays: ${permissionMode})`);
100194
100497
  return;
100195
100498
  }
@@ -100569,6 +100872,8 @@ Staying in the current session.
100569
100872
  suggestionGate.cancel("new turn started");
100570
100873
  if (origin === "operator") {
100571
100874
  consecutiveAutoWakes = 0;
100875
+ removeSplash?.();
100876
+ removeSplash = undefined;
100572
100877
  }
100573
100878
  const displayLine = summarizeSubmittedLine(line);
100574
100879
  if (chrome.isBusy()) {
@@ -100874,7 +101179,7 @@ Staying in the current session.
100874
101179
  const args = parseSearchProviderArgs(line.slice(16));
100875
101180
  const all = searchProviderController.configurable();
100876
101181
  if (args.providerId === undefined) {
100877
- await chrome.withOverlay(() => searchProviderWizardInTui(otui, r, searchProviderController));
101182
+ await chrome.withOverlay(() => searchProviderWizardInTui(otui, chrome, searchProviderController));
100878
101183
  input.focus();
100879
101184
  return;
100880
101185
  }
@@ -100910,7 +101215,7 @@ Staying in the current session.
100910
101215
  `);
100911
101216
  return;
100912
101217
  }
100913
- const picked = await chrome.withOverlay(() => pickSearchProviderStep(otui, r, selectable));
101218
+ const picked = await chrome.withOverlay(() => pickSearchProviderStep(otui, chrome, selectable));
100914
101219
  input.focus();
100915
101220
  if (picked === undefined) {
100916
101221
  return;
@@ -101100,12 +101405,10 @@ Usage: /reasoning <${REASONING_EFFORT_LEVELS.join("|")}>
101100
101405
  const ns = await chrome.withOverlay(() => command.name === "/connect" ? selectProviderModelInTui(otui, chrome, detected, { onlyConnected: true, env: process.env }) : selectProviderModelInTui(otui, chrome, detected));
101101
101406
  if (ns !== undefined) {
101102
101407
  await switchTo(ns);
101103
- } else {
101104
- if (command.name === "/connect") {
101105
- chrome.showToast("No connected providers found. Run /provider to configure one first.");
101106
- }
101107
- input.focus();
101408
+ } else if (command.name === "/connect") {
101409
+ chrome.showToast("No connected providers found. Run /provider to configure one first.");
101108
101410
  }
101411
+ input.focus();
101109
101412
  })();
101110
101413
  return;
101111
101414
  }
@@ -106920,7 +107223,7 @@ import { readFileSync as readFileSync9 } from "fs";
106920
107223
 
106921
107224
  // src/agents/bootstrap.ts
106922
107225
  import { mkdir as mkdir64, readFile as readFile101, writeFile as writeFile55 } from "fs/promises";
106923
- import { homedir as homedir9 } from "os";
107226
+ import { homedir as homedir10 } from "os";
106924
107227
  import path200 from "path";
106925
107228
  init_fs();
106926
107229
  var AGENT_BOOTSTRAP_START = "<!-- keryx:global-bootstrap -->";
@@ -106989,7 +107292,7 @@ function resolveAgentBootstrapRuntimes(ids) {
106989
107292
  }
106990
107293
  return { runtimes, unknown };
106991
107294
  }
106992
- async function agentBootstrapStatus(runtime, homeRoot = homedir9()) {
107295
+ async function agentBootstrapStatus(runtime, homeRoot = homedir10()) {
106993
107296
  const filePath = runtime.filePath(homeRoot);
106994
107297
  const exists = await pathExists(filePath);
106995
107298
  const content = exists ? await readFile101(filePath, "utf8") : "";
@@ -106999,7 +107302,7 @@ async function agentBootstrapStatus(runtime, homeRoot = homedir9()) {
106999
107302
  return { runtime: runtime.id, label: runtime.label, filePath, exists, installed, current };
107000
107303
  }
107001
107304
  async function installAgentBootstrap(runtime, options = {}) {
107002
- const homeRoot = options.homeRoot ?? homedir9();
107305
+ const homeRoot = options.homeRoot ?? homedir10();
107003
107306
  const filePath = runtime.filePath(homeRoot);
107004
107307
  const exists = await pathExists(filePath);
107005
107308
  const current = exists ? await readFile101(filePath, "utf8") : "";
@@ -107014,7 +107317,7 @@ async function installAgentBootstrap(runtime, options = {}) {
107014
107317
  return { ...status, wrote, dryRun };
107015
107318
  }
107016
107319
  async function uninstallAgentBootstrap(runtime, options = {}) {
107017
- const homeRoot = options.homeRoot ?? homedir9();
107320
+ const homeRoot = options.homeRoot ?? homedir10();
107018
107321
  const filePath = runtime.filePath(homeRoot);
107019
107322
  const exists = await pathExists(filePath);
107020
107323
  const current = exists ? await readFile101(filePath, "utf8") : "";