@mrciphersmith/keryx 0.2.99 → 0.2.101
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 +697 -279
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -298,6 +298,91 @@ var init_provider_config = __esm(() => {
|
|
|
298
298
|
];
|
|
299
299
|
});
|
|
300
300
|
|
|
301
|
+
// src/lib/oauth/catalog.ts
|
|
302
|
+
function extraRequestHeaders(provider) {
|
|
303
|
+
if (provider === "github-copilot") {
|
|
304
|
+
return GITHUB_COPILOT_REQUEST_HEADERS;
|
|
305
|
+
}
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
function authCatalogEntry(provider) {
|
|
309
|
+
return PROVIDER_AUTH_CATALOG.find((entry) => entry.provider === provider);
|
|
310
|
+
}
|
|
311
|
+
function catalogMethods(provider) {
|
|
312
|
+
return authCatalogEntry(provider)?.methods ?? ["api-key"];
|
|
313
|
+
}
|
|
314
|
+
function catalogAllows(provider, method) {
|
|
315
|
+
return catalogMethods(provider).includes(method);
|
|
316
|
+
}
|
|
317
|
+
function catalogRefusal(provider) {
|
|
318
|
+
return authCatalogEntry(provider)?.refusal;
|
|
319
|
+
}
|
|
320
|
+
function deviceCodeMethodLabel(provider) {
|
|
321
|
+
if (provider === "grok")
|
|
322
|
+
return "SuperGrok Subscription";
|
|
323
|
+
if (provider === "github-copilot")
|
|
324
|
+
return "Login with GitHub Copilot";
|
|
325
|
+
if (provider === "openai")
|
|
326
|
+
return "ChatGPT Plus/Pro (headless)";
|
|
327
|
+
return "Device authorization";
|
|
328
|
+
}
|
|
329
|
+
var XAI_GROK_DEVICE, GITHUB_COPILOT_REQUEST_HEADERS, GITHUB_COPILOT_OAUTH_CLIENT_ID = "Iv1.b507a08c87ecfe98", GITHUB_COPILOT_DEVICE, PROVIDER_AUTH_CATALOG;
|
|
330
|
+
var init_catalog = __esm(() => {
|
|
331
|
+
XAI_GROK_DEVICE = {
|
|
332
|
+
deviceAuthorizationEndpoint: "https://auth.x.ai/oauth2/device/code",
|
|
333
|
+
tokenEndpoint: "https://auth.x.ai/oauth2/token",
|
|
334
|
+
clientId: "b1a00492-073a-47ea-816f-4c329264a828",
|
|
335
|
+
scope: "openid profile email offline_access grok-cli:access api:access",
|
|
336
|
+
extraForm: { referrer: "keryx" },
|
|
337
|
+
headers: { "User-Agent": "keryx" }
|
|
338
|
+
};
|
|
339
|
+
GITHUB_COPILOT_REQUEST_HEADERS = {
|
|
340
|
+
Accept: "application/json",
|
|
341
|
+
"User-Agent": "GitHubCopilotChat/0.26.7",
|
|
342
|
+
"Editor-Version": "vscode/1.99.3",
|
|
343
|
+
"Editor-Plugin-Version": "copilot-chat/0.26.7",
|
|
344
|
+
"Copilot-Integration-Id": "vscode-chat"
|
|
345
|
+
};
|
|
346
|
+
GITHUB_COPILOT_DEVICE = {
|
|
347
|
+
deviceAuthorizationEndpoint: "https://github.com/login/device/code",
|
|
348
|
+
tokenEndpoint: "https://github.com/login/oauth/access_token",
|
|
349
|
+
clientId: GITHUB_COPILOT_OAUTH_CLIENT_ID,
|
|
350
|
+
scope: "read:user",
|
|
351
|
+
headers: GITHUB_COPILOT_REQUEST_HEADERS,
|
|
352
|
+
afterToken: "github-copilot"
|
|
353
|
+
};
|
|
354
|
+
PROVIDER_AUTH_CATALOG = [
|
|
355
|
+
{
|
|
356
|
+
provider: "grok",
|
|
357
|
+
label: "xAI (Grok)",
|
|
358
|
+
methods: ["device-code", "api-key"],
|
|
359
|
+
device: XAI_GROK_DEVICE
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
provider: "openai",
|
|
363
|
+
label: "OpenAI",
|
|
364
|
+
methods: ["oauth-pkce-loopback", "device-code", "api-key"]
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
provider: "github-copilot",
|
|
368
|
+
label: "GitHub Copilot",
|
|
369
|
+
methods: ["device-code"],
|
|
370
|
+
device: GITHUB_COPILOT_DEVICE
|
|
371
|
+
},
|
|
372
|
+
{ provider: "anthropic", label: "Anthropic", methods: ["api-key"], refusal: "Anthropic does not permit third-party Claude Pro/Max login. Use an API key from the Anthropic Console." },
|
|
373
|
+
{ provider: "gemini", label: "Google Gemini", methods: ["api-key"], refusal: "Gemini Google-account login is for Gemini CLI. In keryx use GEMINI_API_KEY from Google AI Studio." },
|
|
374
|
+
{ provider: "deepseek", label: "DeepSeek", methods: ["api-key"], refusal: "DeepSeek has no consumer OAuth grant. Use DEEPSEEK_API_KEY." },
|
|
375
|
+
{ provider: "openrouter", label: "OpenRouter", methods: ["api-key"] },
|
|
376
|
+
{ provider: "zai", label: "Z.AI (GLM)", methods: ["api-key"] },
|
|
377
|
+
{ provider: "zai-coding", label: "Z.AI GLM Coding Plan", methods: ["api-key"] },
|
|
378
|
+
{ provider: "cerebras", label: "Cerebras", methods: ["api-key"] },
|
|
379
|
+
{ provider: "groq", label: "Groq", methods: ["api-key"] },
|
|
380
|
+
{ provider: "moonshot", label: "Moonshot (Kimi)", methods: ["api-key"] },
|
|
381
|
+
{ provider: "ollama", label: "Ollama", methods: ["none"] },
|
|
382
|
+
{ provider: "rapid-mlx", label: "Rapid-MLX (Local)", methods: ["none"] }
|
|
383
|
+
];
|
|
384
|
+
});
|
|
385
|
+
|
|
301
386
|
// src/lib/shell-config.ts
|
|
302
387
|
import { existsSync as existsSync2 } from "fs";
|
|
303
388
|
import path3 from "path";
|
|
@@ -641,8 +726,13 @@ async function fetchOpenAiCompatModelsDetailed(fetchFn, provider, apiKey, opts)
|
|
|
641
726
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
642
727
|
try {
|
|
643
728
|
const init = { signal: controller.signal };
|
|
729
|
+
const extraHeaders = extraRequestHeaders(provider.name);
|
|
730
|
+
const headers = extraHeaders === undefined ? {} : { ...extraHeaders };
|
|
644
731
|
if (apiKey !== undefined && apiKey.length > 0) {
|
|
645
|
-
|
|
732
|
+
headers.authorization = `Bearer ${apiKey}`;
|
|
733
|
+
}
|
|
734
|
+
if (Object.keys(headers).length > 0) {
|
|
735
|
+
init.headers = headers;
|
|
646
736
|
}
|
|
647
737
|
const res = await fetchFn(url, init);
|
|
648
738
|
if (!res.ok) {
|
|
@@ -890,6 +980,7 @@ with a stated reason and exits 0 \u2014 that is a normal configuration, not an e
|
|
|
890
980
|
var DEFAULT_MODELS_PATH = "/v1/models", OPENAI_COMPAT_PROVIDERS, MODELS_FETCH_TIMEOUT_MS = 1e4, MODELS_ERROR_BODY_LIMIT = 4096, BALANCE_FETCH_TIMEOUT_MS = 8000;
|
|
891
981
|
var init_providers = __esm(() => {
|
|
892
982
|
init_provider_config();
|
|
983
|
+
init_catalog();
|
|
893
984
|
init_grants();
|
|
894
985
|
init_shell_config();
|
|
895
986
|
init_args();
|
|
@@ -8310,6 +8401,7 @@ function makeProvider(name, _model, opts) {
|
|
|
8310
8401
|
if (needsKey && (apiKey === undefined || apiKey.length === 0)) {
|
|
8311
8402
|
return new FakeProvider([]);
|
|
8312
8403
|
}
|
|
8404
|
+
const extraHeaders = extraRequestHeaders(name);
|
|
8313
8405
|
const grant = {
|
|
8314
8406
|
network: true,
|
|
8315
8407
|
...compat.streamUsage === true ? { streamUsage: true } : {},
|
|
@@ -8317,7 +8409,8 @@ function makeProvider(name, _model, opts) {
|
|
|
8317
8409
|
...compat.allowLoopback === true ? { allowLoopback: true } : {},
|
|
8318
8410
|
...compat.allowPrivateLan === true ? { allowPrivateLan: true } : {},
|
|
8319
8411
|
...compat.chatPath !== undefined ? { chatPath: compat.chatPath } : {},
|
|
8320
|
-
...apiKey !== undefined ? { apiKey } : {}
|
|
8412
|
+
...apiKey !== undefined ? { apiKey } : {},
|
|
8413
|
+
...extraHeaders !== undefined ? { headers: extraHeaders } : {}
|
|
8321
8414
|
};
|
|
8322
8415
|
return new OpenAiCompatEngine({ fetch: opts.fetch, grant }, { ...OLLAMA_COMPAT_IDENTITY, providerLabel: compat.label });
|
|
8323
8416
|
}
|
|
@@ -8326,6 +8419,7 @@ function makeProvider(name, _model, opts) {
|
|
|
8326
8419
|
var OLLAMA_COMPAT_IDENTITY;
|
|
8327
8420
|
var init_make_provider = __esm(() => {
|
|
8328
8421
|
init_providers();
|
|
8422
|
+
init_catalog();
|
|
8329
8423
|
init_anthropic_provider();
|
|
8330
8424
|
init_openai_compat_provider();
|
|
8331
8425
|
init_fake_provider();
|
|
@@ -40876,78 +40970,6 @@ async function startStdioTransport(server) {
|
|
|
40876
40970
|
await server.connect(transport);
|
|
40877
40971
|
}
|
|
40878
40972
|
|
|
40879
|
-
// src/lib/oauth/catalog.ts
|
|
40880
|
-
function authCatalogEntry(provider) {
|
|
40881
|
-
return PROVIDER_AUTH_CATALOG.find((entry) => entry.provider === provider);
|
|
40882
|
-
}
|
|
40883
|
-
function catalogMethods(provider) {
|
|
40884
|
-
return authCatalogEntry(provider)?.methods ?? ["api-key"];
|
|
40885
|
-
}
|
|
40886
|
-
function catalogAllows(provider, method) {
|
|
40887
|
-
return catalogMethods(provider).includes(method);
|
|
40888
|
-
}
|
|
40889
|
-
function catalogRefusal(provider) {
|
|
40890
|
-
return authCatalogEntry(provider)?.refusal;
|
|
40891
|
-
}
|
|
40892
|
-
function deviceCodeMethodLabel(provider) {
|
|
40893
|
-
if (provider === "grok")
|
|
40894
|
-
return "SuperGrok Subscription";
|
|
40895
|
-
if (provider === "github-copilot")
|
|
40896
|
-
return "Login with GitHub Copilot";
|
|
40897
|
-
if (provider === "openai")
|
|
40898
|
-
return "ChatGPT Plus/Pro (headless)";
|
|
40899
|
-
return "Device authorization";
|
|
40900
|
-
}
|
|
40901
|
-
var XAI_GROK_DEVICE, GITHUB_COPILOT_DEVICE, PROVIDER_AUTH_CATALOG;
|
|
40902
|
-
var init_catalog = __esm(() => {
|
|
40903
|
-
XAI_GROK_DEVICE = {
|
|
40904
|
-
deviceAuthorizationEndpoint: "https://auth.x.ai/oauth2/device/code",
|
|
40905
|
-
tokenEndpoint: "https://auth.x.ai/oauth2/token",
|
|
40906
|
-
clientId: "b1a00492-073a-47ea-816f-4c329264a828",
|
|
40907
|
-
scope: "openid profile email offline_access grok-cli:access api:access",
|
|
40908
|
-
extraForm: { referrer: "keryx" },
|
|
40909
|
-
headers: { "User-Agent": "keryx" }
|
|
40910
|
-
};
|
|
40911
|
-
GITHUB_COPILOT_DEVICE = {
|
|
40912
|
-
deviceAuthorizationEndpoint: "https://github.com/login/device/code",
|
|
40913
|
-
tokenEndpoint: "https://github.com/login/oauth/access_token",
|
|
40914
|
-
clientId: "Ov23li8tweQw6odWQebz",
|
|
40915
|
-
scope: "read:user",
|
|
40916
|
-
headers: { Accept: "application/json", "User-Agent": "keryx" },
|
|
40917
|
-
afterToken: "github-copilot"
|
|
40918
|
-
};
|
|
40919
|
-
PROVIDER_AUTH_CATALOG = [
|
|
40920
|
-
{
|
|
40921
|
-
provider: "grok",
|
|
40922
|
-
label: "xAI (Grok)",
|
|
40923
|
-
methods: ["device-code", "api-key"],
|
|
40924
|
-
device: XAI_GROK_DEVICE
|
|
40925
|
-
},
|
|
40926
|
-
{
|
|
40927
|
-
provider: "openai",
|
|
40928
|
-
label: "OpenAI",
|
|
40929
|
-
methods: ["oauth-pkce-loopback", "device-code", "api-key"]
|
|
40930
|
-
},
|
|
40931
|
-
{
|
|
40932
|
-
provider: "github-copilot",
|
|
40933
|
-
label: "GitHub Copilot",
|
|
40934
|
-
methods: ["device-code"],
|
|
40935
|
-
device: GITHUB_COPILOT_DEVICE
|
|
40936
|
-
},
|
|
40937
|
-
{ provider: "anthropic", label: "Anthropic", methods: ["api-key"], refusal: "Anthropic does not permit third-party Claude Pro/Max login. Use an API key from the Anthropic Console." },
|
|
40938
|
-
{ provider: "gemini", label: "Google Gemini", methods: ["api-key"], refusal: "Gemini Google-account login is for Gemini CLI. In keryx use GEMINI_API_KEY from Google AI Studio." },
|
|
40939
|
-
{ provider: "deepseek", label: "DeepSeek", methods: ["api-key"], refusal: "DeepSeek has no consumer OAuth grant. Use DEEPSEEK_API_KEY." },
|
|
40940
|
-
{ provider: "openrouter", label: "OpenRouter", methods: ["api-key"] },
|
|
40941
|
-
{ provider: "zai", label: "Z.AI (GLM)", methods: ["api-key"] },
|
|
40942
|
-
{ provider: "zai-coding", label: "Z.AI GLM Coding Plan", methods: ["api-key"] },
|
|
40943
|
-
{ provider: "cerebras", label: "Cerebras", methods: ["api-key"] },
|
|
40944
|
-
{ provider: "groq", label: "Groq", methods: ["api-key"] },
|
|
40945
|
-
{ provider: "moonshot", label: "Moonshot (Kimi)", methods: ["api-key"] },
|
|
40946
|
-
{ provider: "ollama", label: "Ollama", methods: ["none"] },
|
|
40947
|
-
{ provider: "rapid-mlx", label: "Rapid-MLX (Local)", methods: ["none"] }
|
|
40948
|
-
];
|
|
40949
|
-
});
|
|
40950
|
-
|
|
40951
40973
|
// src/lib/oauth/device-code.ts
|
|
40952
40974
|
function requestInit(base, signal) {
|
|
40953
40975
|
return signal === undefined ? base : { ...base, signal };
|
|
@@ -41115,38 +41137,71 @@ var init_device_code = __esm(() => {
|
|
|
41115
41137
|
});
|
|
41116
41138
|
|
|
41117
41139
|
// src/lib/oauth/copilot.ts
|
|
41140
|
+
function expiresInSecondsFrom(rec, now) {
|
|
41141
|
+
if (typeof rec.expires_at === "string") {
|
|
41142
|
+
const expires = Date.parse(rec.expires_at);
|
|
41143
|
+
if (Number.isFinite(expires)) {
|
|
41144
|
+
return Math.max(1, Math.floor((expires - now) / 1000));
|
|
41145
|
+
}
|
|
41146
|
+
}
|
|
41147
|
+
if (typeof rec.expires_at === "number" && Number.isFinite(rec.expires_at)) {
|
|
41148
|
+
const expiresMs = rec.expires_at < 1000000000000 ? rec.expires_at * 1000 : rec.expires_at;
|
|
41149
|
+
return Math.max(1, Math.floor((expiresMs - now) / 1000));
|
|
41150
|
+
}
|
|
41151
|
+
if (typeof rec.refresh_in === "number" && Number.isFinite(rec.refresh_in) && rec.refresh_in > 0) {
|
|
41152
|
+
return Math.floor(rec.refresh_in);
|
|
41153
|
+
}
|
|
41154
|
+
return;
|
|
41155
|
+
}
|
|
41156
|
+
function describeExchangeFailure(status, json, body) {
|
|
41157
|
+
if (typeof json === "object" && json !== null) {
|
|
41158
|
+
const rec = json;
|
|
41159
|
+
if (rec.error_details?.copilot_access_denied === true) {
|
|
41160
|
+
return `GitHub Copilot token exchange failed (HTTP ${status}): Copilot access denied \u2014 check the GitHub Copilot subscription on this account`;
|
|
41161
|
+
}
|
|
41162
|
+
if (typeof rec.message === "string" && rec.message.length > 0) {
|
|
41163
|
+
return `GitHub Copilot token exchange failed (HTTP ${status}): ${rec.message}`;
|
|
41164
|
+
}
|
|
41165
|
+
}
|
|
41166
|
+
if (/zscaler|blocked because it does not comply/i.test(body)) {
|
|
41167
|
+
return `GitHub Copilot token exchange failed (HTTP ${status}): request was blocked by a gateway or proxy`;
|
|
41168
|
+
}
|
|
41169
|
+
return `GitHub Copilot token exchange failed (HTTP ${status})`;
|
|
41170
|
+
}
|
|
41118
41171
|
async function exchangeGithubTokenForCopilot(githubToken, http) {
|
|
41119
41172
|
const init = {
|
|
41120
41173
|
method: "GET",
|
|
41121
41174
|
headers: {
|
|
41122
|
-
|
|
41123
|
-
|
|
41124
|
-
"User-Agent": "keryx"
|
|
41175
|
+
...GITHUB_COPILOT_REQUEST_HEADERS,
|
|
41176
|
+
Authorization: `token ${githubToken}`
|
|
41125
41177
|
}
|
|
41126
41178
|
};
|
|
41127
41179
|
if (http.signal !== undefined) {
|
|
41128
41180
|
init.signal = http.signal;
|
|
41129
41181
|
}
|
|
41130
41182
|
const response = await http.fetch(COPILOT_TOKEN_URL, init);
|
|
41131
|
-
const
|
|
41183
|
+
const body = await response.text().catch(() => "");
|
|
41184
|
+
let json = {};
|
|
41185
|
+
if (body.length > 0) {
|
|
41186
|
+
try {
|
|
41187
|
+
json = JSON.parse(body);
|
|
41188
|
+
} catch {
|
|
41189
|
+
json = {};
|
|
41190
|
+
}
|
|
41191
|
+
}
|
|
41132
41192
|
if (!response.ok || typeof json !== "object" || json === null) {
|
|
41133
|
-
throw new DeviceCodeError("failed",
|
|
41193
|
+
throw new DeviceCodeError("failed", describeExchangeFailure(response.status, json, body));
|
|
41134
41194
|
}
|
|
41135
41195
|
const rec = json;
|
|
41136
41196
|
if (typeof rec.token !== "string" || rec.token.length === 0) {
|
|
41137
41197
|
throw new DeviceCodeError("failed", "GitHub Copilot token exchange returned no token");
|
|
41138
41198
|
}
|
|
41139
|
-
|
|
41140
|
-
if (typeof rec.expires_at === "string") {
|
|
41141
|
-
const expires = Date.parse(rec.expires_at);
|
|
41142
|
-
if (Number.isFinite(expires)) {
|
|
41143
|
-
expiresInSeconds = Math.max(1, Math.floor((expires - Date.now()) / 1000));
|
|
41144
|
-
}
|
|
41145
|
-
}
|
|
41199
|
+
const expiresInSeconds = expiresInSecondsFrom(rec, Date.now());
|
|
41146
41200
|
return { accessToken: rec.token, ...expiresInSeconds !== undefined ? { expiresInSeconds } : {} };
|
|
41147
41201
|
}
|
|
41148
41202
|
var COPILOT_TOKEN_URL = "https://api.github.com/copilot_internal/v2/token";
|
|
41149
41203
|
var init_copilot = __esm(() => {
|
|
41204
|
+
init_catalog();
|
|
41150
41205
|
init_device_code();
|
|
41151
41206
|
});
|
|
41152
41207
|
|
|
@@ -48173,7 +48228,7 @@ import path47 from "path";
|
|
|
48173
48228
|
// package.json
|
|
48174
48229
|
var package_default = {
|
|
48175
48230
|
name: "@mrciphersmith/keryx",
|
|
48176
|
-
version: "0.2.
|
|
48231
|
+
version: "0.2.101",
|
|
48177
48232
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
48178
48233
|
private: false,
|
|
48179
48234
|
publishConfig: {
|
|
@@ -73168,7 +73223,7 @@ async function connectStdioMcpServer(argv, options, handshakeTimeoutMs, signal,
|
|
|
73168
73223
|
}
|
|
73169
73224
|
|
|
73170
73225
|
// src/mcp-servers/runtime.ts
|
|
73171
|
-
import
|
|
73226
|
+
import path169 from "path";
|
|
73172
73227
|
|
|
73173
73228
|
// src/mcp-servers/manager.ts
|
|
73174
73229
|
var DEFAULT_CONNECT_CONCURRENCY = 4;
|
|
@@ -73356,6 +73411,152 @@ function credentialSummary(raw) {
|
|
|
73356
73411
|
return parts.join(" + ");
|
|
73357
73412
|
}
|
|
73358
73413
|
|
|
73414
|
+
// src/mcp-servers/store.ts
|
|
73415
|
+
init_config_dir();
|
|
73416
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
73417
|
+
import path168 from "path";
|
|
73418
|
+
var SERVER_NAME_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
73419
|
+
function userConfigFile(configDir) {
|
|
73420
|
+
return path168.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers.json");
|
|
73421
|
+
}
|
|
73422
|
+
function projectConfigFile(projectRoot) {
|
|
73423
|
+
return path168.join(projectRoot, ".keryx", "mcp-servers.json");
|
|
73424
|
+
}
|
|
73425
|
+
function overlayFile(configDir) {
|
|
73426
|
+
return path168.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers-disabled.json");
|
|
73427
|
+
}
|
|
73428
|
+
function readForWrite(file) {
|
|
73429
|
+
const read = readConfigFile(file);
|
|
73430
|
+
if (!read.ok) {
|
|
73431
|
+
return isDefiniteAbsence(read.reason) ? { doc: { schemaVersion: 1, servers: Object.create(null) }, existed: false } : { error: `${file} could not be read (${read.reason}); nothing was written` };
|
|
73432
|
+
}
|
|
73433
|
+
let parsed;
|
|
73434
|
+
try {
|
|
73435
|
+
parsed = parseJsonTolerant(read.text);
|
|
73436
|
+
} catch (error) {
|
|
73437
|
+
return {
|
|
73438
|
+
error: `${file} is not valid JSON (${error instanceof Error ? error.message : String(error)}); nothing was written. Fix it by hand first.`
|
|
73439
|
+
};
|
|
73440
|
+
}
|
|
73441
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
73442
|
+
return { error: `${file} is not a JSON object; nothing was written` };
|
|
73443
|
+
}
|
|
73444
|
+
const doc = parsed;
|
|
73445
|
+
const servers = doc.servers;
|
|
73446
|
+
if (servers !== undefined && (typeof servers !== "object" || servers === null || Array.isArray(servers))) {
|
|
73447
|
+
return { error: `${file} has a \`servers\` that is not an object; nothing was written` };
|
|
73448
|
+
}
|
|
73449
|
+
return {
|
|
73450
|
+
doc: {
|
|
73451
|
+
...parsed,
|
|
73452
|
+
schemaVersion: typeof doc.schemaVersion === "number" ? doc.schemaVersion : 1,
|
|
73453
|
+
servers: Object.assign(Object.create(null), servers)
|
|
73454
|
+
},
|
|
73455
|
+
existed: true
|
|
73456
|
+
};
|
|
73457
|
+
}
|
|
73458
|
+
function writeDocument(file, doc, scope) {
|
|
73459
|
+
const body = `${JSON.stringify(doc, null, 2)}
|
|
73460
|
+
`;
|
|
73461
|
+
if (scope === "user") {
|
|
73462
|
+
writeOwnerOnlyFileAtomic(file, body);
|
|
73463
|
+
return;
|
|
73464
|
+
}
|
|
73465
|
+
mkdirSync6(path168.dirname(file), { recursive: true });
|
|
73466
|
+
writeFileSync7(file, body, { encoding: "utf8" });
|
|
73467
|
+
}
|
|
73468
|
+
function addServer(options) {
|
|
73469
|
+
if (!SERVER_NAME_PATTERN.test(options.name)) {
|
|
73470
|
+
return { ok: false, error: `server name "${options.name}" must match ${SERVER_NAME_PATTERN.source}` };
|
|
73471
|
+
}
|
|
73472
|
+
if (!/^[A-Za-z_]/.test(options.name)) {
|
|
73473
|
+
return {
|
|
73474
|
+
ok: false,
|
|
73475
|
+
error: `server name "${options.name}" must start with a letter or underscore, or none of its tools can be named`
|
|
73476
|
+
};
|
|
73477
|
+
}
|
|
73478
|
+
if (options.name.length > MAX_SERVER_NAME_LENGTH) {
|
|
73479
|
+
return {
|
|
73480
|
+
ok: false,
|
|
73481
|
+
error: `server name "${options.name}" is ${options.name.length} characters; the limit is ${MAX_SERVER_NAME_LENGTH} so that "<server>__<tool>" fits in 64`
|
|
73482
|
+
};
|
|
73483
|
+
}
|
|
73484
|
+
const file = fileForScope(options.scope, options);
|
|
73485
|
+
if (typeof file !== "string")
|
|
73486
|
+
return file;
|
|
73487
|
+
const loaded = readForWrite(file);
|
|
73488
|
+
if ("error" in loaded)
|
|
73489
|
+
return { ok: false, error: loaded.error };
|
|
73490
|
+
if (loaded.doc.servers[options.name] !== undefined && options.force !== true) {
|
|
73491
|
+
return {
|
|
73492
|
+
ok: false,
|
|
73493
|
+
error: `server "${options.name}" already exists in ${file}. Pass --force to replace it, or remove it first.`
|
|
73494
|
+
};
|
|
73495
|
+
}
|
|
73496
|
+
loaded.doc.servers[options.name] = options.entry;
|
|
73497
|
+
writeDocument(file, loaded.doc, options.scope);
|
|
73498
|
+
return { ok: true, file, created: !loaded.existed };
|
|
73499
|
+
}
|
|
73500
|
+
function removeServer(options) {
|
|
73501
|
+
const file = fileForScope(options.scope, options);
|
|
73502
|
+
if (typeof file !== "string")
|
|
73503
|
+
return file;
|
|
73504
|
+
const loaded = readForWrite(file);
|
|
73505
|
+
if ("error" in loaded)
|
|
73506
|
+
return { ok: false, error: loaded.error };
|
|
73507
|
+
if (loaded.doc.servers[options.name] === undefined) {
|
|
73508
|
+
return { ok: false, error: `server "${options.name}" is not defined in ${file}` };
|
|
73509
|
+
}
|
|
73510
|
+
delete loaded.doc.servers[options.name];
|
|
73511
|
+
writeDocument(file, loaded.doc, options.scope);
|
|
73512
|
+
return { ok: true, file, created: false };
|
|
73513
|
+
}
|
|
73514
|
+
function setServerEnabled(options) {
|
|
73515
|
+
const file = overlayFile(options.configDir);
|
|
73516
|
+
const read = readConfigFile(file);
|
|
73517
|
+
let overlay = {};
|
|
73518
|
+
if (read.ok) {
|
|
73519
|
+
try {
|
|
73520
|
+
const parsed = parseJsonTolerant(read.text);
|
|
73521
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
73522
|
+
overlay = parsed;
|
|
73523
|
+
}
|
|
73524
|
+
} catch {
|
|
73525
|
+
overlay = {};
|
|
73526
|
+
}
|
|
73527
|
+
} else if (!isDefiniteAbsence(read.reason)) {
|
|
73528
|
+
return { ok: false, error: `${file} could not be read (${read.reason}); nothing was written` };
|
|
73529
|
+
}
|
|
73530
|
+
const overrides = { ...overlay.overrides, [options.name]: options.enabled };
|
|
73531
|
+
writeOwnerOnlyFileAtomic(file, `${JSON.stringify({ ...overlay, overrides }, null, 2)}
|
|
73532
|
+
`);
|
|
73533
|
+
if (options.source === "user") {
|
|
73534
|
+
clearStickyUserFlag(options.name, options.enabled, options.configDir);
|
|
73535
|
+
}
|
|
73536
|
+
return { ok: true, file, created: !read.ok };
|
|
73537
|
+
}
|
|
73538
|
+
function clearStickyUserFlag(name, enabled, configDir) {
|
|
73539
|
+
const file = userConfigFile(configDir);
|
|
73540
|
+
const loaded = readForWrite(file);
|
|
73541
|
+
if ("error" in loaded)
|
|
73542
|
+
return;
|
|
73543
|
+
const entry = loaded.doc.servers[name];
|
|
73544
|
+
if (entry === undefined)
|
|
73545
|
+
return;
|
|
73546
|
+
if (entry.enabled === undefined || entry.enabled === enabled)
|
|
73547
|
+
return;
|
|
73548
|
+
delete entry.enabled;
|
|
73549
|
+
writeDocument(file, loaded.doc, "user");
|
|
73550
|
+
}
|
|
73551
|
+
function fileForScope(scope, targets) {
|
|
73552
|
+
if (scope === "user")
|
|
73553
|
+
return userConfigFile(targets.configDir);
|
|
73554
|
+
if (targets.projectRoot === undefined) {
|
|
73555
|
+
return { ok: false, error: "--scope project needs a project root; none was resolved" };
|
|
73556
|
+
}
|
|
73557
|
+
return projectConfigFile(targets.projectRoot);
|
|
73558
|
+
}
|
|
73559
|
+
|
|
73359
73560
|
// src/harness/external/env.ts
|
|
73360
73561
|
var EXTERNAL_ENV_DENY = [
|
|
73361
73562
|
"ANTHROPIC_API_KEY",
|
|
@@ -73585,11 +73786,32 @@ function createMcpRuntime(options) {
|
|
|
73585
73786
|
const kills = [];
|
|
73586
73787
|
const env = options.env ?? process.env;
|
|
73587
73788
|
const connect = options.connect ?? ((server) => defaultConnect(server, env, dialling.signal, kills, options.configDir));
|
|
73789
|
+
const closedAlready = new WeakSet;
|
|
73790
|
+
const mutated = new Set;
|
|
73588
73791
|
const settled = startServers(launchable, connect).then((result) => {
|
|
73589
|
-
|
|
73590
|
-
|
|
73792
|
+
for (const incoming of result.servers) {
|
|
73793
|
+
if (!mutated.has(incoming.name))
|
|
73794
|
+
continue;
|
|
73795
|
+
if (incoming.connection !== undefined && !closedAlready.has(incoming.connection)) {
|
|
73796
|
+
closedAlready.add(incoming.connection);
|
|
73797
|
+
incoming.connection.close().catch(() => {});
|
|
73798
|
+
}
|
|
73799
|
+
}
|
|
73800
|
+
const kept = states.filter((state) => mutated.has(state.name));
|
|
73801
|
+
const incoming = result.servers.filter((state) => !mutated.has(state.name));
|
|
73802
|
+
const heldStill = heldStates.filter((state) => !mutated.has(state.name));
|
|
73803
|
+
states = uniqueByName([...heldStill, ...incoming, ...kept]).sort((a, b) => a.name.localeCompare(b.name));
|
|
73804
|
+
catalog = mergeCatalogs([
|
|
73805
|
+
{
|
|
73806
|
+
entries: result.catalog.entries.filter((entry) => !mutated.has(entry.server)),
|
|
73807
|
+
skipped: result.catalog.skipped.filter((skip) => !mutated.has(skip.server))
|
|
73808
|
+
},
|
|
73809
|
+
{
|
|
73810
|
+
entries: catalog.entries.filter((entry) => mutated.has(entry.server)),
|
|
73811
|
+
skipped: catalog.skipped.filter((skip) => mutated.has(skip.server))
|
|
73812
|
+
}
|
|
73813
|
+
]);
|
|
73591
73814
|
}).catch(() => {});
|
|
73592
|
-
const closedAlready = new WeakSet;
|
|
73593
73815
|
const closeOnce = async (list) => {
|
|
73594
73816
|
const fresh = list.filter((state) => state.connection !== undefined && !closedAlready.has(state.connection));
|
|
73595
73817
|
for (const state of fresh) {
|
|
@@ -73599,6 +73821,7 @@ function createMcpRuntime(options) {
|
|
|
73599
73821
|
await closeServers(fresh);
|
|
73600
73822
|
};
|
|
73601
73823
|
let closing;
|
|
73824
|
+
const inflight = new Map;
|
|
73602
73825
|
return {
|
|
73603
73826
|
catalog: () => catalog,
|
|
73604
73827
|
servers: () => states,
|
|
@@ -73608,8 +73831,100 @@ function createMcpRuntime(options) {
|
|
|
73608
73831
|
ready: async () => {
|
|
73609
73832
|
await settled;
|
|
73610
73833
|
},
|
|
73834
|
+
connectServer: (name) => runExclusive(name, () => connectOne(name)),
|
|
73835
|
+
disconnectServer: (name) => runExclusive(name, () => disconnectOne(name)),
|
|
73611
73836
|
close: () => closing ??= closeNow()
|
|
73612
73837
|
};
|
|
73838
|
+
function runExclusive(name, work) {
|
|
73839
|
+
const existing = inflight.get(name);
|
|
73840
|
+
if (existing !== undefined)
|
|
73841
|
+
return existing;
|
|
73842
|
+
const pending = work().finally(() => {
|
|
73843
|
+
inflight.delete(name);
|
|
73844
|
+
});
|
|
73845
|
+
inflight.set(name, pending);
|
|
73846
|
+
return pending;
|
|
73847
|
+
}
|
|
73848
|
+
function replaceState(next) {
|
|
73849
|
+
states = uniqueByName([...states.filter((state) => state.name !== next.name), next]).sort((a, b) => a.name.localeCompare(b.name));
|
|
73850
|
+
}
|
|
73851
|
+
function stripCatalog(name) {
|
|
73852
|
+
catalog = {
|
|
73853
|
+
entries: catalog.entries.filter((entry) => entry.server !== name),
|
|
73854
|
+
skipped: catalog.skipped.filter((skip) => skip.server !== name)
|
|
73855
|
+
};
|
|
73856
|
+
}
|
|
73857
|
+
async function persistEnabled(server, enabled) {
|
|
73858
|
+
const written = setServerEnabled({
|
|
73859
|
+
name: server.name,
|
|
73860
|
+
enabled,
|
|
73861
|
+
source: server.source,
|
|
73862
|
+
...options.configDir === undefined ? {} : { configDir: options.configDir }
|
|
73863
|
+
});
|
|
73864
|
+
return written.ok ? undefined : { ok: false, message: written.error };
|
|
73865
|
+
}
|
|
73866
|
+
async function connectOne(name) {
|
|
73867
|
+
if (closing !== undefined)
|
|
73868
|
+
return { ok: false, message: "session is closing" };
|
|
73869
|
+
const server = byName.get(name);
|
|
73870
|
+
if (server === undefined)
|
|
73871
|
+
return { ok: false, message: `no server named "${name}"` };
|
|
73872
|
+
if (requiresApproval(server, loadTrustStore(options.configDir))) {
|
|
73873
|
+
return { ok: false, message: `project server not approved; run \`keryx mcp trust ${name}\`` };
|
|
73874
|
+
}
|
|
73875
|
+
const current = states.find((state) => state.name === name);
|
|
73876
|
+
if (current?.status === "connected")
|
|
73877
|
+
return { ok: true };
|
|
73878
|
+
if (current?.status === "connecting") {
|
|
73879
|
+
return { ok: false, message: `server "${name}" is already connecting` };
|
|
73880
|
+
}
|
|
73881
|
+
const persist = await persistEnabled(server, true);
|
|
73882
|
+
if (persist !== undefined)
|
|
73883
|
+
return persist;
|
|
73884
|
+
mutated.add(name);
|
|
73885
|
+
if (current?.connection !== undefined) {
|
|
73886
|
+
if (!closedAlready.has(current.connection))
|
|
73887
|
+
closedAlready.add(current.connection);
|
|
73888
|
+
await closeServers([current]);
|
|
73889
|
+
}
|
|
73890
|
+
stripCatalog(name);
|
|
73891
|
+
replaceState({ name, status: "connecting", toolCount: 0 });
|
|
73892
|
+
const result = await startServers([{ ...server, enabled: true }], connect);
|
|
73893
|
+
const next = result.servers[0];
|
|
73894
|
+
if (next === undefined) {
|
|
73895
|
+
replaceState({ name, status: "failed", error: "dial returned nothing", toolCount: 0 });
|
|
73896
|
+
return { ok: false, message: "dial returned nothing" };
|
|
73897
|
+
}
|
|
73898
|
+
replaceState(next);
|
|
73899
|
+
if (next.status === "connected") {
|
|
73900
|
+
catalog = mergeCatalogs([catalog, result.catalog]);
|
|
73901
|
+
return { ok: true };
|
|
73902
|
+
}
|
|
73903
|
+
return { ok: false, message: next.error ?? `server "${name}" did not connect` };
|
|
73904
|
+
}
|
|
73905
|
+
async function disconnectOne(name) {
|
|
73906
|
+
if (closing !== undefined)
|
|
73907
|
+
return { ok: false, message: "session is closing" };
|
|
73908
|
+
const server = byName.get(name);
|
|
73909
|
+
if (server === undefined)
|
|
73910
|
+
return { ok: false, message: `no server named "${name}"` };
|
|
73911
|
+
const current = states.find((state) => state.name === name);
|
|
73912
|
+
if (current?.status === "connecting") {
|
|
73913
|
+
return { ok: false, message: `server "${name}" is still connecting` };
|
|
73914
|
+
}
|
|
73915
|
+
const persist = await persistEnabled(server, false);
|
|
73916
|
+
if (persist !== undefined)
|
|
73917
|
+
return persist;
|
|
73918
|
+
mutated.add(name);
|
|
73919
|
+
if (current?.connection !== undefined) {
|
|
73920
|
+
if (!closedAlready.has(current.connection))
|
|
73921
|
+
closedAlready.add(current.connection);
|
|
73922
|
+
await closeServers([current]);
|
|
73923
|
+
}
|
|
73924
|
+
stripCatalog(name);
|
|
73925
|
+
replaceState({ name, status: "disabled", toolCount: 0 });
|
|
73926
|
+
return { ok: true };
|
|
73927
|
+
}
|
|
73613
73928
|
async function closeNow() {
|
|
73614
73929
|
{
|
|
73615
73930
|
dialling.abort();
|
|
@@ -73620,6 +73935,17 @@ function createMcpRuntime(options) {
|
|
|
73620
73935
|
}
|
|
73621
73936
|
}
|
|
73622
73937
|
}
|
|
73938
|
+
function uniqueByName(list) {
|
|
73939
|
+
const seen = new Set;
|
|
73940
|
+
const out = [];
|
|
73941
|
+
for (const state of list) {
|
|
73942
|
+
if (seen.has(state.name))
|
|
73943
|
+
continue;
|
|
73944
|
+
seen.add(state.name);
|
|
73945
|
+
out.push(state);
|
|
73946
|
+
}
|
|
73947
|
+
return out;
|
|
73948
|
+
}
|
|
73623
73949
|
function handshakeBudgetMs(server) {
|
|
73624
73950
|
const configured = server.startup_timeout_sec;
|
|
73625
73951
|
if (configured === undefined || !Number.isFinite(configured) || configured <= 0) {
|
|
@@ -73685,153 +74011,7 @@ async function defaultConnect(server, env, signal, kills, configDir) {
|
|
|
73685
74011
|
function defaultServerCwd(server, fallbackCwd = process.cwd()) {
|
|
73686
74012
|
if (server.source !== "project")
|
|
73687
74013
|
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);
|
|
74014
|
+
return path169.dirname(path169.dirname(server.file));
|
|
73835
74015
|
}
|
|
73836
74016
|
|
|
73837
74017
|
// src/commands/mcp-servers.ts
|
|
@@ -88242,36 +88422,257 @@ function buildConsumerModel(input) {
|
|
|
88242
88422
|
project: ${input.projectFile}`
|
|
88243
88423
|
};
|
|
88244
88424
|
}
|
|
88245
|
-
|
|
88425
|
+
var MCP_CONSUMER_FOOTER = [
|
|
88426
|
+
{ key: "\u2191/\u2193", label: "select" },
|
|
88427
|
+
{ key: "click", label: "row: select/act" },
|
|
88428
|
+
{ key: "c/d", label: "connect/disconnect" },
|
|
88429
|
+
{ key: "y", label: "confirm" },
|
|
88430
|
+
{ key: "esc", label: "close" }
|
|
88431
|
+
];
|
|
88432
|
+
var MCP_CONSUMER_HEADER = "Servers keryx CONNECTS TO. c/d then y to connect or disconnect. Held project servers need `keryx mcp trust` first.";
|
|
88433
|
+
var STATUS_GLYPH = {
|
|
88434
|
+
connected: "\u25CF connected",
|
|
88435
|
+
connecting: "\u2026 connecting",
|
|
88436
|
+
failed: "\u2717 failed",
|
|
88437
|
+
disabled: "\u25CB disabled",
|
|
88438
|
+
"needs-approval": "! needs approval",
|
|
88439
|
+
needs_auth: "! needs auth"
|
|
88440
|
+
};
|
|
88441
|
+
function consumerActionFor(status) {
|
|
88442
|
+
if (status === "connected")
|
|
88443
|
+
return "disconnect";
|
|
88444
|
+
if (status === "disabled" || status === "failed")
|
|
88445
|
+
return "connect";
|
|
88446
|
+
return;
|
|
88447
|
+
}
|
|
88448
|
+
function statusGlyph(status) {
|
|
88449
|
+
return STATUS_GLYPH[status] ?? status;
|
|
88450
|
+
}
|
|
88451
|
+
function formatConsumerModalRow(row, isSelected, status) {
|
|
88452
|
+
const mark = isSelected ? ">" : " ";
|
|
88453
|
+
const name = row.name.padEnd(18);
|
|
88454
|
+
const source = row.source.padEnd(10);
|
|
88455
|
+
const transport = row.transport.padEnd(6);
|
|
88456
|
+
const glyph = statusGlyph(row.status).padEnd(18);
|
|
88457
|
+
const tools = row.status === "connected" ? `${row.toolCount} tool(s)` : "";
|
|
88458
|
+
let action;
|
|
88459
|
+
const offered = consumerActionFor(row.status);
|
|
88460
|
+
if (status.kind === "armed" && status.target.name === row.name) {
|
|
88461
|
+
action = ` [click again or press y to ${status.target.action}]`;
|
|
88462
|
+
} else if (status.kind === "running" && status.target.name === row.name) {
|
|
88463
|
+
action = ` ${status.target.action === "connect" ? "connecting\u2026" : "disconnecting\u2026"}`;
|
|
88464
|
+
} else if (status.kind === "done" && status.target.name === row.name) {
|
|
88465
|
+
action = status.outcome.ok ? " \u2713 done" : ` \u2717 ${status.outcome.message}`;
|
|
88466
|
+
} else if (offered === "disconnect") {
|
|
88467
|
+
action = " [d] disconnect";
|
|
88468
|
+
} else if (offered === "connect") {
|
|
88469
|
+
action = " [c] connect";
|
|
88470
|
+
} else if (row.action !== undefined) {
|
|
88471
|
+
action = ` \u2192 ${row.action}`;
|
|
88472
|
+
} else {
|
|
88473
|
+
action = "";
|
|
88474
|
+
}
|
|
88475
|
+
const creds = row.credentials.length > 0 ? ` [reads ${row.credentials.join(", ")}]` : "";
|
|
88476
|
+
return `${mark} ${name} ${source} ${transport} ${glyph}${tools}${action} ${row.target}${creds}`.trimEnd();
|
|
88477
|
+
}
|
|
88478
|
+
function formatConsumerModalLines(model, selected, status) {
|
|
88246
88479
|
if (model === undefined) {
|
|
88247
88480
|
return [
|
|
88248
88481
|
"No MCP session yet \u2014 this shell has not built a tool list.",
|
|
88249
88482
|
"Configured servers are listed by `keryx mcp list`; `keryx mcp doctor` dials them."
|
|
88250
88483
|
];
|
|
88251
88484
|
}
|
|
88485
|
+
const header = [MCP_CONSUMER_HEADER];
|
|
88486
|
+
const problems = model.problems.flatMap((problem) => problem.split(`
|
|
88487
|
+
`).map((line, index) => index === 0 ? `config problem \u2014 ${line}` : ` ${line}`));
|
|
88488
|
+
if (model.emptyHint !== undefined) {
|
|
88489
|
+
return [...header, ...problems, ...model.emptyHint.split(`
|
|
88490
|
+
`)];
|
|
88491
|
+
}
|
|
88252
88492
|
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
|
-
})
|
|
88493
|
+
...header,
|
|
88494
|
+
...problems,
|
|
88495
|
+
...model.rows.map((row, index) => formatConsumerModalRow(row, index === selected, status))
|
|
88268
88496
|
];
|
|
88269
88497
|
}
|
|
88270
|
-
function
|
|
88271
|
-
const
|
|
88272
|
-
|
|
88273
|
-
|
|
88274
|
-
|
|
88498
|
+
function asRowTarget2(body) {
|
|
88499
|
+
const parent = body;
|
|
88500
|
+
if (parent.add === undefined || parent.getChildren === undefined || parent.remove === undefined) {
|
|
88501
|
+
return;
|
|
88502
|
+
}
|
|
88503
|
+
return {
|
|
88504
|
+
add: parent.add.bind(parent),
|
|
88505
|
+
getChildren: parent.getChildren.bind(parent),
|
|
88506
|
+
remove: parent.remove.bind(parent)
|
|
88507
|
+
};
|
|
88508
|
+
}
|
|
88509
|
+
function presentMcpConsumer(openModalFn, otui, chrome, options) {
|
|
88510
|
+
let selected = 0;
|
|
88511
|
+
let scroll = 0;
|
|
88512
|
+
let status = { kind: "idle" };
|
|
88513
|
+
let body;
|
|
88514
|
+
let rowCtor;
|
|
88515
|
+
let activeRenderer;
|
|
88516
|
+
let unsubscribeKey;
|
|
88517
|
+
const rendererHint = options.renderer ?? chrome?.renderer;
|
|
88518
|
+
const bodyRows = options.visibleRows ?? (typeof rendererHint?.width === "number" && typeof rendererHint.height === "number" ? modalBodyRows(resolveModalPanelSize(rendererHint.width, rendererHint.height).height) : 13);
|
|
88519
|
+
const rowsOf = () => options.snapshot()?.rows ?? [];
|
|
88520
|
+
const paint = () => {
|
|
88521
|
+
if (body === undefined || rowCtor === undefined)
|
|
88522
|
+
return;
|
|
88523
|
+
clearTranscriptChildren(body);
|
|
88524
|
+
const model = options.snapshot();
|
|
88525
|
+
const rows = model?.rows ?? [];
|
|
88526
|
+
selected = rows.length === 0 ? 0 : Math.min(selected, rows.length - 1);
|
|
88527
|
+
scroll = scrollToReveal3(selected, scroll, bodyRows);
|
|
88528
|
+
scroll = clampScroll3(scroll, Math.max(rows.length, 1), bodyRows);
|
|
88529
|
+
const lines = formatConsumerModalLines(model, selected, status);
|
|
88530
|
+
if (rows.length === 0) {
|
|
88531
|
+
for (const [i, line] of lines.entries()) {
|
|
88532
|
+
body.add(new rowCtor(activeRenderer, { id: `mcp-consumer-line-${i}`, content: line }));
|
|
88533
|
+
}
|
|
88534
|
+
return;
|
|
88535
|
+
}
|
|
88536
|
+
body.add(new rowCtor(activeRenderer, { id: "mcp-consumer-header", content: MCP_CONSUMER_HEADER }));
|
|
88537
|
+
const problems = model?.problems ?? [];
|
|
88538
|
+
for (const [i, problem] of problems.entries()) {
|
|
88539
|
+
body.add(new rowCtor(activeRenderer, {
|
|
88540
|
+
id: `mcp-consumer-problem-${i}`,
|
|
88541
|
+
content: `config problem \u2014 ${problem}`
|
|
88542
|
+
}));
|
|
88543
|
+
}
|
|
88544
|
+
const start = clampScroll3(scroll, rows.length, bodyRows);
|
|
88545
|
+
for (const [i, row] of rows.slice(start, start + bodyRows).entries()) {
|
|
88546
|
+
const index = start + i;
|
|
88547
|
+
body.add(new rowCtor(activeRenderer, {
|
|
88548
|
+
id: `mcp-consumer-row-${row.name}`,
|
|
88549
|
+
content: formatConsumerModalRow(row, index === selected, status),
|
|
88550
|
+
onMouseDown: () => handleRowClick(row.name, index)
|
|
88551
|
+
}));
|
|
88552
|
+
}
|
|
88553
|
+
};
|
|
88554
|
+
const moveSelection = (next) => {
|
|
88555
|
+
const rows = rowsOf();
|
|
88556
|
+
if (rows.length === 0)
|
|
88557
|
+
return;
|
|
88558
|
+
const clamped = Math.min(rows.length - 1, Math.max(0, next));
|
|
88559
|
+
if (clamped === selected)
|
|
88560
|
+
return;
|
|
88561
|
+
selected = clamped;
|
|
88562
|
+
status = { kind: "idle" };
|
|
88563
|
+
paint();
|
|
88564
|
+
};
|
|
88565
|
+
const runAction = () => {
|
|
88566
|
+
if (status.kind !== "armed")
|
|
88567
|
+
return;
|
|
88568
|
+
const target = status.target;
|
|
88569
|
+
status = { kind: "running", target };
|
|
88570
|
+
paint();
|
|
88571
|
+
const fn = target.action === "connect" ? options.connect : options.disconnect;
|
|
88572
|
+
fn(target.name).then((outcome) => {
|
|
88573
|
+
status = { kind: "done", target, outcome };
|
|
88574
|
+
paint();
|
|
88575
|
+
});
|
|
88576
|
+
};
|
|
88577
|
+
const armFor = (name) => {
|
|
88578
|
+
const rows = rowsOf();
|
|
88579
|
+
const index = rows.findIndex((row) => row.name === name);
|
|
88580
|
+
const row = rows[index];
|
|
88581
|
+
const offered = row === undefined ? undefined : consumerActionFor(row.status);
|
|
88582
|
+
if (index < 0 || row === undefined || offered === undefined || status.kind === "running")
|
|
88583
|
+
return;
|
|
88584
|
+
selected = index;
|
|
88585
|
+
status = { kind: "armed", target: { name: row.name, action: offered } };
|
|
88586
|
+
paint();
|
|
88587
|
+
};
|
|
88588
|
+
const handleRowClick = (name, index) => {
|
|
88589
|
+
if (status.kind === "armed" && status.target.name === name) {
|
|
88590
|
+
runAction();
|
|
88591
|
+
return;
|
|
88592
|
+
}
|
|
88593
|
+
if (status.kind === "running")
|
|
88594
|
+
return;
|
|
88595
|
+
const row = rowsOf()[index];
|
|
88596
|
+
if (row === undefined || consumerActionFor(row.status) === undefined) {
|
|
88597
|
+
if (index !== selected) {
|
|
88598
|
+
selected = index;
|
|
88599
|
+
status = { kind: "idle" };
|
|
88600
|
+
paint();
|
|
88601
|
+
}
|
|
88602
|
+
return;
|
|
88603
|
+
}
|
|
88604
|
+
armFor(name);
|
|
88605
|
+
};
|
|
88606
|
+
const handle = openModalFn(otui, chrome, {
|
|
88607
|
+
title: "MCP servers",
|
|
88608
|
+
tabs: [{ id: "servers", label: "Servers" }],
|
|
88609
|
+
initialTab: "servers",
|
|
88610
|
+
footer: MCP_CONSUMER_FOOTER,
|
|
88611
|
+
renderTab: (_tabId, tabBody, ctx) => {
|
|
88612
|
+
const renderer = options.renderer ?? chrome?.renderer;
|
|
88613
|
+
const ctor = otui.TextRenderable;
|
|
88614
|
+
const target = asRowTarget2(tabBody);
|
|
88615
|
+
if (target === undefined || ctor === undefined)
|
|
88616
|
+
return;
|
|
88617
|
+
rowCtor = ctor;
|
|
88618
|
+
activeRenderer = renderer;
|
|
88619
|
+
body = target;
|
|
88620
|
+
paint();
|
|
88621
|
+
},
|
|
88622
|
+
onClose: () => {
|
|
88623
|
+
unsubscribeKey?.();
|
|
88624
|
+
}
|
|
88625
|
+
});
|
|
88626
|
+
if (handle === undefined)
|
|
88627
|
+
return;
|
|
88628
|
+
if (options.onKeypress !== undefined) {
|
|
88629
|
+
unsubscribeKey = options.onKeypress((key) => {
|
|
88630
|
+
const token = key.name || key.sequence;
|
|
88631
|
+
const rows = rowsOf();
|
|
88632
|
+
if (status.kind === "armed") {
|
|
88633
|
+
if (token === "y") {
|
|
88634
|
+
runAction();
|
|
88635
|
+
} else {
|
|
88636
|
+
status = { kind: "idle" };
|
|
88637
|
+
paint();
|
|
88638
|
+
}
|
|
88639
|
+
return;
|
|
88640
|
+
}
|
|
88641
|
+
if (token === "c") {
|
|
88642
|
+
const row = rows[selected];
|
|
88643
|
+
if (row !== undefined && consumerActionFor(row.status) === "connect" && status.kind !== "running") {
|
|
88644
|
+
status = { kind: "armed", target: { name: row.name, action: "connect" } };
|
|
88645
|
+
paint();
|
|
88646
|
+
}
|
|
88647
|
+
return;
|
|
88648
|
+
}
|
|
88649
|
+
if (token === "d") {
|
|
88650
|
+
const row = rows[selected];
|
|
88651
|
+
if (row !== undefined && consumerActionFor(row.status) === "disconnect" && status.kind !== "running") {
|
|
88652
|
+
status = { kind: "armed", target: { name: row.name, action: "disconnect" } };
|
|
88653
|
+
paint();
|
|
88654
|
+
}
|
|
88655
|
+
return;
|
|
88656
|
+
}
|
|
88657
|
+
if (token === "up" || token === "k") {
|
|
88658
|
+
moveSelection(selected - 1);
|
|
88659
|
+
return;
|
|
88660
|
+
}
|
|
88661
|
+
if (token === "down" || token === "j") {
|
|
88662
|
+
moveSelection(selected + 1);
|
|
88663
|
+
return;
|
|
88664
|
+
}
|
|
88665
|
+
if (token === "pageup" || token === "pagedown") {
|
|
88666
|
+
const step = token === "pageup" ? -bodyRows : bodyRows;
|
|
88667
|
+
scroll = clampScroll3(scroll + step, Math.max(rows.length, 1), bodyRows);
|
|
88668
|
+
paint();
|
|
88669
|
+
}
|
|
88670
|
+
});
|
|
88671
|
+
}
|
|
88672
|
+
return handle;
|
|
88673
|
+
}
|
|
88674
|
+
function openMcpConsumer(otui, chrome, options) {
|
|
88675
|
+
return presentMcpConsumer((hostOtui, hostChrome, input) => openModal(hostOtui, hostChrome, input), otui, chrome, options);
|
|
88275
88676
|
}
|
|
88276
88677
|
|
|
88277
88678
|
// src/tui/tui-shell.ts
|
|
@@ -89048,7 +89449,7 @@ var AGENT_SLASH_COMMANDS = [
|
|
|
89048
89449
|
},
|
|
89049
89450
|
{
|
|
89050
89451
|
name: "/mcp",
|
|
89051
|
-
description: "MCP servers keryx is connected to \u2014 status,
|
|
89452
|
+
description: "MCP servers keryx is connected to \u2014 status, connect/disconnect",
|
|
89052
89453
|
modes: AGENT_ONLY
|
|
89053
89454
|
},
|
|
89054
89455
|
{
|
|
@@ -91817,7 +92218,7 @@ function stepQueueNavAction(selected, direction) {
|
|
|
91817
92218
|
}
|
|
91818
92219
|
|
|
91819
92220
|
// src/tui/worker-fleet.ts
|
|
91820
|
-
var
|
|
92221
|
+
var STATUS_GLYPH2 = {
|
|
91821
92222
|
queued: "\u25CB",
|
|
91822
92223
|
running: "\u25D0",
|
|
91823
92224
|
done: "\u25CF",
|
|
@@ -91956,7 +92357,7 @@ function formatFleetSidebar(workers, maxLines = 14) {
|
|
|
91956
92357
|
const room = Math.max(2, maxLines - lines.length);
|
|
91957
92358
|
const shown = sorted.slice(0, room);
|
|
91958
92359
|
for (const w of shown) {
|
|
91959
|
-
const g =
|
|
92360
|
+
const g = STATUS_GLYPH2[w.status];
|
|
91960
92361
|
const phase = humanFleetPhase(w.status, w.detail);
|
|
91961
92362
|
const label = clip4(w.label, 12);
|
|
91962
92363
|
const det = phase.length > 0 ? ` ${clip4(phase, 12)}` : "";
|
|
@@ -92050,7 +92451,7 @@ class WorkerFleet {
|
|
|
92050
92451
|
var MAX_SUBAGENT_EVENTS = 200;
|
|
92051
92452
|
var MAX_SUBAGENT_EVENT_CHARS = 2000;
|
|
92052
92453
|
var EXTERNAL_SUBAGENT_MARK = "\u2933";
|
|
92053
|
-
var
|
|
92454
|
+
var STATUS_GLYPH3 = {
|
|
92054
92455
|
queued: "\u25CB",
|
|
92055
92456
|
running: "\u25D0",
|
|
92056
92457
|
done: "\u25CF",
|
|
@@ -92092,7 +92493,7 @@ function formatElapsed(ms) {
|
|
|
92092
92493
|
return minRem === 0 ? `${hours}h` : `${hours}h ${minRem}m`;
|
|
92093
92494
|
}
|
|
92094
92495
|
function formatSubagentRow(session, width = SIDEBAR_TEXT_WIDTH) {
|
|
92095
|
-
const glyph =
|
|
92496
|
+
const glyph = STATUS_GLYPH3[session.status];
|
|
92096
92497
|
const phase = humanFleetPhase(session.status, session.detail);
|
|
92097
92498
|
const mark = session.runtime === "external" ? EXTERNAL_SUBAGENT_MARK : "";
|
|
92098
92499
|
const budget = Math.max(4, width - glyph.length - 1);
|
|
@@ -93451,7 +93852,7 @@ function openExternalInspector(otui, chrome, options) {
|
|
|
93451
93852
|
|
|
93452
93853
|
// src/tui/background-job-session.ts
|
|
93453
93854
|
var MAX_BACKGROUND_JOB_OUTPUT_CHARS = 20000;
|
|
93454
|
-
var
|
|
93855
|
+
var STATUS_GLYPH4 = {
|
|
93455
93856
|
running: "\u25D0",
|
|
93456
93857
|
exited: "\u25CF",
|
|
93457
93858
|
killed: "\u2717"
|
|
@@ -93469,7 +93870,7 @@ function formatJobListHeader(count) {
|
|
|
93469
93870
|
return `Background Jobs ${count}`;
|
|
93470
93871
|
}
|
|
93471
93872
|
function formatJobRow(entry, width = SIDEBAR_TEXT_WIDTH) {
|
|
93472
|
-
const glyph =
|
|
93873
|
+
const glyph = STATUS_GLYPH4[entry.status];
|
|
93473
93874
|
const budget = Math.max(1, width - glyph.length - 1);
|
|
93474
93875
|
const cmd = clip7(entry.command, budget);
|
|
93475
93876
|
return clip7(`${glyph} ${cmd}`, width);
|
|
@@ -96059,19 +96460,36 @@ Staying in the current session.
|
|
|
96059
96460
|
})();
|
|
96060
96461
|
};
|
|
96061
96462
|
const showMcpConsumer = () => {
|
|
96062
|
-
const runtime = deps.mcpRuntime?.();
|
|
96063
96463
|
const cwd = inspectorCwd();
|
|
96064
|
-
const
|
|
96065
|
-
|
|
96066
|
-
|
|
96067
|
-
|
|
96068
|
-
|
|
96069
|
-
|
|
96464
|
+
const snapshot = () => {
|
|
96465
|
+
const live = deps.mcpRuntime?.();
|
|
96466
|
+
if (live === undefined)
|
|
96467
|
+
return;
|
|
96468
|
+
return buildConsumerModel({
|
|
96469
|
+
configured: live.configured(),
|
|
96470
|
+
states: live.servers(),
|
|
96471
|
+
problems: live.problems(),
|
|
96472
|
+
userFile: userConfigFile(),
|
|
96473
|
+
projectFile: projectConfigFile(cwd)
|
|
96474
|
+
});
|
|
96475
|
+
};
|
|
96476
|
+
openMcpConsumer(otui, chrome, {
|
|
96477
|
+
snapshot,
|
|
96478
|
+
connect: async (name) => {
|
|
96479
|
+
const live = deps.mcpRuntime?.();
|
|
96480
|
+
if (live === undefined)
|
|
96481
|
+
return { ok: false, message: "No MCP session yet" };
|
|
96482
|
+
return live.connectServer(name);
|
|
96483
|
+
},
|
|
96484
|
+
disconnect: async (name) => {
|
|
96485
|
+
const live = deps.mcpRuntime?.();
|
|
96486
|
+
if (live === undefined)
|
|
96487
|
+
return { ok: false, message: "No MCP session yet" };
|
|
96488
|
+
return live.disconnectServer(name);
|
|
96489
|
+
},
|
|
96490
|
+
renderer: r,
|
|
96491
|
+
...inspectorKeys
|
|
96070
96492
|
});
|
|
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
96493
|
};
|
|
96076
96494
|
const showTools = () => {
|
|
96077
96495
|
(async () => {
|
|
@@ -103099,7 +103517,7 @@ function monitorCommand(args) {
|
|
|
103099
103517
|
}
|
|
103100
103518
|
renderAgentsTree(snapshot);
|
|
103101
103519
|
}
|
|
103102
|
-
var
|
|
103520
|
+
var STATUS_GLYPH5 = {
|
|
103103
103521
|
running: "\u25D0",
|
|
103104
103522
|
done: "\u25CF",
|
|
103105
103523
|
blocked: "\u25FC",
|
|
@@ -103112,7 +103530,7 @@ function renderAgentsTree(snapshot) {
|
|
|
103112
103530
|
console.log(`run ${snapshot.runId ?? "(unknown)"} \u2014 ${snapshot.agents.length} subagent(s)`);
|
|
103113
103531
|
for (const agent of snapshot.agents) {
|
|
103114
103532
|
const tokens = `\u2191${agent.usage.inputTokens} \u2193${agent.usage.outputTokens}${agent.usage.exact ? "" : "~"}`;
|
|
103115
|
-
const glyph =
|
|
103533
|
+
const glyph = STATUS_GLYPH5[agent.status] ?? "\u25CB";
|
|
103116
103534
|
console.log(` ${glyph} ${agent.dispatchId} ${agent.status} ${agent.model ?? "-"} ${tokens}`);
|
|
103117
103535
|
}
|
|
103118
103536
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.101",
|
|
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": {
|