@mrciphersmith/keryx 0.2.34 → 0.2.36

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 (27) hide show
  1. package/dist/cli.js +735 -116
  2. package/docs/requirements/shared-agent-context/schemas/README.md +65 -0
  3. package/docs/requirements/shared-agent-context/schemas/access-receipt.schema.json +13 -0
  4. package/docs/requirements/shared-agent-context/schemas/fixtures/invalid-accepted-transition-failed-gate.json +1 -0
  5. package/docs/requirements/shared-agent-context/schemas/fixtures/invalid-accepted-transition-no-target-write.json +1 -0
  6. package/docs/requirements/shared-agent-context/schemas/fixtures/invalid-bound-work-no-flow-ref.json +1 -0
  7. package/docs/requirements/shared-agent-context/schemas/fixtures/invalid-duplicate-roles.json +1 -0
  8. package/docs/requirements/shared-agent-context/schemas/fixtures/invalid-evidence-missing-revision.json +1 -0
  9. package/docs/requirements/shared-agent-context/schemas/fixtures/invalid-proposal.json +1 -0
  10. package/docs/requirements/shared-agent-context/schemas/fixtures/invalid-resource-egress.json +1 -0
  11. package/docs/requirements/shared-agent-context/schemas/fixtures/invalid-spoofed-viewer-mutation.json +1 -0
  12. package/docs/requirements/shared-agent-context/schemas/fixtures/invalid-stale-evidence.json +1 -0
  13. package/docs/requirements/shared-agent-context/schemas/fixtures/invalid-time-order.json +1 -0
  14. package/docs/requirements/shared-agent-context/schemas/fixtures/invalid-unsafe-uri.json +1 -0
  15. package/docs/requirements/shared-agent-context/schemas/fixtures/invalid-workspace.json +1 -0
  16. package/docs/requirements/shared-agent-context/schemas/fixtures/replay-idempotency-corpus.json +1 -0
  17. package/docs/requirements/shared-agent-context/schemas/fixtures/valid-accepted-transition.json +1 -0
  18. package/docs/requirements/shared-agent-context/schemas/fixtures/valid-access-receipt.json +1 -0
  19. package/docs/requirements/shared-agent-context/schemas/fixtures/valid-fwk-receipt.json +1 -0
  20. package/docs/requirements/shared-agent-context/schemas/fixtures/valid-proposal.json +1 -0
  21. package/docs/requirements/shared-agent-context/schemas/fixtures/valid-review-decision.json +1 -0
  22. package/docs/requirements/shared-agent-context/schemas/fixtures/valid-workspace.json +1 -0
  23. package/docs/requirements/shared-agent-context/schemas/fwk-receipt.schema.json +28 -0
  24. package/docs/requirements/shared-agent-context/schemas/review-decision.schema.json +18 -0
  25. package/docs/requirements/shared-agent-context/schemas/workspace-manifest.schema.json +37 -0
  26. package/docs/requirements/shared-agent-context/schemas/workspace-proposal.schema.json +22 -0
  27. package/package.json +8 -2
package/dist/cli.js CHANGED
@@ -34772,9 +34772,10 @@ function traceRefFor(traceId) {
34772
34772
  }
34773
34773
 
34774
34774
  // src/sac/index.ts
34775
- import { readFile as readFile62, realpath as realpath3 } from "fs/promises";
34775
+ import { access as access3, readFile as readFile62, realpath as realpath3 } from "fs/promises";
34776
34776
  import { createHash as createHash10 } from "crypto";
34777
34777
  import path115 from "path";
34778
+ import { fileURLToPath as fileURLToPath6 } from "url";
34778
34779
  var idPattern = /^[a-z][a-z0-9-]{2,63}$/;
34779
34780
  var subjectPattern = /^(?:user|team|service|agent):[a-z0-9][a-z0-9._-]{0,127}$/;
34780
34781
  var revisionPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
@@ -34791,6 +34792,28 @@ var normativeSchemaFiles = {
34791
34792
  "review-decision": "review-decision.schema.json"
34792
34793
  };
34793
34794
  var normativeSchemas = new Map;
