@mrciphersmith/keryx 0.2.99 → 0.2.100
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.
- package/dist/cli.js +556 -193
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -48173,7 +48173,7 @@ import path47 from "path";
|
|
|
48173
48173
|
// package.json
|
|
48174
48174
|
var package_default = {
|
|
48175
48175
|
name: "@mrciphersmith/keryx",
|
|
48176
|
-
version: "0.2.
|
|
48176
|
+
version: "0.2.100",
|
|
48177
48177
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
48178
48178
|
private: false,
|
|
48179
48179
|
publishConfig: {
|
|
@@ -73168,7 +73168,7 @@ async function connectStdioMcpServer(argv, options, handshakeTimeoutMs, signal,
|
|
|
73168
73168
|
}
|
|
73169
73169
|
|
|
73170
73170
|
// src/mcp-servers/runtime.ts
|
|
73171
|
-
import
|
|
73171
|
+
import path169 from "path";
|
|
73172
73172
|
|
|
73173
73173
|
// src/mcp-servers/manager.ts
|
|
73174
73174
|
var DEFAULT_CONNECT_CONCURRENCY = 4;
|
|
@@ -73356,6 +73356,152 @@ function credentialSummary(raw) {
|
|
|
73356
73356
|
return parts.join(" + ");
|
|
73357
73357
|
}
|
|
73358
73358
|
|
|
73359
|
+
// src/mcp-servers/store.ts
|
|
73360
|
+
init_config_dir();
|
|
73361
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
73362
|
+
import path168 from "path";
|
|
73363
|
+
var SERVER_NAME_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
73364
|
+
function userConfigFile(configDir) {
|
|
73365
|
+
return path168.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers.json");
|
|
73366
|
+
}
|
|
73367
|
+
function projectConfigFile(projectRoot) {
|
|
73368
|
+
return path168.join(projectRoot, ".keryx", "mcp-servers.json");
|
|
73369
|
+
}
|
|
73370
|
+
function overlayFile(configDir) {
|
|
73371
|
+
return path168.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers-disabled.json");
|
|
73372
|
+
}
|
|
73373
|
+
function readForWrite(file) {
|
|
73374
|
+
const read = readConfigFile(file);
|
|
73375
|
+
if (!read.ok) {
|
|
73376
|
+
return isDefiniteAbsence(read.reason) ? { doc: { schemaVersion: 1, servers: Object.create(null) }, existed: false } : { error: `${file} could not be read (${read.reason}); nothing was written` };
|
|
73377
|
+
}
|
|
73378
|
+
let parsed;
|
|
73379
|
+
try {
|
|
73380
|
+
parsed = parseJsonTolerant(read.text);
|
|
73381
|
+
} catch (error) {
|
|
73382
|
+
return {
|
|
73383
|
+
error: `${file} is not valid JSON (${error instanceof Error ? error.message : String(error)}); nothing was written. Fix it by hand first.`
|
|
73384
|
+
};
|
|
73385
|
+
}
|
|
73386
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
73387
|
+
return { error: `${file} is not a JSON object; nothing was written` };
|
|
73388
|
+
}
|
|
73389
|
+
const doc = parsed;
|
|
73390
|
+
const servers = doc.servers;
|
|
73391
|
+
if (servers !== undefined && (typeof servers !== "object" || servers === null || Array.isArray(servers))) {
|
|
73392
|
+
return { error: `${file} has a \`servers\` that is not an object; nothing was written` };
|
|
73393
|
+
}
|
|
73394
|
+
return {
|
|
73395
|
+
doc: {
|
|
73396
|
+
...parsed,
|
|
73397
|
+
schemaVersion: typeof doc.schemaVersion === "number" ? doc.schemaVersion : 1,
|
|
73398
|
+
servers: Object.assign(Object.create(null), servers)
|
|
73399
|
+
},
|
|
73400
|
+
existed: true
|
|
73401
|
+
};
|
|
73402
|
+
}
|
|
73403
|
+
function writeDocument(file, doc, scope) {
|
|
73404
|
+
const body = `${JSON.stringify(doc, null, 2)}
|
|
73405
|
+
`;
|
|
73406
|
+
if (scope === "user") {
|
|
73407
|
+
writeOwnerOnlyFileAtomic(file, body);
|
|
73408
|
+
return;
|
|
73409
|
+
}
|
|
73410
|
+
mkdirSync6(path168.dirname(file), { recursive: true });
|
|
73411
|
+
writeFileSync7(file, body, { encoding: "utf8" });
|
|
73412
|
+
}
|
|
73413
|
+
function addServer(options) {
|
|
73414
|
+
if (!SERVER_NAME_PATTERN.test(options.name)) {
|
|
73415
|
+
return { ok: false, error: `server name "${options.name}" must match ${SERVER_NAME_PATTERN.source}` };
|
|
73416
|
+
}
|
|
73417
|
+
if (!/^[A-Za-z_]/.test(options.name)) {
|
|
73418
|
+
return {
|
|
73419
|
+
ok: false,
|
|
73420
|
+
error: `server name "${options.name}" must start with a letter or underscore, or none of its tools can be named`
|
|
73421
|
+
};
|
|
73422
|
+
}
|
|
73423
|
+
if (options.name.length > MAX_SERVER_NAME_LENGTH) {
|
|
73424
|
+
return {
|
|
73425
|
+
ok: false,
|
|
73426
|
+
error: `server name "${options.name}" is ${options.name.length} characters; the limit is ${MAX_SERVER_NAME_LENGTH} so that "<server>__<tool>" fits in 64`
|
|
73427
|
+
};
|
|
73428
|
+
}
|
|
73429
|
+
const file = fileForScope(options.scope, options);
|
|
73430
|
+
if (typeof file !== "string")
|
|
73431
|
+
return file;
|
|
73432
|
+
const loaded = readForWrite(file);
|
|
73433
|
+
if ("error" in loaded)
|
|
73434
|
+
return { ok: false, error: loaded.error };
|
|
73435
|
+
if (loaded.doc.servers[options.name] !== undefined && options.force !== true) {
|
|
73436
|
+
return {
|
|
73437
|
+
ok: false,
|
|
73438
|
+
error: `server "${options.name}" already exists in ${file}. Pass --force to replace it, or remove it first.`
|
|
73439
|
+
};
|
|
73440
|
+
}
|
|
73441
|
+
loaded.doc.servers[options.name] = options.entry;
|
|
73442
|
+
writeDocument(file, loaded.doc, options.scope);
|
|
73443
|
+
return { ok: true, file, created: !loaded.existed };
|
|
73444
|
+
}
|
|
73445
|
+
function removeServer(options) {
|
|
73446
|
+
const file = fileForScope(options.scope, options);
|
|
73447
|
+
if (typeof file !== "string")
|
|
73448
|
+
return file;
|
|
73449
|
+
const loaded = readForWrite(file);
|
|
73450
|
+
if ("error" in loaded)
|
|
73451
|
+
return { ok: false, error: loaded.error };
|
|
73452
|
+
if (loaded.doc.servers[options.name] === undefined) {
|
|
73453
|
+
return { ok: false, error: `server "${options.name}" is not defined in ${file}` };
|
|
73454
|
+
}
|
|
73455
|
+
delete loaded.doc.servers[options.name];
|
|
73456
|
+
writeDocument(file, loaded.doc, options.scope);
|
|
73457
|
+
return { ok: true, file, created: false };
|
|
73458
|
+
}
|
|
73459
|
+
function setServerEnabled(options) {
|
|
73460
|
+
const file = overlayFile(options.configDir);
|
|
73461
|
+
const read = readConfigFile(file);
|
|
73462
|
+
let overlay = {};
|
|
73463
|
+
if (read.ok) {
|
|
73464
|
+
try {
|
|
73465
|
+
const parsed = parseJsonTolerant(read.text);
|
|
73466
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
73467
|
+
overlay = parsed;
|
|
73468
|
+
}
|
|
73469
|
+
} catch {
|
|
73470
|
+
overlay = {};
|
|
73471
|
+
}
|
|
73472
|
+
} else if (!isDefiniteAbsence(read.reason)) {
|
|
73473
|
+
return { ok: false, error: `${file} could not be read (${read.reason}); nothing was written` };
|
|
73474
|
+
}
|
|
73475
|
+
const overrides = { ...overlay.overrides, [options.name]: options.enabled };
|
|
73476
|
+
writeOwnerOnlyFileAtomic(file, `${JSON.stringify({ ...overlay, overrides }, null, 2)}
|
|
73477
|
+
`);
|
|
73478
|
+
if (options.source === "user") {
|
|
73479
|
+
clearStickyUserFlag(options.name, options.enabled, options.configDir);
|
|
73480
|
+
}
|
|
73481
|
+
return { ok: true, file, created: !read.ok };
|
|
73482
|
+
}
|
|
73483
|
+
function clearStickyUserFlag(name, enabled, configDir) {
|
|
73484
|
+
const file = userConfigFile(configDir);
|
|
73485
|
+
const loaded = readForWrite(file);
|
|
73486
|
+
if ("error" in loaded)
|
|
73487
|
+
return;
|
|
73488
|
+
const entry = loaded.doc.servers[name];
|
|
73489
|
+
if (entry === undefined)
|
|
73490
|
+
return;
|
|
73491
|
+
if (entry.enabled === undefined || entry.enabled === enabled)
|
|
73492
|
+
return;
|
|
73493
|
+
delete entry.enabled;
|
|
73494
|
+
writeDocument(file, loaded.doc, "user");
|
|
73495
|
+
}
|
|
73496
|
+
function fileForScope(scope, targets) {
|
|
73497
|
+
if (scope === "user")
|
|
73498
|
+
return userConfigFile(targets.configDir);
|
|
73499
|
+
if (targets.projectRoot === undefined) {
|
|
73500
|
+
return { ok: false, error: "--scope project needs a project root; none was resolved" };
|
|
73501
|
+
}
|
|
73502
|
+
return projectConfigFile(targets.projectRoot);
|
|
73503
|
+
}
|
|
73504
|
+
|
|
73359
73505
|
// src/harness/external/env.ts
|
|
73360
73506
|
var EXTERNAL_ENV_DENY = [
|
|
73361
73507
|
"ANTHROPIC_API_KEY",
|
|
@@ -73585,11 +73731,32 @@ function createMcpRuntime(options) {
|
|
|
73585
73731
|
const kills = [];
|
|
73586
73732
|
const env = options.env ?? process.env;
|
|
73587
73733
|
const connect = options.connect ?? ((server) => defaultConnect(server, env, dialling.signal, kills, options.configDir));
|
|
73734
|
+
const closedAlready = new WeakSet;
|
|
73735
|
+
const mutated = new Set;
|
|
73588
73736
|
const settled = startServers(launchable, connect).then((result) => {
|
|
73589
|
-
|
|
73590
|
-
|
|
73737
|
+
for (const incoming of result.servers) {
|
|
73738
|
+
if (!mutated.has(incoming.name))
|
|
73739
|
+
continue;
|
|
73740
|
+
if (incoming.connection !== undefined && !closedAlready.has(incoming.connection)) {
|
|
73741
|
+
closedAlready.add(incoming.connection);
|
|
73742
|
+
incoming.connection.close().catch(() => {});
|
|
73743
|
+
}
|
|
73744
|
+
}
|
|
73745
|
+
const kept = states.filter((state) => mutated.has(state.name));
|
|
73746
|
+
const incoming = result.servers.filter((state) => !mutated.has(state.name));
|
|
73747
|
+
const heldStill = heldStates.filter((state) => !mutated.has(state.name));
|
|
73748
|
+
states = uniqueByName([...heldStill, ...incoming, ...kept]).sort((a, b) => a.name.localeCompare(b.name));
|
|
73749
|
+
catalog = mergeCatalogs([
|
|
73750
|
+
{
|
|
73751
|
+
entries: result.catalog.entries.filter((entry) => !mutated.has(entry.server)),
|
|
73752
|
+
skipped: result.catalog.skipped.filter((skip) => !mutated.has(skip.server))
|
|
73753
|
+
},
|
|
73754
|
+
{
|
|
73755
|
+
entries: catalog.entries.filter((entry) => mutated.has(entry.server)),
|
|
73756
|
+
skipped: catalog.skipped.filter((skip) => mutated.has(skip.server))
|
|
73757
|
+
}
|
|
73758
|
+
]);
|
|
73591
73759
|
}).catch(() => {});
|
|
73592
|
-
const closedAlready = new WeakSet;
|
|
73593
73760
|
const closeOnce = async (list) => {
|
|
73594
73761
|
const fresh = list.filter((state) => state.connection !== undefined && !closedAlready.has(state.connection));
|
|
73595
73762
|
for (const state of fresh) {
|
|
@@ -73599,6 +73766,7 @@ function createMcpRuntime(options) {
|
|
|
73599
73766
|
await closeServers(fresh);
|
|
73600
73767
|
};
|
|
73601
73768
|
let closing;
|
|
73769
|
+
const inflight = new Map;
|
|
73602
73770
|
return {
|
|
73603
73771
|
catalog: () => catalog,
|
|
73604
73772
|
servers: () => states,
|
|
@@ -73608,8 +73776,100 @@ function createMcpRuntime(options) {
|
|
|
73608
73776
|
ready: async () => {
|
|
73609
73777
|
await settled;
|
|
73610
73778
|
},
|
|
73779
|
+
connectServer: (name) => runExclusive(name, () => connectOne(name)),
|
|
73780
|
+
disconnectServer: (name) => runExclusive(name, () => disconnectOne(name)),
|
|
73611
73781
|
close: () => closing ??= closeNow()
|
|
73612
73782
|
};
|
|
73783
|
+
function runExclusive(name, work) {
|
|
73784
|
+
const existing = inflight.get(name);
|
|
73785
|
+
if (existing !== undefined)
|
|
73786
|
+
return existing;
|
|
73787
|
+
const pending = work().finally(() => {
|
|
73788
|
+
inflight.delete(name);
|
|
73789
|
+
});
|
|
73790
|
+
inflight.set(name, pending);
|
|
73791
|
+
return pending;
|
|
73792
|
+
}
|
|
73793
|
+
function replaceState(next) {
|
|
73794
|
+
states = uniqueByName([...states.filter((state) => state.name !== next.name), next]).sort((a, b) => a.name.localeCompare(b.name));
|
|
73795
|
+
}
|
|
73796
|
+
function stripCatalog(name) {
|
|
73797
|
+
catalog = {
|
|
73798
|
+
entries: catalog.entries.filter((entry) => entry.server !== name),
|
|
73799
|
+
skipped: catalog.skipped.filter((skip) => skip.server !== name)
|
|
73800
|
+
};
|
|
73801
|
+
}
|
|
73802
|
+
async function persistEnabled(server, enabled) {
|
|
73803
|
+
const written = setServerEnabled({
|
|
73804
|
+
name: server.name,
|
|
73805
|
+
enabled,
|
|
73806
|
+
source: server.source,
|
|
73807
|
+
...options.configDir === undefined ? {} : { configDir: options.configDir }
|
|
73808
|
+
});
|
|
73809
|
+
return written.ok ? undefined : { ok: false, message: written.error };
|
|
73810
|
+
}
|
|
73811
|
+
async function connectOne(name) {
|
|
73812
|
+
if (closing !== undefined)
|
|
73813
|
+
return { ok: false, message: "session is closing" };
|
|
73814
|
+
const server = byName.get(name);
|
|
73815
|
+
if (server === undefined)
|
|
73816
|
+
return { ok: false, message: `no server named "${name}"` };
|
|
73817
|
+
if (requiresApproval(server, loadTrustStore(options.configDir))) {
|
|
73818
|
+
return { ok: false, message: `project server not approved; run \`keryx mcp trust ${name}\`` };
|
|
73819
|
+
}
|
|
73820
|
+
const current = states.find((state) => state.name === name);
|
|
73821
|
+
if (current?.status === "connected")
|
|
73822
|
+
return { ok: true };
|
|
73823
|
+
if (current?.status === "connecting") {
|
|
73824
|
+
return { ok: false, message: `server "${name}" is already connecting` };
|
|
73825
|
+
}
|
|
73826
|
+
const persist = await persistEnabled(server, true);
|
|
73827
|
+
if (persist !== undefined)
|
|
73828
|
+
return persist;
|
|
73829
|
+
mutated.add(name);
|
|
73830
|
+
if (current?.connection !== undefined) {
|
|
73831
|
+
if (!closedAlready.has(current.connection))
|
|
73832
|
+
closedAlready.add(current.connection);
|
|
73833
|
+
await closeServers([current]);
|
|
73834
|
+
}
|
|
73835
|
+
stripCatalog(name);
|
|
73836
|
+
replaceState({ name, status: "connecting", toolCount: 0 });
|
|
73837
|
+
const result = await startServers([{ ...server, enabled: true }], connect);
|
|
73838
|
+
const next = result.servers[0];
|
|
73839
|
+
if (next === undefined) {
|
|
73840
|
+
replaceState({ name, status: "failed", error: "dial returned nothing", toolCount: 0 });
|
|
73841
|
+
return { ok: false, message: "dial returned nothing" };
|
|
73842
|
+
}
|
|
73843
|
+
replaceState(next);
|
|
73844
|
+
if (next.status === "connected") {
|
|
73845
|
+
catalog = mergeCatalogs([catalog, result.catalog]);
|
|
73846
|
+
return { ok: true };
|
|
73847
|
+
}
|
|
73848
|
+
return { ok: false, message: next.error ?? `server "${name}" did not connect` };
|
|
73849
|
+
}
|
|
73850
|
+
async function disconnectOne(name) {
|
|
73851
|
+
if (closing !== undefined)
|
|
73852
|
+
return { ok: false, message: "session is closing" };
|
|
73853
|
+
const server = byName.get(name);
|
|
73854
|
+
if (server === undefined)
|
|
73855
|
+
return { ok: false, message: `no server named "${name}"` };
|
|
73856
|
+
const current = states.find((state) => state.name === name);
|
|
73857
|
+
if (current?.status === "connecting") {
|
|
73858
|
+
return { ok: false, message: `server "${name}" is still connecting` };
|
|
73859
|
+
}
|
|
73860
|
+
const persist = await persistEnabled(server, false);
|
|
73861
|
+
if (persist !== undefined)
|
|
73862
|
+
return persist;
|
|
73863
|
+
mutated.add(name);
|
|
73864
|
+
if (current?.connection !== undefined) {
|
|
73865
|
+
if (!closedAlready.has(current.connection))
|
|
73866
|
+
closedAlready.add(current.connection);
|
|
73867
|
+
await closeServers([current]);
|
|
73868
|
+
}
|
|
73869
|
+
stripCatalog(name);
|
|
73870
|
+
replaceState({ name, status: "disabled", toolCount: 0 });
|
|
73871
|
+
return { ok: true };
|
|
73872
|
+
}
|
|
73613
73873
|
async function closeNow() {
|
|
73614
73874
|
{
|
|
73615
73875
|
dialling.abort();
|
|
@@ -73620,6 +73880,17 @@ function createMcpRuntime(options) {
|
|
|
73620
73880
|
}
|
|
73621
73881
|
}
|
|
73622
73882
|
}
|
|
73883
|
+
function uniqueByName(list) {
|
|
73884
|
+
const seen = new Set;
|
|
73885
|
+
const out = [];
|
|
73886
|
+
for (const state of list) {
|
|
73887
|
+
if (seen.has(state.name))
|
|
73888
|
+
continue;
|
|
73889
|
+
seen.add(state.name);
|
|
73890
|
+
out.push(state);
|
|
73891
|
+
}
|
|
73892
|
+
return out;
|
|
73893
|
+
}
|
|
73623
73894
|
function handshakeBudgetMs(server) {
|
|
73624
73895
|
const configured = server.startup_timeout_sec;
|
|
73625
73896
|
if (configured === undefined || !Number.isFinite(configured) || configured <= 0) {
|
|
@@ -73685,153 +73956,7 @@ async function defaultConnect(server, env, signal, kills, configDir) {
|
|
|
73685
73956
|
function defaultServerCwd(server, fallbackCwd = process.cwd()) {
|
|
73686
73957
|
if (server.source !== "project")
|
|
73687
73958
|
return fallbackCwd;
|
|
73688
|
-
return
|
|
73689
|
-
}
|
|
73690
|
-
|
|
73691
|
-
// src/mcp-servers/store.ts
|
|
73692
|
-
init_config_dir();
|
|
73693
|
-
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
73694
|
-
import path169 from "path";
|
|
73695
|
-
var SERVER_NAME_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
73696
|
-
function userConfigFile(configDir) {
|
|
73697
|
-
return path169.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers.json");
|
|
73698
|
-
}
|
|
73699
|
-
function projectConfigFile(projectRoot) {
|
|
73700
|
-
return path169.join(projectRoot, ".keryx", "mcp-servers.json");
|
|
73701
|
-
}
|
|
73702
|
-
function overlayFile(configDir) {
|
|
73703
|
-
return path169.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers-disabled.json");
|
|
73704
|
-
}
|
|
73705
|
-
function readForWrite(file) {
|
|
73706
|
-
const read = readConfigFile(file);
|
|
73707
|
-
if (!read.ok) {
|
|
73708
|
-
return isDefiniteAbsence(read.reason) ? { doc: { schemaVersion: 1, servers: Object.create(null) }, existed: false } : { error: `${file} could not be read (${read.reason}); nothing was written` };
|
|
73709
|
-
}
|
|
73710
|
-
let parsed;
|
|
73711
|
-
try {
|
|
73712
|
-
parsed = parseJsonTolerant(read.text);
|
|
73713
|
-
} catch (error) {
|
|
73714
|
-
return {
|
|
73715
|
-
error: `${file} is not valid JSON (${error instanceof Error ? error.message : String(error)}); nothing was written. Fix it by hand first.`
|
|
73716
|
-
};
|
|
73717
|
-
}
|
|
73718
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
73719
|
-
return { error: `${file} is not a JSON object; nothing was written` };
|
|
73720
|
-
}
|
|
73721
|
-
const doc = parsed;
|
|
73722
|
-
const servers = doc.servers;
|
|
73723
|
-
if (servers !== undefined && (typeof servers !== "object" || servers === null || Array.isArray(servers))) {
|
|
73724
|
-
return { error: `${file} has a \`servers\` that is not an object; nothing was written` };
|
|
73725
|
-
}
|
|
73726
|
-
return {
|
|
73727
|
-
doc: {
|
|
73728
|
-
...parsed,
|
|
73729
|
-
schemaVersion: typeof doc.schemaVersion === "number" ? doc.schemaVersion : 1,
|
|
73730
|
-
servers: Object.assign(Object.create(null), servers)
|
|
73731
|
-
},
|
|
73732
|
-
existed: true
|
|
73733
|
-
};
|
|
73734
|
-
}
|
|
73735
|
-
function writeDocument(file, doc, scope) {
|
|
73736
|
-
const body = `${JSON.stringify(doc, null, 2)}
|
|
73737
|
-
`;
|
|
73738
|
-
if (scope === "user") {
|
|
73739
|
-
writeOwnerOnlyFileAtomic(file, body);
|
|
73740
|
-
return;
|
|
73741
|
-
}
|
|
73742
|
-
mkdirSync6(path169.dirname(file), { recursive: true });
|
|
73743
|
-
writeFileSync7(file, body, { encoding: "utf8" });
|
|
73744
|
-
}
|
|
73745
|
-
function addServer(options) {
|
|
73746
|
-
if (!SERVER_NAME_PATTERN.test(options.name)) {
|
|
73747
|
-
return { ok: false, error: `server name "${options.name}" must match ${SERVER_NAME_PATTERN.source}` };
|
|
73748
|
-
}
|
|
73749
|
-
if (!/^[A-Za-z_]/.test(options.name)) {
|
|
73750
|
-
return {
|
|
73751
|
-
ok: false,
|
|
73752
|
-
error: `server name "${options.name}" must start with a letter or underscore, or none of its tools can be named`
|
|
73753
|
-
};
|
|
73754
|
-
}
|
|
73755
|
-
if (options.name.length > MAX_SERVER_NAME_LENGTH) {
|
|
73756
|
-
return {
|
|
73757
|
-
ok: false,
|
|
73758
|
-
error: `server name "${options.name}" is ${options.name.length} characters; the limit is ${MAX_SERVER_NAME_LENGTH} so that "<server>__<tool>" fits in 64`
|
|
73759
|
-
};
|
|
73760
|
-
}
|
|
73761
|
-
const file = fileForScope(options.scope, options);
|
|
73762
|
-
if (typeof file !== "string")
|
|
73763
|
-
return file;
|
|
73764
|
-
const loaded = readForWrite(file);
|
|
73765
|
-
if ("error" in loaded)
|
|
73766
|
-
return { ok: false, error: loaded.error };
|
|
73767
|
-
if (loaded.doc.servers[options.name] !== undefined && options.force !== true) {
|
|
73768
|
-
return {
|
|
73769
|
-
ok: false,
|
|
73770
|
-
error: `server "${options.name}" already exists in ${file}. Pass --force to replace it, or remove it first.`
|
|
73771
|
-
};
|
|
73772
|
-
}
|
|
73773
|
-
loaded.doc.servers[options.name] = options.entry;
|
|
73774
|
-
writeDocument(file, loaded.doc, options.scope);
|
|
73775
|
-
return { ok: true, file, created: !loaded.existed };
|
|
73776
|
-
}
|
|
73777
|
-
function removeServer(options) {
|
|
73778
|
-
const file = fileForScope(options.scope, options);
|
|
73779
|
-
if (typeof file !== "string")
|
|
73780
|
-
return file;
|
|
73781
|
-
const loaded = readForWrite(file);
|
|
73782
|
-
if ("error" in loaded)
|
|
73783
|
-
return { ok: false, error: loaded.error };
|
|
73784
|
-
if (loaded.doc.servers[options.name] === undefined) {
|
|
73785
|
-
return { ok: false, error: `server "${options.name}" is not defined in ${file}` };
|
|
73786
|
-
}
|
|
73787
|
-
delete loaded.doc.servers[options.name];
|
|
73788
|
-
writeDocument(file, loaded.doc, options.scope);
|
|
73789
|
-
return { ok: true, file, created: false };
|
|
73790
|
-
}
|
|
73791
|
-
function setServerEnabled(options) {
|
|
73792
|
-
const file = overlayFile(options.configDir);
|
|
73793
|
-
const read = readConfigFile(file);
|
|
73794
|
-
let overlay = {};
|
|
73795
|
-
if (read.ok) {
|
|
73796
|
-
try {
|
|
73797
|
-
const parsed = parseJsonTolerant(read.text);
|
|
73798
|
-
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
73799
|
-
overlay = parsed;
|
|
73800
|
-
}
|
|
73801
|
-
} catch {
|
|
73802
|
-
overlay = {};
|
|
73803
|
-
}
|
|
73804
|
-
} else if (!isDefiniteAbsence(read.reason)) {
|
|
73805
|
-
return { ok: false, error: `${file} could not be read (${read.reason}); nothing was written` };
|
|
73806
|
-
}
|
|
73807
|
-
const overrides = { ...overlay.overrides, [options.name]: options.enabled };
|
|
73808
|
-
writeOwnerOnlyFileAtomic(file, `${JSON.stringify({ ...overlay, overrides }, null, 2)}
|
|
73809
|
-
`);
|
|
73810
|
-
if (options.source === "user") {
|
|
73811
|
-
clearStickyUserFlag(options.name, options.enabled, options.configDir);
|
|
73812
|
-
}
|
|
73813
|
-
return { ok: true, file, created: !read.ok };
|
|
73814
|
-
}
|
|
73815
|
-
function clearStickyUserFlag(name, enabled, configDir) {
|
|
73816
|
-
const file = userConfigFile(configDir);
|
|
73817
|
-
const loaded = readForWrite(file);
|
|
73818
|
-
if ("error" in loaded)
|
|
73819
|
-
return;
|
|
73820
|
-
const entry = loaded.doc.servers[name];
|
|
73821
|
-
if (entry === undefined)
|
|
73822
|
-
return;
|
|
73823
|
-
if (entry.enabled === undefined || entry.enabled === enabled)
|
|
73824
|
-
return;
|
|
73825
|
-
delete entry.enabled;
|
|
73826
|
-
writeDocument(file, loaded.doc, "user");
|
|
73827
|
-
}
|
|
73828
|
-
function fileForScope(scope, targets) {
|
|
73829
|
-
if (scope === "user")
|
|
73830
|
-
return userConfigFile(targets.configDir);
|
|
73831
|
-
if (targets.projectRoot === undefined) {
|
|
73832
|
-
return { ok: false, error: "--scope project needs a project root; none was resolved" };
|
|
73833
|
-
}
|
|
73834
|
-
return projectConfigFile(targets.projectRoot);
|
|
73959
|
+
return path169.dirname(path169.dirname(server.file));
|
|
73835
73960
|
}
|
|
73836
73961
|
|
|
73837
73962
|
// src/commands/mcp-servers.ts
|
|
@@ -88242,36 +88367,257 @@ function buildConsumerModel(input) {
|
|
|
88242
88367
|
project: ${input.projectFile}`
|
|
88243
88368
|
};
|
|
88244
88369
|
}
|
|
88245
|
-
|
|
88370
|
+
var MCP_CONSUMER_FOOTER = [
|
|
88371
|
+
{ key: "\u2191/\u2193", label: "select" },
|
|
88372
|
+
{ key: "click", label: "row: select/act" },
|
|
88373
|
+
{ key: "c/d", label: "connect/disconnect" },
|
|
88374
|
+
{ key: "y", label: "confirm" },
|
|
88375
|
+
{ key: "esc", label: "close" }
|
|
88376
|
+
];
|
|
88377
|
+
var MCP_CONSUMER_HEADER = "Servers keryx CONNECTS TO. c/d then y to connect or disconnect. Held project servers need `keryx mcp trust` first.";
|
|
88378
|
+
var STATUS_GLYPH = {
|
|
88379
|
+
connected: "\u25CF connected",
|
|
88380
|
+
connecting: "\u2026 connecting",
|
|
88381
|
+
failed: "\u2717 failed",
|
|
88382
|
+
disabled: "\u25CB disabled",
|
|
88383
|
+
"needs-approval": "! needs approval",
|
|
88384
|
+
needs_auth: "! needs auth"
|
|
88385
|
+
};
|
|
88386
|
+
function consumerActionFor(status) {
|
|
88387
|
+
if (status === "connected")
|
|
88388
|
+
return "disconnect";
|
|
88389
|
+
if (status === "disabled" || status === "failed")
|
|
88390
|
+
return "connect";
|
|
88391
|
+
return;
|
|
88392
|
+
}
|
|
88393
|
+
function statusGlyph(status) {
|
|
88394
|
+
return STATUS_GLYPH[status] ?? status;
|
|
88395
|
+
}
|
|
88396
|
+
function formatConsumerModalRow(row, isSelected, status) {
|
|
88397
|
+
const mark = isSelected ? ">" : " ";
|
|
88398
|
+
const name = row.name.padEnd(18);
|
|
88399
|
+
const source = row.source.padEnd(10);
|
|
88400
|
+
const transport = row.transport.padEnd(6);
|
|
88401
|
+
const glyph = statusGlyph(row.status).padEnd(18);
|
|
88402
|
+
const tools = row.status === "connected" ? `${row.toolCount} tool(s)` : "";
|
|
88403
|
+
let action;
|
|
88404
|
+
const offered = consumerActionFor(row.status);
|
|
88405
|
+
if (status.kind === "armed" && status.target.name === row.name) {
|
|
88406
|
+
action = ` [click again or press y to ${status.target.action}]`;
|
|
88407
|
+
} else if (status.kind === "running" && status.target.name === row.name) {
|
|
88408
|
+
action = ` ${status.target.action === "connect" ? "connecting\u2026" : "disconnecting\u2026"}`;
|
|
88409
|
+
} else if (status.kind === "done" && status.target.name === row.name) {
|
|
88410
|
+
action = status.outcome.ok ? " \u2713 done" : ` \u2717 ${status.outcome.message}`;
|
|
88411
|
+
} else if (offered === "disconnect") {
|
|
88412
|
+
action = " [d] disconnect";
|
|
88413
|
+
} else if (offered === "connect") {
|
|
88414
|
+
action = " [c] connect";
|
|
88415
|
+
} else if (row.action !== undefined) {
|
|
88416
|
+
action = ` \u2192 ${row.action}`;
|
|
88417
|
+
} else {
|
|
88418
|
+
action = "";
|
|
88419
|
+
}
|
|
88420
|
+
const creds = row.credentials.length > 0 ? ` [reads ${row.credentials.join(", ")}]` : "";
|
|
88421
|
+
return `${mark} ${name} ${source} ${transport} ${glyph}${tools}${action} ${row.target}${creds}`.trimEnd();
|
|
88422
|
+
}
|
|
88423
|
+
function formatConsumerModalLines(model, selected, status) {
|
|
88246
88424
|
if (model === undefined) {
|
|
88247
88425
|
return [
|
|
88248
88426
|
"No MCP session yet \u2014 this shell has not built a tool list.",
|
|
88249
88427
|
"Configured servers are listed by `keryx mcp list`; `keryx mcp doctor` dials them."
|
|
88250
88428
|
];
|
|
88251
88429
|
}
|
|
88430
|
+
const header = [MCP_CONSUMER_HEADER];
|
|
88431
|
+
const problems = model.problems.flatMap((problem) => problem.split(`
|
|
88432
|
+
`).map((line, index) => index === 0 ? `config problem \u2014 ${line}` : ` ${line}`));
|
|
88433
|
+
if (model.emptyHint !== undefined) {
|
|
88434
|
+
return [...header, ...problems, ...model.emptyHint.split(`
|
|
88435
|
+
`)];
|
|
88436
|
+
}
|
|
88252
88437
|
return [
|
|
88253
|
-
...
|
|
88254
|
-
|
|
88255
|
-
...model.
|
|
88256
|
-
`),
|
|
88257
|
-
...model.rows.flatMap((row) => {
|
|
88258
|
-
const lines = [formatConsumerRow(row)];
|
|
88259
|
-
if (row.detail !== undefined) {
|
|
88260
|
-
for (const line of row.detail.split(`
|
|
88261
|
-
`))
|
|
88262
|
-
lines.push(` ${line}`);
|
|
88263
|
-
}
|
|
88264
|
-
if (row.action !== undefined)
|
|
88265
|
-
lines.push(` \u2192 ${row.action}`);
|
|
88266
|
-
return lines;
|
|
88267
|
-
})
|
|
88438
|
+
...header,
|
|
88439
|
+
...problems,
|
|
88440
|
+
...model.rows.map((row, index) => formatConsumerModalRow(row, index === selected, status))
|
|
88268
88441
|
];
|
|
88269
88442
|
}
|
|
88270
|
-
function
|
|
88271
|
-
const
|
|
88272
|
-
|
|
88273
|
-
|
|
88274
|
-
|
|
88443
|
+
function asRowTarget2(body) {
|
|
88444
|
+
const parent = body;
|
|
88445
|
+
if (parent.add === undefined || parent.getChildren === undefined || parent.remove === undefined) {
|
|
88446
|
+
return;
|
|
88447
|
+
}
|
|
88448
|
+
return {
|
|
88449
|
+
add: parent.add.bind(parent),
|
|
88450
|
+
getChildren: parent.getChildren.bind(parent),
|
|
88451
|
+
remove: parent.remove.bind(parent)
|
|
88452
|
+
};
|
|
88453
|
+
}
|
|
88454
|
+
function presentMcpConsumer(openModalFn, otui, chrome, options) {
|
|
88455
|
+
let selected = 0;
|
|
88456
|
+
let scroll = 0;
|
|
88457
|
+
let status = { kind: "idle" };
|
|
88458
|
+
let body;
|
|
88459
|
+
let rowCtor;
|
|
88460
|
+
let activeRenderer;
|
|
88461
|
+
let unsubscribeKey;
|
|
88462
|
+
const rendererHint = options.renderer ?? chrome?.renderer;
|
|
88463
|
+
const bodyRows = options.visibleRows ?? (typeof rendererHint?.width === "number" && typeof rendererHint.height === "number" ? modalBodyRows(resolveModalPanelSize(rendererHint.width, rendererHint.height).height) : 13);
|
|
88464
|
+
const rowsOf = () => options.snapshot()?.rows ?? [];
|
|
88465
|
+
const paint = () => {
|
|
88466
|
+
if (body === undefined || rowCtor === undefined)
|
|
88467
|
+
return;
|
|
88468
|
+
clearTranscriptChildren(body);
|
|
88469
|
+
const model = options.snapshot();
|
|
88470
|
+
const rows = model?.rows ?? [];
|
|
88471
|
+
selected = rows.length === 0 ? 0 : Math.min(selected, rows.length - 1);
|
|
88472
|
+
scroll = scrollToReveal3(selected, scroll, bodyRows);
|
|
88473
|
+
scroll = clampScroll3(scroll, Math.max(rows.length, 1), bodyRows);
|
|
88474
|
+
const lines = formatConsumerModalLines(model, selected, status);
|
|
88475
|
+
if (rows.length === 0) {
|
|
88476
|
+
for (const [i, line] of lines.entries()) {
|
|
88477
|
+
body.add(new rowCtor(activeRenderer, { id: `mcp-consumer-line-${i}`, content: line }));
|
|
88478
|
+
}
|
|
88479
|
+
return;
|
|
88480
|
+
}
|
|
88481
|
+
body.add(new rowCtor(activeRenderer, { id: "mcp-consumer-header", content: MCP_CONSUMER_HEADER }));
|
|
88482
|
+
const problems = model?.problems ?? [];
|
|
88483
|
+
for (const [i, problem] of problems.entries()) {
|
|
88484
|
+
body.add(new rowCtor(activeRenderer, {
|
|
88485
|
+
id: `mcp-consumer-problem-${i}`,
|
|
88486
|
+
content: `config problem \u2014 ${problem}`
|
|
88487
|
+
}));
|
|
88488
|
+
}
|
|
88489
|
+
const start = clampScroll3(scroll, rows.length, bodyRows);
|
|
88490
|
+
for (const [i, row] of rows.slice(start, start + bodyRows).entries()) {
|
|
88491
|
+
const index = start + i;
|
|
88492
|
+
body.add(new rowCtor(activeRenderer, {
|
|
88493
|
+
id: `mcp-consumer-row-${row.name}`,
|
|
88494
|
+
content: formatConsumerModalRow(row, index === selected, status),
|
|
88495
|
+
onMouseDown: () => handleRowClick(row.name, index)
|
|
88496
|
+
}));
|
|
88497
|
+
}
|
|
88498
|
+
};
|
|
88499
|
+
const moveSelection = (next) => {
|
|
88500
|
+
const rows = rowsOf();
|
|
88501
|
+
if (rows.length === 0)
|
|
88502
|
+
return;
|
|
88503
|
+
const clamped = Math.min(rows.length - 1, Math.max(0, next));
|
|
88504
|
+
if (clamped === selected)
|
|
88505
|
+
return;
|
|
88506
|
+
selected = clamped;
|
|
88507
|
+
status = { kind: "idle" };
|
|
88508
|
+
paint();
|
|
88509
|
+
};
|
|
88510
|
+
const runAction = () => {
|
|
88511
|
+
if (status.kind !== "armed")
|
|
88512
|
+
return;
|
|
88513
|
+
const target = status.target;
|
|
88514
|
+
status = { kind: "running", target };
|
|
88515
|
+
paint();
|
|
88516
|
+
const fn = target.action === "connect" ? options.connect : options.disconnect;
|
|
88517
|
+
fn(target.name).then((outcome) => {
|
|
88518
|
+
status = { kind: "done", target, outcome };
|
|
88519
|
+
paint();
|
|
88520
|
+
});
|
|
88521
|
+
};
|
|
88522
|
+
const armFor = (name) => {
|
|
88523
|
+
const rows = rowsOf();
|
|
88524
|
+
const index = rows.findIndex((row) => row.name === name);
|
|
88525
|
+
const row = rows[index];
|
|
88526
|
+
const offered = row === undefined ? undefined : consumerActionFor(row.status);
|
|
88527
|
+
if (index < 0 || row === undefined || offered === undefined || status.kind === "running")
|
|
88528
|
+
return;
|
|
88529
|
+
selected = index;
|
|
88530
|
+
status = { kind: "armed", target: { name: row.name, action: offered } };
|
|
88531
|
+
paint();
|
|
88532
|
+
};
|
|
88533
|
+
const handleRowClick = (name, index) => {
|
|
88534
|
+
if (status.kind === "armed" && status.target.name === name) {
|
|
88535
|
+
runAction();
|
|
88536
|
+
return;
|
|
88537
|
+
}
|
|
88538
|
+
if (status.kind === "running")
|
|
88539
|
+
return;
|
|
88540
|
+
const row = rowsOf()[index];
|
|
88541
|
+
if (row === undefined || consumerActionFor(row.status) === undefined) {
|
|
88542
|
+
if (index !== selected) {
|
|
88543
|
+
selected = index;
|
|
88544
|
+
status = { kind: "idle" };
|
|
88545
|
+
paint();
|
|
88546
|
+
}
|
|
88547
|
+
return;
|
|
88548
|
+
}
|
|
88549
|
+
armFor(name);
|
|
88550
|
+
};
|
|
88551
|
+
const handle = openModalFn(otui, chrome, {
|
|
88552
|
+
title: "MCP servers",
|
|
88553
|
+
tabs: [{ id: "servers", label: "Servers" }],
|
|
88554
|
+
initialTab: "servers",
|
|
88555
|
+
footer: MCP_CONSUMER_FOOTER,
|
|
88556
|
+
renderTab: (_tabId, tabBody, ctx) => {
|
|
88557
|
+
const renderer = options.renderer ?? chrome?.renderer;
|
|
88558
|
+
const ctor = otui.TextRenderable;
|
|
88559
|
+
const target = asRowTarget2(tabBody);
|
|
88560
|
+
if (target === undefined || ctor === undefined)
|
|
88561
|
+
return;
|
|
88562
|
+
rowCtor = ctor;
|
|
88563
|
+
activeRenderer = renderer;
|
|
88564
|
+
body = target;
|
|
88565
|
+
paint();
|
|
88566
|
+
},
|
|
88567
|
+
onClose: () => {
|
|
88568
|
+
unsubscribeKey?.();
|
|
88569
|
+
}
|
|
88570
|
+
});
|
|
88571
|
+
if (handle === undefined)
|
|
88572
|
+
return;
|
|
88573
|
+
if (options.onKeypress !== undefined) {
|
|
88574
|
+
unsubscribeKey = options.onKeypress((key) => {
|
|
88575
|
+
const token = key.name || key.sequence;
|
|
88576
|
+
const rows = rowsOf();
|
|
88577
|
+
if (status.kind === "armed") {
|
|
88578
|
+
if (token === "y") {
|
|
88579
|
+
runAction();
|
|
88580
|
+
} else {
|
|
88581
|
+
status = { kind: "idle" };
|
|
88582
|
+
paint();
|
|
88583
|
+
}
|
|
88584
|
+
return;
|
|
88585
|
+
}
|
|
88586
|
+
if (token === "c") {
|
|
88587
|
+
const row = rows[selected];
|
|
88588
|
+
if (row !== undefined && consumerActionFor(row.status) === "connect" && status.kind !== "running") {
|
|
88589
|
+
status = { kind: "armed", target: { name: row.name, action: "connect" } };
|
|
88590
|
+
paint();
|
|
88591
|
+
}
|
|
88592
|
+
return;
|
|
88593
|
+
}
|
|
88594
|
+
if (token === "d") {
|
|
88595
|
+
const row = rows[selected];
|
|
88596
|
+
if (row !== undefined && consumerActionFor(row.status) === "disconnect" && status.kind !== "running") {
|
|
88597
|
+
status = { kind: "armed", target: { name: row.name, action: "disconnect" } };
|
|
88598
|
+
paint();
|
|
88599
|
+
}
|
|
88600
|
+
return;
|
|
88601
|
+
}
|
|
88602
|
+
if (token === "up" || token === "k") {
|
|
88603
|
+
moveSelection(selected - 1);
|
|
88604
|
+
return;
|
|
88605
|
+
}
|
|
88606
|
+
if (token === "down" || token === "j") {
|
|
88607
|
+
moveSelection(selected + 1);
|
|
88608
|
+
return;
|
|
88609
|
+
}
|
|
88610
|
+
if (token === "pageup" || token === "pagedown") {
|
|
88611
|
+
const step = token === "pageup" ? -bodyRows : bodyRows;
|
|
88612
|
+
scroll = clampScroll3(scroll + step, Math.max(rows.length, 1), bodyRows);
|
|
88613
|
+
paint();
|
|
88614
|
+
}
|
|
88615
|
+
});
|
|
88616
|
+
}
|
|
88617
|
+
return handle;
|
|
88618
|
+
}
|
|
88619
|
+
function openMcpConsumer(otui, chrome, options) {
|
|
88620
|
+
return presentMcpConsumer((hostOtui, hostChrome, input) => openModal(hostOtui, hostChrome, input), otui, chrome, options);
|
|
88275
88621
|
}
|
|
88276
88622
|
|
|
88277
88623
|
// src/tui/tui-shell.ts
|
|
@@ -89048,7 +89394,7 @@ var AGENT_SLASH_COMMANDS = [
|
|
|
89048
89394
|
},
|
|
89049
89395
|
{
|
|
89050
89396
|
name: "/mcp",
|
|
89051
|
-
description: "MCP servers keryx is connected to \u2014 status,
|
|
89397
|
+
description: "MCP servers keryx is connected to \u2014 status, connect/disconnect",
|
|
89052
89398
|
modes: AGENT_ONLY
|
|
89053
89399
|
},
|
|
89054
89400
|
{
|
|
@@ -91817,7 +92163,7 @@ function stepQueueNavAction(selected, direction) {
|
|
|
91817
92163
|
}
|
|
91818
92164
|
|
|
91819
92165
|
// src/tui/worker-fleet.ts
|
|
91820
|
-
var
|
|
92166
|
+
var STATUS_GLYPH2 = {
|
|
91821
92167
|
queued: "\u25CB",
|
|
91822
92168
|
running: "\u25D0",
|
|
91823
92169
|
done: "\u25CF",
|
|
@@ -91956,7 +92302,7 @@ function formatFleetSidebar(workers, maxLines = 14) {
|
|
|
91956
92302
|
const room = Math.max(2, maxLines - lines.length);
|
|
91957
92303
|
const shown = sorted.slice(0, room);
|
|
91958
92304
|
for (const w of shown) {
|
|
91959
|
-
const g =
|
|
92305
|
+
const g = STATUS_GLYPH2[w.status];
|
|
91960
92306
|
const phase = humanFleetPhase(w.status, w.detail);
|
|
91961
92307
|
const label = clip4(w.label, 12);
|
|
91962
92308
|
const det = phase.length > 0 ? ` ${clip4(phase, 12)}` : "";
|
|
@@ -92050,7 +92396,7 @@ class WorkerFleet {
|
|
|
92050
92396
|
var MAX_SUBAGENT_EVENTS = 200;
|
|
92051
92397
|
var MAX_SUBAGENT_EVENT_CHARS = 2000;
|
|
92052
92398
|
var EXTERNAL_SUBAGENT_MARK = "\u2933";
|
|
92053
|
-
var
|
|
92399
|
+
var STATUS_GLYPH3 = {
|
|
92054
92400
|
queued: "\u25CB",
|
|
92055
92401
|
running: "\u25D0",
|
|
92056
92402
|
done: "\u25CF",
|
|
@@ -92092,7 +92438,7 @@ function formatElapsed(ms) {
|
|
|
92092
92438
|
return minRem === 0 ? `${hours}h` : `${hours}h ${minRem}m`;
|
|
92093
92439
|
}
|
|
92094
92440
|
function formatSubagentRow(session, width = SIDEBAR_TEXT_WIDTH) {
|
|
92095
|
-
const glyph =
|
|
92441
|
+
const glyph = STATUS_GLYPH3[session.status];
|
|
92096
92442
|
const phase = humanFleetPhase(session.status, session.detail);
|
|
92097
92443
|
const mark = session.runtime === "external" ? EXTERNAL_SUBAGENT_MARK : "";
|
|
92098
92444
|
const budget = Math.max(4, width - glyph.length - 1);
|
|
@@ -93451,7 +93797,7 @@ function openExternalInspector(otui, chrome, options) {
|
|
|
93451
93797
|
|
|
93452
93798
|
// src/tui/background-job-session.ts
|
|
93453
93799
|
var MAX_BACKGROUND_JOB_OUTPUT_CHARS = 20000;
|
|
93454
|
-
var
|
|
93800
|
+
var STATUS_GLYPH4 = {
|
|
93455
93801
|
running: "\u25D0",
|
|
93456
93802
|
exited: "\u25CF",
|
|
93457
93803
|
killed: "\u2717"
|
|
@@ -93469,7 +93815,7 @@ function formatJobListHeader(count) {
|
|
|
93469
93815
|
return `Background Jobs ${count}`;
|
|
93470
93816
|
}
|
|
93471
93817
|
function formatJobRow(entry, width = SIDEBAR_TEXT_WIDTH) {
|
|
93472
|
-
const glyph =
|
|
93818
|
+
const glyph = STATUS_GLYPH4[entry.status];
|
|
93473
93819
|
const budget = Math.max(1, width - glyph.length - 1);
|
|
93474
93820
|
const cmd = clip7(entry.command, budget);
|
|
93475
93821
|
return clip7(`${glyph} ${cmd}`, width);
|
|
@@ -96059,19 +96405,36 @@ Staying in the current session.
|
|
|
96059
96405
|
})();
|
|
96060
96406
|
};
|
|
96061
96407
|
const showMcpConsumer = () => {
|
|
96062
|
-
const runtime = deps.mcpRuntime?.();
|
|
96063
96408
|
const cwd = inspectorCwd();
|
|
96064
|
-
const
|
|
96065
|
-
|
|
96066
|
-
|
|
96067
|
-
|
|
96068
|
-
|
|
96069
|
-
|
|
96409
|
+
const snapshot = () => {
|
|
96410
|
+
const live = deps.mcpRuntime?.();
|
|
96411
|
+
if (live === undefined)
|
|
96412
|
+
return;
|
|
96413
|
+
return buildConsumerModel({
|
|
96414
|
+
configured: live.configured(),
|
|
96415
|
+
states: live.servers(),
|
|
96416
|
+
problems: live.problems(),
|
|
96417
|
+
userFile: userConfigFile(),
|
|
96418
|
+
projectFile: projectConfigFile(cwd)
|
|
96419
|
+
});
|
|
96420
|
+
};
|
|
96421
|
+
openMcpConsumer(otui, chrome, {
|
|
96422
|
+
snapshot,
|
|
96423
|
+
connect: async (name) => {
|
|
96424
|
+
const live = deps.mcpRuntime?.();
|
|
96425
|
+
if (live === undefined)
|
|
96426
|
+
return { ok: false, message: "No MCP session yet" };
|
|
96427
|
+
return live.connectServer(name);
|
|
96428
|
+
},
|
|
96429
|
+
disconnect: async (name) => {
|
|
96430
|
+
const live = deps.mcpRuntime?.();
|
|
96431
|
+
if (live === undefined)
|
|
96432
|
+
return { ok: false, message: "No MCP session yet" };
|
|
96433
|
+
return live.disconnectServer(name);
|
|
96434
|
+
},
|
|
96435
|
+
renderer: r,
|
|
96436
|
+
...inspectorKeys
|
|
96070
96437
|
});
|
|
96071
|
-
const lines = renderConsumerLines(model);
|
|
96072
|
-
for (const line of lines) {
|
|
96073
|
-
transcript.add(new otui.TextRenderable(r, { id: `mcp${uid++}`, content: otui.t`${otui.dim(line)}` }));
|
|
96074
|
-
}
|
|
96075
96438
|
};
|
|
96076
96439
|
const showTools = () => {
|
|
96077
96440
|
(async () => {
|
|
@@ -103099,7 +103462,7 @@ function monitorCommand(args) {
|
|
|
103099
103462
|
}
|
|
103100
103463
|
renderAgentsTree(snapshot);
|
|
103101
103464
|
}
|
|
103102
|
-
var
|
|
103465
|
+
var STATUS_GLYPH5 = {
|
|
103103
103466
|
running: "\u25D0",
|
|
103104
103467
|
done: "\u25CF",
|
|
103105
103468
|
blocked: "\u25FC",
|
|
@@ -103112,7 +103475,7 @@ function renderAgentsTree(snapshot) {
|
|
|
103112
103475
|
console.log(`run ${snapshot.runId ?? "(unknown)"} \u2014 ${snapshot.agents.length} subagent(s)`);
|
|
103113
103476
|
for (const agent of snapshot.agents) {
|
|
103114
103477
|
const tokens = `\u2191${agent.usage.inputTokens} \u2193${agent.usage.outputTokens}${agent.usage.exact ? "" : "~"}`;
|
|
103115
|
-
const glyph =
|
|
103478
|
+
const glyph = STATUS_GLYPH5[agent.status] ?? "\u25CB";
|
|
103116
103479
|
console.log(` ${glyph} ${agent.dispatchId} ${agent.status} ${agent.model ?? "-"} ${tokens}`);
|
|
103117
103480
|
}
|
|
103118
103481
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.100",
|
|
4
4
|
"description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|