@mrciphersmith/keryx 0.2.114 → 0.2.116

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 +271 -142
  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.114",
50255
+ version: "0.2.116",
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: {
@@ -88889,7 +88889,6 @@ function createBlockNavController(options) {
88889
88889
  const schedule = options.schedule ?? ((run) => void setTimeout(run, 0));
88890
88890
  const onChange = options.onChange ?? (() => {});
88891
88891
  let focusOwner = "composer";
88892
- let savedScrollTop = 0;
88893
88892
  const active = () => focusOwner === "blocks";
88894
88893
  const paint = (id) => {
88895
88894
  const state = registry.get(id);
@@ -88977,7 +88976,6 @@ function createBlockNavController(options) {
88977
88976
  }
88978
88977
  focusOwner = "blocks";
88979
88978
  registry.focus(target.id);
88980
- savedScrollTop = scroll.scrollTop;
88981
88979
  scroll.stickyScroll = false;
88982
88980
  blurComposer();
88983
88981
  paintAll();
@@ -88990,8 +88988,12 @@ function createBlockNavController(options) {
88990
88988
  }
88991
88989
  focusOwner = "composer";
88992
88990
  paintAll();
88993
- scroll.scrollTop = savedScrollTop;
88994
- scroll.stickyScroll = true;
88991
+ if (typeof scroll.scrollHeight === "number" && typeof scroll.height === "number") {
88992
+ const atBottom = scroll.scrollTop >= scroll.scrollHeight - scroll.height - 3;
88993
+ scroll.stickyScroll = atBottom;
88994
+ } else {
88995
+ scroll.stickyScroll = true;
88996
+ }
88995
88997
  focusComposer();
88996
88998
  onChange();
88997
88999
  };
@@ -89073,7 +89075,7 @@ function onKeypress(r, handler) {
89073
89075
  }
89074
89076
  var BACKDROP_ID = "modal-backdrop";
89075
89077
  var PANEL_ID = "modal-panel";
89076
- var BACKDROP_ALPHA = 0.85;
89078
+ var BACKDROP_ALPHA = 1;
89077
89079
  function backdropFillColor(otui) {
89078
89080
  const color = otui.RGBA.fromHex(getTheme().bg);
89079
89081
  color.a = BACKDROP_ALPHA;
@@ -89084,13 +89086,23 @@ var MODAL_PANEL_CHROME_X = 4;
89084
89086
  var MODAL_PANEL_MIN_WIDTH = 72;
89085
89087
  var MODAL_PANEL_MIN_HEIGHT = 18;
89086
89088
  var MODAL_PANEL_SIZE_RATIO = 0.95;
89089
+ var MODAL_PANEL_MAX_HEIGHT_RATIO = 0.85;
89087
89090
  var MODAL_CHROME_ROWS = 5;
89088
89091
  var MODAL_PANEL_WIDTH = MODAL_PANEL_MIN_WIDTH;
89089
89092
  var MODAL_PANEL_HEIGHT = MODAL_PANEL_MIN_HEIGHT;
89090
89093
  var MODAL_PANEL_INNER_WIDTH = MODAL_PANEL_MIN_WIDTH - MODAL_PANEL_CHROME_X;
89091
- function resolveModalPanelSize(cols, rows) {
89094
+ function resolveModalPanelSize(cols, rows, contentRows) {
89095
+ const width = Math.max(MODAL_PANEL_MIN_WIDTH, Math.round(cols * MODAL_PANEL_SIZE_RATIO));
89096
+ if (contentRows !== undefined) {
89097
+ const maxHeight = Math.max(6, Math.round(rows * MODAL_PANEL_MAX_HEIGHT_RATIO));
89098
+ const needed = Math.max(6, contentRows + MODAL_CHROME_ROWS);
89099
+ return {
89100
+ width,
89101
+ height: Math.min(maxHeight, needed)
89102
+ };
89103
+ }
89092
89104
  return {
89093
- width: Math.max(MODAL_PANEL_MIN_WIDTH, Math.round(cols * MODAL_PANEL_SIZE_RATIO)),
89105
+ width,
89094
89106
  height: Math.max(MODAL_PANEL_MIN_HEIGHT, Math.round(rows * MODAL_PANEL_SIZE_RATIO))
89095
89107
  };
89096
89108
  }
@@ -89098,6 +89110,19 @@ function modalBodyRows(panelHeight) {
89098
89110
  return Math.max(1, panelHeight - MODAL_CHROME_ROWS);
89099
89111
  }
89100
89112
  var CLOSE_HINT = "[x] esc";
89113
+ var UNMEASURED_SIDEBAR_WIDTH = 30;
89114
+ function resolveModalAvailableWidth(chrome) {
89115
+ const rWidth = chrome.renderer.width;
89116
+ const mainWidth = chrome.main?.width ?? 0;
89117
+ if (mainWidth > 0) {
89118
+ return Math.min(rWidth, mainWidth);
89119
+ }
89120
+ if (chrome.sidebar !== undefined) {
89121
+ const sidebarWidth = chrome.sidebar.width > 0 ? chrome.sidebar.width : UNMEASURED_SIDEBAR_WIDTH;
89122
+ return Math.max(20, rWidth - sidebarWidth);
89123
+ }
89124
+ return rWidth;
89125
+ }
89101
89126
  function resolveModalInnerWidth(availableWidth) {
89102
89127
  return Math.max(20, availableWidth - MODAL_PANEL_CHROME_X);
89103
89128
  }
@@ -89181,9 +89206,11 @@ function mountTab(state, input, tabId) {
89181
89206
  unmountActiveTab(state);
89182
89207
  state.active = tabId;
89183
89208
  paintTabs(state);
89184
- const size = resolveModalPanelSize(state.chrome.renderer.width, state.chrome.renderer.height);
89209
+ const availWidth = resolveModalAvailableWidth(state.chrome);
89210
+ const size = resolveModalPanelSize(availWidth, state.chrome.renderer.height, input.contentRows);
89211
+ const panelWidth = Math.min(availWidth, size.width);
89185
89212
  const cleanup = input.renderTab(tabId, state.body, {
89186
- width: resolveModalInnerWidth(size.width),
89213
+ width: resolveModalInnerWidth(panelWidth),
89187
89214
  height: modalBodyRows(size.height)
89188
89215
  });
89189
89216
  state.tabCleanup = typeof cleanup === "function" ? cleanup : undefined;
@@ -89305,7 +89332,8 @@ function ensureHost(otui, chrome) {
89305
89332
  panel.add(scroll);
89306
89333
  panel.add(footer);
89307
89334
  backdrop.add(panel);
89308
- r.root.add(backdrop);
89335
+ const mountTarget = chrome.main ?? r.root;
89336
+ mountTarget.add(backdrop);
89309
89337
  const state = {
89310
89338
  otui,
89311
89339
  chrome,
@@ -89425,8 +89453,9 @@ function openModal(otui, chrome, input) {
89425
89453
  return;
89426
89454
  }
89427
89455
  const state = ensureHost(otui, chrome);
89428
- const size = resolveModalPanelSize(chrome.renderer.width, chrome.renderer.height);
89429
- state.panel.width = size.width;
89456
+ const availWidth = resolveModalAvailableWidth(chrome);
89457
+ const size = resolveModalPanelSize(availWidth, chrome.renderer.height, input.contentRows);
89458
+ state.panel.width = Math.min(availWidth, size.width);
89430
89459
  state.panel.height = size.height;
89431
89460
  if (state.open) {
89432
89461
  unmountActiveTab(state);
@@ -89448,7 +89477,10 @@ function openModal(otui, chrome, input) {
89448
89477
  paintHeader(state, input.title);
89449
89478
  paintFooter(state, input.footer);
89450
89479
  mountTab(state, input, resolveInitialTab(input.tabs, input.initialTab));
89451
- state.tabStrip.focus();
89480
+ const focused = chrome.renderer.currentFocusedRenderable;
89481
+ if (focused === null || !containsNode(state.scroll, focused)) {
89482
+ state.tabStrip.focus();
89483
+ }
89452
89484
  return makeHandle(state, generation);
89453
89485
  }
89454
89486
 
@@ -90760,6 +90792,10 @@ var REVIEW_FOOTER = [
90760
90792
  { key: "\u2191/\u2193", label: "scroll" },
90761
90793
  { key: "esc", label: "close" }
90762
90794
  ];
90795
+ var REVIEW_EMPTY_FOOTER = [
90796
+ { key: "\u2190/\u2192", label: "tabs" },
90797
+ { key: "esc", label: "close" }
90798
+ ];
90763
90799
  function isReviewCommand(line) {
90764
90800
  const token = line.trim().split(/\s+/)[0] ?? "";
90765
90801
  return token === REVIEW_COMMAND;
@@ -91061,6 +91097,7 @@ function presentReview(openModal, otui, chrome, options) {
91061
91097
  paintSelection();
91062
91098
  });
91063
91099
  };
91100
+ const isCompactEmpty = items.length === 0;
91064
91101
  const handle = openModal(otui, chrome, {
91065
91102
  title: REVIEW_COMMAND,
91066
91103
  tabs: [
@@ -91068,7 +91105,8 @@ function presentReview(openModal, otui, chrome, options) {
91068
91105
  { id: "detail", label: "Detail" }
91069
91106
  ],
91070
91107
  initialTab: "list",
91071
- footer: REVIEW_FOOTER,
91108
+ footer: isCompactEmpty ? REVIEW_EMPTY_FOOTER : REVIEW_FOOTER,
91109
+ ...isCompactEmpty ? { contentRows: 3 } : {},
91072
91110
  onArrowKeys: (key, direction) => {
91073
91111
  if (items[selected]?.type === "proposal" && status.kind !== "running" && status.kind !== "done" && handle?.activeTab() === "detail") {
91074
91112
  focusedAction = direction === "left" ? "accept" : "decline";
@@ -92740,14 +92778,36 @@ function describeUnavailableCommand(line, mode) {
92740
92778
  return `${command.name} is only available in ${where} \u2014 this is ${mode} mode. ` + `Type /help for the commands available here.
92741
92779
  `;
92742
92780
  }
92743
- function renderCommandHelp(mode, only) {
92781
+ function renderCommandHelp(mode, only, maxColumns) {
92744
92782
  const allow = only === undefined ? undefined : new Set(only);
92745
92783
  const options = commandsForMode(mode).filter((c) => allow === undefined || allow.has(c.name));
92746
92784
  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(`
92785
+ const descColumn = width + 4;
92786
+ const wrapBudget = maxColumns !== undefined && maxColumns - descColumn >= MIN_HELP_WRAP_COLUMNS ? maxColumns - descColumn : undefined;
92787
+ const rows = options.flatMap((c) => {
92788
+ const lines = c.description.split(`
92789
+ `).flatMap((line) => wrapBudget === undefined ? [line] : wrapWords(line, wrapBudget));
92790
+ return lines.map((line, i) => (i === 0 ? ` ${c.name.padEnd(width)} ${line}` : `${" ".repeat(descColumn)}${line}`).trimEnd());
92791
+ });
92792
+ return ["Commands:", ...rows].join(`
92748
92793
  `) + `
92749
92794
  `;
92750
92795
  }
92796
+ var MIN_HELP_WRAP_COLUMNS = 10;
92797
+ function wrapWords(line, budget) {
92798
+ const out = [];
92799
+ let cur = "";
92800
+ for (const word of line.split(/\s+/).filter(Boolean)) {
92801
+ if (cur.length > 0 && cur.length + 1 + word.length > budget) {
92802
+ out.push(cur);
92803
+ cur = word;
92804
+ } else {
92805
+ cur = cur.length > 0 ? `${cur} ${word}` : word;
92806
+ }
92807
+ }
92808
+ out.push(cur);
92809
+ return out;
92810
+ }
92751
92811
 
92752
92812
  // src/tui/theme-picker.ts
92753
92813
  function isThemeCommand(line) {
@@ -95100,6 +95160,10 @@ async function createShellChrome(otui, renderer, opts) {
95100
95160
  syncComposerHeight();
95101
95161
  const submitHandlers = new Set;
95102
95162
  const emitSubmit = (line) => {
95163
+ if (line.length > 0) {
95164
+ scroll.scrollTop = scroll.scrollHeight;
95165
+ scroll.stickyScroll = true;
95166
+ }
95103
95167
  for (const handler of [...submitHandlers]) {
95104
95168
  handler(line);
95105
95169
  }
@@ -98524,7 +98588,12 @@ function pickProviderStep(otui, r, detected) {
98524
98588
  });
98525
98589
  });
98526
98590
  }
98527
- function selectProviderModelInTui(otui, r, detected, options = {}) {
98591
+ function isModalChrome(target) {
98592
+ return target !== null && typeof target === "object" && "renderer" in target && "focusComposer" in target;
98593
+ }
98594
+ function selectProviderModelInTui(otui, rOrChrome, detected, options = {}) {
98595
+ const chrome = isModalChrome(rOrChrome) ? rOrChrome : undefined;
98596
+ const r = chrome !== undefined ? chrome.renderer : rOrChrome;
98528
98597
  return new Promise((resolve) => {
98529
98598
  if (detected.length === 0) {
98530
98599
  resolve(undefined);
@@ -98657,7 +98726,9 @@ function selectProviderModelInTui(otui, r, detected, options = {}) {
98657
98726
  models = await modelsForPicker(selectedProvider, modelDeps);
98658
98727
  }
98659
98728
  }
98660
- const model = await pickModelInTui(otui, r, models.models, modelPickerNotice(label, models));
98729
+ const model = await pickModelInTui(otui, chrome ?? r, models.models, modelPickerNotice(label, models), {
98730
+ escLabel: "back"
98731
+ });
98661
98732
  if (model === undefined) {
98662
98733
  continue;
98663
98734
  }
@@ -98671,49 +98742,120 @@ function adaptiveSelectHeight(count, available, per = 1) {
98671
98742
  const min = Math.max(1, Math.floor(available / 4));
98672
98743
  return Math.min(available, Math.max(min, count * per));
98673
98744
  }
98674
- function pickModelInTui(otui, r, models, notice) {
98745
+ function pickModelInTui(otui, rOrChrome, models, notice, options = {}) {
98746
+ const chrome = isModalChrome(rOrChrome) ? rOrChrome : undefined;
98747
+ const r = chrome !== undefined ? chrome.renderer : rOrChrome;
98748
+ const showNotice = models.length === 0 && notice !== undefined;
98749
+ const noticeText = () => new otui.TextRenderable(r, { id: "mp-notice", content: otui.t`${otui.red("\u2717")} ${notice ?? ""}` });
98750
+ const listSpec = {
98751
+ idPrefix: "mp",
98752
+ items: models,
98753
+ toOption: (m) => ({ name: m, description: "" }),
98754
+ matches: (m, q) => m.toLowerCase().includes(q),
98755
+ emptyLabel: notice ?? "(no models found)",
98756
+ filterHint: (filter, shown, total) => `filter: ${filter} (${shown}/${total})`,
98757
+ showDescription: false
98758
+ };
98759
+ const fixedRows = 1 + (showNotice ? 1 : 0);
98760
+ if (chrome !== undefined) {
98761
+ return new Promise((resolve) => {
98762
+ let chosen;
98763
+ const handle = openModal(otui, chrome, {
98764
+ title: "Select a model",
98765
+ tabs: [{ id: "models", label: "Models" }],
98766
+ footer: [
98767
+ { key: "\u2191/\u2193", label: "select" },
98768
+ { key: "Enter", label: "confirm" },
98769
+ { key: "esc", label: options.escLabel ?? "close" }
98770
+ ],
98771
+ contentRows: fixedRows + Math.max(1, models.length),
98772
+ renderTab: (_tabId, body, ctx) => {
98773
+ const parent = body;
98774
+ if (showNotice) {
98775
+ parent.add(noticeText());
98776
+ }
98777
+ const list = mountFilterList(otui, r, parent, {
98778
+ ...listSpec,
98779
+ idleHint: "type to filter by name",
98780
+ width: "100%",
98781
+ height: adaptiveSelectHeight(models.length, Math.max(1, ctx.height - fixedRows)),
98782
+ onPick: (m) => {
98783
+ chosen = m;
98784
+ handle?.close();
98785
+ }
98786
+ });
98787
+ return onKeypress4(r, list.onKey);
98788
+ },
98789
+ onClose: () => resolve(chosen)
98790
+ });
98791
+ if (handle === undefined) {
98792
+ resolve(undefined);
98793
+ }
98794
+ });
98795
+ }
98675
98796
  return new Promise((resolve) => {
98676
- const all = models;
98677
- const NO_MODELS = notice ?? "(no models found)";
98678
98797
  const box = overlayBox(otui, r, "model-picker");
98679
98798
  r.root.add(box);
98680
98799
  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}` }));
98800
+ if (showNotice) {
98801
+ box.add(noticeText());
98683
98802
  }
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
98803
  const rHeight = r.height;
98688
98804
  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",
98805
+ let unsub = () => {};
98806
+ const finish = (m) => {
98807
+ unsub();
98808
+ r.root.remove(box);
98809
+ resolve(m);
98810
+ };
98811
+ const list = mountFilterList(otui, r, box, {
98812
+ ...listSpec,
98813
+ idleHint: "type to filter \xB7 \u2191/\u2193 Enter \xB7 Esc to go back",
98692
98814
  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"
98815
+ height: adaptiveSelectHeight(models.length, available),
98816
+ onPick: finish
98699
98817
  });
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) => {
98818
+ unsub = onKeypress4(r, (key) => {
98710
98819
  if (key.name === "escape") {
98711
- cleanup();
98712
- resolve(undefined);
98820
+ finish(undefined);
98713
98821
  key.preventDefault();
98714
98822
  key.stopPropagation();
98715
98823
  return;
98716
98824
  }
98825
+ list.onKey(key);
98826
+ });
98827
+ });
98828
+ }
98829
+ function mountFilterList(otui, r, parent, spec) {
98830
+ const filterLine = new otui.TextRenderable(r, { id: `${spec.idPrefix}-filter`, content: "" });
98831
+ parent.add(filterLine);
98832
+ const sel = new otui.SelectRenderable(r, {
98833
+ id: `${spec.idPrefix}-sel`,
98834
+ width: spec.width,
98835
+ showDescription: spec.showDescription,
98836
+ height: spec.height,
98837
+ showScrollIndicator: true,
98838
+ wrapSelection: true,
98839
+ options: [],
98840
+ selectedTextColor: "#ffd166"
98841
+ });
98842
+ parent.add(sel);
98843
+ sel.focus();
98844
+ let filter = "";
98845
+ let shown = spec.items;
98846
+ const apply = () => {
98847
+ const q = filter.trim().toLowerCase();
98848
+ shown = q.length > 0 ? spec.items.filter((item) => spec.matches(item, q)) : spec.items;
98849
+ sel.options = shown.length > 0 ? shown.map(spec.toOption) : [{ name: spec.items.length === 0 ? spec.emptyLabel : "(no match)", description: "" }];
98850
+ sel.selectedIndex = 0;
98851
+ filterLine.content = otui.t`${otui.dim(q.length > 0 ? spec.filterHint(filter, shown.length, spec.items.length) : spec.idleHint)}`;
98852
+ };
98853
+ apply();
98854
+ sel.on(otui.SelectRenderableEvents.ITEM_SELECTED, () => {
98855
+ spec.onPick(shown[sel.getSelectedIndex()]);
98856
+ });
98857
+ return {
98858
+ onKey: (key) => {
98717
98859
  if (key.name === "backspace") {
98718
98860
  filter = filter.slice(0, -1);
98719
98861
  apply();
@@ -98728,113 +98870,95 @@ function pickModelInTui(otui, r, models, notice) {
98728
98870
  key.preventDefault();
98729
98871
  key.stopPropagation();
98730
98872
  }
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
- });
98873
+ }
98874
+ };
98743
98875
  }
98744
98876
  function formatSessionDate(iso) {
98745
98877
  const short = iso.trim().replace("T", " ");
98746
98878
  return short.length >= 16 ? short.slice(0, 16) : short;
98747
98879
  }
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
- };
98880
+ function pickSessionInTui(otui, rOrChrome, sessions) {
98881
+ const chrome = isModalChrome(rOrChrome) ? rOrChrome : undefined;
98882
+ const r = chrome !== undefined ? chrome.renderer : rOrChrome;
98883
+ const all = sessions.map((s) => {
98884
+ const created = formatSessionDate(s.createdAt);
98885
+ const updated = formatSessionDate(s.updatedAt);
98886
+ const short = shortSessionId(s.id);
98887
+ const title = s.title.length > 52 ? `${s.title.slice(0, 49)}\u2026` : s.title;
98888
+ return {
98889
+ value: s.id,
98890
+ label: `${short} \xB7 ${title}`,
98891
+ description: `${s.projectPath} \xB7 created ${created} \xB7 updated ${updated}`,
98892
+ search: `${s.id} ${short} ${s.projectPath} ${s.title} ${created} ${updated}`.toLowerCase()
98893
+ };
98894
+ });
98895
+ const listSpec = {
98896
+ idPrefix: "sp",
98897
+ items: all,
98898
+ toOption: (row) => ({ name: row.label, description: row.description }),
98899
+ matches: (row, q) => row.search.includes(q),
98900
+ emptyLabel: "(no match)",
98901
+ filterHint: (filter, shown) => `filter: ${filter} (${shown})`,
98902
+ showDescription: true
98903
+ };
98904
+ const ROWS_PER_SESSION = 2;
98905
+ if (chrome !== undefined) {
98906
+ return new Promise((resolve) => {
98907
+ let chosen;
98908
+ const handle = openModal(otui, chrome, {
98909
+ title: "Session Switcher",
98910
+ tabs: [{ id: "sessions", label: "Sessions" }],
98911
+ footer: [
98912
+ { key: "\u2191/\u2193", label: "select" },
98913
+ { key: "Enter", label: "open" },
98914
+ { key: "esc", label: "cancel" }
98915
+ ],
98916
+ contentRows: 1 + Math.max(1, all.length) * ROWS_PER_SESSION,
98917
+ renderTab: (_tabId, body, ctx) => {
98918
+ const list = mountFilterList(otui, r, body, {
98919
+ ...listSpec,
98920
+ idleHint: "type to filter by id, title, project, created, updated",
98921
+ width: "100%",
98922
+ height: adaptiveSelectHeight(all.length, Math.max(1, ctx.height - 1), ROWS_PER_SESSION),
98923
+ onPick: (row) => {
98924
+ chosen = row?.value;
98925
+ handle?.close();
98926
+ }
98927
+ });
98928
+ return onKeypress4(r, list.onKey);
98929
+ },
98930
+ onClose: () => resolve(chosen)
98931
+ });
98932
+ if (handle === undefined) {
98933
+ resolve(undefined);
98934
+ }
98761
98935
  });
98936
+ }
98937
+ return new Promise((resolve) => {
98762
98938
  const box = overlayBox(otui, r, "session-picker");
98763
98939
  r.root.add(box);
98764
98940
  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",
98941
+ let unsub = () => {};
98942
+ const finish = (row) => {
98943
+ unsub();
98944
+ r.root.remove(box);
98945
+ resolve(row?.value);
98946
+ };
98947
+ const list = mountFilterList(otui, r, box, {
98948
+ ...listSpec,
98949
+ idleHint: "type to filter \xB7 \u2191/\u2193 Enter \xB7 Esc to cancel",
98773
98950
  width: "100%",
98774
- showDescription: true,
98775
98951
  height: 14,
98776
- showScrollIndicator: true,
98777
- wrapSelection: true,
98778
- options: [],
98779
- selectedTextColor: "#ffd166"
98952
+ onPick: finish
98780
98953
  });
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) => {
98954
+ unsub = onKeypress4(r, (key) => {
98802
98955
  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();
98812
- key.preventDefault();
98813
- key.stopPropagation();
98814
- return;
98815
- }
98816
- const ch = key.sequence;
98817
- if (!key.ctrl && !key.meta && typeof ch === "string" && ch.length === 1 && ch >= " ") {
98818
- filter += ch;
98819
- apply();
98956
+ finish(undefined);
98820
98957
  key.preventDefault();
98821
98958
  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
98959
  return;
98835
98960
  }
98836
- const matched = matches.find((row) => row.value === chosen.value);
98837
- resolve(matched?.value);
98961
+ list.onKey(key);
98838
98962
  });
98839
98963
  });
98840
98964
  }
@@ -99554,7 +99678,12 @@ async function launchTuiAgentShell(opts) {
99554
99678
  return chosen;
99555
99679
  };
99556
99680
  setAskUserHost(askUserInteractive);
99557
- const helpText = () => renderCommandHelp("agent");
99681
+ const TRANSCRIPT_CHROME_COLS = 4;
99682
+ const HELP_MIN_COLS = 40;
99683
+ const helpText = () => {
99684
+ const mainWidth = chrome.main.width > 0 ? chrome.main.width : r.width - SIDEBAR_WIDTH;
99685
+ return renderCommandHelp("agent", undefined, Math.max(HELP_MIN_COLS, mainWidth - TRANSCRIPT_CHROME_COLS));
99686
+ };
99558
99687
  const sessionCwd = opts.session?.cwd ?? process.cwd();
99559
99688
  let permissionMode = opts.initialPermissionMode ?? getProjectPermissionMode(sessionCwd) ?? DEFAULT_PERMISSION_MODE;
99560
99689
  io.permissionMode = () => permissionMode;
@@ -99601,7 +99730,7 @@ async function launchTuiAgentShell(opts) {
99601
99730
  return;
99602
99731
  }
99603
99732
  chrome.hideMenu();
99604
- const pickId = await chrome.withOverlay(() => pickSessionInTui(otui, r, rows));
99733
+ const pickId = await chrome.withOverlay(() => pickSessionInTui(otui, chrome, rows));
99605
99734
  input.focus();
99606
99735
  if (pickId === undefined) {
99607
99736
  return;
@@ -100931,7 +101060,7 @@ Usage: /reasoning <${REASONING_EFFORT_LEVELS.join("|")}>
100931
101060
  const prov = detected.find((d) => d.name === currentSel.provider);
100932
101061
  const models = prov !== undefined ? await modelsForPicker(prov) : undefined;
100933
101062
  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));
101063
+ const chosen = await chrome.withOverlay(() => pickModelInTui(otui, chrome, models?.models ?? [], notice));
100935
101064
  if (chosen !== undefined) {
100936
101065
  await switchTo(currentSel.baseUrl === undefined ? { provider: currentSel.provider, model: chosen } : { provider: currentSel.provider, model: chosen, baseUrl: currentSel.baseUrl });
100937
101066
  try {
@@ -100968,7 +101097,7 @@ Usage: /reasoning <${REASONING_EFFORT_LEVELS.join("|")}>
100968
101097
  if (command.name === "/connect" || command.name === "/provider") {
100969
101098
  (async () => {
100970
101099
  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));
101100
+ const ns = await chrome.withOverlay(() => command.name === "/connect" ? selectProviderModelInTui(otui, chrome, detected, { onlyConnected: true, env: process.env }) : selectProviderModelInTui(otui, chrome, detected));
100972
101101
  if (ns !== undefined) {
100973
101102
  await switchTo(ns);
100974
101103
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.2.114",
3
+ "version": "0.2.116",
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": {