34795
+ var NORMATIVE_SCHEMA_DIR = path115.join("docs", "requirements", "shared-agent-context", "schemas");
34796
+ async function resolveSacNormativeSchemaPath(fileName, searchFrom = [fileURLToPath6(new URL(".", import.meta.url)), process.cwd()]) {
34797
+ const seen = new Set;
34798
+ for (const start of searchFrom) {
34799
+ let dir = path115.resolve(start);
34800
+ for (let i = 0;i < 10; i++) {
34801
+ if (seen.has(dir))
34802
+ break;
34803
+ seen.add(dir);
34804
+ const candidate = path115.join(dir, NORMATIVE_SCHEMA_DIR, fileName);
34805
+ try {
34806
+ await access3(candidate);
34807
+ return candidate;
34808
+ } catch {}
34809
+ const parent = path115.dirname(dir);
34810
+ if (parent === dir)
34811
+ break;
34812
+ dir = parent;
34813
+ }
34814
+ }
34815
+ throw new Error(`SAC normative schema not found: ${fileName}`);
34816
+ }
34794
34817
  function parseStrictRfc3339Utc(value) {
34795
34818
  if (typeof value !== "string")
34796
34819
  return;
@@ -34829,7 +34852,7 @@ function isRecord2(value) {
34829
34852
  async function loadNormativeSchema(schema) {
34830
34853
  let pending = normativeSchemas.get(schema);
34831
34854
  if (!pending) {
34832
- pending = readFile62(new URL(`../../docs/requirements/shared-agent-context/schemas/${normativeSchemaFiles[schema]}`, import.meta.url), "utf8").then((source) => JSON.parse(source));
34855
+ pending = resolveSacNormativeSchemaPath(normativeSchemaFiles[schema]).then((file) => readFile62(file, "utf8")).then((source) => JSON.parse(source));
34833
34856
  normativeSchemas.set(schema, pending);
34834
34857
  }
34835
34858
  return pending;
@@ -44773,6 +44796,492 @@ class LiveMarkdownBlock {
44773
44796
 
44774
44797
  // src/tui/tui-shell.ts
44775
44798
  import { spawnSync as spawnSync2 } from "child_process";
44799
+ // package.json
44800
+ var package_default = {
44801
+ name: "@mrciphersmith/keryx",
44802
+ version: "0.2.36",
44803
+ description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
44804
+ private: false,
44805
+ publishConfig: {
44806
+ access: "public"
44807
+ },
44808
+ license: "MIT",
44809
+ type: "module",
44810
+ repository: {
44811
+ type: "git",
44812
+ url: "git+ssh://git@github.com/MrCipherSmith/keryx.git"
44813
+ },
44814
+ keywords: [
44815
+ "ai-agents",
44816
+ "coding-agents",
44817
+ "agent-harness",
44818
+ "agent-context",
44819
+ "repository-context",
44820
+ "code-graph",
44821
+ "project-memory",
44822
+ "test-impact-analysis",
44823
+ "developer-tools",
44824
+ "model-context-protocol",
44825
+ "mcp",
44826
+ "claude-code",
44827
+ "cursor",
44828
+ "codex",
44829
+ "cli",
44830
+ "bun"
44831
+ ],
44832
+ bin: {
44833
+ keryx: "./dist/cli.js"
44834
+ },
44835
+ scripts: {
44836
+ keryx: "bun ./src/cli.ts",
44837
+ build: "bun build ./src/cli.ts --outdir ./dist --target bun --external @modelcontextprotocol/sdk --external web-tree-sitter --external @opentui/core && bun build ./src/harness/process/sandbox/proxy-worker.ts --outdir ./dist --target bun --external @modelcontextprotocol/sdk --external web-tree-sitter --external @opentui/core",
44838
+ prepare: "bun run build",
44839
+ typecheck: "tsc --noEmit",
44840
+ test: "bun test",
44841
+ check: "tsc --noEmit && bun test",
44842
+ "check:doc-links": "bun scripts/check-doc-links.ts",
44843
+ "test:guards": "bun test src/lib/config-dir.ast.test.ts src/lib/config-dir.readers.test.ts src/lib/production-graph.test.ts src/harness/policy/profiles.test.ts src/lib/serve-server.test.ts"
44844
+ },
44845
+ files: [
44846
+ "dist",
44847
+ "docs/requirements/shared-agent-context/schemas",
44848
+ "src/gdgraph",
44849
+ "src/gdskills/bundled",
44850
+ "src/gdskills/contracts",
44851
+ "LICENSE",
44852
+ "README.md",
44853
+ "package.json"
44854
+ ],
44855
+ dependencies: {},
44856
+ optionalDependencies: {
44857
+ "@modelcontextprotocol/sdk": "^1.0.0",
44858
+ "@opentui/core": "^0.4.5",
44859
+ "web-tree-sitter": "^0.22.0"
44860
+ },
44861
+ devDependencies: {
44862
+ "@types/bun": "latest",
44863
+ "@xenova/transformers": "^2.17.2",
44864
+ "bun-types": "latest",
44865
+ typescript: "^5"
44866
+ },
44867
+ engines: {
44868
+ bun: ">=1.1.0"
44869
+ },
44870
+ trustedDependencies: [
44871
+ "protobufjs",
44872
+ "sharp"
44873
+ ]
44874
+ };
44875
+
44876
+ // src/tui/modal-host.ts
44877
+ function onKeypress(r, handler) {
44878
+ r._internalKeyInput.onInternal("keypress", handler);
44879
+ return () => r._internalKeyInput.offInternal("keypress", handler);
44880
+ }
44881
+ var BACKDROP_ID = "modal-backdrop";
44882
+ var PANEL_ID = "modal-panel";
44883
+ var BACKDROP_OPACITY = 0.45;
44884
+ var hosts = new WeakMap;
44885
+ function clearChildren(box) {
44886
+ for (const child of [...box.getChildren()]) {
44887
+ box.remove(child);
44888
+ }
44889
+ }
44890
+ function containsNode(root, node) {
44891
+ if (root === node) {
44892
+ return true;
44893
+ }
44894
+ for (const child of root.getChildren()) {
44895
+ if (containsNode(child, node)) {
44896
+ return true;
44897
+ }
44898
+ }
44899
+ return false;
44900
+ }
44901
+ function resolveInitialTab(tabs, initialTab) {
44902
+ const first = tabs[0];
44903
+ if (first === undefined) {
44904
+ return "";
44905
+ }
44906
+ if (initialTab !== undefined && tabs.some((tab) => tab.id === initialTab)) {
44907
+ return initialTab;
44908
+ }
44909
+ return first.id;
44910
+ }
44911
+ function paintTabs(state) {
44912
+ clearChildren(state.tabStrip);
44913
+ const labels = state.tabs.map((tab) => tab.id === state.active ? `[${tab.label}]` : ` ${tab.label} `).join(" ");
44914
+ state.tabStrip.add(new state.otui.TextRenderable(state.chrome.renderer, {
44915
+ id: "modal-tabs",
44916
+ content: state.otui.t`${state.otui.dim(labels)}`
44917
+ }));
44918
+ }
44919
+ function unmountActiveTab(state) {
44920
+ const cleanup = state.tabCleanup;
44921
+ state.tabCleanup = undefined;
44922
+ if (cleanup !== undefined) {
44923
+ cleanup();
44924
+ }
44925
+ clearChildren(state.body);
44926
+ }
44927
+ function mountTab(state, input2, tabId) {
44928
+ unmountActiveTab(state);
44929
+ state.active = tabId;
44930
+ paintTabs(state);
44931
+ const cleanup = input2.renderTab(tabId, state.body);
44932
+ state.tabCleanup = typeof cleanup === "function" ? cleanup : undefined;
44933
+ }
44934
+ function closeHost(state, opts) {
44935
+ if (!state.open) {
44936
+ return;
44937
+ }
44938
+ unmountActiveTab(state);
44939
+ state.open = false;
44940
+ state.input = undefined;
44941
+ state.backdrop.visible = false;
44942
+ const onClose = state.onClose;
44943
+ state.onClose = undefined;
44944
+ if (opts.restoreFocus) {
44945
+ const scroll = state.chrome.scroll;
44946
+ if (state.savedScrollTop !== undefined && scroll !== undefined) {
44947
+ scroll.scrollTop = state.savedScrollTop;
44948
+ }
44949
+ state.chrome.focusComposer();
44950
+ }
44951
+ if (opts.runOnClose && onClose !== undefined) {
44952
+ onClose();
44953
+ }
44954
+ }
44955
+ function ensureHost(otui, chrome) {
44956
+ const existing = hosts.get(chrome.renderer);
44957
+ if (existing !== undefined) {
44958
+ return existing;
44959
+ }
44960
+ const r = chrome.renderer;
44961
+ const backdrop = new otui.BoxRenderable(r, {
44962
+ id: BACKDROP_ID,
44963
+ position: "absolute",
44964
+ top: 0,
44965
+ left: 0,
44966
+ width: "100%",
44967
+ height: "100%",
44968
+ backgroundColor: "#000000",
44969
+ opacity: BACKDROP_OPACITY,
44970
+ zIndex: 100,
44971
+ flexDirection: "column",
44972
+ justifyContent: "center",
44973
+ alignItems: "center",
44974
+ visible: false
44975
+ });
44976
+ const panel = new otui.BoxRenderable(r, {
44977
+ id: PANEL_ID,
44978
+ width: "80%",
44979
+ maxWidth: 72,
44980
+ maxHeight: "80%",
44981
+ flexShrink: 0,
44982
+ flexDirection: "column",
44983
+ borderStyle: "rounded",
44984
+ border: true,
44985
+ borderColor: "#3a4a4a",
44986
+ backgroundColor: "#0f1b1b",
44987
+ paddingLeft: 1,
44988
+ paddingRight: 1,
44989
+ zIndex: 101
44990
+ });
44991
+ const titleText = new otui.TextRenderable(r, {
44992
+ id: "modal-title",
44993
+ content: ""
44994
+ });
44995
+ const tabStrip = new otui.BoxRenderable(r, {
44996
+ id: "modal-tab-strip",
44997
+ flexShrink: 0,
44998
+ width: "100%",
44999
+ flexDirection: "row",
45000
+ focusable: true
45001
+ });
45002
+ const body = new otui.BoxRenderable(r, {
45003
+ id: "modal-body",
45004
+ width: "100%",
45005
+ minHeight: 1,
45006
+ flexDirection: "column"
45007
+ });
45008
+ panel.add(titleText);
45009
+ panel.add(tabStrip);
45010
+ panel.add(body);
45011
+ backdrop.add(panel);
45012
+ r.root.add(backdrop);
45013
+ const state = {
45014
+ otui,
45015
+ chrome,
45016
+ backdrop,
45017
+ panel,
45018
+ titleText,
45019
+ tabStrip,
45020
+ body,
45021
+ open: false,
45022
+ generation: 0,
45023
+ tabs: [],
45024
+ active: "",
45025
+ tabCleanup: undefined,
45026
+ onClose: undefined,
45027
+ input: undefined,
45028
+ releaseOverlay: undefined,
45029
+ unsubKeys: undefined,
45030
+ savedScrollTop: undefined
45031
+ };
45032
+ state.releaseOverlay = chrome.addOverlaySource(() => state.open);
45033
+ state.unsubKeys = onKeypress(r, (key) => {
45034
+ if (!state.open || state.input === undefined) {
45035
+ return;
45036
+ }
45037
+ if (key.name === "escape") {
45038
+ closeHost(state, { restoreFocus: true, runOnClose: true });
45039
+ key.preventDefault();
45040
+ key.stopPropagation();
45041
+ return;
45042
+ }
45043
+ const idx = state.tabs.findIndex((tab) => tab.id === state.active);
45044
+ const focused = r.currentFocusedRenderable;
45045
+ const onStrip = focused !== null && containsNode(state.tabStrip, focused);
45046
+ if (key.name === "left" || onStrip && key.name === "tab" && key.shift === true) {
45047
+ const prev = idx > 0 ? state.tabs[idx - 1] : undefined;
45048
+ if (prev !== undefined) {
45049
+ mountTab(state, state.input, prev.id);
45050
+ }
45051
+ key.preventDefault();
45052
+ key.stopPropagation();
45053
+ return;
45054
+ }
45055
+ if (key.name === "right" || onStrip && key.name === "tab") {
45056
+ const next = state.tabs[idx + 1];
45057
+ if (next !== undefined) {
45058
+ mountTab(state, state.input, next.id);
45059
+ }
45060
+ key.preventDefault();
45061
+ key.stopPropagation();
45062
+ return;
45063
+ }
45064
+ if (focused !== null && containsNode(state.body, focused)) {
45065
+ return;
45066
+ }
45067
+ const digit = key.sequence.length === 1 ? key.sequence : key.name;
45068
+ if (digit.length === 1 && digit >= "1" && digit <= "9") {
45069
+ const jump = state.tabs[Number(digit) - 1];
45070
+ if (jump !== undefined) {
45071
+ mountTab(state, state.input, jump.id);
45072
+ key.preventDefault();
45073
+ key.stopPropagation();
45074
+ }
45075
+ }
45076
+ });
45077
+ hosts.set(r, state);
45078
+ return state;
45079
+ }
45080
+ function makeHandle(state, generation) {
45081
+ return {
45082
+ close() {
45083
+ if (state.generation !== generation) {
45084
+ return;
45085
+ }
45086
+ closeHost(state, { restoreFocus: true, runOnClose: true });
45087
+ },
45088
+ setTab(id) {
45089
+ if (state.generation !== generation || !state.open || state.input === undefined) {
45090
+ return;
45091
+ }
45092
+ if (!state.tabs.some((tab) => tab.id === id) || id === state.active) {
45093
+ return;
45094
+ }
45095
+ mountTab(state, state.input, id);
45096
+ },
45097
+ activeTab() {
45098
+ return state.active;
45099
+ }
45100
+ };
45101
+ }
45102
+ function openModal(otui, chrome, input2) {
45103
+ if (otui === undefined || chrome === undefined || input2.tabs.length < 1) {
45104
+ return;
45105
+ }
45106
+ const state = ensureHost(otui, chrome);
45107
+ if (state.open) {
45108
+ unmountActiveTab(state);
45109
+ const previousClose = state.onClose;
45110
+ state.onClose = undefined;
45111
+ previousClose?.();
45112
+ } else {
45113
+ state.savedScrollTop = chrome.scroll.scrollTop;
45114
+ chrome.hideMenu();
45115
+ chrome.blurComposer();
45116
+ state.backdrop.visible = true;
45117
+ state.open = true;
45118
+ }
45119
+ state.generation += 1;
45120
+ const generation = state.generation;
45121
+ state.input = input2;
45122
+ state.tabs = input2.tabs;
45123
+ state.onClose = input2.onClose;
45124
+ state.titleText.content = otui.t`${otui.bold(input2.title)}`;
45125
+ mountTab(state, input2, resolveInitialTab(input2.tabs, input2.initialTab));
45126
+ state.tabStrip.focus();
45127
+ return makeHandle(state, generation);
45128
+ }
45129
+
45130
+ // src/tui/session-info.ts
45131
+ var SESSION_INFO_COMMANDS = ["/session-info", "/status", "/info"];
45132
+ var MISSING = "\u2014";
45133
+ function isSessionInfoCommand(line) {
45134
+ const token = line.trim().split(/\s+/)[0] ?? "";
45135
+ return SESSION_INFO_COMMANDS.includes(token);
45136
+ }
45137
+ function displayOrMissing(value) {
45138
+ return value !== undefined && value.length > 0 ? value : MISSING;
45139
+ }
45140
+ function formatUtc(iso) {
45141
+ if (iso === undefined || iso.length === 0) {
45142
+ return MISSING;
45143
+ }
45144
+ const date = new Date(iso);
45145
+ if (Number.isNaN(date.getTime())) {
45146
+ return MISSING;
45147
+ }
45148
+ return `${date.toISOString()} UTC`;
45149
+ }
45150
+ function formatCount(value) {
45151
+ return value === undefined ? MISSING : String(value);
45152
+ }
45153
+ function usageUsed(usage) {
45154
+ if (usage === undefined) {
45155
+ return;
45156
+ }
45157
+ if (usage.totalTokens !== undefined) {
45158
+ return usage.totalTokens;
45159
+ }
45160
+ if (usage.inputTokens === undefined && usage.outputTokens === undefined) {
45161
+ return;
45162
+ }
45163
+ return (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
45164
+ }
45165
+ function contextRow(source) {
45166
+ const used = usageUsed(source.usage);
45167
+ if (used !== undefined) {
45168
+ return `${used} tokens`;
45169
+ }
45170
+ if (source.estimateTokens !== undefined) {
45171
+ return `${source.estimateTokens} tokens (estimate)`;
45172
+ }
45173
+ return MISSING;
45174
+ }
45175
+ function buildSessionInfoSnapshot(source) {
45176
+ const id = source.summary?.id ?? "";
45177
+ const provider = source.selection?.provider ?? source.summary?.provider;
45178
+ const model = source.selection?.model ?? source.summary?.model;
45179
+ const sessionRows = [
45180
+ { label: "Title", value: displayOrMissing(source.summary?.title) },
45181
+ { label: "Version", value: displayOrMissing(source.version) },
45182
+ { label: "Session id", value: displayOrMissing(source.summary?.id) },
45183
+ { label: "Project", value: displayOrMissing(source.summary?.projectPath) },
45184
+ { label: "Provider", value: displayOrMissing(provider) },
45185
+ { label: "Model", value: displayOrMissing(model) }
45186
+ ];
45187
+ if (source.summary?.parentSessionId !== undefined && source.summary.parentSessionId.length > 0) {
45188
+ sessionRows.push({ label: "Parent", value: source.summary.parentSessionId });
45189
+ }
45190
+ const messages = source.summary?.messageCount === undefined && source.summary?.archiveMessageCount === undefined ? MISSING : `${formatCount(source.summary.messageCount)} / ${formatCount(source.summary.archiveMessageCount)}`;
45191
+ sessionRows.push({ label: "Created", value: formatUtc(source.summary?.createdAt) }, { label: "Updated", value: formatUtc(source.summary?.updatedAt) }, { label: "Messages", value: messages }, { label: "Compactions", value: formatCount(source.summary?.compactCount) }, { label: "Context", value: contextRow(source) });
45192
+ const estimate = source.estimateTokens === undefined ? MISSING : `${source.estimateTokens} tokens (estimate)`;
45193
+ const usageRows = [
45194
+ {
45195
+ label: "Last turn input",
45196
+ value: source.usage?.inputTokens === undefined ? MISSING : String(source.usage.inputTokens)
45197
+ },
45198
+ {
45199
+ label: "Last turn output",
45200
+ value: source.usage?.outputTokens === undefined ? MISSING : String(source.usage.outputTokens)
45201
+ },
45202
+ { label: "Context estimate", value: estimate }
45203
+ ];
45204
+ return { sessionId: id, sessionRows, usageRows };
45205
+ }
45206
+ function formatSection(title, rows) {
45207
+ const width = rows.reduce((max, row) => Math.max(max, row.label.length), 0);
45208
+ return [title, ...rows.map((row) => ` ${row.label.padEnd(width)} ${row.value}`)].join(`
45209
+ `);
45210
+ }
45211
+ function formatSessionInfoText(snapshot) {
45212
+ return `${formatSection("Session", snapshot.sessionRows)}
45213
+
45214
+ ${formatSection("Usage", snapshot.usageRows)}
45215
+ `;
45216
+ }
45217
+ function sessionIdCopyText(snapshot) {
45218
+ return snapshot.sessionId;
45219
+ }
45220
+ function sessionBlockCopyText(snapshot) {
45221
+ return formatSessionInfoText(snapshot);
45222
+ }
45223
+ function paintRows(otui, renderer, body, rows) {
45224
+ if (otui === undefined || otui === null || body === undefined || body === null) {
45225
+ return;
45226
+ }
45227
+ const parent = body;
45228
+ const ctor = otui.TextRenderable;
45229
+ if (parent.add === undefined || ctor === undefined) {
45230
+ return;
45231
+ }
45232
+ const width = rows.reduce((max, row) => Math.max(max, row.label.length), 0);
45233
+ parent.add(new ctor(renderer, {
45234
+ id: "session-info-body",
45235
+ content: rows.map((row) => `${row.label.padEnd(width)} ${row.value}`).join(`
45236
+ `)
45237
+ }));
45238
+ }
45239
+ function presentSessionInfo(openModal2, otui, chrome, options) {
45240
+ const { snapshot, toast } = options;
45241
+ const copy = (text) => {
45242
+ if (text.length === 0) {
45243
+ return;
45244
+ }
45245
+ try {
45246
+ options.copyText(text);
45247
+ toast("Copied to clipboard");
45248
+ } catch {}
45249
+ };
45250
+ let unsubscribeKey;
45251
+ const handle = openModal2(otui, chrome, {
45252
+ title: "Session",
45253
+ tabs: [
45254
+ { id: "session", label: "Session" },
45255
+ { id: "usage", label: "Usage" }
45256
+ ],
45257
+ initialTab: "session",
45258
+ renderTab: (tabId, body) => {
45259
+ const rows = tabId === "usage" ? snapshot.usageRows : snapshot.sessionRows;
45260
+ const renderer = options.renderer ?? chrome?.renderer;
45261
+ paintRows(otui, renderer, body, rows);
45262
+ },
45263
+ onClose: () => {
45264
+ unsubscribeKey?.();
45265
+ }
45266
+ });
45267
+ if (handle === undefined) {
45268
+ return;
45269
+ }
45270
+ if (options.onKeypress !== undefined) {
45271
+ unsubscribeKey = options.onKeypress((key) => {
45272
+ const token = key.name || key.sequence;
45273
+ if (token === "c") {
45274
+ copy(sessionIdCopyText(snapshot));
45275
+ } else if (token === "y") {
45276
+ copy(sessionBlockCopyText(snapshot));
45277
+ }
45278
+ });
45279
+ }
45280
+ return handle;
45281
+ }
45282
+ function openSessionInfo(otui, chrome, options) {
45283
+ return presentSessionInfo((hostOtui, hostChrome, input2) => openModal(hostOtui, hostChrome, input2), otui, chrome, options);
45284
+ }
44776
45285
 
44777
45286
  // src/commands/agent-commands.ts
44778
45287
  var CHAT_ONLY = ["chat"];
@@ -44800,7 +45309,7 @@ var AGENT_SLASH_COMMANDS = [
44800
45309
  modes: BOTH,
44801
45310
  modeDescriptions: {
44802
45311
  agent: "Connect to an already configured provider (interactive picker)",
44803
- chat: "Show how to set a provider API key in the environment"
45312
+ chat: "Switch to a connected provider (picker when available)"
44804
45313
  }
44805
45314
  },
44806
45315
  {
@@ -44831,6 +45340,13 @@ var AGENT_SLASH_COMMANDS = [
44831
45340
  { name: "/new", description: "Start a new session (old kept on disk)", modes: BOTH },
44832
45341
  { name: "/resume", description: "Resume a prior session in this project", modes: AGENT_ONLY },
44833
45342
  { name: "/sessions", description: "Open the session list and switch to one", modes: AGENT_ONLY },
45343
+ {
45344
+ name: "/session-info",
45345
+ description: "Show session identity and context usage",
45346
+ modes: BOTH
45347
+ },
45348
+ { name: "/status", description: "Alias of /session-info", modes: BOTH },
45349
+ { name: "/info", description: "Alias of /session-info", modes: BOTH },
44834
45350
  {
44835
45351
  name: "/compact",
44836
45352
  description: "Compact model context \u2014 /compact [focus] (archive kept)",
@@ -45311,7 +45827,7 @@ function selectBoxHeight(count, withDescription) {
45311
45827
  const per = withDescription ? 2 : 1;
45312
45828
  return Math.min(Math.max(count * per, per), 16);
45313
45829
  }
45314
- function onKeypress(r, handler) {
45830
+ function onKeypress2(r, handler) {
45315
45831
  r._internalKeyInput.onInternal("keypress", handler);
45316
45832
  return () => r._internalKeyInput.offInternal("keypress", handler);
45317
45833
  }
@@ -45412,7 +45928,7 @@ function showComposerChoice(otui, r, dock, request) {
45412
45928
  key.stopPropagation();
45413
45929
  }
45414
45930
  };
45415
- const unsub = onKeypress(r, onKey);
45931
+ const unsub = onKeypress2(r, onKey);
45416
45932
  sel.on(otui.SelectRenderableEvents.ITEM_SELECTED, () => {
45417
45933
  const chosen = sel.getSelectedOption();
45418
45934
  const value = chosen?.value;
@@ -45700,12 +46216,13 @@ async function checkVersion(options) {
45700
46216
  }
45701
46217
 
45702
46218
  // src/tui/shell-chrome.ts
45703
- function onKeypress2(r, handler) {
46219
+ function onKeypress3(r, handler) {
45704
46220
  r._internalKeyInput.onInternal("keypress", handler);
45705
46221
  return () => r._internalKeyInput.offInternal("keypress", handler);
45706
46222
  }
45707
46223
  var COMPOSER_MIN_ROWS = 1;
45708
46224
  var COMPOSER_MAX_ROWS = 6;
46225
+ var COMPOSER_BORDER_ROWS = 2;
45709
46226
  var MENU_HEIGHT = 10;
45710
46227
  var SIDEBAR_WIDTH = 30;
45711
46228
  var SIDEBAR_BORDER_LEFT = 1;
@@ -45723,9 +46240,29 @@ ${result.installCommand.slice(split)}`);
45723
46240
  var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
45724
46241
  var SPINNER_MS = 120;
45725
46242
  var TOAST_MS = 5000;
45726
- function composerHeightForLines(visualLines) {
46243
+ function composerHeightForLines(visualLines, maxRows = COMPOSER_MAX_ROWS) {
46244
+ const cap = Number.isFinite(maxRows) && maxRows >= COMPOSER_MIN_ROWS ? Math.floor(maxRows) : COMPOSER_MAX_ROWS;
45727
46245
  const n = Number.isFinite(visualLines) ? Math.floor(visualLines) : COMPOSER_MIN_ROWS;
45728
- return Math.min(COMPOSER_MAX_ROWS, Math.max(COMPOSER_MIN_ROWS, n < 1 ? COMPOSER_MIN_ROWS : n));
46246
+ return Math.min(cap, Math.max(COMPOSER_MIN_ROWS, n < 1 ? COMPOSER_MIN_ROWS : n));
46247
+ }
46248
+ function composerMaxRowsForViewport(viewportRows) {
46249
+ if (!Number.isFinite(viewportRows) || viewportRows < 1) {
46250
+ return COMPOSER_MAX_ROWS;
46251
+ }
46252
+ return Math.max(COMPOSER_MIN_ROWS, Math.floor(viewportRows / 3));
46253
+ }
46254
+ function wrappedLineCount(text, width) {
46255
+ const inner = Number.isFinite(width) ? Math.floor(width) : 0;
46256
+ const paragraphs = text.length === 0 ? [""] : text.split(`
46257
+ `);
46258
+ if (inner < 1) {
46259
+ return Math.max(COMPOSER_MIN_ROWS, paragraphs.length);
46260
+ }
46261
+ let total = 0;
46262
+ for (const paragraph of paragraphs) {
46263
+ total += Math.max(1, Math.ceil(Math.max(paragraph.length, 1) / inner));
46264
+ }
46265
+ return total;
45729
46266
  }
45730
46267
  function prefixFilter(commands, query) {
45731
46268
  const q = query.trim().toLowerCase();
@@ -45903,6 +46440,9 @@ async function createShellChrome(otui, renderer, opts) {
45903
46440
  const composer = new otui.BoxRenderable(r, {
45904
46441
  id: "composer",
45905
46442
  flexShrink: 0,
46443
+ minWidth: 0,
46444
+ width: "100%",
46445
+ flexDirection: "column",
45906
46446
  borderStyle: "rounded",
45907
46447
  border: true,
45908
46448
  paddingLeft: 1,
@@ -45912,10 +46452,12 @@ async function createShellChrome(otui, renderer, opts) {
45912
46452
  id: "prompt",
45913
46453
  placeholder: opts.placeholder,
45914
46454
  wrapMode: "word",
46455
+ minWidth: 0,
46456
+ width: "100%",
45915
46457
  minHeight: COMPOSER_MIN_ROWS,
45916
- maxHeight: COMPOSER_MAX_ROWS,
45917
46458
  height: COMPOSER_MIN_ROWS,
45918
- width: "100%",
46459
+ flexShrink: 0,
46460
+ overflow: "scroll",
45919
46461
  keyBindings: [
45920
46462
  { name: "return", action: "submit" },
45921
46463
  { name: "kpenter", action: "submit" },
@@ -45929,17 +46471,28 @@ async function createShellChrome(otui, renderer, opts) {
45929
46471
  });
45930
46472
  composer.add(textarea);
45931
46473
  main.add(composer);
46474
+ const viewportRows = () => {
46475
+ const h = r.height;
46476
+ return typeof h === "number" && h > 0 ? h : COMPOSER_MAX_ROWS * 3;
46477
+ };
45932
46478
  const syncComposerHeight = () => {
46479
+ const cap = composerMaxRowsForViewport(viewportRows());
45933
46480
  let lines = 1;
45934
46481
  try {
45935
- lines = Math.max(textarea.virtualLineCount || 0, textarea.lineCount || 0, 1);
46482
+ const wrapWidth = typeof textarea.width === "number" && textarea.width > 0 ? textarea.width : 0;
46483
+ lines = Math.max(textarea.virtualLineCount || 0, textarea.lineCount || 0, wrappedLineCount(textarea.plainText, wrapWidth), 1);
45936
46484
  } catch {
45937
- lines = Math.max(1, (textarea.plainText.match(/\n/g)?.length ?? 0) + 1);
46485
+ lines = wrappedLineCount(textarea.plainText, 0);
45938
46486
  }
45939
- const h = composerHeightForLines(lines);
46487
+ const h = composerHeightForLines(lines, cap);
45940
46488
  if (textarea.height !== h) {
45941
46489
  textarea.height = h;
45942
46490
  }
46491
+ const boxH = h + COMPOSER_BORDER_ROWS;
46492
+ if (composer.height !== boxH) {
46493
+ composer.height = boxH;
46494
+ }
46495
+ textarea.maxHeight = cap;
45943
46496
  };
45944
46497
  const input2 = {
45945
46498
  get value() {
@@ -46067,6 +46620,10 @@ async function createShellChrome(otui, renderer, opts) {
46067
46620
  syncComposerHeight();
46068
46621
  refilter();
46069
46622
  };
46623
+ const onComposerResized = () => {
46624
+ syncComposerHeight();
46625
+ };
46626
+ textarea.on(otui.LayoutEvents.RESIZED, onComposerResized);
46070
46627
  textarea.focus();
46071
46628
  syncComposerHeight();
46072
46629
  const submitHandlers = new Set;
@@ -46089,7 +46646,7 @@ async function createShellChrome(otui, renderer, opts) {
46089
46646
  syncComposerHeight();
46090
46647
  emitSubmit(line);
46091
46648
  };
46092
- const unsubscribeMenuKeys = onKeypress2(r, (key) => {
46649
+ const unsubscribeMenuKeys = onKeypress3(r, (key) => {
46093
46650
  if (!menu.visible || !menuNav || overlayActive()) {
46094
46651
  return;
46095
46652
  }
@@ -46167,6 +46724,9 @@ async function createShellChrome(otui, renderer, opts) {
46167
46724
  clearBusyTimer();
46168
46725
  clearToastTimer();
46169
46726
  unsubscribeMenuKeys();
46727
+ try {
46728
+ textarea.off(otui.LayoutEvents.RESIZED, onComposerResized);
46729
+ } catch {}
46170
46730
  try {
46171
46731
  r.off(otui.CliRenderEvents.SELECTION, onSelection);
46172
46732
  } catch {}
@@ -47266,12 +47826,8 @@ async function filterConnectedDetectedProviders(detected, options = {}) {
47266
47826
  }
47267
47827
  const requiresApiKey = registry.requiresApiKey ?? true;
47268
47828
  const envKey = prov.envKey ?? registry.envKey;
47269
- if (!requiresApiKey || envKey === undefined) {
47270
- connected.push(prov);
47271
- continue;
47272
- }
47273
- const raw = env[envKey];
47274
- if (raw === undefined || raw.length === 0) {
47829
+ const raw = envKey !== undefined ? env[envKey] : undefined;
47830
+ if (requiresApiKey && (raw === undefined || raw.length === 0)) {
47275
47831
  continue;
47276
47832
  }
47277
47833
  const compat = {
@@ -47280,13 +47836,13 @@ async function filterConnectedDetectedProviders(detected, options = {}) {
47280
47836
  ...prov.chatPath !== undefined ? { chatPath: prov.chatPath } : {},
47281
47837
  ...prov.modelsPath !== undefined ? { modelsPath: prov.modelsPath } : {}
47282
47838
  };
47283
- const result = await fetchOpenAiCompatModelsDetailed(fetchFn, compat, raw, {
47839
+ const result = await fetchOpenAiCompatModelsDetailed(fetchFn, compat, raw ?? "", {
47284
47840
  timeoutMs: MODELS_FETCH_TIMEOUT_MS
47285
47841
  });
47286
47842
  if (result.source !== "live" || result.models.length === 0) {
47287
47843
  continue;
47288
47844
  }
47289
- connected.push(prov);
47845
+ connected.push({ ...prov, models: result.models });
47290
47846
  }
47291
47847
  return connected;
47292
47848
  }
@@ -47538,7 +48094,7 @@ function overlayBox(otui, r, id) {
47538
48094
  padding: 1
47539
48095
  });
47540
48096
  }
47541
- function onKeypress3(r, handler) {
48097
+ function onKeypress4(r, handler) {
47542
48098
  r._internalKeyInput.onInternal("keypress", handler);
47543
48099
  return () => r._internalKeyInput.offInternal("keypress", handler);
47544
48100
  }
@@ -47583,7 +48139,7 @@ function promptBaseUrlStep(otui, r, label, baseUrl2) {
47583
48139
  unsub();
47584
48140
  r.root.remove(box);
47585
48141
  };
47586
- const unsub = onKeypress3(r, (key) => {
48142
+ const unsub = onKeypress4(r, (key) => {
47587
48143
  if (key.name === "escape") {
47588
48144
  cleanup();
47589
48145
  resolve3(undefined);
@@ -47619,7 +48175,7 @@ function promptApiKeyStep(otui, r, opts) {
47619
48175
  key.stopPropagation();
47620
48176
  }
47621
48177
  };
47622
- const unsub = onKeypress3(r, onKey);
48178
+ const unsub = onKeypress4(r, onKey);
47623
48179
  const cleanup = () => {
47624
48180
  unsub();
47625
48181
  r.root.remove(box);
@@ -47659,7 +48215,7 @@ function pickProviderStep(otui, r, detected) {
47659
48215
  key.stopPropagation();
47660
48216
  }
47661
48217
  };
47662
- const unsub = onKeypress3(r, onKey);
48218
+ const unsub = onKeypress4(r, onKey);
47663
48219
  const cleanup = () => {
47664
48220
  unsub();
47665
48221
  r.root.remove(box);
@@ -47693,11 +48249,11 @@ function selectProviderModelInTui(otui, r, detected, options = {}) {
47693
48249
  resolve3(undefined);
47694
48250
  return;
47695
48251
  }
47696
- const selectedBaseUrl = prov.baseUrl !== undefined ? await promptBaseUrlStep(otui, r, prov.label ?? prov.name, prov.baseUrl) : prov.baseUrl;
47697
- if (prov.baseUrl !== undefined && selectedBaseUrl === undefined) {
48252
+ const selectedBaseUrl = options.onlyConnected || prov.baseUrl === undefined ? prov.baseUrl : await promptBaseUrlStep(otui, r, prov.label ?? prov.name, prov.baseUrl);
48253
+ if (!options.onlyConnected && prov.baseUrl !== undefined && selectedBaseUrl === undefined) {
47698
48254
  continue;
47699
48255
  }
47700
- if (selectedBaseUrl !== undefined)
48256
+ if (!options.onlyConnected && selectedBaseUrl !== undefined)
47701
48257
  saveProviderBaseUrl(prov.name, selectedBaseUrl);
47702
48258
  const selectedProvider = selectedBaseUrl === undefined ? prov : { ...prov, baseUrl: selectedBaseUrl };
47703
48259
  const envKey = prov.envKey;
@@ -47777,7 +48333,7 @@ function pickModelInTui(otui, r, models) {
47777
48333
  key.stopPropagation();
47778
48334
  }
47779
48335
  };
47780
- const unsub = onKeypress3(r, onKey);
48336
+ const unsub = onKeypress4(r, onKey);
47781
48337
  const cleanup = () => {
47782
48338
  unsub();
47783
48339
  r.root.remove(box);
@@ -47869,7 +48425,7 @@ function pickSessionInTui(otui, r, sessions) {
47869
48425
  key.stopPropagation();
47870
48426
  }
47871
48427
  };
47872
- const unsub = onKeypress3(r, onKey);
48428
+ const unsub = onKeypress4(r, onKey);
47873
48429
  const cleanup = () => {
47874
48430
  unsub();
47875
48431
  r.root.remove(box);
@@ -48054,6 +48610,12 @@ async function launchTuiAgentShell(opts) {
48054
48610
  hasExactUsage = true;
48055
48611
  }
48056
48612
  });
48613
+ let lastUsage;
48614
+ const recordedUsage = io.onUsage?.bind(io);
48615
+ io.onUsage = (usage) => {
48616
+ lastUsage = usage;
48617
+ recordedUsage?.(usage);
48618
+ };
48057
48619
  attachBlockIo(io, addBlock, {
48058
48620
  onReasoning: () => {
48059
48621
  setBusyPhase("thinking");
@@ -48450,6 +49012,22 @@ Staying in the current session.
48450
49012
  `);
48451
49013
  };
48452
49014
  paintSessionHeader();
49015
+ const showSessionInfo = () => {
49016
+ const snapshot = buildSessionInfoSnapshot({
49017
+ summary: liveSession.summary,
49018
+ selection: currentSel,
49019
+ version: package_default.version,
49020
+ usage: lastUsage,
49021
+ estimateTokens: estimateContextTokens(history)
49022
+ });
49023
+ openSessionInfo(otui, chrome, {
49024
+ snapshot,
49025
+ copyText: (text) => r.copyToClipboardOSC52(text),
49026
+ toast: (message2) => chrome.showToast(message2),
49027
+ renderer: r,
49028
+ onKeypress: (handler) => onKeypress4(r, (key) => handler(key))
49029
+ });
49030
+ };
48453
49031
  const updateModelLabels = () => {
48454
49032
  paintSessionHeader();
48455
49033
  const label = `${currentSel.provider}/${currentSel.model}`;
@@ -48655,6 +49233,10 @@ Staying in the current session.
48655
49233
  `);
48656
49234
  return;
48657
49235
  }
49236
+ if (command2 !== undefined && isSessionInfoCommand(command2.name)) {
49237
+ showSessionInfo();
49238
+ return;
49239
+ }
48658
49240
  if (command2 !== undefined || line.startsWith("/")) {
48659
49241
  transcript.add(new otui.TextRenderable(r, {
48660
49242
  id: `c${uid++}`,
@@ -48796,6 +49378,10 @@ Staying in the current session.
48796
49378
  }
48797
49379
  return;
48798
49380
  }
49381
+ if (isSessionInfoCommand(command.name)) {
49382
+ showSessionInfo();
49383
+ return;
49384
+ }
48799
49385
  if (command.name === "/copy") {
48800
49386
  const target = newestBlock();
48801
49387
  if (target === undefined || !copyBlock(target.id)) {
@@ -49015,7 +49601,7 @@ Staying in the current session.
49015
49601
  focusComposer();
49016
49602
  });
49017
49603
  };
49018
- onKeypress3(r, (key) => {
49604
+ onKeypress4(r, (key) => {
49019
49605
  nav.handleKey(key);
49020
49606
  });
49021
49607
  chrome.onSubmit((line) => {
@@ -49141,6 +49727,9 @@ function createChatBridge(hooks = {}) {
49141
49727
  close();
49142
49728
  return "exit";
49143
49729
  }
49730
+ if (isSessionInfoCommand(value)) {
49731
+ return "local";
49732
+ }
49144
49733
  if ((turn || queue.length > 0) && value.startsWith("/")) {
49145
49734
  return "deferred";
49146
49735
  }
@@ -49264,6 +49853,22 @@ async function mountChatShell(otui, renderer, opts) {
49264
49853
  opts.onExit?.();
49265
49854
  return;
49266
49855
  }
49856
+ if (result === "local") {
49857
+ const snapshot = buildSessionInfoSnapshot({
49858
+ summary: opts.deps.session !== undefined ? latestSession(opts.deps.session.cwd) : undefined,
49859
+ selection,
49860
+ version: package_default.version,
49861
+ estimateTokens: estimateContextTokens(seen)
49862
+ });
49863
+ openSessionInfo(otui, chrome, {
49864
+ snapshot,
49865
+ copyText: (text) => r.copyToClipboardOSC52(text),
49866
+ toast: (message2) => chrome.showToast(message2),
49867
+ renderer: r,
49868
+ onKeypress: (handler) => onKeypress4(r, (key) => handler(key))
49869
+ });
49870
+ return;
49871
+ }
49267
49872
  if (result === "deferred") {
49268
49873
  append(otui.t`${otui.yellow("\u25C7 a reply is still streaming \u2014 command deferred. Wait for it to finish.")}`);
49269
49874
  }
@@ -49315,6 +49920,12 @@ async function launchTuiChatShell(opts) {
49315
49920
  runShell: opts.runShell,
49316
49921
  pickSelection: async (pickOpts) => {
49317
49922
  const detected = opts.redetect !== undefined ? await opts.redetect() : opts.detected;
49923
+ if (pickOpts?.onlyConnected === true) {
49924
+ return await selectProviderModelInTui(otui, r, detected, {
49925
+ onlyConnected: true,
49926
+ env: process.env
49927
+ });
49928
+ }
49318
49929
  const only = pickOpts?.onlyProvider;
49319
49930
  if (only === undefined) {
49320
49931
  return await selectProviderModelInTui(otui, r, detected);
@@ -49344,76 +49955,6 @@ async function launchTuiChatShell(opts) {
49344
49955
 
49345
49956
  // src/commands/shell.ts
49346
49957
  init_shell_config();
49347
- // package.json
49348
- var package_default = {
49349
- name: "@mrciphersmith/keryx",
49350
- version: "0.2.34",
49351
- description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
49352
- private: false,
49353
- publishConfig: {
49354
- access: "public"
49355
- },
49356
- license: "MIT",
49357
- type: "module",
49358
- repository: {
49359
- type: "git",
49360
- url: "git+ssh://git@github.com/MrCipherSmith/keryx.git"
49361
- },
49362
- keywords: [
49363
- "ai-agents",
49364
- "coding-agents",
49365
- "agent-harness",
49366
- "agent-context",
49367
- "repository-context",
49368
- "code-graph",
49369
- "project-memory",
49370
- "test-impact-analysis",
49371
- "developer-tools",
49372
- "model-context-protocol",
49373
- "mcp",
49374
- "claude-code",
49375
- "cursor",
49376
- "codex",
49377
- "cli",
49378
- "bun"
49379
- ],
49380
- bin: {
49381
- keryx: "./dist/cli.js"
49382
- },
49383
- scripts: {
49384
- keryx: "bun ./src/cli.ts",
49385
- build: "bun build ./src/cli.ts --outdir ./dist --target bun --external @modelcontextprotocol/sdk --external web-tree-sitter --external @opentui/core && bun build ./src/harness/process/sandbox/proxy-worker.ts --outdir ./dist --target bun --external @modelcontextprotocol/sdk --external web-tree-sitter --external @opentui/core",
49386
- prepare: "bun run build",
49387
- typecheck: "tsc --noEmit",
49388
- test: "bun test",
49389
- check: "tsc --noEmit && bun test",
49390
- "check:doc-links": "bun scripts/check-doc-links.ts",
49391
- "test:guards": "bun test src/lib/config-dir.ast.test.ts src/lib/config-dir.readers.test.ts src/lib/production-graph.test.ts src/harness/policy/profiles.test.ts src/lib/serve-server.test.ts"
49392
- },
49393
- files: [
49394
- "dist",
49395
- "src/gdgraph",
49396
- "src/gdskills/bundled",
49397
- "src/gdskills/contracts",
49398
- "LICENSE",
49399
- "README.md",
49400
- "package.json"
49401
- ],
49402
- dependencies: {},
49403
- optionalDependencies: {
49404
- "@modelcontextprotocol/sdk": "^1.0.0",
49405
- "@opentui/core": "^0.4.5",
49406
- "web-tree-sitter": "^0.22.0"
49407
- },
49408
- devDependencies: {
49409
- "@types/bun": "latest",
49410
- "bun-types": "latest",
49411
- typescript: "^5"
49412
- },
49413
- engines: {
49414
- bun: ">=1.1.0"
49415
- }
49416
- };
49417
49958
 
49418
49959
  // src/commands/select.ts
49419
49960
  init_guard2();
@@ -49652,6 +50193,9 @@ var READLINE_AGENT_COMMANDS = [
49652
50193
  "/new",
49653
50194
  "/clear",
49654
50195
  "/compact",
50196
+ "/session-info",
50197
+ "/status",
50198
+ "/info",
49655
50199
  "/exit"
49656
50200
  ];
49657
50201
  function readlineAgentHelpText() {
@@ -49740,6 +50284,15 @@ Starting a new session.
49740
50284
  system(HELP_TEXT);
49741
50285
  continue;
49742
50286
  }
50287
+ if (isSessionInfoCommand(command)) {
50288
+ system(formatSessionInfoText(buildSessionInfoSnapshot({
50289
+ summary: live?.summary,
50290
+ selection: { provider: providerName, model: modelName },
50291
+ version: package_default.version,
50292
+ estimateTokens: estimateContextTokens(history)
50293
+ })));
50294
+ continue;
50295
+ }
49743
50296
  if (command === "/clear" || command === "/new") {
49744
50297
  if (sessionsOn) {
49745
50298
  live = createSession({ cwd: sessionCwd, provider: providerName, model: modelName });
@@ -49809,7 +50362,12 @@ Starting a new session.
49809
50362
  continue;
49810
50363
  }
49811
50364
  if (command === "/connect") {
49812
- system(CONNECT_GUIDANCE);
50365
+ if (deps.selectProviderModel === undefined) {
50366
+ system(CONNECT_GUIDANCE);
50367
+ continue;
50368
+ }
50369
+ const picked = await deps.selectProviderModel(io, { onlyConnected: true });
50370
+ applySelection(picked);
49813
50371
  continue;
49814
50372
  }
49815
50373
  const wrongMode = describeUnavailableCommand(command, "chat");
@@ -50382,6 +50940,14 @@ New session ${shortSessionId(live.summary.id)}.
50382
50940
  }
50383
50941
  if (command === "/help") {
50384
50942
  agentIo.onSystem?.(readlineAgentHelpText());
50943
+ } else if (isSessionInfoCommand(command)) {
50944
+ agentIo.onSystem?.(formatSessionInfoText(buildSessionInfoSnapshot({
50945
+ summary: live?.summary,
50946
+ selection: { provider: deps.providerId, model: deps.modelId },
50947
+ version: package_default.version,
50948
+ usage: lastUsage,
50949
+ estimateTokens: estimateContextTokens(history)
50950
+ })));
50385
50951
  } else if (command === "/expand") {
50386
50952
  const expanded = expandedToolOutput(lastToolName, lastToolOutput);
50387
50953
  if (expanded !== undefined) {
@@ -53020,9 +53586,9 @@ function printHelp17() {
53020
53586
  // src/commands/update.ts
53021
53587
  import { spawn as spawn5 } from "child_process";
53022
53588
  import { chmod as chmod4, mkdir as mkdir52, readFile as readFile75, readdir as readdir22, writeFile as writeFile47 } from "fs/promises";
53023
- import { access as access3, constants as constants2, existsSync as existsSync29 } from "fs";
53589
+ import { access as access4, constants as constants2, existsSync as existsSync29 } from "fs";
53024
53590
  import path141 from "path";
53025
- import { fileURLToPath as fileURLToPath6 } from "url";
53591
+ import { fileURLToPath as fileURLToPath7 } from "url";
53026
53592
  init_config();
53027
53593
  init_config2();
53028
53594
  init_templates2();
@@ -54118,7 +54684,7 @@ async function runPostUpdateHooks(projectRoot) {
54118
54684
  }
54119
54685
  async function accessExecutable(filePath) {
54120
54686
  await new Promise((resolve3, reject) => {
54121
- access3(filePath, constants2.X_OK, (error2) => {
54687
+ access4(filePath, constants2.X_OK, (error2) => {
54122
54688
  if (error2) {
54123
54689
  reject(error2);
54124
54690
  return;
@@ -54166,12 +54732,12 @@ async function copyFileIfChanged2(from, to) {
54166
54732
  await writeFile47(to, next, "utf8");
54167
54733
  }
54168
54734
  function runtimeSourcePath2(relativePath) {
54169
- const directPath = fileURLToPath6(new URL(relativePath, import.meta.url));
54735
+ const directPath = fileURLToPath7(new URL(relativePath, import.meta.url));
54170
54736
  if (existsSync29(directPath)) {
54171
54737
  return directPath;
54172
54738
  }
54173
54739
  if (relativePath.startsWith("../")) {
54174
- const packagedSourcePath = path141.join(path141.dirname(fileURLToPath6(import.meta.url)), "..", "src", relativePath.slice(3));
54740
+ const packagedSourcePath = path141.join(path141.dirname(fileURLToPath7(import.meta.url)), "..", "src", relativePath.slice(3));
54175
54741
  if (existsSync29(packagedSourcePath)) {
54176
54742
  return packagedSourcePath;
54177
54743
  }
@@ -56564,6 +57130,53 @@ async function versionCommand(args2, deps = {}) {
56564
57130
  init_args();
56565
57131
  import { randomUUID as randomUUID21 } from "crypto";
56566
57132
  import { writeFile as writeFile50 } from "fs/promises";
57133
+
57134
+ // src/sac/fwk-explain.ts
57135
+ function isOverflow(result) {
57136
+ return "code" in result;
57137
+ }
57138
+ function formatFwkExplain(result) {
57139
+ if (isOverflow(result)) {
57140
+ return [`SAC explain: context_overflow (${result.code})`, "No successful manifest/receipt. Shrink the scope or raise the budget."].join(`
57141
+ `);
57142
+ }
57143
+ const facts = Array.isArray(result.manifest.facts) ? result.manifest.facts : [];
57144
+ const knowHow = Array.isArray(result.manifest.knowHow) ? result.manifest.knowHow : [];
57145
+ const work = result.manifest.work;
57146
+ const byKind = { wiki: 0, memory: 0, skill: 0, other: 0 };
57147
+ const knowHowLines = knowHow.map((item) => {
57148
+ const row = item;
57149
+ const kind = row.kind === "wiki" || row.kind === "memory" || row.kind === "skill" ? row.kind : "other";
57150
+ byKind[kind] += 1;
57151
+ return ` - ${kind} ${row.uri ?? "?"} revision=${row.revision ?? "?"} status=${row.status ?? "?"}`;
57152
+ });
57153
+ const workState = work?.state ?? "unbound";
57154
+ const lines = [
57155
+ "SAC explain (FWK \u2014 Facts / Work / Know-how)",
57156
+ ` freshness: ${result.manifest.freshness}`,
57157
+ ` receipt: ${result.receipt.id} decision=${result.receipt.decision}`,
57158
+ ` Facts (${facts.length}) \u2014 evidence-linked, task-local; not durable knowledge`,
57159
+ ...facts.map((fact) => {
57160
+ const row = fact;
57161
+ const ev = row.evidence?.[0];
57162
+ return ` - ${row.statement ?? "(no statement)"} uri=${ev?.uri ?? "?"} revision=${ev?.revision ?? "?"} freshness=${row.freshness ?? "?"}`;
57163
+ }),
57164
+ ` Work (${workState}) \u2014 Flow projection only; SAC does not write flow.json`,
57165
+ ...workState === "bound" ? [
57166
+ ` - flow=${work?.flowRef?.uri ?? "?"} snapshot=${work?.flowRef?.snapshot ?? "?"} revision=${work?.flowRef?.revision ?? "?"}`,
57167
+ ` - completed=${(work?.completed ?? []).join(",") || "(none)"} next=${(work?.next ?? []).join(",") || "(none)"} blocked=${(work?.blocked ?? []).join(",") || "(none)"}`
57168
+ ] : [" - no flow resource bound on this workspace"],
57169
+ ` Know-how (${knowHow.length}: wiki=${byKind.wiki} memory=${byKind.memory} skill=${byKind.skill}) \u2014 references to owning stores, not SAC copies`,
57170
+ ...knowHowLines.length > 0 ? knowHowLines : [" - (none accepted/visible in this budget)"],
57171
+ " Not written here: graph nodes/edges (navigation only), session transcripts, hidden reasoning."
57172
+ ];
57173
+ if (result.partial)
57174
+ lines.push(` partial: omitted optional ${result.omittedOptional.join(", ") || "(none)"}`);
57175
+ return lines.join(`
57176
+ `);
57177
+ }
57178
+
57179
+ // src/commands/workspace.ts
56567
57180
  var PROPOSAL_KINDS = ["decision", "wiki-update", "memory-entry", "follow-up", "contract-change", "risk"];
56568
57181
  function service4() {
56569
57182
  return new WorkspaceService({
@@ -56612,30 +57225,36 @@ async function workspaceCommand(args2) {
56612
57225
  return;
56613
57226
  }
56614
57227
  if (subcommand === "overview") {
56615
- rejectUnknownOptions(args2.slice(2), new Set(["--max-items", "--max-tokens"]));
57228
+ rejectUnknownOptions(args2.slice(2), new Set(["--max-items", "--max-tokens", "--explain"]));
56616
57229
  const workspaceId = args2[1];
56617
57230
  if (!workspaceId)
56618
- throw new Error("Usage: keryx workspace overview <workspace-id> [--max-items N] [--max-tokens N]");
57231
+ throw new Error("Usage: keryx workspace overview <workspace-id> [--max-items N] [--max-tokens N] [--explain]");
56619
57232
  const maxItems = Number(optionValue(args2, "--max-items") ?? "32");
56620
57233
  const maxTokens = Number(optionValue(args2, "--max-tokens") ?? "4096");
56621
57234
  if (!Number.isInteger(maxItems) || !Number.isInteger(maxTokens) || maxItems < 0 || maxTokens < 0)
56622
57235
  throw new Error("--max-items and --max-tokens must be non-negative integers");
56623
57236
  const result = await createLocalFwkReadService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID21(), budget: { maxItems, maxTokens } });
56624
- console.log(JSON.stringify(normalizeFwkResult(result), null, 2));
57237
+ const normalized = normalizeFwkResult(result);
57238
+ console.log(JSON.stringify(normalized, null, 2));
57239
+ if (args2.includes("--explain"))
57240
+ console.error(formatFwkExplain(normalized));
56625
57241
  return;
56626
57242
  }
56627
57243
  if (subcommand === "read") {
56628
- rejectUnknownOptions(args2.slice(3), new Set(["--max-items", "--max-tokens"]));
57244
+ rejectUnknownOptions(args2.slice(3), new Set(["--max-items", "--max-tokens", "--explain"]));
56629
57245
  const workspaceId = args2[1];
56630
57246
  const itemId = args2[2];
56631
57247
  if (!workspaceId || !itemId)
56632
- throw new Error("Usage: keryx workspace read <workspace-id> <item-id> [--max-items N] [--max-tokens N]");
57248
+ throw new Error("Usage: keryx workspace read <workspace-id> <item-id> [--max-items N] [--max-tokens N] [--explain]");
56633
57249
  const maxItems = Number(optionValue(args2, "--max-items") ?? "1");
56634
57250
  const maxTokens = Number(optionValue(args2, "--max-tokens") ?? "4096");
56635
57251
  if (!Number.isInteger(maxItems) || !Number.isInteger(maxTokens) || maxItems < 0 || maxTokens < 0)
56636
57252
  throw new Error("--max-items and --max-tokens must be non-negative integers");
56637
57253
  const result = await createLocalFwkReadService(process.cwd()).read({ workspaceId, itemId, request: undefined, requestCorrelationId: randomUUID21(), budget: { maxItems, maxTokens } });
56638
- console.log(JSON.stringify(normalizeFwkResult(result), null, 2));
57254
+ const normalized = normalizeFwkResult(result);
57255
+ console.log(JSON.stringify(normalized, null, 2));
57256
+ if (args2.includes("--explain"))
57257
+ console.error(formatFwkExplain(normalized));
56639
57258
  return;
56640
57259
  }
56641
57260
  if (subcommand === "propose") {
@@ -56714,8 +57333,8 @@ function printHelp20() {
56714
57333
  keryx workspace list
56715
57334
  keryx workspace show <workspace-id>
56716
57335
  keryx workspace add-resource <workspace-id> --kind <kind> --uri <workspace-relative-ref> [--revision <revision>]
56717
- keryx workspace overview <workspace-id> [--max-items N] [--max-tokens N]
56718
- keryx workspace read <workspace-id> <item-id> [--max-items N] [--max-tokens N]
57336
+ keryx workspace overview <workspace-id> [--max-items N] [--max-tokens N] [--explain]
57337
+ keryx workspace read <workspace-id> <item-id> [--max-items N] [--max-tokens N] [--explain]
56719
57338
  keryx workspace propose <workspace-id> --kind <` + PROPOSAL_KINDS.join("|") + `> --session <session-id> [--note <one-line note>]
56720
57339
  keryx workspace review <workspace-id> <proposal-id> --decision <accepted|rejected|dismissed> [--reason <reason>] [--idempotency-key <key>]
56721
57340
  keryx workspace collaboration <workspace-id>