@mrciphersmith/keryx 0.2.115 → 0.2.117

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 +341 -176
  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.115",
50255
+ version: "0.2.117",
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: {
@@ -82502,8 +82502,6 @@ function realSpawner() {
82502
82502
  env,
82503
82503
  detached: true
82504
82504
  });
82505
- let dataCb;
82506
- let exitCb;
82507
82505
  let netClosed = false;
82508
82506
  const closeNetOnce = async () => {
82509
82507
  if (netClosed)
@@ -82511,43 +82509,81 @@ function realSpawner() {
82511
82509
  netClosed = true;
82512
82510
  await netClose();
82513
82511
  };
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
- },
82512
+ return superviseSpawnedProcess(proc, {
82545
82513
  kill: (signal) => {
82546
82514
  try {
82547
82515
  process.kill(-proc.pid, signal);
82548
82516
  } catch {}
82517
+ },
82518
+ afterExit: closeNetOnce
82519
+ });
82520
+ };
82521
+ }
82522
+ var EXIT_OUTPUT_DRAIN_MS = 2000;
82523
+ function superviseSpawnedProcess(proc, opts) {
82524
+ const drainMs = opts.drainMs ?? EXIT_OUTPUT_DRAIN_MS;
82525
+ let dataCb;
82526
+ let exitCb;
82527
+ const pendingChunks = [];
82528
+ let pendingExit;
82529
+ const emitData = (chunk, stream) => {
82530
+ if (dataCb === undefined)
82531
+ pendingChunks.push({ chunk, stream });
82532
+ else
82533
+ dataCb(chunk, stream);
82534
+ };
82535
+ const outDecoder = new TextDecoder;
82536
+ const errDecoder = new TextDecoder;
82537
+ const pump = async (stream, kind, decoder) => {
82538
+ if (stream === undefined)
82539
+ return;
82540
+ const reader = stream.getReader();
82541
+ try {
82542
+ for (;; ) {
82543
+ const chunk = await reader.read();
82544
+ if (chunk.done)
82545
+ break;
82546
+ if (chunk.value !== undefined)
82547
+ emitData(decoder.decode(chunk.value, { stream: true }), kind);
82549
82548
  }
82550
- };
82549
+ } catch {} finally {
82550
+ reader.releaseLock();
82551
+ }
82552
+ };
82553
+ const drained = Promise.all([
82554
+ pump(proc.stdout, "stdout", outDecoder),
82555
+ pump(proc.stderr, "stderr", errDecoder)
82556
+ ]);
82557
+ proc.exited.then(async (exitCode) => {
82558
+ let timer;
82559
+ await Promise.race([
82560
+ drained,
82561
+ new Promise((resolve) => {
82562
+ timer = setTimeout(resolve, drainMs);
82563
+ timer.unref?.();
82564
+ })
82565
+ ]);
82566
+ if (timer !== undefined)
82567
+ clearTimeout(timer);
82568
+ if (exitCb === undefined)
82569
+ pendingExit = { exitCode };
82570
+ else
82571
+ exitCb({ exitCode });
82572
+ await opts.afterExit?.();
82573
+ });
82574
+ return {
82575
+ pid: proc.pid,
82576
+ onOutput: (cb) => {
82577
+ dataCb = cb;
82578
+ for (const { chunk, stream } of pendingChunks.splice(0))
82579
+ cb(chunk, stream);
82580
+ },
82581
+ onExit: (cb) => {
82582
+ exitCb = cb;
82583
+ if (pendingExit !== undefined)
82584
+ cb(pendingExit);
82585
+ },
82586
+ kill: opts.kill
82551
82587
  };
82552
82588
  }
