@mrciphersmith/keryx 0.2.35 → 0.2.37

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 +1091 -114
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -41726,7 +41726,7 @@ function harnessWave(args, deps) {
41726
41726
  // src/commands/shell.ts
41727
41727
  init_make_provider();
41728
41728
  init_orient();
41729
- import { randomUUID as randomUUID17 } from "crypto";
41729
+ import { randomUUID as randomUUID18 } from "crypto";
41730
41730
  import * as readline2 from "readline";
41731
41731
 
41732
41732
  // src/harness/tool/builtin/ask-user-tool.ts
@@ -44796,6 +44796,906 @@ class LiveMarkdownBlock {
44796
44796
 
44797
44797
  // src/tui/tui-shell.ts
44798
44798
  import { spawnSync as spawnSync2 } from "child_process";
44799
+ // package.json
44800
+ var package_default = {
44801
+ name: "@mrciphersmith/keryx",
44802
+ version: "0.2.37",
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 MODAL_PANEL_WIDTH = 72;
44885
+ var MODAL_PANEL_HEIGHT = 18;
44886
+ var MODAL_PANEL_INNER_WIDTH = MODAL_PANEL_WIDTH - 4;
44887
+ var CLOSE_HINT = "[x] esc";
44888
+ var DEFAULT_FOOTER = [
44889
+ { key: "\u2190/\u2192", label: "tabs" },
44890
+ { key: "esc", label: "close" }
44891
+ ];
44892
+ function formatModalFooter(actions) {
44893
+ return actions.map((action) => `${action.key} ${action.label}`).join(" \xB7 ");
44894
+ }
44895
+ var hosts = new WeakMap;
44896
+ function clearChildren(box) {
44897
+ for (const child of [...box.getChildren()]) {
44898
+ box.remove(child);
44899
+ }
44900
+ }
44901
+ function containsNode(root, node) {
44902
+ if (root === node) {
44903
+ return true;
44904
+ }
44905
+ for (const child of root.getChildren()) {
44906
+ if (containsNode(child, node)) {
44907
+ return true;
44908
+ }
44909
+ }
44910
+ return false;
44911
+ }
44912
+ function resolveInitialTab(tabs, initialTab) {
44913
+ const first = tabs[0];
44914
+ if (first === undefined) {
44915
+ return "";
44916
+ }
44917
+ if (initialTab !== undefined && tabs.some((tab) => tab.id === initialTab)) {
44918
+ return initialTab;
44919
+ }
44920
+ return first.id;
44921
+ }
44922
+ function paintTabs(state) {
44923
+ clearChildren(state.tabStrip);
44924
+ const labels = state.tabs.map((tab) => tab.id === state.active ? `[${tab.label}]` : ` ${tab.label} `).join(" ");
44925
+ state.tabStrip.add(new state.otui.TextRenderable(state.chrome.renderer, {
44926
+ id: "modal-tabs",
44927
+ content: state.otui.t`${state.otui.dim(labels)}`
44928
+ }));
44929
+ }
44930
+ function paintHeader(state, title) {
44931
+ state.titleText.content = state.otui.t`${state.otui.bold(title)}`;
44932
+ state.closeText.content = state.otui.t`${state.otui.dim(CLOSE_HINT)}`;
44933
+ }
44934
+ function paintFooter(state, actions) {
44935
+ const items = actions !== undefined && actions.length > 0 ? actions : DEFAULT_FOOTER;
44936
+ state.footerText.content = state.otui.t`${state.otui.dim(formatModalFooter(items))}`;
44937
+ }
44938
+ function unmountActiveTab(state) {
44939
+ const cleanup = state.tabCleanup;
44940
+ state.tabCleanup = undefined;
44941
+ if (cleanup !== undefined) {
44942
+ cleanup();
44943
+ }
44944
+ clearChildren(state.body);
44945
+ }
44946
+ function mountTab(state, input2, tabId) {
44947
+ unmountActiveTab(state);
44948
+ state.active = tabId;
44949
+ paintTabs(state);
44950
+ const cleanup = input2.renderTab(tabId, state.body);
44951
+ state.tabCleanup = typeof cleanup === "function" ? cleanup : undefined;
44952
+ }
44953
+ function closeHost(state, opts) {
44954
+ if (!state.open) {
44955
+ return;
44956
+ }
44957
+ unmountActiveTab(state);
44958
+ state.open = false;
44959
+ state.input = undefined;
44960
+ state.backdrop.visible = false;
44961
+ const onClose = state.onClose;
44962
+ state.onClose = undefined;
44963
+ if (opts.restoreFocus) {
44964
+ const scroll = state.chrome.scroll;
44965
+ if (state.savedScrollTop !== undefined && scroll !== undefined) {
44966
+ scroll.scrollTop = state.savedScrollTop;
44967
+ }
44968
+ state.chrome.focusComposer();
44969
+ }
44970
+ if (opts.runOnClose && onClose !== undefined) {
44971
+ onClose();
44972
+ }
44973
+ }
44974
+ function ensureHost(otui, chrome) {
44975
+ const existing = hosts.get(chrome.renderer);
44976
+ if (existing !== undefined) {
44977
+ return existing;
44978
+ }
44979
+ const r = chrome.renderer;
44980
+ const backdrop = new otui.BoxRenderable(r, {
44981
+ id: BACKDROP_ID,
44982
+ position: "absolute",
44983
+ top: 0,
44984
+ left: 0,
44985
+ width: "100%",
44986
+ height: "100%",
44987
+ backgroundColor: "#000000",
44988
+ opacity: BACKDROP_OPACITY,
44989
+ zIndex: 100,
44990
+ flexDirection: "column",
44991
+ justifyContent: "center",
44992
+ alignItems: "center",
44993
+ visible: false
44994
+ });
44995
+ const panel = new otui.BoxRenderable(r, {
44996
+ id: PANEL_ID,
44997
+ width: MODAL_PANEL_WIDTH,
44998
+ height: MODAL_PANEL_HEIGHT,
44999
+ flexShrink: 0,
45000
+ flexGrow: 0,
45001
+ flexDirection: "column",
45002
+ borderStyle: "rounded",
45003
+ border: true,
45004
+ borderColor: "#3a4a4a",
45005
+ backgroundColor: "#0f1b1b",
45006
+ paddingLeft: 1,
45007
+ paddingRight: 1,
45008
+ zIndex: 101
45009
+ });
45010
+ const header3 = new otui.BoxRenderable(r, {
45011
+ id: "modal-header",
45012
+ width: "100%",
45013
+ height: 1,
45014
+ flexShrink: 0,
45015
+ flexDirection: "row",
45016
+ justifyContent: "space-between"
45017
+ });
45018
+ const titleText = new otui.TextRenderable(r, {
45019
+ id: "modal-title",
45020
+ content: "",
45021
+ flexGrow: 1
45022
+ });
45023
+ const closeText = new otui.TextRenderable(r, {
45024
+ id: "modal-close",
45025
+ content: "",
45026
+ flexShrink: 0
45027
+ });
45028
+ header3.add(titleText);
45029
+ header3.add(closeText);
45030
+ const tabStrip = new otui.BoxRenderable(r, {
45031
+ id: "modal-tab-strip",
45032
+ flexShrink: 0,
45033
+ width: "100%",
45034
+ height: 1,
45035
+ flexDirection: "row",
45036
+ focusable: true
45037
+ });
45038
+ const body = new otui.BoxRenderable(r, {
45039
+ id: "modal-body",
45040
+ width: "100%",
45041
+ flexGrow: 1,
45042
+ minHeight: 1,
45043
+ flexDirection: "column"
45044
+ });
45045
+ const footer = new otui.BoxRenderable(r, {
45046
+ id: "modal-footer",
45047
+ width: "100%",
45048
+ height: 1,
45049
+ flexShrink: 0,
45050
+ flexDirection: "row"
45051
+ });
45052
+ const footerText = new otui.TextRenderable(r, {
45053
+ id: "modal-footer-text",
45054
+ content: ""
45055
+ });
45056
+ footer.add(footerText);
45057
+ panel.add(header3);
45058
+ panel.add(tabStrip);
45059
+ panel.add(body);
45060
+ panel.add(footer);
45061
+ backdrop.add(panel);
45062
+ r.root.add(backdrop);
45063
+ const state = {
45064
+ otui,
45065
+ chrome,
45066
+ backdrop,
45067
+ panel,
45068
+ header: header3,
45069
+ titleText,
45070
+ closeText,
45071
+ tabStrip,
45072
+ body,
45073
+ footer,
45074
+ footerText,
45075
+ open: false,
45076
+ generation: 0,
45077
+ tabs: [],
45078
+ active: "",
45079
+ tabCleanup: undefined,
45080
+ onClose: undefined,
45081
+ input: undefined,
45082
+ releaseOverlay: undefined,
45083
+ unsubKeys: undefined,
45084
+ savedScrollTop: undefined
45085
+ };
45086
+ state.releaseOverlay = chrome.addOverlaySource(() => state.open);
45087
+ state.unsubKeys = onKeypress(r, (key) => {
45088
+ if (!state.open || state.input === undefined) {
45089
+ return;
45090
+ }
45091
+ if (key.name === "escape" || key.name === "x" || key.sequence === "x") {
45092
+ closeHost(state, { restoreFocus: true, runOnClose: true });
45093
+ key.preventDefault();
45094
+ key.stopPropagation();
45095
+ return;
45096
+ }
45097
+ const idx = state.tabs.findIndex((tab) => tab.id === state.active);
45098
+ const focused = r.currentFocusedRenderable;
45099
+ const onStrip = focused !== null && containsNode(state.tabStrip, focused);
45100
+ if (key.name === "left" || onStrip && key.name === "tab" && key.shift === true) {
45101
+ const prev = idx > 0 ? state.tabs[idx - 1] : undefined;
45102
+ if (prev !== undefined) {
45103
+ mountTab(state, state.input, prev.id);
45104
+ }
45105
+ key.preventDefault();
45106
+ key.stopPropagation();
45107
+ return;
45108
+ }
45109
+ if (key.name === "right" || onStrip && key.name === "tab") {
45110
+ const next = state.tabs[idx + 1];
45111
+ if (next !== undefined) {
45112
+ mountTab(state, state.input, next.id);
45113
+ }
45114
+ key.preventDefault();
45115
+ key.stopPropagation();
45116
+ return;
45117
+ }
45118
+ if (focused !== null && containsNode(state.body, focused)) {
45119
+ return;
45120
+ }
45121
+ const digit = key.sequence.length === 1 ? key.sequence : key.name;
45122
+ if (digit.length === 1 && digit >= "1" && digit <= "9") {
45123
+ const jump = state.tabs[Number(digit) - 1];
45124
+ if (jump !== undefined) {
45125
+ mountTab(state, state.input, jump.id);
45126
+ key.preventDefault();
45127
+ key.stopPropagation();
45128
+ }
45129
+ }
45130
+ });
45131
+ hosts.set(r, state);
45132
+ return state;
45133
+ }
45134
+ function makeHandle(state, generation) {
45135
+ return {
45136
+ close() {
45137
+ if (state.generation !== generation) {
45138
+ return;
45139
+ }
45140
+ closeHost(state, { restoreFocus: true, runOnClose: true });
45141
+ },
45142
+ setTab(id) {
45143
+ if (state.generation !== generation || !state.open || state.input === undefined) {
45144
+ return;
45145
+ }
45146
+ if (!state.tabs.some((tab) => tab.id === id) || id === state.active) {
45147
+ return;
45148
+ }
45149
+ mountTab(state, state.input, id);
45150
+ },
45151
+ activeTab() {
45152
+ return state.active;
45153
+ }
45154
+ };
45155
+ }
45156
+ function openModal(otui, chrome, input2) {
45157
+ if (otui === undefined || chrome === undefined || input2.tabs.length < 1) {
45158
+ return;
45159
+ }
45160
+ const state = ensureHost(otui, chrome);
45161
+ if (state.open) {
45162
+ unmountActiveTab(state);
45163
+ const previousClose = state.onClose;
45164
+ state.onClose = undefined;
45165
+ previousClose?.();
45166
+ } else {
45167
+ state.savedScrollTop = chrome.scroll.scrollTop;
45168
+ chrome.hideMenu();
45169
+ chrome.blurComposer();
45170
+ state.backdrop.visible = true;
45171
+ state.open = true;
45172
+ }
45173
+ state.generation += 1;
45174
+ const generation = state.generation;
45175
+ state.input = input2;
45176
+ state.tabs = input2.tabs;
45177
+ state.onClose = input2.onClose;
45178
+ paintHeader(state, input2.title);
45179
+ paintFooter(state, input2.footer);
45180
+ mountTab(state, input2, resolveInitialTab(input2.tabs, input2.initialTab));
45181
+ state.tabStrip.focus();
45182
+ return makeHandle(state, generation);
45183
+ }
45184
+
45185
+ // src/tui/flow-inspector.ts
45186
+ var FLOWS_COMMAND = "/flows";
45187
+ var FLOWS_FOOTER = [
45188
+ { key: "\u2191/\u2193", label: "select" },
45189
+ { key: "\u2190/\u2192", label: "tabs" },
45190
+ { key: "esc", label: "close" }
45191
+ ];
45192
+ function isFlowsCommand(line) {
45193
+ const token = line.trim().split(/\s+/)[0] ?? "";
45194
+ return token === FLOWS_COMMAND;
45195
+ }
45196
+ function findFlowItem(items, query) {
45197
+ const needle = query.trim();
45198
+ if (needle.length === 0) {
45199
+ return;
45200
+ }
45201
+ const padded = /^\d+$/.test(needle) ? needle.padStart(3, "0") : needle;
45202
+ return items.find((item) => item.id === needle || item.id === padded || item.dir === needle || item.dir.endsWith(`/${needle}`)) ?? items.find((item) => item.slug === needle);
45203
+ }
45204
+ function formatFlowListLines(items, selected) {
45205
+ if (items.length === 0) {
45206
+ return ["No flows in this project."];
45207
+ }
45208
+ return items.map((item, index) => {
45209
+ const mark = index === selected ? ">" : " ";
45210
+ return `${mark} ${item.id} ${item.status} ${item.tasksDone}/${item.tasksTotal} ${item.title}`;
45211
+ });
45212
+ }
45213
+ function formatFlowDetailLines(item) {
45214
+ const taskLines = item.tasks.length === 0 ? [" (no tasks)"] : item.tasks.map((task) => ` ${task.id} ${task.status} ${task.title}`);
45215
+ return [
45216
+ `${item.id} ${item.title}`,
45217
+ `Status ${item.status}`,
45218
+ `Dir ${item.dir}`,
45219
+ `Tasks ${item.tasksDone}/${item.tasksTotal}`,
45220
+ `PR ${item.prUrl ?? "\u2014"}`,
45221
+ `Source ${item.source}`,
45222
+ `Created ${item.createdAt}`,
45223
+ `Updated ${item.updatedAt}`,
45224
+ "",
45225
+ "Tasks",
45226
+ ...taskLines
45227
+ ];
45228
+ }
45229
+ function formatFlowListText(items) {
45230
+ if (items.length === 0) {
45231
+ return `Flows
45232
+ No flows in this project.
45233
+ `;
45234
+ }
45235
+ return [
45236
+ "Flows",
45237
+ ...items.map((item) => ` ${item.id} ${item.status} ${item.tasksDone}/${item.tasksTotal} ${item.title}`),
45238
+ ""
45239
+ ].join(`
45240
+ `);
45241
+ }
45242
+ function formatFlowDetailText(item) {
45243
+ return `${formatFlowDetailLines(item).join(`
45244
+ `)}
45245
+ `;
45246
+ }
45247
+ function paintLines(otui, renderer, body, lines) {
45248
+ if (otui === undefined || otui === null || body === undefined || body === null) {
45249
+ return;
45250
+ }
45251
+ const parent = body;
45252
+ const ctor = otui.TextRenderable;
45253
+ if (parent.add === undefined || ctor === undefined) {
45254
+ return;
45255
+ }
45256
+ const node = new ctor(renderer, { id: "flows-body", content: lines.join(`
45257
+ `) });
45258
+ parent.add(node);
45259
+ return node;
45260
+ }
45261
+ function presentFlows(openModal2, otui, chrome, options) {
45262
+ const items = options.items;
45263
+ let selected = 0;
45264
+ let listNode;
45265
+ let detailNode;
45266
+ let unsubscribeKey;
45267
+ const paintSelection = () => {
45268
+ if (listNode !== undefined) {
45269
+ listNode.content = formatFlowListLines(items, selected).join(`
45270
+ `);
45271
+ }
45272
+ if (detailNode !== undefined) {
45273
+ const item = items[selected];
45274
+ detailNode.content = item !== undefined ? formatFlowDetailLines(item).join(`
45275
+ `) : "No flow selected.";
45276
+ }
45277
+ };
45278
+ const handle = openModal2(otui, chrome, {
45279
+ title: "/flows",
45280
+ tabs: [
45281
+ { id: "list", label: "Flows" },
45282
+ { id: "detail", label: "Detail" }
45283
+ ],
45284
+ initialTab: "list",
45285
+ footer: FLOWS_FOOTER,
45286
+ renderTab: (tabId, body) => {
45287
+ const renderer = options.renderer ?? chrome?.renderer;
45288
+ if (tabId === "list") {
45289
+ listNode = paintLines(otui, renderer, body, formatFlowListLines(items, selected));
45290
+ return;
45291
+ }
45292
+ const item = items[selected];
45293
+ detailNode = paintLines(otui, renderer, body, item !== undefined ? formatFlowDetailLines(item) : ["No flow selected."]);
45294
+ },
45295
+ onClose: () => {
45296
+ unsubscribeKey?.();
45297
+ }
45298
+ });
45299
+ if (handle === undefined) {
45300
+ return;
45301
+ }
45302
+ if (options.onKeypress !== undefined) {
45303
+ unsubscribeKey = options.onKeypress((key) => {
45304
+ const token = key.name || key.sequence;
45305
+ if (items.length === 0) {
45306
+ return;
45307
+ }
45308
+ if (token === "up" || token === "k") {
45309
+ selected = selected > 0 ? selected - 1 : 0;
45310
+ paintSelection();
45311
+ } else if (token === "down" || token === "j") {
45312
+ selected = selected < items.length - 1 ? selected + 1 : selected;
45313
+ paintSelection();
45314
+ } else if (token === "return" || token === "enter") {
45315
+ handle.setTab("detail");
45316
+ }
45317
+ });
45318
+ }
45319
+ return handle;
45320
+ }
45321
+ function openFlows(otui, chrome, options) {
45322
+ return presentFlows((hostOtui, hostChrome, input2) => openModal(hostOtui, hostChrome, input2), otui, chrome, options);
45323
+ }
45324
+
45325
+ // src/tui/inspector-sources.ts
45326
+ init_store2();
45327
+ import { randomUUID as randomUUID17 } from "crypto";
45328
+ function workspaceFromManifest(manifest) {
45329
+ return {
45330
+ id: manifest.id,
45331
+ title: manifest.title,
45332
+ status: manifest.status,
45333
+ resources: manifest.resources.map((resource) => ({ kind: resource.kind, uri: resource.uri }))
45334
+ };
45335
+ }
45336
+ function workspacesInSession(workspaces, opts) {
45337
+ const text = opts.sessionText ?? "";
45338
+ const sessionId = opts.sessionId ?? "";
45339
+ return workspaces.filter((workspace) => {
45340
+ if (text.includes(workspace.id)) {
45341
+ return true;
45342
+ }
45343
+ return workspace.resources.some((resource) => resource.kind === "session" && sessionId.length > 0 && resource.uri.includes(sessionId));
45344
+ });
45345
+ }
45346
+ function explicitFlowMention(item, text) {
45347
+ if (text.length === 0) {
45348
+ return false;
45349
+ }
45350
+ const id = item.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45351
+ const slug2 = item.slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45352
+ const pattern = new RegExp(`(?:flow\\s+#?|/flows\\s+|\\.metaproject/flows/)${id}\\b` + (slug2.length > 0 ? `|(?:flow\\s+)${slug2}\\b` : ""), "i");
45353
+ return pattern.test(text);
45354
+ }
45355
+ function flowsInSession(flows, opts) {
45356
+ const sessionId = opts.sessionId ?? "";
45357
+ const text = opts.sessionText ?? "";
45358
+ return flows.filter((flow) => {
45359
+ if (sessionId.length > 0 && flow.sessionIds.includes(sessionId)) {
45360
+ return true;
45361
+ }
45362
+ return explicitFlowMention(flow, text);
45363
+ });
45364
+ }
45365
+ function flowItemFromState(flow, dir) {
45366
+ const sessionIds = [
45367
+ ...new Set(flow.tasks.map((task) => task.runLink?.sessionId).filter((id) => typeof id === "string" && id.length > 0))
45368
+ ];
45369
+ return {
45370
+ id: flow.id,
45371
+ slug: flow.slug,
45372
+ title: flow.title,
45373
+ status: flow.status,
45374
+ dir: `.metaproject/flows/${dir}`,
45375
+ tasksDone: flow.tasks.filter((task) => task.status === "done").length,
45376
+ tasksTotal: flow.tasks.length,
45377
+ sessionIds,
45378
+ prUrl: flow.pr.url,
45379
+ createdAt: flow.createdAt,
45380
+ updatedAt: flow.updatedAt,
45381
+ source: flow.source.ref ?? flow.source.type,
45382
+ tasks: flow.tasks.map((task) => ({ id: task.id, title: task.title, status: task.status }))
45383
+ };
45384
+ }
45385
+ async function loadInspectorWorkspaces(cwd) {
45386
+ try {
45387
+ const service4 = new WorkspaceService({
45388
+ workspaceRoot: cwd,
45389
+ authorizationServer: localWorkspaceAuthorizationServer(),
45390
+ strictGuard: {
45391
+ mode: "strict",
45392
+ availability: "available",
45393
+ decision: "pass",
45394
+ policyRevision: "local-offline-v1"
45395
+ }
45396
+ });
45397
+ const listed = await service4.list({ request: undefined, requestCorrelationId: randomUUID17() });
45398
+ return listed.map(workspaceFromManifest);
45399
+ } catch {
45400
+ return [];
45401
+ }
45402
+ }
45403
+ async function loadInspectorFlows(cwd) {
45404
+ try {
45405
+ const dirs = await listFlowDirs(cwd);
45406
+ const items = [];
45407
+ for (const dir of dirs) {
45408
+ try {
45409
+ items.push(flowItemFromState(await readFlow(cwd, dir), dir));
45410
+ } catch {}
45411
+ }
45412
+ return items;
45413
+ } catch {
45414
+ return [];
45415
+ }
45416
+ }
45417
+ function formatWorkspaceLines(workspaces) {
45418
+ if (workspaces.length === 0) {
45419
+ return ["No workspaces recorded in this session."];
45420
+ }
45421
+ const width = workspaces.reduce((max, workspace) => Math.max(max, workspace.id.length), 0);
45422
+ return workspaces.map((workspace) => `${workspace.id.padEnd(width)} ${workspace.status} ${workspace.title}`);
45423
+ }
45424
+ function formatSessionFlowLines(flows) {
45425
+ if (flows.length === 0) {
45426
+ return ["No flows recorded in this session."];
45427
+ }
45428
+ return flows.map((flow) => `${flow.id} ${flow.status} ${flow.tasksDone}/${flow.tasksTotal} ${flow.title}`);
45429
+ }
45430
+
45431
+ // src/tui/context-usage.ts
45432
+ var CONTEXT_BAR_WIDTH = 28;
45433
+ function lastTurnUsed(usage) {
45434
+ if (usage === undefined) {
45435
+ return;
45436
+ }
45437
+ if (usage.totalTokens !== undefined) {
45438
+ return usage.totalTokens;
45439
+ }
45440
+ if (usage.inputTokens === undefined && usage.outputTokens === undefined) {
45441
+ return;
45442
+ }
45443
+ return (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
45444
+ }
45445
+ function renderUsageBar(filled, width = CONTEXT_BAR_WIDTH) {
45446
+ const n = Math.max(0, Math.min(width, filled > 0 ? width : 0));
45447
+ return `[${"\u2588".repeat(n)}${"\u2591".repeat(width - n)}]`;
45448
+ }
45449
+ function buildContextUsage(source) {
45450
+ const lastIn = source.usage?.inputTokens;
45451
+ const lastOut = source.usage?.outputTokens;
45452
+ const last = lastTurnUsed(source.usage);
45453
+ const hasEstimate = source.estimateTokens !== undefined;
45454
+ const total = hasEstimate ? source.estimateTokens ?? 0 : last ?? 0;
45455
+ const estimated = hasEstimate || last === undefined;
45456
+ const segments2 = [];
45457
+ if (hasEstimate) {
45458
+ segments2.push({ id: "history", label: "history (est.)", tokens: source.estimateTokens ?? 0 });
45459
+ }
45460
+ if (lastIn !== undefined) {
45461
+ segments2.push({ id: "last-in", label: "last in", tokens: lastIn });
45462
+ }
45463
+ if (lastOut !== undefined) {
45464
+ segments2.push({ id: "last-out", label: "last out", tokens: lastOut });
45465
+ }
45466
+ return {
45467
+ total,
45468
+ estimated,
45469
+ segments: segments2,
45470
+ bar: renderUsageBar(total),
45471
+ note: total === 0 ? "No context usage yet." : "No model context window is known. The bar is relative to used tokens, not a billed limit."
45472
+ };
45473
+ }
45474
+ function formatContextUsageText(view) {
45475
+ if (view.total === 0 && view.segments.every((segment) => segment.tokens === 0)) {
45476
+ return `Context
45477
+ ${view.note}
45478
+ `;
45479
+ }
45480
+ const kind = view.estimated ? "tokens (estimate)" : "tokens";
45481
+ const width = view.segments.reduce((max, segment) => Math.max(max, segment.label.length), 0);
45482
+ const rows = view.segments.map((segment) => ` ${segment.label.padEnd(width)} ${segment.tokens}`);
45483
+ return [`Context`, ` Used ${view.total} ${kind}`, ` ${view.bar}`, ...rows, "", ` ${view.note}`, ""].join(`
45484
+ `);
45485
+ }
45486
+
45487
+ // src/tui/session-info.ts
45488
+ var SESSION_INFO_COMMANDS = ["/status"];
45489
+ var MISSING = "\u2014";
45490
+ var SESSION_INFO_FOOTER = [
45491
+ { key: "c", label: "copy id" },
45492
+ { key: "\u2190/\u2192", label: "tabs" },
45493
+ { key: "esc", label: "close" }
45494
+ ];
45495
+ function isSessionInfoCommand(line) {
45496
+ const token = line.trim().split(/\s+/)[0] ?? "";
45497
+ return SESSION_INFO_COMMANDS.includes(token);
45498
+ }
45499
+ function displayOrMissing(value) {
45500
+ return value !== undefined && value.length > 0 ? value : MISSING;
45501
+ }
45502
+ function formatUtc(iso) {
45503
+ if (iso === undefined || iso.length === 0) {
45504
+ return MISSING;
45505
+ }
45506
+ const date = new Date(iso);
45507
+ if (Number.isNaN(date.getTime())) {
45508
+ return MISSING;
45509
+ }
45510
+ return `${date.toISOString()} UTC`;
45511
+ }
45512
+ function formatCount(value) {
45513
+ return value === undefined ? MISSING : String(value);
45514
+ }
45515
+ function usageUsed(usage) {
45516
+ if (usage === undefined) {
45517
+ return;
45518
+ }
45519
+ if (usage.totalTokens !== undefined) {
45520
+ return usage.totalTokens;
45521
+ }
45522
+ if (usage.inputTokens === undefined && usage.outputTokens === undefined) {
45523
+ return;
45524
+ }
45525
+ return (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
45526
+ }
45527
+ function contextRow(source) {
45528
+ const used = usageUsed(source.usage);
45529
+ if (used !== undefined) {
45530
+ return `${used} tokens`;
45531
+ }
45532
+ if (source.estimateTokens !== undefined) {
45533
+ return `${source.estimateTokens} tokens (estimate)`;
45534
+ }
45535
+ return MISSING;
45536
+ }
45537
+ function buildSessionInfoSnapshot(source) {
45538
+ const id = source.summary?.id ?? "";
45539
+ const provider = source.selection?.provider ?? source.summary?.provider;
45540
+ const model = source.selection?.model ?? source.summary?.model;
45541
+ const sessionRows = [
45542
+ { label: "Title", value: displayOrMissing(source.summary?.title) },
45543
+ { label: "Version", value: displayOrMissing(source.version) },
45544
+ { label: "Session id", value: displayOrMissing(source.summary?.id) },
45545
+ { label: "Project", value: displayOrMissing(source.summary?.projectPath) },
45546
+ { label: "Provider", value: displayOrMissing(provider) },
45547
+ { label: "Model", value: displayOrMissing(model) }
45548
+ ];
45549
+ if (source.summary?.parentSessionId !== undefined && source.summary.parentSessionId.length > 0) {
45550
+ sessionRows.push({ label: "Parent", value: source.summary.parentSessionId });
45551
+ }
45552
+ const messages = source.summary?.messageCount === undefined && source.summary?.archiveMessageCount === undefined ? MISSING : `${formatCount(source.summary.messageCount)} / ${formatCount(source.summary.archiveMessageCount)}`;
45553
+ 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) });
45554
+ const estimate = source.estimateTokens === undefined ? MISSING : `${source.estimateTokens} tokens (estimate)`;
45555
+ const usageRows = [
45556
+ {
45557
+ label: "Last turn input",
45558
+ value: source.usage?.inputTokens === undefined ? MISSING : String(source.usage.inputTokens)
45559
+ },
45560
+ {
45561
+ label: "Last turn output",
45562
+ value: source.usage?.outputTokens === undefined ? MISSING : String(source.usage.outputTokens)
45563
+ },
45564
+ { label: "Context estimate", value: estimate }
45565
+ ];
45566
+ const context = buildContextUsage({
45567
+ estimateTokens: source.estimateTokens,
45568
+ usage: source.usage
45569
+ });
45570
+ const sessionWorkspaces = workspacesInSession(source.workspaces ?? [], {
45571
+ sessionId: id,
45572
+ ...source.sessionText !== undefined ? { sessionText: source.sessionText } : {}
45573
+ });
45574
+ const sessionFlows = flowsInSession(source.flows ?? [], {
45575
+ sessionId: id,
45576
+ ...source.sessionText !== undefined ? { sessionText: source.sessionText } : {}
45577
+ });
45578
+ return {
45579
+ sessionId: id,
45580
+ sessionRows,
45581
+ usageRows,
45582
+ context,
45583
+ workspaceLines: formatWorkspaceLines(sessionWorkspaces),
45584
+ flowLines: formatSessionFlowLines(sessionFlows),
45585
+ hasWorkspaces: sessionWorkspaces.length > 0,
45586
+ hasFlows: sessionFlows.length > 0
45587
+ };
45588
+ }
45589
+ function formatSection(title, rows) {
45590
+ const width = rows.reduce((max, row) => Math.max(max, row.label.length), 0);
45591
+ return [title, ...rows.map((row) => ` ${row.label.padEnd(width)} ${row.value}`)].join(`
45592
+ `);
45593
+ }
45594
+ function formatSessionInfoText(snapshot) {
45595
+ const extra = [];
45596
+ extra.push(formatContextUsageText(snapshot.context));
45597
+ if (snapshot.hasWorkspaces) {
45598
+ extra.push(["Workspaces", ...snapshot.workspaceLines.map((line) => ` ${line}`), ""].join(`
45599
+ `));
45600
+ }
45601
+ if (snapshot.hasFlows) {
45602
+ extra.push(["Flow", ...snapshot.flowLines.map((line) => ` ${line}`), ""].join(`
45603
+ `));
45604
+ }
45605
+ return `${formatSection("Session", snapshot.sessionRows)}
45606
+
45607
+ ${formatSection("Usage", snapshot.usageRows)}
45608
+
45609
+ ${extra.join(`
45610
+ `)}`;
45611
+ }
45612
+ function sessionIdCopyText(snapshot) {
45613
+ return snapshot.sessionId;
45614
+ }
45615
+ function statusModalTabs(snapshot) {
45616
+ const tabs = [
45617
+ { id: "status", label: "Status" },
45618
+ { id: "context", label: "Context" }
45619
+ ];
45620
+ if (snapshot.hasWorkspaces) {
45621
+ tabs.push({ id: "workspaces", label: "Workspaces" });
45622
+ }
45623
+ if (snapshot.hasFlows) {
45624
+ tabs.push({ id: "flow", label: "Flow" });
45625
+ }
45626
+ return tabs;
45627
+ }
45628
+ function paintContent(otui, renderer, body, content) {
45629
+ if (otui === undefined || otui === null || body === undefined || body === null) {
45630
+ return;
45631
+ }
45632
+ const parent = body;
45633
+ const ctor = otui.TextRenderable;
45634
+ if (parent.add === undefined || ctor === undefined) {
45635
+ return;
45636
+ }
45637
+ parent.add(new ctor(renderer, { id: "session-info-body", content }));
45638
+ }
45639
+ function paintRows(otui, renderer, body, rows) {
45640
+ const width = rows.reduce((max, row) => Math.max(max, row.label.length), 0);
45641
+ paintContent(otui, renderer, body, rows.map((row) => `${row.label.padEnd(width)} ${row.value}`).join(`
45642
+ `));
45643
+ }
45644
+ function presentSessionInfo(openModal2, otui, chrome, options) {
45645
+ const { snapshot, toast } = options;
45646
+ const copy = (text) => {
45647
+ if (text.length === 0) {
45648
+ return;
45649
+ }
45650
+ try {
45651
+ options.copyText(text);
45652
+ toast("Copied to clipboard");
45653
+ } catch {}
45654
+ };
45655
+ let unsubscribeKey;
45656
+ const handle = openModal2(otui, chrome, {
45657
+ title: "/status",
45658
+ tabs: statusModalTabs(snapshot),
45659
+ initialTab: "status",
45660
+ footer: SESSION_INFO_FOOTER,
45661
+ renderTab: (tabId, body) => {
45662
+ const renderer = options.renderer ?? chrome?.renderer;
45663
+ if (tabId === "context") {
45664
+ paintContent(otui, renderer, body, formatContextUsageText(snapshot.context).trimEnd());
45665
+ return;
45666
+ }
45667
+ if (tabId === "workspaces") {
45668
+ paintContent(otui, renderer, body, snapshot.workspaceLines.join(`
45669
+ `));
45670
+ return;
45671
+ }
45672
+ if (tabId === "flow") {
45673
+ paintContent(otui, renderer, body, snapshot.flowLines.join(`
45674
+ `));
45675
+ return;
45676
+ }
45677
+ paintRows(otui, renderer, body, snapshot.sessionRows);
45678
+ },
45679
+ onClose: () => {
45680
+ unsubscribeKey?.();
45681
+ }
45682
+ });
45683
+ if (handle === undefined) {
45684
+ return;
45685
+ }
45686
+ if (options.onKeypress !== undefined) {
45687
+ unsubscribeKey = options.onKeypress((key) => {
45688
+ const token = key.name || key.sequence;
45689
+ if (token === "c") {
45690
+ copy(sessionIdCopyText(snapshot));
45691
+ }
45692
+ });
45693
+ }
45694
+ return handle;
45695
+ }
45696
+ function openSessionInfo(otui, chrome, options) {
45697
+ return presentSessionInfo((hostOtui, hostChrome, input2) => openModal(hostOtui, hostChrome, input2), otui, chrome, options);
45698
+ }
44799
45699
 
44800
45700
  // src/commands/agent-commands.ts
44801
45701
  var CHAT_ONLY = ["chat"];
@@ -44854,6 +45754,12 @@ var AGENT_SLASH_COMMANDS = [
44854
45754
  { name: "/new", description: "Start a new session (old kept on disk)", modes: BOTH },
44855
45755
  { name: "/resume", description: "Resume a prior session in this project", modes: AGENT_ONLY },
44856
45756
  { name: "/sessions", description: "Open the session list and switch to one", modes: AGENT_ONLY },
45757
+ {
45758
+ name: "/status",
45759
+ description: "Show session identity, context, workspaces, and flows",
45760
+ modes: BOTH
45761
+ },
45762
+ { name: "/flows", description: "Browse project flows and inspect one", modes: BOTH },
44857
45763
  {
44858
45764
  name: "/compact",
44859
45765
  description: "Compact model context \u2014 /compact [focus] (archive kept)",
@@ -45334,7 +46240,7 @@ function selectBoxHeight(count, withDescription) {
45334
46240
  const per = withDescription ? 2 : 1;
45335
46241
  return Math.min(Math.max(count * per, per), 16);
45336
46242
  }
45337
- function onKeypress(r, handler) {
46243
+ function onKeypress2(r, handler) {
45338
46244
  r._internalKeyInput.onInternal("keypress", handler);
45339
46245
  return () => r._internalKeyInput.offInternal("keypress", handler);
45340
46246
  }
@@ -45435,7 +46341,7 @@ function showComposerChoice(otui, r, dock, request) {
45435
46341
  key.stopPropagation();
45436
46342
  }
45437
46343
  };
45438
- const unsub = onKeypress(r, onKey);
46344
+ const unsub = onKeypress2(r, onKey);
45439
46345
  sel.on(otui.SelectRenderableEvents.ITEM_SELECTED, () => {
45440
46346
  const chosen = sel.getSelectedOption();
45441
46347
  const value = chosen?.value;
@@ -45723,7 +46629,7 @@ async function checkVersion(options) {
45723
46629
  }
45724
46630
 
45725
46631
  // src/tui/shell-chrome.ts
45726
- function onKeypress2(r, handler) {
46632
+ function onKeypress3(r, handler) {
45727
46633
  r._internalKeyInput.onInternal("keypress", handler);
45728
46634
  return () => r._internalKeyInput.offInternal("keypress", handler);
45729
46635
  }
@@ -46153,7 +47059,7 @@ async function createShellChrome(otui, renderer, opts) {
46153
47059
  syncComposerHeight();
46154
47060
  emitSubmit(line);
46155
47061
  };
46156
- const unsubscribeMenuKeys = onKeypress2(r, (key) => {
47062
+ const unsubscribeMenuKeys = onKeypress3(r, (key) => {
46157
47063
  if (!menu.visible || !menuNav || overlayActive()) {
46158
47064
  return;
46159
47065
  }
@@ -46958,7 +47864,7 @@ function createBlockView(otui, renderer, parent, block, options = {}) {
46958
47864
  let body;
46959
47865
  let bodyText;
46960
47866
  let painted;
46961
- const paintHeader = (state, focused) => {
47867
+ const paintHeader2 = (state, focused) => {
46962
47868
  const hint = state.collapsed ? options.hint : options.expandedHint ?? options.hint;
46963
47869
  const label = blockLabel({
46964
47870
  kind: state.kind,
@@ -47019,7 +47925,7 @@ function createBlockView(otui, renderer, parent, block, options = {}) {
47019
47925
  return {
47020
47926
  id: block.id,
47021
47927
  render: (state, opts = {}) => {
47022
- paintHeader(state, opts.focused === true);
47928
+ paintHeader2(state, opts.focused === true);
47023
47929
  if (state.collapsed) {
47024
47930
  dropBody();
47025
47931
  return;
@@ -47601,7 +48507,7 @@ function overlayBox(otui, r, id) {
47601
48507
  padding: 1
47602
48508
  });
47603
48509
  }
47604
- function onKeypress3(r, handler) {
48510
+ function onKeypress4(r, handler) {
47605
48511
  r._internalKeyInput.onInternal("keypress", handler);
47606
48512
  return () => r._internalKeyInput.offInternal("keypress", handler);
47607
48513
  }
@@ -47646,7 +48552,7 @@ function promptBaseUrlStep(otui, r, label, baseUrl2) {
47646
48552
  unsub();
47647
48553
  r.root.remove(box);
47648
48554
  };
47649
- const unsub = onKeypress3(r, (key) => {
48555
+ const unsub = onKeypress4(r, (key) => {
47650
48556
  if (key.name === "escape") {
47651
48557
  cleanup();
47652
48558
  resolve3(undefined);
@@ -47682,7 +48588,7 @@ function promptApiKeyStep(otui, r, opts) {
47682
48588
  key.stopPropagation();
47683
48589
  }
47684
48590
  };
47685
- const unsub = onKeypress3(r, onKey);
48591
+ const unsub = onKeypress4(r, onKey);
47686
48592
  const cleanup = () => {
47687
48593
  unsub();
47688
48594
  r.root.remove(box);
@@ -47722,7 +48628,7 @@ function pickProviderStep(otui, r, detected) {
47722
48628
  key.stopPropagation();
47723
48629
  }
47724
48630
  };
47725
- const unsub = onKeypress3(r, onKey);
48631
+ const unsub = onKeypress4(r, onKey);
47726
48632
  const cleanup = () => {
47727
48633
  unsub();
47728
48634
  r.root.remove(box);
@@ -47840,7 +48746,7 @@ function pickModelInTui(otui, r, models) {
47840
48746
  key.stopPropagation();
47841
48747
  }
47842
48748
  };
47843
- const unsub = onKeypress3(r, onKey);
48749
+ const unsub = onKeypress4(r, onKey);
47844
48750
  const cleanup = () => {
47845
48751
  unsub();
47846
48752
  r.root.remove(box);
@@ -47932,7 +48838,7 @@ function pickSessionInTui(otui, r, sessions) {
47932
48838
  key.stopPropagation();
47933
48839
  }
47934
48840
  };
47935
- const unsub = onKeypress3(r, onKey);
48841
+ const unsub = onKeypress4(r, onKey);
47936
48842
  const cleanup = () => {
47937
48843
  unsub();
47938
48844
  r.root.remove(box);
@@ -48117,6 +49023,12 @@ async function launchTuiAgentShell(opts) {
48117
49023
  hasExactUsage = true;
48118
49024
  }
48119
49025
  });
49026
+ let lastUsage;
49027
+ const recordedUsage = io.onUsage?.bind(io);
49028
+ io.onUsage = (usage) => {
49029
+ lastUsage = usage;
49030
+ recordedUsage?.(usage);
49031
+ };
48120
49032
  attachBlockIo(io, addBlock, {
48121
49033
  onReasoning: () => {
48122
49034
  setBusyPhase("thinking");
@@ -48513,6 +49425,42 @@ Staying in the current session.
48513
49425
  `);
48514
49426
  };
48515
49427
  paintSessionHeader();
49428
+ const inspectorKeys = { onKeypress: (handler) => onKeypress4(r, (key) => handler(key)) };
49429
+ const inspectorCwd = () => opts.session?.cwd ?? liveSession.summary.projectPath;
49430
+ const showSessionInfo = () => {
49431
+ (async () => {
49432
+ const cwd = inspectorCwd();
49433
+ const [workspaces, flows] = await Promise.all([loadInspectorWorkspaces(cwd), loadInspectorFlows(cwd)]);
49434
+ const snapshot = buildSessionInfoSnapshot({
49435
+ summary: liveSession.summary,
49436
+ selection: currentSel,
49437
+ version: package_default.version,
49438
+ usage: lastUsage,
49439
+ estimateTokens: estimateContextTokens(history),
49440
+ sessionText: history.map((message2) => message2.content).join(`
49441
+ `),
49442
+ workspaces,
49443
+ flows
49444
+ });
49445
+ openSessionInfo(otui, chrome, {
49446
+ snapshot,
49447
+ copyText: (text) => r.copyToClipboardOSC52(text),
49448
+ toast: (message2) => chrome.showToast(message2),
49449
+ renderer: r,
49450
+ ...inspectorKeys
49451
+ });
49452
+ })();
49453
+ };
49454
+ const showFlows = () => {
49455
+ (async () => {
49456
+ const items = await loadInspectorFlows(inspectorCwd());
49457
+ openFlows(otui, chrome, {
49458
+ items,
49459
+ renderer: r,
49460
+ ...inspectorKeys
49461
+ });
49462
+ })();
49463
+ };
48516
49464
  const updateModelLabels = () => {
48517
49465
  paintSessionHeader();
48518
49466
  const label = `${currentSel.provider}/${currentSel.model}`;
@@ -48718,6 +49666,14 @@ Staying in the current session.
48718
49666
  `);
48719
49667
  return;
48720
49668
  }
49669
+ if (command2 !== undefined && isSessionInfoCommand(command2.name)) {
49670
+ showSessionInfo();
49671
+ return;
49672
+ }
49673
+ if (command2 !== undefined && isFlowsCommand(command2.name)) {
49674
+ showFlows();
49675
+ return;
49676
+ }
48721
49677
  if (command2 !== undefined || line.startsWith("/")) {
48722
49678
  transcript.add(new otui.TextRenderable(r, {
48723
49679
  id: `c${uid++}`,
@@ -48859,6 +49815,14 @@ Staying in the current session.
48859
49815
  }
48860
49816
  return;
48861
49817
  }
49818
+ if (isSessionInfoCommand(command.name)) {
49819
+ showSessionInfo();
49820
+ return;
49821
+ }
49822
+ if (isFlowsCommand(command.name)) {
49823
+ showFlows();
49824
+ return;
49825
+ }
48862
49826
  if (command.name === "/copy") {
48863
49827
  const target = newestBlock();
48864
49828
  if (target === undefined || !copyBlock(target.id)) {
@@ -49078,7 +50042,7 @@ Staying in the current session.
49078
50042
  focusComposer();
49079
50043
  });
49080
50044
  };
49081
- onKeypress3(r, (key) => {
50045
+ onKeypress4(r, (key) => {
49082
50046
  nav.handleKey(key);
49083
50047
  });
49084
50048
  chrome.onSubmit((line) => {
@@ -49204,6 +50168,9 @@ function createChatBridge(hooks = {}) {
49204
50168
  close();
49205
50169
  return "exit";
49206
50170
  }
50171
+ if (isSessionInfoCommand(value) || isFlowsCommand(value)) {
50172
+ return "local";
50173
+ }
49207
50174
  if ((turn || queue.length > 0) && value.startsWith("/")) {
49208
50175
  return "deferred";
49209
50176
  }
@@ -49327,6 +50294,36 @@ async function mountChatShell(otui, renderer, opts) {
49327
50294
  opts.onExit?.();
49328
50295
  return;
49329
50296
  }
50297
+ if (result === "local") {
50298
+ const cwd = opts.deps.session?.cwd;
50299
+ const keys = { onKeypress: (handler) => onKeypress4(r, (key) => handler(key)) };
50300
+ (async () => {
50301
+ const [workspaces, flows] = cwd === undefined ? [[], []] : await Promise.all([loadInspectorWorkspaces(cwd), loadInspectorFlows(cwd)]);
50302
+ if (isFlowsCommand(line)) {
50303
+ openFlows(otui, chrome, { items: flows, renderer: r, ...keys });
50304
+ return;
50305
+ }
50306
+ const summary = cwd !== undefined ? latestSession(cwd) : undefined;
50307
+ const snapshot = buildSessionInfoSnapshot({
50308
+ summary,
50309
+ selection,
50310
+ version: package_default.version,
50311
+ estimateTokens: estimateContextTokens(seen),
50312
+ sessionText: seen.map((message2) => message2.content).join(`
50313
+ `),
50314
+ workspaces,
50315
+ flows
50316
+ });
50317
+ openSessionInfo(otui, chrome, {
50318
+ snapshot,
50319
+ copyText: (text) => r.copyToClipboardOSC52(text),
50320
+ toast: (message2) => chrome.showToast(message2),
50321
+ renderer: r,
50322
+ ...keys
50323
+ });
50324
+ })();
50325
+ return;
50326
+ }
49330
50327
  if (result === "deferred") {
49331
50328
  append(otui.t`${otui.yellow("\u25C7 a reply is still streaming \u2014 command deferred. Wait for it to finish.")}`);
49332
50329
  }
@@ -49413,82 +50410,6 @@ async function launchTuiChatShell(opts) {
49413
50410
 
49414
50411
  // src/commands/shell.ts
49415
50412
  init_shell_config();
49416
- // package.json
49417
- var package_default = {
49418
- name: "@mrciphersmith/keryx",
49419
- version: "0.2.35",
49420
- description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
49421
- private: false,
49422
- publishConfig: {
49423
- access: "public"
49424
- },
49425
- license: "MIT",
49426
- type: "module",
49427
- repository: {
49428
- type: "git",
49429
- url: "git+ssh://git@github.com/MrCipherSmith/keryx.git"
49430
- },
49431
- keywords: [
49432
- "ai-agents",
49433
- "coding-agents",
49434
- "agent-harness",
49435
- "agent-context",
49436
- "repository-context",
49437
- "code-graph",
49438
- "project-memory",
49439
- "test-impact-analysis",
49440
- "developer-tools",
49441
- "model-context-protocol",
49442
- "mcp",
49443
- "claude-code",
49444
- "cursor",
49445
- "codex",
49446
- "cli",
49447
- "bun"
49448
- ],
49449
- bin: {
49450
- keryx: "./dist/cli.js"
49451
- },
49452
- scripts: {
49453
- keryx: "bun ./src/cli.ts",
49454
- 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",
49455
- prepare: "bun run build",
49456
- typecheck: "tsc --noEmit",
49457
- test: "bun test",
49458
- check: "tsc --noEmit && bun test",
49459
- "check:doc-links": "bun scripts/check-doc-links.ts",
49460
- "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"
49461
- },
49462
- files: [
49463
- "dist",
49464
- "docs/requirements/shared-agent-context/schemas",
49465
- "src/gdgraph",
49466
- "src/gdskills/bundled",
49467
- "src/gdskills/contracts",
49468
- "LICENSE",
49469
- "README.md",
49470
- "package.json"
49471
- ],
49472
- dependencies: {},
49473
- optionalDependencies: {
49474
- "@modelcontextprotocol/sdk": "^1.0.0",
49475
- "@opentui/core": "^0.4.5",
49476
- "web-tree-sitter": "^0.22.0"
49477
- },
49478
- devDependencies: {
49479
- "@types/bun": "latest",
49480
- "@xenova/transformers": "^2.17.2",
49481
- "bun-types": "latest",
49482
- typescript: "^5"
49483
- },
49484
- engines: {
49485
- bun: ">=1.1.0"
49486
- },
49487
- trustedDependencies: [
49488
- "protobufjs",
49489
- "sharp"
49490
- ]
49491
- };
49492
50413
 
49493
50414
  // src/commands/select.ts
49494
50415
  init_guard2();
@@ -49727,6 +50648,8 @@ var READLINE_AGENT_COMMANDS = [
49727
50648
  "/new",
49728
50649
  "/clear",
49729
50650
  "/compact",
50651
+ "/status",
50652
+ "/flows",
49730
50653
  "/exit"
49731
50654
  ];
49732
50655
  function readlineAgentHelpText() {
@@ -49815,6 +50738,34 @@ Starting a new session.
49815
50738
  system(HELP_TEXT);
49816
50739
  continue;
49817
50740
  }
50741
+ if (isSessionInfoCommand(command)) {
50742
+ const cwd = deps.session?.cwd;
50743
+ const [workspaces, flows] = cwd === undefined ? [[], []] : await Promise.all([loadInspectorWorkspaces(cwd), loadInspectorFlows(cwd)]);
50744
+ system(formatSessionInfoText(buildSessionInfoSnapshot({
50745
+ summary: live?.summary,
50746
+ selection: { provider: providerName, model: modelName },
50747
+ version: package_default.version,
50748
+ estimateTokens: estimateContextTokens(history),
50749
+ sessionText: history.map((message2) => message2.content).join(`
50750
+ `),
50751
+ workspaces,
50752
+ flows
50753
+ })));
50754
+ continue;
50755
+ }
50756
+ if (isFlowsCommand(command)) {
50757
+ const cwd = deps.session?.cwd;
50758
+ const items = cwd === undefined ? [] : await loadInspectorFlows(cwd);
50759
+ const query = argument.trim();
50760
+ if (query.length > 0) {
50761
+ const found = findFlowItem(items, query);
50762
+ system(found !== undefined ? formatFlowDetailText(found) : `No flow matching '${query}'.
50763
+ `);
50764
+ continue;
50765
+ }
50766
+ system(formatFlowListText(items));
50767
+ continue;
50768
+ }
49818
50769
  if (command === "/clear" || command === "/new") {
49819
50770
  if (sessionsOn) {
49820
50771
  live = createSession({ cwd: sessionCwd, provider: providerName, model: modelName });
@@ -50462,6 +51413,32 @@ New session ${shortSessionId(live.summary.id)}.
50462
51413
  }
50463
51414
  if (command === "/help") {
50464
51415
  agentIo.onSystem?.(readlineAgentHelpText());
51416
+ } else if (isSessionInfoCommand(command)) {
51417
+ const cwd = sessionCwd;
51418
+ const [workspaces, flows] = await Promise.all([
51419
+ loadInspectorWorkspaces(cwd),
51420
+ loadInspectorFlows(cwd)
51421
+ ]);
51422
+ agentIo.onSystem?.(formatSessionInfoText(buildSessionInfoSnapshot({
51423
+ summary: live?.summary,
51424
+ selection: { provider: deps.providerId, model: deps.modelId },
51425
+ version: package_default.version,
51426
+ usage: lastUsage,
51427
+ estimateTokens: estimateContextTokens(history),
51428
+ sessionText: history.map((message2) => message2.content).join(`
51429
+ `),
51430
+ workspaces,
51431
+ flows
51432
+ })));
51433
+ } else if (isFlowsCommand(command)) {
51434
+ const items = await loadInspectorFlows(sessionCwd);
51435
+ if (rest.length > 0) {
51436
+ const found = findFlowItem(items, rest);
51437
+ agentIo.onSystem?.(found !== undefined ? formatFlowDetailText(found) : `No flow matching '${rest}'.
51438
+ `);
51439
+ } else {
51440
+ agentIo.onSystem?.(formatFlowListText(items));
51441
+ }
50465
51442
  } else if (command === "/expand") {
50466
51443
  const expanded = expandedToolOutput(lastToolName, lastToolOutput);
50467
51444
  if (expanded !== undefined) {
@@ -50748,7 +51725,7 @@ async function shellCommand(args2, runtime = {}) {
50748
51725
  modelId: sel.model
50749
51726
  }),
50750
51727
  maxToolCalls: resolveAgentMaxToolCalls(),
50751
- idSeq: () => randomUUID17()
51728
+ idSeq: () => randomUUID18()
50752
51729
  };
50753
51730
  };
50754
51731
  const redetect = () => detectProviders({
@@ -50779,7 +51756,7 @@ async function shellCommand(args2, runtime = {}) {
50779
51756
  makeShellDeps: (sel) => ({
50780
51757
  makeProvider: chatFactory,
50781
51758
  clock: () => new Date().toISOString(),
50782
- idSeq: () => randomUUID17(),
51759
+ idSeq: () => randomUUID18(),
50783
51760
  initial: sel,
50784
51761
  session: {
50785
51762
  cwd,
@@ -50849,7 +51826,7 @@ async function shellCommand(args2, runtime = {}) {
50849
51826
  const deps = {
50850
51827
  makeProvider: baseFactory,
50851
51828
  clock: () => new Date().toISOString(),
50852
- idSeq: () => randomUUID17(),
51829
+ idSeq: () => randomUUID18(),
50853
51830
  initial: baseUrl2 === undefined ? { provider, model } : { provider, model, baseUrl: baseUrl2 },
50854
51831
  selectProviderModel: realSelectProviderModel(baseUrl2)
50855
51832
  };
@@ -50897,7 +51874,7 @@ async function shellCommand(args2, runtime = {}) {
50897
51874
  modelId: model
50898
51875
  }),
50899
51876
  maxToolCalls: resolveAgentMaxToolCalls(),
50900
- idSeq: () => randomUUID17()
51877
+ idSeq: () => randomUUID18()
50901
51878
  };
50902
51879
  let resumeId = flags.resumeId;
50903
51880
  if (flags.resumePick === true && resumeId === undefined) {
@@ -51229,7 +52206,7 @@ function printHelp16() {
51229
52206
  }
51230
52207
 
51231
52208
  // src/commands/serve.ts
51232
- import { randomUUID as randomUUID20 } from "crypto";
52209
+ import { randomUUID as randomUUID21 } from "crypto";
51233
52210
 
51234
52211
  // src/lib/serve-config.ts
51235
52212
  init_config_dir();
@@ -51556,7 +52533,7 @@ function saveServeConfig(config, dir, onWarn) {
51556
52533
 
51557
52534
  // src/lib/serve-credential.ts
51558
52535
  init_config_dir();
51559
- import { createHash as createHash29, randomBytes as randomBytes2, randomUUID as randomUUID18 } from "crypto";
52536
+ import { createHash as createHash29, randomBytes as randomBytes2, randomUUID as randomUUID19 } from "crypto";
51560
52537
  import {
51561
52538
  chmodSync as chmodSync4,
51562
52539
  closeSync as closeSync3,
@@ -51640,7 +52617,7 @@ function readServeCredential(dir) {
51640
52617
  }
51641
52618
  function writeStore(store, dir) {
51642
52619
  const file = serveCredentialPath(dir);
51643
- const temp = `${file}.${randomUUID18()}.tmp`;
52620
+ const temp = `${file}.${randomUUID19()}.tmp`;
51644
52621
  try {
51645
52622
  ensureKeryxConfigDir(dir);
51646
52623
  const handle = openSync3(temp, "wx", 384);
@@ -51680,7 +52657,7 @@ function mintRecord(now) {
51680
52657
  const salt = randomBytes2(32).toString("hex");
51681
52658
  return {
51682
52659
  token,
51683
- record: { id: randomUUID18(), algorithm: "sha256", salt, hash: hashToken(salt, token), createdAt: now }
52660
+ record: { id: randomUUID19(), algorithm: "sha256", salt, hash: hashToken(salt, token), createdAt: now }
51684
52661
  };
51685
52662
  }
51686
52663
  function issueServeToken(dir, now = () => new Date().toISOString(), onWaiting) {
@@ -51994,7 +52971,7 @@ function releaseIdempotencyKey(project, idempotencyKey, turnId, dir) {
51994
52971
  }
51995
52972
 
51996
52973
  // src/lib/serve-turn.ts
51997
- import { randomUUID as randomUUID19 } from "crypto";
52974
+ import { randomUUID as randomUUID20 } from "crypto";
51998
52975
  import path140 from "path";
51999
52976
  init_service();
52000
52977
  var REMOTE_ORIGIN = "remote:http";
@@ -52113,7 +53090,7 @@ async function redactOut(security, text) {
52113
53090
  async function runRemoteTurn(input2) {
52114
53091
  const scanRoot = input2.scanRoot;
52115
53092
  const security = createSecurityService(scanRoot);
52116
- const newId = input2.newId ?? (() => randomUUID19());
53093
+ const newId = input2.newId ?? (() => randomUUID20());
52117
53094
  const clock = input2.clock ?? (() => new Date().toISOString());
52118
53095
  const turnId = input2.turnId ?? newId();
52119
53096
  const sessionId = input2.request.sessionId ?? newId();
@@ -52256,7 +53233,7 @@ function outcomeOf(status, gate, unresolvedBlockerIds) {
52256
53233
  }
52257
53234
  function createSubmitTurn(deps) {
52258
53235
  return async (request, project) => {
52259
- const turnId = (deps.newId ?? (() => randomUUID19()))();
53236
+ const turnId = (deps.newId ?? (() => randomUUID20()))();
52260
53237
  const scanned = await scanPrompt(deps.dir, request.prompt);
52261
53238
  if (scanned.rejected) {
52262
53239
  return { kind: "rejected" };
@@ -52943,7 +53920,7 @@ function runConfig(args2) {
52943
53920
  return;
52944
53921
  }
52945
53922
  const credential2 = readServeCredential();
52946
- const credentialId = credential2.status === "ok" ? credential2.record.id : randomUUID20();
53923
+ const credentialId = credential2.status === "ok" ? credential2.record.id : randomUUID21();
52947
53924
  const config = defaultServeConfig(credentialId, {
52948
53925
  address: parsed.parsed.values.get("--bind") ?? DEFAULT_SERVE_BIND_ADDRESS,
52949
53926
  port: port ?? DEFAULT_SERVE_PORT,
@@ -56642,7 +57619,7 @@ async function versionCommand(args2, deps = {}) {
56642
57619
 
56643
57620
  // src/commands/workspace.ts
56644
57621
  init_args();
56645
- import { randomUUID as randomUUID21 } from "crypto";
57622
+ import { randomUUID as randomUUID22 } from "crypto";
56646
57623
  import { writeFile as writeFile50 } from "fs/promises";
56647
57624
 
56648
57625
  // src/sac/fwk-explain.ts
@@ -56710,13 +57687,13 @@ async function workspaceCommand(args2) {
56710
57687
  const component = optionValue(args2, "--component");
56711
57688
  if (!title)
56712
57689
  throw new Error("Usage: keryx workspace create --title <title> [--component <workspace-relative-ref>]");
56713
- const workspace = await service4().create({ request: undefined, requestCorrelationId: randomUUID21(), id: newWorkspaceId(), title, ...component ? { component: { kind: "component", uri: component } } : {} });
57690
+ const workspace = await service4().create({ request: undefined, requestCorrelationId: randomUUID22(), id: newWorkspaceId(), title, ...component ? { component: { kind: "component", uri: component } } : {} });
56714
57691
  console.log(JSON.stringify(workspace, null, 2));
56715
57692
  return;
56716
57693
  }
56717
57694
  if (subcommand === "list") {
56718
57695
  rejectUnknownOptions(args2.slice(1), new Set);
56719
- console.log(JSON.stringify(await service4().list({ request: undefined, requestCorrelationId: randomUUID21() }), null, 2));
57696
+ console.log(JSON.stringify(await service4().list({ request: undefined, requestCorrelationId: randomUUID22() }), null, 2));
56720
57697
  return;
56721
57698
  }
56722
57699
  if (subcommand === "show") {
@@ -56724,7 +57701,7 @@ async function workspaceCommand(args2) {
56724
57701
  const id = args2[1];
56725
57702
  if (!id)
56726
57703
  throw new Error("Usage: keryx workspace show <workspace-id>");
56727
- console.log(JSON.stringify(await service4().show({ request: undefined, requestCorrelationId: randomUUID21(), workspaceId: id }), null, 2));
57704
+ console.log(JSON.stringify(await service4().show({ request: undefined, requestCorrelationId: randomUUID22(), workspaceId: id }), null, 2));
56728
57705
  return;
56729
57706
  }
56730
57707
  if (subcommand === "add-resource") {
@@ -56735,7 +57712,7 @@ async function workspaceCommand(args2) {
56735
57712
  const revision = optionValue(args2, "--revision");
56736
57713
  if (!workspaceId || !kind || !uri)
56737
57714
  throw new Error("Usage: keryx workspace add-resource <workspace-id> --kind <kind> --uri <workspace-relative-ref> [--revision <revision>]");
56738
- console.log(JSON.stringify(await service4().addResource({ request: undefined, requestCorrelationId: randomUUID21(), workspaceId, resource: { kind, uri, ...revision ? { revision } : {} } }), null, 2));
57715
+ console.log(JSON.stringify(await service4().addResource({ request: undefined, requestCorrelationId: randomUUID22(), workspaceId, resource: { kind, uri, ...revision ? { revision } : {} } }), null, 2));
56739
57716
  return;
56740
57717
  }
56741
57718
  if (subcommand === "overview") {
@@ -56747,7 +57724,7 @@ async function workspaceCommand(args2) {
56747
57724
  const maxTokens = Number(optionValue(args2, "--max-tokens") ?? "4096");
56748
57725
  if (!Number.isInteger(maxItems) || !Number.isInteger(maxTokens) || maxItems < 0 || maxTokens < 0)
56749
57726
  throw new Error("--max-items and --max-tokens must be non-negative integers");
56750
- const result = await createLocalFwkReadService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID21(), budget: { maxItems, maxTokens } });
57727
+ const result = await createLocalFwkReadService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID22(), budget: { maxItems, maxTokens } });
56751
57728
  const normalized = normalizeFwkResult(result);
56752
57729
  console.log(JSON.stringify(normalized, null, 2));
56753
57730
  if (args2.includes("--explain"))
@@ -56764,7 +57741,7 @@ async function workspaceCommand(args2) {
56764
57741
  const maxTokens = Number(optionValue(args2, "--max-tokens") ?? "4096");
56765
57742
  if (!Number.isInteger(maxItems) || !Number.isInteger(maxTokens) || maxItems < 0 || maxTokens < 0)
56766
57743
  throw new Error("--max-items and --max-tokens must be non-negative integers");
56767
- const result = await createLocalFwkReadService(process.cwd()).read({ workspaceId, itemId, request: undefined, requestCorrelationId: randomUUID21(), budget: { maxItems, maxTokens } });
57744
+ const result = await createLocalFwkReadService(process.cwd()).read({ workspaceId, itemId, request: undefined, requestCorrelationId: randomUUID22(), budget: { maxItems, maxTokens } });
56768
57745
  const normalized = normalizeFwkResult(result);
56769
57746
  console.log(JSON.stringify(normalized, null, 2));
56770
57747
  if (args2.includes("--explain"))
@@ -56787,12 +57764,12 @@ async function workspaceCommand(args2) {
56787
57764
  if (!session)
56788
57765
  throw new Error(`no session matching "${sessionRef}" in this project \u2014 use \`keryx sessions list\``);
56789
57766
  const { service: service5, wrapUpAuthority, authorizationServer } = createHarnessProposalLifecycleService(cwd, { workspaceId, ...note2 ? { note: note2 } : {} });
56790
- const requestCorrelationId = randomUUID21();
57767
+ const requestCorrelationId = randomUUID22();
56791
57768
  const actor = await authorizationServer.actorContextFor(undefined, requestCorrelationId);
56792
57769
  if (!actor)
56793
57770
  throw new Error("trusted ActorContext is required");
56794
57771
  const wrapUp = await wrapUpAuthority.issue({ actor, source: "session", sourceRef: sessionEvidenceRef(workspaceId, session.id) });
56795
- const proposal = await service5.create({ request: undefined, requestCorrelationId, workspaceId, id: `proposal-${randomUUID21().replace(/-/g, "").slice(0, 16)}`, proposalRevision, kind, wrapUp });
57772
+ const proposal = await service5.create({ request: undefined, requestCorrelationId, workspaceId, id: `proposal-${randomUUID22().replace(/-/g, "").slice(0, 16)}`, proposalRevision, kind, wrapUp });
56796
57773
  if (note2)
56797
57774
  await writeFile50(proposalNotePath(cwd, workspaceId, proposal.id), note2, "utf8");
56798
57775
  console.log(JSON.stringify(normalizeProposalLifecycleResult(proposal), null, 2));
@@ -56804,10 +57781,10 @@ async function workspaceCommand(args2) {
56804
57781
  const proposalId = args2[2];
56805
57782
  const decision = optionValue(args2, "--decision");
56806
57783
  const reason = optionValue(args2, "--reason");
56807
- const idempotencyKey = optionValue(args2, "--idempotency-key") ?? randomUUID21();
57784
+ const idempotencyKey = optionValue(args2, "--idempotency-key") ?? randomUUID22();
56808
57785
  if (!workspaceId || !proposalId || !decision)
56809
57786
  throw new Error("Usage: keryx workspace review <workspace-id> <proposal-id> --decision <accepted|rejected|dismissed> [--reason <reason>] [--idempotency-key <key>]");
56810
- const result = await createHarnessProposalLifecycleService(process.cwd(), { workspaceId }).service.review({ request: undefined, requestCorrelationId: randomUUID21(), workspaceId, proposalId, decision, idempotencyKey, ...reason ? { reason } : {} });
57787
+ const result = await createHarnessProposalLifecycleService(process.cwd(), { workspaceId }).service.review({ request: undefined, requestCorrelationId: randomUUID22(), workspaceId, proposalId, decision, idempotencyKey, ...reason ? { reason } : {} });
56811
57788
  console.log(JSON.stringify(normalizeProposalLifecycleResult(result), null, 2));
56812
57789
  return;
56813
57790
  }
@@ -56816,7 +57793,7 @@ async function workspaceCommand(args2) {
56816
57793
  const workspaceId = args2[1];
56817
57794
  if (!workspaceId)
56818
57795
  throw new Error("Usage: keryx workspace collaboration <workspace-id>");
56819
- console.log(JSON.stringify(await createLocalCollaborationService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID21() }), null, 2));
57796
+ console.log(JSON.stringify(await createLocalCollaborationService(process.cwd()).overview({ workspaceId, request: undefined, requestCorrelationId: randomUUID22() }), null, 2));
56820
57797
  return;
56821
57798
  }
56822
57799
  if (subcommand === "policy-readiness") {