82553
82589
  function createJobRegistry(options) {
@@ -88889,7 +88925,6 @@ function createBlockNavController(options) {
88889
88925
  const schedule = options.schedule ?? ((run) => void setTimeout(run, 0));
88890
88926
  const onChange = options.onChange ?? (() => {});
88891
88927
  let focusOwner = "composer";
88892
- let savedScrollTop = 0;
88893
88928
  const active = () => focusOwner === "blocks";
88894
88929
  const paint = (id) => {
88895
88930
  const state = registry.get(id);
@@ -88977,7 +89012,6 @@ function createBlockNavController(options) {
88977
89012
  }
88978
89013
  focusOwner = "blocks";
88979
89014
  registry.focus(target.id);
88980
- savedScrollTop = scroll.scrollTop;
88981
89015
  scroll.stickyScroll = false;
88982
89016
  blurComposer();
88983
89017
  paintAll();
@@ -88990,8 +89024,12 @@ function createBlockNavController(options) {
88990
89024
  }
88991
89025
  focusOwner = "composer";
88992
89026
  paintAll();
88993
- scroll.scrollTop = savedScrollTop;
88994
- scroll.stickyScroll = true;
89027
+ if (typeof scroll.scrollHeight === "number" && typeof scroll.height === "number") {
89028
+ const atBottom = scroll.scrollTop >= scroll.scrollHeight - scroll.height - 3;
89029
+ scroll.stickyScroll = atBottom;
89030
+ } else {
89031
+ scroll.stickyScroll = true;
89032
+ }
88995
89033
  focusComposer();
88996
89034
  onChange();
88997
89035
  };
@@ -89073,7 +89111,7 @@ function onKeypress(r, handler) {
89073
89111
  }
89074
89112
  var BACKDROP_ID = "modal-backdrop";
89075
89113
  var PANEL_ID = "modal-panel";
89076
- var BACKDROP_ALPHA = 0.85;
89114
+ var BACKDROP_ALPHA = 1;
89077
89115
  function backdropFillColor(otui) {
89078
89116
  const color = otui.RGBA.fromHex(getTheme().bg);
89079
89117
  color.a = BACKDROP_ALPHA;
@@ -89084,13 +89122,23 @@ var MODAL_PANEL_CHROME_X = 4;
89084
89122
  var MODAL_PANEL_MIN_WIDTH = 72;
89085
89123
  var MODAL_PANEL_MIN_HEIGHT = 18;
89086
89124
  var MODAL_PANEL_SIZE_RATIO = 0.95;
89125
+ var MODAL_PANEL_MAX_HEIGHT_RATIO = 0.85;
89087
89126
  var MODAL_CHROME_ROWS = 5;
89088
89127
  var MODAL_PANEL_WIDTH = MODAL_PANEL_MIN_WIDTH;
89089
89128
  var MODAL_PANEL_HEIGHT = MODAL_PANEL_MIN_HEIGHT;
89090
89129
  var MODAL_PANEL_INNER_WIDTH = MODAL_PANEL_MIN_WIDTH - MODAL_PANEL_CHROME_X;
89091
- function resolveModalPanelSize(cols, rows) {
89130
+ function resolveModalPanelSize(cols, rows, contentRows) {
89131
+ const width = Math.max(MODAL_PANEL_MIN_WIDTH, Math.round(cols * MODAL_PANEL_SIZE_RATIO));
89132
+ if (contentRows !== undefined) {
89133
+ const maxHeight = Math.max(6, Math.round(rows * MODAL_PANEL_MAX_HEIGHT_RATIO));
89134
+ const needed = Math.max(6, contentRows + MODAL_CHROME_ROWS);
89135
+ return {
89136
+ width,
89137
+ height: Math.min(maxHeight, needed)
89138
+ };
89139
+ }
89092
89140
  return {
89093
- width: Math.max(MODAL_PANEL_MIN_WIDTH, Math.round(cols * MODAL_PANEL_SIZE_RATIO)),
89141
+ width,
89094
89142
  height: Math.max(MODAL_PANEL_MIN_HEIGHT, Math.round(rows * MODAL_PANEL_SIZE_RATIO))
89095
89143
  };
89096
89144
  }
@@ -89098,6 +89146,19 @@ function modalBodyRows(panelHeight) {
89098
89146
  return Math.max(1, panelHeight - MODAL_CHROME_ROWS);
89099
89147
  }
89100
89148
  var CLOSE_HINT = "[x] esc";
89149
+ var UNMEASURED_SIDEBAR_WIDTH = 30;
89150
+ function resolveModalAvailableWidth(chrome) {
89151
+ const rWidth = chrome.renderer.width;
89152
+ const mainWidth = chrome.main?.width ?? 0;
89153
+ if (mainWidth > 0) {
89154
+ return Math.min(rWidth, mainWidth);
89155
+ }
89156
+ if (chrome.sidebar !== undefined) {
89157
+ const sidebarWidth = chrome.sidebar.width > 0 ? chrome.sidebar.width : UNMEASURED_SIDEBAR_WIDTH;
89158
+ return Math.max(20, rWidth - sidebarWidth);
89159
+ }
89160
+ return rWidth;
89161
+ }
89101
89162
  function resolveModalInnerWidth(availableWidth) {
89102
89163
  return Math.max(20, availableWidth - MODAL_PANEL_CHROME_X);
89103
89164
  }
@@ -89181,9 +89242,11 @@ function mountTab(state, input, tabId) {
89181
89242
  unmountActiveTab(state);
89182
89243
  state.active = tabId;
89183
89244
  paintTabs(state);
89184
- const size = resolveModalPanelSize(state.chrome.renderer.width, state.chrome.renderer.height);
89245
+ const availWidth = resolveModalAvailableWidth(state.chrome);
89246
+ const size = resolveModalPanelSize(availWidth, state.chrome.renderer.height, input.contentRows);
89247
+ const panelWidth = Math.min(availWidth, size.width);
89185
89248
  const cleanup = input.renderTab(tabId, state.body, {
89186
- width: resolveModalInnerWidth(size.width),
89249
+ width: resolveModalInnerWidth(panelWidth),
89187
89250
  height: modalBodyRows(size.height)
89188
89251
  });
89189
89252
  state.tabCleanup = typeof cleanup === "function" ? cleanup : undefined;
@@ -89305,7 +89368,8 @@ function ensureHost(otui, chrome) {
89305
89368
  panel.add(scroll);
89306
89369
  panel.add(footer);
89307
89370
  backdrop.add(panel);
89308
- r.root.add(backdrop);
89371
+ const mountTarget = chrome.main ?? r.root;
89372
+ mountTarget.add(backdrop);
89309
89373
  const state = {
89310
89374
  otui,
89311
89375
  chrome,
@@ -89425,8 +89489,9 @@ function openModal(otui, chrome, input) {
89425
89489
  return;
89426
89490
  }
89427
89491
  const state = ensureHost(otui, chrome);
89428
- const size = resolveModalPanelSize(chrome.renderer.width, chrome.renderer.height);
89429
- state.panel.width = size.width;
89492
+ const availWidth = resolveModalAvailableWidth(chrome);
89493
+ const size = resolveModalPanelSize(availWidth, chrome.renderer.height, input.contentRows);
89494
+ state.panel.width = Math.min(availWidth, size.width);
89430
89495
  state.panel.height = size.height;
89431
89496
  if (state.open) {
89432
89497
  unmountActiveTab(state);
@@ -89448,7 +89513,10 @@ function openModal(otui, chrome, input) {
89448
89513
  paintHeader(state, input.title);
89449
89514
  paintFooter(state, input.footer);
89450
89515
  mountTab(state, input, resolveInitialTab(input.tabs, input.initialTab));
89451
- state.tabStrip.focus();
89516
+ const focused = chrome.renderer.currentFocusedRenderable;
89517
+ if (focused === null || !containsNode(state.scroll, focused)) {
89518
+ state.tabStrip.focus();
89519
+ }
89452
89520
  return makeHandle(state, generation);
89453
89521
  }
89454
89522
 
@@ -90760,6 +90828,10 @@ var REVIEW_FOOTER = [
90760
90828
  { key: "\u2191/\u2193", label: "scroll" },
90761
90829
  { key: "esc", label: "close" }
90762
90830
  ];
90831
+ var REVIEW_EMPTY_FOOTER = [
90832
+ { key: "\u2190/\u2192", label: "tabs" },
90833
+ { key: "esc", label: "close" }
90834
+ ];
90763
90835
  function isReviewCommand(line) {
90764
90836
  const token = line.trim().split(/\s+/)[0] ?? "";
90765
90837
  return token === REVIEW_COMMAND;
@@ -91061,6 +91133,7 @@ function presentReview(openModal, otui, chrome, options) {
91061
91133
  paintSelection();
91062
91134
  });
91063
91135
  };
91136
+ const isCompactEmpty = items.length === 0;
91064
91137
  const handle = openModal(otui, chrome, {
91065
91138
  title: REVIEW_COMMAND,
91066
91139
  tabs: [
@@ -91068,7 +91141,8 @@ function presentReview(openModal, otui, chrome, options) {
91068
91141
  { id: "detail", label: "Detail" }
91069
91142
  ],
91070
91143
  initialTab: "list",
91071
- footer: REVIEW_FOOTER,
91144
+ footer: isCompactEmpty ? REVIEW_EMPTY_FOOTER : REVIEW_FOOTER,
91145
+ ...isCompactEmpty ? { contentRows: 3 } : {},
91072
91146
  onArrowKeys: (key, direction) => {
91073
91147
  if (items[selected]?.type === "proposal" && status.kind !== "running" && status.kind !== "done" && handle?.activeTab() === "detail") {
91074
91148
  focusedAction = direction === "left" ? "accept" : "decline";
@@ -92740,14 +92814,36 @@ function describeUnavailableCommand(line, mode) {
92740
92814
  return `${command.name} is only available in ${where} \u2014 this is ${mode} mode. ` + `Type /help for the commands available here.
92741
92815
  `;
92742
92816
  }
92743
- function renderCommandHelp(mode, only) {
92817
+ function renderCommandHelp(mode, only, maxColumns) {
92744
92818
  const allow = only === undefined ? undefined : new Set(only);
92745
92819
  const options = commandsForMode(mode).filter((c) => allow === undefined || allow.has(c.name));
92746
92820
  const width = options.reduce((w, c) => Math.max(w, c.name.length), 0);
92747
- return ["Commands:", ...options.map((c) => ` ${c.name.padEnd(width)} ${c.description}`)].join(`
92821
+ const descColumn = width + 4;
92822
+ const wrapBudget = maxColumns !== undefined && maxColumns - descColumn >= MIN_HELP_WRAP_COLUMNS ? maxColumns - descColumn : undefined;
92823
+ const rows = options.flatMap((c) => {
92824
+ const lines = c.description.split(`
92825
+ `).flatMap((line) => wrapBudget === undefined ? [line] : wrapWords(line, wrapBudget));
92826
+ return lines.map((line, i) => (i === 0 ? ` ${c.name.padEnd(width)} ${line}` : `${" ".repeat(descColumn)}${line}`).trimEnd());
92827
+ });
92828
+ return ["Commands:", ...rows].join(`
92748
92829
  `) + `
92749
92830
  `;
92750
92831
  }
92832
+ var MIN_HELP_WRAP_COLUMNS = 10;
92833
+ function wrapWords(line, budget) {
92834
+ const out = [];
92835
+ let cur = "";
92836
+ for (const word of line.split(/\s+/).filter(Boolean)) {
92837
+ if (cur.length > 0 && cur.length + 1 + word.length > budget) {
92838
+ out.push(cur);
92839
+ cur = word;
92840
+ } else {
92841
+ cur = cur.length > 0 ? `${cur} ${word}` : word;
92842
+ }
92843
+ }
92844
+ out.push(cur);
92845
+ return out;
92846
+ }
92751
92847
 
92752
92848
  // src/tui/theme-picker.ts
92753
92849
  function isThemeCommand(line) {
@@ -95100,6 +95196,10 @@ async function createShellChrome(otui, renderer, opts) {
95100
95196
  syncComposerHeight();
95101
95197
  const submitHandlers = new Set;
95102
95198
  const emitSubmit = (line) => {
95199
+ if (line.length > 0) {
95200
+ scroll.scrollTop = scroll.scrollHeight;
95201
+ scroll.stickyScroll = true;
95202
+ }
95103
95203
  for (const handler of [...submitHandlers]) {
95104
95204
  handler(line);
95105
95205
  }
@@ -98524,7 +98624,12 @@ function pickProviderStep(otui, r, detected) {
98524
98624
  });
98525
98625
  });
98526
98626
  }
98527
- function selectProviderModelInTui(otui, r, detected, options = {}) {
98627
+ function isModalChrome(target) {
98628
+ return target !== null && typeof target === "object" && "renderer" in target && "focusComposer" in target;
98629
+ }
98630
+ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
98631
+ const chrome = isModalChrome(rOrChrome) ? rOrChrome : undefined;
98632
+ const r = chrome !== undefined ? chrome.renderer : rOrChrome;
98528
98633
  return new Promise((resolve) => {
98529
98634
  if (detected.length === 0) {
98530
98635
  resolve(undefined);
@@ -98657,7 +98762,9 @@ function selectProviderModelInTui(otui, r, detected, options = {}) {
98657
98762
  models = await modelsForPicker(selectedProvider, modelDeps);
98658
98763
  }
98659
98764
  }
98660
- const model = await pickModelInTui(otui, r, models.models, modelPickerNotice(label, models));
98765
+ const model = await pickModelInTui(otui, chrome ?? r, models.models, modelPickerNotice(label, models), {
98766
+ escLabel: "back"
98767
+ });
98661
98768
  if (model === undefined) {
98662
98769
  continue;
98663
98770
  }
@@ -98671,49 +98778,120 @@ function adaptiveSelectHeight(count, available, per = 1) {
98671
98778
  const min = Math.max(1, Math.floor(available / 4));
98672
98779
  return Math.min(available, Math.max(min, count * per));
98673
98780
  }
98674
- function pickModelInTui(otui, r, models, notice) {
98781
+ function pickModelInTui(otui, rOrChrome, models, notice, options = {}) {
98782
+ const chrome = isModalChrome(rOrChrome) ? rOrChrome : undefined;
98783
+ const r = chrome !== undefined ? chrome.renderer : rOrChrome;
98784
+ const showNotice = models.length === 0 && notice !== undefined;
98785
+ const noticeText = () => new otui.TextRenderable(r, { id: "mp-notice", content: otui.t`${otui.red("\u2717")} ${notice ?? ""}` });
98786
+ const listSpec = {
98787
+ idPrefix: "mp",
98788
+ items: models,
98789
+ toOption: (m) => ({ name: m, description: "" }),
98790
+ matches: (m, q) => m.toLowerCase().includes(q),
98791
+ emptyLabel: notice ?? "(no models found)",
98792
+ filterHint: (filter, shown, total) => `filter: ${filter} (${shown}/${total})`,
98793
+ showDescription: false
98794
+ };
98795
+ const fixedRows = 1 + (showNotice ? 1 : 0);
98796
+ if (chrome !== undefined) {
98797
+ return new Promise((resolve) => {
98798
+ let chosen;
98799
+ const handle = openModal(otui, chrome, {
98800
+ title: "Select a model",
98801
+ tabs: [{ id: "models", label: "Models" }],
98802
+ footer: [
98803
+ { key: "\u2191/\u2193", label: "select" },
98804
+ { key: "Enter", label: "confirm" },
98805
+ { key: "esc", label: options.escLabel ?? "close" }
98806
+ ],
98807
+ contentRows: fixedRows + Math.max(1, models.length),
98808
+ renderTab: (_tabId, body, ctx) => {
98809
+ const parent = body;
98810
+ if (showNotice) {
98811
+ parent.add(noticeText());
98812
+ }
98813
+ const list = mountFilterList(otui, r, parent, {
98814
+ ...listSpec,
98815
+ idleHint: "type to filter by name",
98816
+ width: "100%",
98817
+ height: adaptiveSelectHeight(models.length, Math.max(1, ctx.height - fixedRows)),
98818
+ onPick: (m) => {
98819
+ chosen = m;
98820
+ handle?.close();
98821
+ }
98822
+ });
98823
+ return onKeypress4(r, list.onKey);
98824
+ },
98825
+ onClose: () => resolve(chosen)
98826
+ });
98827
+ if (handle === undefined) {
98828
+ resolve(undefined);
98829
+ }
98830
+ });
98831
+ }
98675
98832
  return new Promise((resolve) => {
98676
- const all = models;
98677
- const NO_MODELS = notice ?? "(no models found)";
98678
98833
  const box = overlayBox(otui, r, "model-picker");
98679
98834
  r.root.add(box);
98680
98835
  box.add(new otui.TextRenderable(r, { id: "mp-title", content: otui.t`${otui.bold("Select a model")}` }));
98681
- if (all.length === 0 && notice !== undefined) {
98682
- box.add(new otui.TextRenderable(r, { id: "mp-notice", content: otui.t`${otui.red("\u2717")} ${notice}` }));
98836
+ if (showNotice) {
98837
+ box.add(noticeText());
98683
98838
  }
98684
- const filterLine = new otui.TextRenderable(r, { id: "mp-filter", content: otui.t`${otui.dim("type to filter \xB7 \u2191/\u2193 Enter \xB7 Esc to go back")}` });
98685
- box.add(filterLine);
98686
- const NO_MATCH = "(no match)";
98687
98839
  const rHeight = r.height;
98688
98840
  const available = typeof rHeight === "number" && rHeight > 0 ? Math.max(4, rHeight - 4) : 16;
98689
- const height = adaptiveSelectHeight(all.length, available);
98690
- const sel = new otui.SelectRenderable(r, {
98691
- id: "mp-sel",
98841
+ let unsub = () => {};
98842
+ const finish = (m) => {
98843
+ unsub();
98844
+ r.root.remove(box);
98845
+ resolve(m);
98846
+ };
98847
+ const list = mountFilterList(otui, r, box, {
98848
+ ...listSpec,
98849
+ idleHint: "type to filter \xB7 \u2191/\u2193 Enter \xB7 Esc to go back",
98692
98850
  width: 72,
98693
- showDescription: false,
98694
- height,
98695
- showScrollIndicator: true,
98696
- wrapSelection: true,
98697
- options: (all.length > 0 ? all : [NO_MODELS]).map((m) => ({ name: m, description: "" })),
98698
- selectedTextColor: "#ffd166"
98851
+ height: adaptiveSelectHeight(models.length, available),
98852
+ onPick: finish
98699
98853
  });
98700
- box.add(sel);
98701
- sel.focus();
98702
- let filter = "";
98703
- const apply = () => {
98704
- const q = filter.trim().toLowerCase();
98705
- const matches = q.length > 0 ? all.filter((m) => m.toLowerCase().includes(q)) : all;
98706
- sel.options = matches.length > 0 ? matches.map((m) => ({ name: m, description: "" })) : [{ name: NO_MATCH, description: "" }];
98707
- filterLine.content = otui.t`${otui.dim(q.length > 0 ? `filter: ${filter} (${matches.length}/${all.length})` : "type to filter \xB7 \u2191/\u2193 Enter \xB7 Esc to go back")}`;
98708
- };
98709
- const onKey = (key) => {
98854
+ unsub = onKeypress4(r, (key) => {
98710
98855
  if (key.name === "escape") {
98711
- cleanup();
98712
- resolve(undefined);
98856
+ finish(undefined);
98713
98857
  key.preventDefault();
98714
98858
  key.stopPropagation();
98715
98859
  return;
98716
98860
  }
98861
+ list.onKey(key);
98862
+ });
98863
+ });
98864
+ }
98865
+ function mountFilterList(otui, r, parent, spec) {
98866
+ const filterLine = new otui.TextRenderable(r, { id: `${spec.idPrefix}-filter`, content: "" });
98867
+ parent.add(filterLine);
98868
+ const sel = new otui.SelectRenderable(r, {
98869
+ id: `${spec.idPrefix}-sel`,
98870
+ width: spec.width,
98871
+ showDescription: spec.showDescription,
98872
+ height: spec.height,
98873
+ showScrollIndicator: true,
98874
+ wrapSelection: true,
98875
+ options: [],
98876
+ selectedTextColor: "#ffd166"
98877
+ });
98878
+ parent.add(sel);
98879
+ sel.focus();
98880
+ let filter = "";
98881
+ let shown = spec.items;
98882
+ const apply = () => {
98883
+ const q = filter.trim().toLowerCase();
98884
+ shown = q.length > 0 ? spec.items.filter((item) => spec.matches(item, q)) : spec.items;
98885
+ sel.options = shown.length > 0 ? shown.map(spec.toOption) : [{ name: spec.items.length === 0 ? spec.emptyLabel : "(no match)", description: "" }];
98886
+ sel.selectedIndex = 0;
98887
+ filterLine.content = otui.t`${otui.dim(q.length > 0 ? spec.filterHint(filter, shown.length, spec.items.length) : spec.idleHint)}`;
98888
+ };
98889
+ apply();
98890
+ sel.on(otui.SelectRenderableEvents.ITEM_SELECTED, () => {
98891
+ spec.onPick(shown[sel.getSelectedIndex()]);
98892
+ });
98893
+ return {
98894
+ onKey: (key) => {
98717
98895
  if (key.name === "backspace") {
98718
98896
  filter = filter.slice(0, -1);
98719
98897
  apply();
@@ -98728,113 +98906,95 @@ function pickModelInTui(otui, r, models, notice) {
98728
98906
  key.preventDefault();
98729
98907
  key.stopPropagation();
98730
98908
  }
98731
- };
98732
- const unsub = onKeypress4(r, onKey);
98733
- const cleanup = () => {
98734
- unsub();
98735
- r.root.remove(box);
98736
- };
98737
- sel.on(otui.SelectRenderableEvents.ITEM_SELECTED, () => {
98738
- const chosen = sel.getSelectedOption();
98739
- cleanup();
98740
- resolve(chosen === null || chosen.name === NO_MATCH || chosen.name === NO_MODELS ? undefined : chosen.name);
98741
- });
98742
- });
98909
+ }
98910
+ };
98743
98911
  }
98744
98912
  function formatSessionDate(iso) {
98745
98913
  const short = iso.trim().replace("T", " ");
98746
98914
  return short.length >= 16 ? short.slice(0, 16) : short;
98747
98915
  }
98748
- function pickSessionInTui(otui, r, sessions) {
98749
- return new Promise((resolve) => {
98750
- const all = sessions.map((s) => {
98751
- const created = formatSessionDate(s.createdAt);
98752
- const updated = formatSessionDate(s.updatedAt);
98753
- const short = shortSessionId(s.id);
98754
- const title = s.title.length > 52 ? `${s.title.slice(0, 49)}\u2026` : s.title;
98755
- return {
98756
- value: s.id,
98757
- label: `${short} \xB7 ${title}`,
98758
- description: `${s.projectPath} \xB7 created ${created} \xB7 updated ${updated}`,
98759
- search: `${s.id} ${short} ${s.projectPath} ${s.title} ${created} ${updated}`.toLowerCase()
98760
- };
98916
+ function pickSessionInTui(otui, rOrChrome, sessions) {
98917
+ const chrome = isModalChrome(rOrChrome) ? rOrChrome : undefined;
98918
+ const r = chrome !== undefined ? chrome.renderer : rOrChrome;
98919
+ const all = sessions.map((s) => {
98920
+ const created = formatSessionDate(s.createdAt);
98921
+ const updated = formatSessionDate(s.updatedAt);
98922
+ const short = shortSessionId(s.id);
98923
+ const title = s.title.length > 52 ? `${s.title.slice(0, 49)}\u2026` : s.title;
98924
+ return {
98925
+ value: s.id,
98926
+ label: `${short} \xB7 ${title}`,
98927
+ description: `${s.projectPath} \xB7 created ${created} \xB7 updated ${updated}`,
98928
+ search: `${s.id} ${short} ${s.projectPath} ${s.title} ${created} ${updated}`.toLowerCase()
98929
+ };
98930
+ });
98931
+ const listSpec = {
98932
+ idPrefix: "sp",
98933
+ items: all,
98934
+ toOption: (row) => ({ name: row.label, description: row.description }),
98935
+ matches: (row, q) => row.search.includes(q),
98936
+ emptyLabel: "(no match)",
98937
+ filterHint: (filter, shown) => `filter: ${filter} (${shown})`,
98938
+ showDescription: true
98939
+ };
98940
+ const ROWS_PER_SESSION = 2;
98941
+ if (chrome !== undefined) {
98942
+ return new Promise((resolve) => {
98943
+ let chosen;
98944
+ const handle = openModal(otui, chrome, {
98945
+ title: "Session Switcher",
98946
+ tabs: [{ id: "sessions", label: "Sessions" }],
98947
+ footer: [
98948
+ { key: "\u2191/\u2193", label: "select" },
98949
+ { key: "Enter", label: "open" },
98950
+ { key: "esc", label: "cancel" }
98951
+ ],
98952
+ contentRows: 1 + Math.max(1, all.length) * ROWS_PER_SESSION,
98953
+ renderTab: (_tabId, body, ctx) => {
98954
+ const list = mountFilterList(otui, r, body, {
98955
+ ...listSpec,
98956
+ idleHint: "type to filter by id, title, project, created, updated",
98957
+ width: "100%",
98958
+ height: adaptiveSelectHeight(all.length, Math.max(1, ctx.height - 1), ROWS_PER_SESSION),
98959
+ onPick: (row) => {
98960
+ chosen = row?.value;
98961
+ handle?.close();
98962
+ }
98963
+ });
98964
+ return onKeypress4(r, list.onKey);
98965
+ },
98966
+ onClose: () => resolve(chosen)
98967
+ });
98968
+ if (handle === undefined) {
98969
+ resolve(undefined);
98970
+ }
98761
98971
  });
98972
+ }
98973
+ return new Promise((resolve) => {
98762
98974
  const box = overlayBox(otui, r, "session-picker");
98763
98975
  r.root.add(box);
98764
98976
  box.add(new otui.TextRenderable(r, { id: "sp-title", content: otui.t`${otui.bold("Open session")} ${otui.dim("\u2191/\u2193 Enter \xB7 Esc to cancel")}` }));
98765
- const filterLine = new otui.TextRenderable(r, {
98766
- id: "sp-filter",
98767
- content: otui.t`${otui.dim("type to filter by id, title, project, created, updated")}`
98768
- });
98769
- box.add(filterLine);
98770
- const NO_MATCH = "(no match)";
98771
- const sel = new otui.SelectRenderable(r, {
98772
- id: "sp-sel",
98977
+ let unsub = () => {};
98978
+ const finish = (row) => {
98979
+ unsub();
98980
+ r.root.remove(box);
98981
+ resolve(row?.value);
98982
+ };
98983
+ const list = mountFilterList(otui, r, box, {
98984
+ ...listSpec,
98985
+ idleHint: "type to filter \xB7 \u2191/\u2193 Enter \xB7 Esc to cancel",
98773
98986
  width: "100%",
98774
- showDescription: true,
98775
98987
  height: 14,
98776
- showScrollIndicator: true,
98777
- wrapSelection: true,
98778
- options: [],
98779
- selectedTextColor: "#ffd166"
98988
+ onPick: finish
98780
98989
  });
98781
- box.add(sel);
98782
- sel.focus();
98783
- let filter = "";
98784
- let matches = all;
98785
- const apply = () => {
98786
- const q = filter.trim().toLowerCase();
98787
- matches = q.length > 0 ? all.filter((row) => row.search.includes(q)) : all;
98788
- const items = matches.length > 0 ? matches : [
98789
- {
98790
- value: "",
98791
- label: NO_MATCH,
98792
- description: "",
98793
- search: ""
98794
- }
98795
- ];
98796
- sel.options = items.map((row) => ({ name: row.label, description: row.description, value: row.value }));
98797
- filterLine.content = otui.t`${otui.dim(q.length > 0 ? `filter: ${filter} (${matches.length})` : "type to filter \xB7 \u2191/\u2193 Enter \xB7 Esc to cancel")}`;
98798
- sel.selectedIndex = 0;
98799
- };
98800
- apply();
98801
- const onKey = (key) => {
98990
+ unsub = onKeypress4(r, (key) => {
98802
98991
  if (key.name === "escape") {
98803
- cleanup();
98804
- resolve(undefined);
98805
- key.preventDefault();
98806
- key.stopPropagation();
98807
- return;
98808
- }
98809
- if (key.name === "backspace") {
98810
- filter = filter.slice(0, -1);
98811
- apply();
98992
+ finish(undefined);
98812
98993
  key.preventDefault();
98813
98994
  key.stopPropagation();
98814
98995
  return;
98815
98996
  }
98816
- const ch = key.sequence;
98817
- if (!key.ctrl && !key.meta && typeof ch === "string" && ch.length === 1 && ch >= " ") {
98818
- filter += ch;
98819
- apply();
98820
- key.preventDefault();
98821
- key.stopPropagation();
98822
- }
98823
- };
98824
- const unsub = onKeypress4(r, onKey);
98825
- const cleanup = () => {
98826
- unsub();
98827
- r.root.remove(box);
98828
- };
98829
- sel.on(otui.SelectRenderableEvents.ITEM_SELECTED, () => {
98830
- const chosen = sel.getSelectedOption();
98831
- cleanup();
98832
- if (chosen === null || chosen.value === "" || chosen.name === NO_MATCH) {
98833
- resolve(undefined);
98834
- return;
98835
- }
98836
- const matched = matches.find((row) => row.value === chosen.value);
98837
- resolve(matched?.value);
98997
+ list.onKey(key);
98838
98998
  });
98839
98999
  });
98840
99000
  }
@@ -99554,7 +99714,12 @@ async function launchTuiAgentShell(opts) {
99554
99714
  return chosen;
99555
99715
  };
99556
99716
  setAskUserHost(askUserInteractive);
99557
- const helpText = () => renderCommandHelp("agent");
99717
+ const TRANSCRIPT_CHROME_COLS = 4;
99718
+ const HELP_MIN_COLS = 40;
99719
+ const helpText = () => {
99720
+ const mainWidth = chrome.main.width > 0 ? chrome.main.width : r.width - SIDEBAR_WIDTH;
99721
+ return renderCommandHelp("agent", undefined, Math.max(HELP_MIN_COLS, mainWidth - TRANSCRIPT_CHROME_COLS));
99722
+ };
99558
99723
  const sessionCwd = opts.session?.cwd ?? process.cwd();
99559
99724
  let permissionMode = opts.initialPermissionMode ?? getProjectPermissionMode(sessionCwd) ?? DEFAULT_PERMISSION_MODE;
99560
99725
  io.permissionMode = () => permissionMode;
@@ -99601,7 +99766,7 @@ async function launchTuiAgentShell(opts) {
99601
99766
  return;
99602
99767
  }
99603
99768
  chrome.hideMenu();
99604
- const pickId = await chrome.withOverlay(() => pickSessionInTui(otui, r, rows));
99769
+ const pickId = await chrome.withOverlay(() => pickSessionInTui(otui, chrome, rows));
99605
99770
  input.focus();
99606
99771
  if (pickId === undefined) {
99607
99772
  return;
@@ -100931,7 +101096,7 @@ Usage: /reasoning <${REASONING_EFFORT_LEVELS.join("|")}>
100931
101096
  const prov = detected.find((d) => d.name === currentSel.provider);
100932
101097
  const models = prov !== undefined ? await modelsForPicker(prov) : undefined;
100933
101098
  const notice = prov === undefined || models === undefined ? undefined : modelPickerNotice(prov.label ?? prov.name, models);
100934
- const chosen = await chrome.withOverlay(() => pickModelInTui(otui, r, models?.models ?? [], notice));
101099
+ const chosen = await chrome.withOverlay(() => pickModelInTui(otui, chrome, models?.models ?? [], notice));
100935
101100
  if (chosen !== undefined) {
100936
101101
  await switchTo(currentSel.baseUrl === undefined ? { provider: currentSel.provider, model: chosen } : { provider: currentSel.provider, model: chosen, baseUrl: currentSel.baseUrl });
100937
101102
  try {
@@ -100968,7 +101133,7 @@ Usage: /reasoning <${REASONING_EFFORT_LEVELS.join("|")}>
100968
101133
  if (command.name === "/connect" || command.name === "/provider") {
100969
101134
  (async () => {
100970
101135
  const detected = opts.redetect !== undefined ? await opts.redetect() : opts.detected;
100971
- const ns = await chrome.withOverlay(() => command.name === "/connect" ? selectProviderModelInTui(otui, r, detected, { onlyConnected: true, env: process.env }) : selectProviderModelInTui(otui, r, detected));
101136
+ const ns = await chrome.withOverlay(() => command.name === "/connect" ? selectProviderModelInTui(otui, chrome, detected, { onlyConnected: true, env: process.env }) : selectProviderModelInTui(otui, chrome, detected));
100972
101137
  if (ns !== undefined) {
100973
101138
  await switchTo(ns);
100974
101139
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.2.115",
3
+ "version": "0.2.117",
4
4
  "description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
5
5
  "private": false,
6
6
  "publishConfig": {