@mrciphersmith/keryx 0.2.95 → 0.2.97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +1077 -486
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -40253,9 +40253,9 @@ var init_memory = __esm(() => {
40253
40253
 
40254
40254
  // src/lib/serve-config.ts
40255
40255
  import { existsSync as existsSync25 } from "fs";
40256
- import path173 from "path";
40256
+ import path174 from "path";
40257
40257
  function serveConfigPath(dir) {
40258
- return path173.join(keryxConfigDir(dir), "serve.json");
40258
+ return path174.join(keryxConfigDir(dir), "serve.json");
40259
40259
  }
40260
40260
  function parseIpv4(value) {
40261
40261
  const parts = value.split(".");
@@ -47249,7 +47249,7 @@ import path43 from "path";
47249
47249
  // package.json
47250
47250
  var package_default = {
47251
47251
  name: "@mrciphersmith/keryx",
47252
- version: "0.2.95",
47252
+ version: "0.2.97",
47253
47253
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
47254
47254
  private: false,
47255
47255
  publishConfig: {
@@ -69894,12 +69894,12 @@ function entryProblems(name, entry) {
69894
69894
  }
69895
69895
  function oauthProblems(name, oauth) {
69896
69896
  const problems = [];
69897
- const known = new Set(["clientId", "clientSecretEnvVar", "scopes", "callbackPort"]);
69897
+ const known = new Set(["clientId", "scopes", "callbackPort"]);
69898
69898
  for (const key of Object.keys(oauth)) {
69899
69899
  if (!known.has(key))
69900
69900
  problems.push(`server "${name}" oauth has unknown field "${key}"`);
69901
69901
  }
69902
- for (const field of ["clientId", "clientSecretEnvVar"]) {
69902
+ for (const field of ["clientId"]) {
69903
69903
  if (oauth[field] !== undefined && typeof oauth[field] !== "string") {
69904
69904
  problems.push(`server "${name}" oauth.${field} must be a string`);
69905
69905
  }
@@ -70459,6 +70459,229 @@ function createMcpInteractiveTools(deps) {
70459
70459
  return [searchTool, useTool];
70460
70460
  }
70461
70461
 
70462
+ // src/mcp-servers/credentials.ts
70463
+ init_config_dir();
70464
+ import path165 from "path";
70465
+ var FORGET_CODE_VERIFIER = { code_verifier: undefined };
70466
+ var CREDENTIALS_SCHEMA_VERSION = 1;
70467
+ function credentialsFile(configDir) {
70468
+ return path165.join(configDir ?? ensureKeryxConfigDir(), "mcp-credentials.json");
70469
+ }
70470
+ function credentialKey(serverName, serverUrl) {
70471
+ return `${serverName}:${serverUrl}`;
70472
+ }
70473
+ function readAll2(configDir) {
70474
+ const file = credentialsFile(configDir);
70475
+ const read = readConfigFile(file);
70476
+ if (!read.ok) {
70477
+ return isDefiniteAbsence(read.reason) ? { file: {} } : { file: {}, problem: `${file} could not be read (${read.reason})` };
70478
+ }
70479
+ try {
70480
+ const parsed = parseJsonTolerant(read.text);
70481
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
70482
+ return { file: {}, problem: `${file} must be a JSON object` };
70483
+ }
70484
+ const parsedFile = parsed;
70485
+ if (typeof parsedFile.schemaVersion === "number" && parsedFile.schemaVersion > CREDENTIALS_SCHEMA_VERSION) {
70486
+ return {
70487
+ file: {},
70488
+ problem: `${file} was written by a newer keryx ` + `(schemaVersion ${parsedFile.schemaVersion} > ${CREDENTIALS_SCHEMA_VERSION}); nothing was changed`
70489
+ };
70490
+ }
70491
+ return { file: parsedFile };
70492
+ } catch (error) {
70493
+ return {
70494
+ file: {},
70495
+ problem: `${file} is not valid JSON (${error instanceof Error ? error.message : String(error)}); nothing was changed`
70496
+ };
70497
+ }
70498
+ }
70499
+ function readCredential(serverName, serverUrl, configDir) {
70500
+ const { file, problem } = readAll2(configDir);
70501
+ const record = file.credentials?.[credentialKey(serverName, serverUrl)];
70502
+ return problem === undefined ? { record } : { record, problem };
70503
+ }
70504
+ function writeCredential(serverName, serverUrl, patch, configDir) {
70505
+ const target = credentialsFile(configDir);
70506
+ const outcome = withFileLock2(`${target}.lock`, () => mergeUnlocked(target, serverName, serverUrl, patch, configDir));
70507
+ if (outcome === null) {
70508
+ return { ok: false, error: `${target} is locked by another keryx; nothing was written` };
70509
+ }
70510
+ return outcome;
70511
+ }
70512
+ function mergeUnlocked(target, serverName, serverUrl, patch, configDir) {
70513
+ const { file, problem } = readAll2(configDir);
70514
+ if (problem !== undefined) {
70515
+ return { ok: false, error: problem };
70516
+ }
70517
+ const key = credentialKey(serverName, serverUrl);
70518
+ const credentials = { ...file.credentials, [key]: { ...file.credentials?.[key], ...patch } };
70519
+ writeOwnerOnlyFileAtomic(target, `${JSON.stringify({ schemaVersion: CREDENTIALS_SCHEMA_VERSION, credentials }, null, 2)}
70520
+ `);
70521
+ return { ok: true, file: target };
70522
+ }
70523
+ function clearCredential(serverName, serverUrl, configDir) {
70524
+ const target = credentialsFile(configDir);
70525
+ const outcome = withFileLock2(`${target}.lock`, () => {
70526
+ const { file, problem } = readAll2(configDir);
70527
+ if (problem !== undefined)
70528
+ return { ok: false, error: problem };
70529
+ const credentials = { ...file.credentials };
70530
+ delete credentials[credentialKey(serverName, serverUrl)];
70531
+ writeOwnerOnlyFileAtomic(target, `${JSON.stringify({ schemaVersion: CREDENTIALS_SCHEMA_VERSION, credentials }, null, 2)}
70532
+ `);
70533
+ return { ok: true, file: target };
70534
+ });
70535
+ if (outcome === null) {
70536
+ return { ok: false, error: `${target} is locked by another keryx; nothing was removed` };
70537
+ }
70538
+ return outcome;
70539
+ }
70540
+ function forgetByName(serverName, configDir) {
70541
+ const target = credentialsFile(configDir);
70542
+ const outcome = withFileLock2(`${target}.lock`, () => {
70543
+ const { file, problem } = readAll2(configDir);
70544
+ if (problem !== undefined)
70545
+ return { count: 0, error: problem };
70546
+ const credentials = { ...file.credentials };
70547
+ let count = 0;
70548
+ for (const key of Object.keys(credentials)) {
70549
+ const separator = key.indexOf(":");
70550
+ if (separator === -1)
70551
+ continue;
70552
+ if (key.slice(0, separator) !== serverName)
70553
+ continue;
70554
+ delete credentials[key];
70555
+ count++;
70556
+ }
70557
+ if (count > 0) {
70558
+ writeOwnerOnlyFileAtomic(target, `${JSON.stringify({ schemaVersion: CREDENTIALS_SCHEMA_VERSION, credentials }, null, 2)}
70559
+ `);
70560
+ }
70561
+ return { count };
70562
+ });
70563
+ return outcome ?? { count: 0, error: `${target} is locked by another keryx; nothing was removed` };
70564
+ }
70565
+ var EXPIRY_MARGIN_MS = 30000;
70566
+ function isExpired(tokens, now) {
70567
+ if (tokens === undefined)
70568
+ return true;
70569
+ if (tokens.expires_at === undefined) {
70570
+ return false;
70571
+ }
70572
+ return tokens.expires_at - EXPIRY_MARGIN_MS <= now;
70573
+ }
70574
+ function describeCredential(record, now) {
70575
+ if (record?.tokens === undefined)
70576
+ return "no stored credential";
70577
+ if (isExpired(record.tokens, now)) {
70578
+ return record.tokens.refresh_token === undefined ? "stored credential has expired; re-authenticate" : "stored credential has expired; it will be refreshed on the next connect";
70579
+ }
70580
+ if (record.tokens.expires_at === undefined)
70581
+ return "stored credential, no stated expiry";
70582
+ const minutes = Math.max(0, Math.round((record.tokens.expires_at - now) / 60000));
70583
+ return `stored credential, valid for about ${minutes} more minute(s)`;
70584
+ }
70585
+ function usesOAuth(server) {
70586
+ if (typeof server.url !== "string" || server.url === "")
70587
+ return false;
70588
+ if (server.oauth === false)
70589
+ return false;
70590
+ if (server.bearer_token_env_var !== undefined && server.bearer_token_env_var !== "")
70591
+ return false;
70592
+ return !Object.keys(server.headers ?? {}).some((name) => name.toLowerCase() === "authorization");
70593
+ }
70594
+
70595
+ // src/mcp-servers/oauth-provider.ts
70596
+ class OAuthInteractionRequiredError extends Error {
70597
+ serverName;
70598
+ constructor(serverName) {
70599
+ super(`server "${serverName}" needs OAuth and this process cannot ask: run \`keryx mcp auth ${serverName}\` in a terminal`);
70600
+ this.serverName = serverName;
70601
+ this.name = "OAuthInteractionRequiredError";
70602
+ }
70603
+ }
70604
+ var CLIENT_NAME = "keryx";
70605
+ function createOAuthProvider(deps) {
70606
+ const now = deps.now ?? Date.now;
70607
+ const read = () => readCredential(deps.serverName, deps.serverUrl, deps.configDir);
70608
+ return {
70609
+ get redirectUrl() {
70610
+ return deps.redirectUrl;
70611
+ },
70612
+ get clientMetadata() {
70613
+ return {
70614
+ client_name: CLIENT_NAME,
70615
+ redirect_uris: deps.redirectUrl === undefined ? [] : [deps.redirectUrl],
70616
+ grant_types: ["authorization_code", "refresh_token"],
70617
+ response_types: ["code"],
70618
+ ...deps.scopes === undefined || deps.scopes.length === 0 ? {} : { scope: deps.scopes.join(" ") },
70619
+ token_endpoint_auth_method: "none"
70620
+ };
70621
+ },
70622
+ state() {
70623
+ if (deps.state === undefined) {
70624
+ if (!deps.interactive)
70625
+ throw new OAuthInteractionRequiredError(deps.serverName);
70626
+ throw new Error("no callback listener is running; state is minted by the listener");
70627
+ }
70628
+ return deps.state;
70629
+ },
70630
+ clientInformation() {
70631
+ if (deps.clientId !== undefined && deps.clientId !== "") {
70632
+ return { client_id: deps.clientId };
70633
+ }
70634
+ return read().record?.client;
70635
+ },
70636
+ saveClientInformation(client) {
70637
+ if (!deps.interactive)
70638
+ throw new OAuthInteractionRequiredError(deps.serverName);
70639
+ const write = writeCredential(deps.serverName, deps.serverUrl, { client }, deps.configDir);
70640
+ if (!write.ok)
70641
+ throw new Error(write.error);
70642
+ },
70643
+ tokens() {
70644
+ const stored = read().record?.tokens;
70645
+ if (stored === undefined)
70646
+ return;
70647
+ if (isExpired(stored, now()) && stored.refresh_token === undefined)
70648
+ return;
70649
+ return stored;
70650
+ },
70651
+ saveTokens(sdkTokens) {
70652
+ const tokens = {
70653
+ access_token: sdkTokens.access_token,
70654
+ ...sdkTokens.token_type === undefined ? {} : { token_type: sdkTokens.token_type },
70655
+ ...sdkTokens.refresh_token === undefined ? {} : { refresh_token: sdkTokens.refresh_token },
70656
+ ...sdkTokens.scope === undefined ? {} : { scope: sdkTokens.scope },
70657
+ ...sdkTokens.expires_in === undefined ? {} : { expires_at: now() + sdkTokens.expires_in * 1000 }
70658
+ };
70659
+ const write = writeCredential(deps.serverName, deps.serverUrl, { tokens, ...FORGET_CODE_VERIFIER }, deps.configDir);
70660
+ if (!write.ok)
70661
+ throw new Error(write.error);
70662
+ },
70663
+ async redirectToAuthorization(url) {
70664
+ if (!deps.interactive)
70665
+ throw new OAuthInteractionRequiredError(deps.serverName);
70666
+ if (deps.openBrowser === undefined)
70667
+ throw new OAuthInteractionRequiredError(deps.serverName);
70668
+ await deps.openBrowser(url);
70669
+ },
70670
+ saveCodeVerifier(codeVerifier) {
70671
+ const write = writeCredential(deps.serverName, deps.serverUrl, { code_verifier: codeVerifier }, deps.configDir);
70672
+ if (!write.ok)
70673
+ throw new Error(write.error);
70674
+ },
70675
+ codeVerifier() {
70676
+ const verifier = read().record?.code_verifier;
70677
+ if (verifier === undefined) {
70678
+ throw new Error("no PKCE verifier stored; the authorisation was not started by this keryx");
70679
+ }
70680
+ return verifier;
70681
+ }
70682
+ };
70683
+ }
70684
+
70462
70685
  // src/mcp-servers/doctor.ts
70463
70686
  function redactValues(values) {
70464
70687
  const out = {};
@@ -70555,12 +70778,38 @@ async function diagnose(server, options) {
70555
70778
  detail: describeHollow(resolved.hollow)
70556
70779
  };
70557
70780
  }
70781
+ const declaresOAuth = server.raw.oauth !== undefined && server.raw.oauth !== false;
70782
+ if (declaresOAuth && usesOAuth(server.raw)) {
70783
+ const stored = readCredential(server.name, server.url ?? "", options.configDir);
70784
+ if (stored.problem !== undefined) {
70785
+ return { ...base, status: "needs_auth", toolCount: 0, skipped: [], detail: stored.problem };
70786
+ }
70787
+ const tokens = stored.record?.tokens;
70788
+ if (tokens === undefined || isExpired(tokens, (options.now ?? Date.now)()) && tokens.refresh_token === undefined) {
70789
+ return {
70790
+ ...base,
70791
+ status: "needs_auth",
70792
+ toolCount: 0,
70793
+ skipped: [],
70794
+ detail: tokens === undefined ? `no stored credential; run \`keryx mcp auth ${server.name}\`` : `stored credential has expired and cannot be refreshed; run \`keryx mcp auth ${server.name}\``
70795
+ };
70796
+ }
70797
+ }
70558
70798
  }
70559
70799
  const timeoutMs = server.startup_timeout_sec !== undefined ? server.startup_timeout_sec * 1000 : options.connectTimeoutMs ?? DEFAULT_DOCTOR_TIMEOUT_MS;
70560
70800
  let connection;
70561
70801
  try {
70562
70802
  connection = await withTimeout(options.connect(server), timeoutMs, server.name);
70563
70803
  } catch (error) {
70804
+ if (base.transport === "http" && usesOAuth(server.raw) && needsAuthorisation(error)) {
70805
+ return {
70806
+ ...base,
70807
+ status: "needs_auth",
70808
+ toolCount: 0,
70809
+ skipped: [],
70810
+ detail: `${displayUrl(server.raw.url)} requires authorisation; run \`keryx mcp auth ${server.name}\``
70811
+ };
70812
+ }
70564
70813
  return {
70565
70814
  ...base,
70566
70815
  status: "failed",
@@ -70586,6 +70835,20 @@ async function diagnose(server, options) {
70586
70835
  } catch {}
70587
70836
  }
70588
70837
  }
70838
+ function needsAuthorisation(error) {
70839
+ if (httpStatusOf(error) === 401)
70840
+ return true;
70841
+ if (error instanceof OAuthInteractionRequiredError)
70842
+ return true;
70843
+ return DEAD_CREDENTIAL_CODES.has(oauthErrorCodeOf(error));
70844
+ }
70845
+ var DEAD_CREDENTIAL_CODES = new Set(["invalid_grant", "invalid_client"]);
70846
+ function oauthErrorCodeOf(error) {
70847
+ if (typeof error !== "object" || error === null)
70848
+ return "";
70849
+ const code = error.errorCode;
70850
+ return typeof code === "string" ? code : "";
70851
+ }
70589
70852
  function explainConnectFailure(transport, server, error) {
70590
70853
  const message = sanitiseForDisplay(messageOf(error));
70591
70854
  if (transport !== "http")
@@ -70710,193 +70973,144 @@ function formatDoctorReport(report) {
70710
70973
  `);
70711
70974
  }
70712
70975
 
70713
- // src/mcp-servers/runtime.ts
70714
- import path166 from "path";
70715
-
70716
- // src/mcp-servers/manager.ts
70717
- var DEFAULT_CONNECT_CONCURRENCY = 4;
70718
- var DEFAULT_STARTUP_TIMEOUT_MS = 15000;
70719
- async function startServers(servers, connect, options = {}) {
70720
- const concurrency = Math.max(1, options.concurrency ?? DEFAULT_CONNECT_CONCURRENCY);
70721
- const states = new Map;
70722
- const catalogs = [];
70723
- const enabled = [];
70724
- for (const server of servers) {
70725
- if (server.enabled) {
70726
- enabled.push(server);
70727
- } else {
70728
- states.set(server.name, { name: server.name, status: "disabled", toolCount: 0 });
70729
- }
70976
+ // src/lib/oauth/open-url.ts
70977
+ import { spawn as spawn5 } from "child_process";
70978
+ function browserOpenPlan(url, platform = process.platform, env = process.env) {
70979
+ if (url.length === 0) {
70980
+ return;
70730
70981
  }
70731
- let next = 0;
70732
- const worker = async () => {
70733
- for (;; ) {
70734
- const index = next++;
70735
- const server = enabled[index];
70736
- if (server === undefined)
70737
- return;
70738
- const timeoutMs = server.startup_timeout_sec !== undefined ? server.startup_timeout_sec * 1000 : options.defaultStartupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
70739
- let connection;
70740
- try {
70741
- connection = await withTimeout2(connect(server), timeoutMs, server.name, (late) => {
70742
- late.close().catch(() => {});
70982
+ if (platform === "darwin") {
70983
+ return { cmd: "open", args: [url] };
70984
+ }
70985
+ if (platform === "win32") {
70986
+ return { cmd: "cmd", args: ["/c", "start", "", url] };
70987
+ }
70988
+ if (typeof env.DISPLAY === "string" && env.DISPLAY.length > 0) {
70989
+ return { cmd: "xdg-open", args: [url] };
70990
+ }
70991
+ if (typeof env.WAYLAND_DISPLAY === "string" && env.WAYLAND_DISPLAY.length > 0) {
70992
+ return { cmd: "xdg-open", args: [url] };
70993
+ }
70994
+ return;
70995
+ }
70996
+ function openVerificationUrl(url, deps = {}) {
70997
+ const plan = browserOpenPlan(url, deps.platform ?? process.platform, deps.env ?? process.env);
70998
+ if (plan === undefined) {
70999
+ return;
71000
+ }
71001
+ const spawnFn = deps.spawn ?? spawn5;
71002
+ try {
71003
+ const child = spawnFn(plan.cmd, plan.args, { stdio: "ignore", detached: true });
71004
+ child.on("error", () => {});
71005
+ child.unref();
71006
+ } catch {}
71007
+ }
71008
+
71009
+ // src/mcp-servers/oauth-callback.ts
71010
+ import { randomBytes as randomBytes4, timingSafeEqual } from "crypto";
71011
+ function matchesState(given, expected) {
71012
+ if (given === null)
71013
+ return false;
71014
+ const a = Buffer.from(given, "utf8");
71015
+ const b = Buffer.from(expected, "utf8");
71016
+ if (a.length !== b.length)
71017
+ return false;
71018
+ return timingSafeEqual(a, b);
71019
+ }
71020
+ var LOOPBACK_HOST = "127.0.0.1";
71021
+ var CALLBACK_TIMEOUT_MS = 5 * 60000;
71022
+ function page(title, detail) {
71023
+ return [
71024
+ "<!doctype html><meta charset=utf-8>",
71025
+ `<title>${title}</title>`,
71026
+ '<body style="font:14px system-ui;padding:3rem;max-width:34rem">',
71027
+ `<h1 style="font-size:1.1rem">${title}</h1>`,
71028
+ `<p>${detail}</p>`,
71029
+ "</body>"
71030
+ ].join("");
71031
+ }
71032
+ function startCallbackListener(options = {}) {
71033
+ const state = randomBytes4(32).toString("base64url");
71034
+ const timeoutMs = options.timeoutMs ?? CALLBACK_TIMEOUT_MS;
71035
+ const serve = options.serve ?? Bun.serve;
71036
+ let settle = () => {};
71037
+ const result = new Promise((resolve) => {
71038
+ settle = resolve;
71039
+ });
71040
+ let done = false;
71041
+ let timer;
71042
+ const server = serve({
71043
+ hostname: LOOPBACK_HOST,
71044
+ port: options.port ?? 0,
71045
+ fetch(request) {
71046
+ const url = new URL(request.url);
71047
+ if (done) {
71048
+ return new Response(page("Already handled", "This authorisation was already completed."), {
71049
+ status: 410,
71050
+ headers: { "content-type": "text/html" }
70743
71051
  });
70744
- const tools = await withTimeout2(connection.listTools(), timeoutMs, server.name);
70745
- const catalog = catalogForServer(server.name, tools);
70746
- catalogs.push(catalog);
70747
- states.set(server.name, {
70748
- name: server.name,
70749
- status: "connected",
70750
- toolCount: catalog.entries.length,
70751
- connection
71052
+ }
71053
+ const given = url.searchParams.get("state");
71054
+ if (!matchesState(given, state)) {
71055
+ return new Response(page("Not this flow", "This request did not come from the authorisation keryx started."), {
71056
+ status: 400,
71057
+ headers: { "content-type": "text/html" }
70752
71058
  });
70753
- } catch (error) {
70754
- if (connection !== undefined) {
70755
- try {
70756
- await connection.close();
70757
- } catch {}
70758
- }
70759
- states.set(server.name, {
70760
- name: server.name,
70761
- status: "failed",
70762
- error: error instanceof Error ? error.message : String(error),
70763
- toolCount: 0
71059
+ }
71060
+ const error = url.searchParams.get("error");
71061
+ if (error !== null) {
71062
+ const description = url.searchParams.get("error_description");
71063
+ done = true;
71064
+ settle({ ok: false, reason: description === null ? error : `${error}: ${description}` });
71065
+ return new Response(page("Authorisation refused", "You can close this tab and return to keryx."), {
71066
+ status: 200,
71067
+ headers: { "content-type": "text/html" }
71068
+ });
71069
+ }
71070
+ const code = url.searchParams.get("code");
71071
+ if (code === null || code === "") {
71072
+ return new Response(page("No code", "The authorisation server returned no code."), {
71073
+ status: 400,
71074
+ headers: { "content-type": "text/html" }
70764
71075
  });
70765
71076
  }
71077
+ done = true;
71078
+ settle({ ok: true, code });
71079
+ return new Response(page("Authorised", "keryx has what it needs. You can close this tab."), {
71080
+ status: 200,
71081
+ headers: { "content-type": "text/html" }
71082
+ });
70766
71083
  }
70767
- };
70768
- await Promise.all(Array.from({ length: Math.min(concurrency, Math.max(enabled.length, 1)) }, worker));
70769
- return {
70770
- servers: [...states.values()].sort((a, b) => a.name.localeCompare(b.name)),
70771
- catalog: mergeCatalogs(catalogs)
70772
- };
70773
- }
70774
- async function withTimeout2(work, ms, name, onAbandoned) {
70775
- let timer;
70776
- let settled = false;
70777
- try {
70778
- return await Promise.race([
70779
- work,
70780
- new Promise((_resolve, reject) => {
70781
- timer = setTimeout(() => reject(new Error(`server "${name}" did not answer within ${ms}ms`)), ms);
70782
- })
70783
- ]).then((value) => {
70784
- settled = true;
70785
- return value;
70786
- });
70787
- } finally {
71084
+ });
71085
+ const close = () => {
70788
71086
  if (timer !== undefined)
70789
71087
  clearTimeout(timer);
70790
- if (!settled && onAbandoned !== undefined) {
70791
- work.then(onAbandoned).catch(() => {});
70792
- }
70793
- }
70794
- }
70795
- async function closeServers(states) {
70796
- await Promise.all(states.map(async (state) => {
71088
+ timer = undefined;
70797
71089
  try {
70798
- await state.connection?.close();
71090
+ server.stop(true);
70799
71091
  } catch {}
70800
- }));
70801
- }
70802
-
70803
- // src/mcp-servers/trust.ts
70804
- init_config_dir();
70805
- import { createHash as createHash31 } from "crypto";
70806
- import path165 from "path";
70807
- function trustFile(configDir) {
70808
- return path165.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers-trust.json");
70809
- }
70810
- function serverFingerprint(server) {
70811
- const material = JSON.stringify({
70812
- command: server.command ?? null,
70813
- args: server.args ?? [],
70814
- url: server.url ?? null,
70815
- env: server.env ?? {},
70816
- cwd: server.cwd ?? null,
70817
- headers: sortedKeys(server.headers),
70818
- bearer_token_env_var: server.bearer_token_env_var ?? null
71092
+ };
71093
+ timer = setTimeout(() => {
71094
+ if (done)
71095
+ return;
71096
+ done = true;
71097
+ settle({
71098
+ ok: false,
71099
+ reason: `no response within ${Math.round(timeoutMs / 1000)}s \u2014 the browser tab was never completed`
71100
+ });
71101
+ }, timeoutMs);
71102
+ timer.unref?.();
71103
+ result.then(() => {
71104
+ const drain = setTimeout(close, 50);
71105
+ drain.unref?.();
70819
71106
  });
70820
- return createHash31("sha256").update(material).digest("hex").slice(0, 32);
70821
- }
70822
- function sortedKeys(values) {
70823
- return Object.entries(values ?? {}).sort(([a], [b]) => a.localeCompare(b));
70824
- }
70825
- function trustKey(server) {
70826
- return `${server.file}::${server.name}`;
70827
- }
70828
- function loadTrustStore(configDir) {
70829
- const read = readConfigFile(trustFile(configDir));
70830
- if (!read.ok)
70831
- return {};
70832
- try {
70833
- const parsed = parseJsonTolerant(read.text);
70834
- const approvals = parsed.approvals;
70835
- if (approvals === undefined)
70836
- return {};
70837
- const clean = {};
70838
- for (const [key, value] of Object.entries(approvals)) {
70839
- if (typeof value === "string")
70840
- clean[key] = value;
70841
- }
70842
- return clean;
70843
- } catch {
70844
- return {};
70845
- }
70846
- }
70847
- function requiresApproval(server, approvals) {
70848
- if (!server.projectLocal)
70849
- return false;
70850
- return approvals[trustKey(server)] !== serverFingerprint(server);
70851
- }
70852
- function approveServer(server, configDir) {
70853
- const file = trustFile(configDir);
70854
- const read = readConfigFile(file);
70855
- if (!read.ok && !isDefiniteAbsence(read.reason)) {
70856
- return { ok: false, error: `${file} could not be read (${read.reason}); nothing was written` };
70857
- }
70858
- const approvals = loadTrustStore(configDir);
70859
- approvals[trustKey(server)] = serverFingerprint(server);
70860
- writeOwnerOnlyFileAtomic(file, `${JSON.stringify({ approvals }, null, 2)}
70861
- `);
70862
- return { ok: true, file };
70863
- }
70864
- function revokeServer(server, configDir) {
70865
- const file = trustFile(configDir);
70866
- const approvals = loadTrustStore(configDir);
70867
- delete approvals[trustKey(server)];
70868
- writeOwnerOnlyFileAtomic(file, `${JSON.stringify({ approvals }, null, 2)}
70869
- `);
70870
- return { ok: true, file };
70871
- }
70872
- function describeForApproval(server) {
70873
- const raw = server.raw;
70874
- if (raw.url !== undefined && raw.url.length > 0) {
70875
- const shown = displayUrl(raw.url);
70876
- const credentials = credentialSummary(raw);
70877
- return credentials === undefined ? shown : `${shown} [sends ${credentials}]`;
70878
- }
70879
- return [raw.command, ...raw.args ?? []].filter(Boolean).join(" ");
70880
- }
70881
- function credentialSummary(raw) {
70882
- const named = new Set;
70883
- if (raw.bearer_token_env_var !== undefined && raw.bearer_token_env_var !== "") {
70884
- named.add(raw.bearer_token_env_var);
70885
- }
70886
- for (const value of Object.values(raw.headers ?? {})) {
70887
- for (const match of value.matchAll(/\$\{([A-Za-z_][A-Za-z0-9_]*)/g))
70888
- named.add(match[1]);
70889
- }
70890
- const headerNames = Object.keys(raw.headers ?? {});
70891
- if (named.size === 0 && headerNames.length === 0)
70892
- return;
70893
- const parts = [];
70894
- if (named.size > 0)
70895
- parts.push([...named].sort().join(", "));
70896
- const literal = headerNames.filter((n) => !/\$\{/.test(raw.headers?.[n] ?? ""));
70897
- if (literal.length > 0)
70898
- parts.push(`literal header(s) ${literal.sort().join(", ")}`);
70899
- return parts.join(" + ");
71107
+ return {
71108
+ redirectUrl: `http://${LOOPBACK_HOST}:${server.port}/callback`,
71109
+ boundHost: server.hostname ?? "",
71110
+ state,
71111
+ result,
71112
+ close
71113
+ };
70900
71114
  }
70901
71115
 
70902
71116
  // src/mcp-client/wire.ts
@@ -71015,6 +71229,7 @@ async function connectHttpMcpServer(url, options = {}) {
71015
71229
  }
71016
71230
  const sdk = await loadHttpSdk();
71017
71231
  const transport = new sdk.StreamableHTTPClientTransport(parsed, {
71232
+ ...options.authProvider === undefined ? {} : { authProvider: options.authProvider },
71018
71233
  fetch: (input, init) => fetch(input, { ...init, redirect: "error" }),
71019
71234
  requestInit: {
71020
71235
  ...Object.keys(headers).length > 0 ? { headers } : {},
@@ -71148,6 +71363,195 @@ async function connectStdioMcpServer(argv, options, handshakeTimeoutMs, signal,
71148
71363
  };
71149
71364
  }
71150
71365
 
71366
+ // src/mcp-servers/runtime.ts
71367
+ import path167 from "path";
71368
+
71369
+ // src/mcp-servers/manager.ts
71370
+ var DEFAULT_CONNECT_CONCURRENCY = 4;
71371
+ var DEFAULT_STARTUP_TIMEOUT_MS = 15000;
71372
+ async function startServers(servers, connect, options = {}) {
71373
+ const concurrency = Math.max(1, options.concurrency ?? DEFAULT_CONNECT_CONCURRENCY);
71374
+ const states = new Map;
71375
+ const catalogs = [];
71376
+ const enabled = [];
71377
+ for (const server of servers) {
71378
+ if (server.enabled) {
71379
+ enabled.push(server);
71380
+ } else {
71381
+ states.set(server.name, { name: server.name, status: "disabled", toolCount: 0 });
71382
+ }
71383
+ }
71384
+ let next = 0;
71385
+ const worker = async () => {
71386
+ for (;; ) {
71387
+ const index = next++;
71388
+ const server = enabled[index];
71389
+ if (server === undefined)
71390
+ return;
71391
+ const timeoutMs = server.startup_timeout_sec !== undefined ? server.startup_timeout_sec * 1000 : options.defaultStartupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
71392
+ let connection;
71393
+ try {
71394
+ connection = await withTimeout2(connect(server), timeoutMs, server.name, (late) => {
71395
+ late.close().catch(() => {});
71396
+ });
71397
+ const tools = await withTimeout2(connection.listTools(), timeoutMs, server.name);
71398
+ const catalog = catalogForServer(server.name, tools);
71399
+ catalogs.push(catalog);
71400
+ states.set(server.name, {
71401
+ name: server.name,
71402
+ status: "connected",
71403
+ toolCount: catalog.entries.length,
71404
+ connection
71405
+ });
71406
+ } catch (error) {
71407
+ if (connection !== undefined) {
71408
+ try {
71409
+ await connection.close();
71410
+ } catch {}
71411
+ }
71412
+ states.set(server.name, {
71413
+ name: server.name,
71414
+ status: "failed",
71415
+ error: error instanceof Error ? error.message : String(error),
71416
+ toolCount: 0
71417
+ });
71418
+ }
71419
+ }
71420
+ };
71421
+ await Promise.all(Array.from({ length: Math.min(concurrency, Math.max(enabled.length, 1)) }, worker));
71422
+ return {
71423
+ servers: [...states.values()].sort((a, b) => a.name.localeCompare(b.name)),
71424
+ catalog: mergeCatalogs(catalogs)
71425
+ };
71426
+ }
71427
+ async function withTimeout2(work, ms, name, onAbandoned) {
71428
+ let timer;
71429
+ let settled = false;
71430
+ try {
71431
+ return await Promise.race([
71432
+ work,
71433
+ new Promise((_resolve, reject) => {
71434
+ timer = setTimeout(() => reject(new Error(`server "${name}" did not answer within ${ms}ms`)), ms);
71435
+ })
71436
+ ]).then((value) => {
71437
+ settled = true;
71438
+ return value;
71439
+ });
71440
+ } finally {
71441
+ if (timer !== undefined)
71442
+ clearTimeout(timer);
71443
+ if (!settled && onAbandoned !== undefined) {
71444
+ work.then(onAbandoned).catch(() => {});
71445
+ }
71446
+ }
71447
+ }
71448
+ async function closeServers(states) {
71449
+ await Promise.all(states.map(async (state) => {
71450
+ try {
71451
+ await state.connection?.close();
71452
+ } catch {}
71453
+ }));
71454
+ }
71455
+
71456
+ // src/mcp-servers/trust.ts
71457
+ init_config_dir();
71458
+ import { createHash as createHash31 } from "crypto";
71459
+ import path166 from "path";
71460
+ function trustFile(configDir) {
71461
+ return path166.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers-trust.json");
71462
+ }
71463
+ function serverFingerprint(server) {
71464
+ const material = JSON.stringify({
71465
+ command: server.command ?? null,
71466
+ args: server.args ?? [],
71467
+ url: server.url ?? null,
71468
+ env: server.env ?? {},
71469
+ cwd: server.cwd ?? null,
71470
+ headers: sortedKeys(server.headers),
71471
+ bearer_token_env_var: server.bearer_token_env_var ?? null
71472
+ });
71473
+ return createHash31("sha256").update(material).digest("hex").slice(0, 32);
71474
+ }
71475
+ function sortedKeys(values) {
71476
+ return Object.entries(values ?? {}).sort(([a], [b]) => a.localeCompare(b));
71477
+ }
71478
+ function trustKey(server) {
71479
+ return `${server.file}::${server.name}`;
71480
+ }
71481
+ function loadTrustStore(configDir) {
71482
+ const read = readConfigFile(trustFile(configDir));
71483
+ if (!read.ok)
71484
+ return {};
71485
+ try {
71486
+ const parsed = parseJsonTolerant(read.text);
71487
+ const approvals = parsed.approvals;
71488
+ if (approvals === undefined)
71489
+ return {};
71490
+ const clean = {};
71491
+ for (const [key, value] of Object.entries(approvals)) {
71492
+ if (typeof value === "string")
71493
+ clean[key] = value;
71494
+ }
71495
+ return clean;
71496
+ } catch {
71497
+ return {};
71498
+ }
71499
+ }
71500
+ function requiresApproval(server, approvals) {
71501
+ if (!server.projectLocal)
71502
+ return false;
71503
+ return approvals[trustKey(server)] !== serverFingerprint(server);
71504
+ }
71505
+ function approveServer(server, configDir) {
71506
+ const file = trustFile(configDir);
71507
+ const read = readConfigFile(file);
71508
+ if (!read.ok && !isDefiniteAbsence(read.reason)) {
71509
+ return { ok: false, error: `${file} could not be read (${read.reason}); nothing was written` };
71510
+ }
71511
+ const approvals = loadTrustStore(configDir);
71512
+ approvals[trustKey(server)] = serverFingerprint(server);
71513
+ writeOwnerOnlyFileAtomic(file, `${JSON.stringify({ approvals }, null, 2)}
71514
+ `);
71515
+ return { ok: true, file };
71516
+ }
71517
+ function revokeServer(server, configDir) {
71518
+ const file = trustFile(configDir);
71519
+ const approvals = loadTrustStore(configDir);
71520
+ delete approvals[trustKey(server)];
71521
+ writeOwnerOnlyFileAtomic(file, `${JSON.stringify({ approvals }, null, 2)}
71522
+ `);
71523
+ return { ok: true, file };
71524
+ }
71525
+ function describeForApproval(server) {
71526
+ const raw = server.raw;
71527
+ if (raw.url !== undefined && raw.url.length > 0) {
71528
+ const shown = displayUrl(raw.url);
71529
+ const credentials = credentialSummary(raw);
71530
+ return credentials === undefined ? shown : `${shown} [sends ${credentials}]`;
71531
+ }
71532
+ return [raw.command, ...raw.args ?? []].filter(Boolean).join(" ");
71533
+ }
71534
+ function credentialSummary(raw) {
71535
+ const named = new Set;
71536
+ if (raw.bearer_token_env_var !== undefined && raw.bearer_token_env_var !== "") {
71537
+ named.add(raw.bearer_token_env_var);
71538
+ }
71539
+ for (const value of Object.values(raw.headers ?? {})) {
71540
+ for (const match of value.matchAll(/\$\{([A-Za-z_][A-Za-z0-9_]*)/g))
71541
+ named.add(match[1]);
71542
+ }
71543
+ const headerNames = Object.keys(raw.headers ?? {});
71544
+ if (named.size === 0 && headerNames.length === 0)
71545
+ return;
71546
+ const parts = [];
71547
+ if (named.size > 0)
71548
+ parts.push([...named].sort().join(", "));
71549
+ const literal = headerNames.filter((n) => !/\$\{/.test(raw.headers?.[n] ?? ""));
71550
+ if (literal.length > 0)
71551
+ parts.push(`literal header(s) ${literal.sort().join(", ")}`);
71552
+ return parts.join(" + ");
71553
+ }
71554
+
71151
71555
  // src/harness/external/env.ts
71152
71556
  var EXTERNAL_ENV_DENY = [
71153
71557
  "ANTHROPIC_API_KEY",
@@ -71332,7 +71736,20 @@ function buildMcpChildEnv(input) {
71332
71736
  // src/mcp-servers/runtime.ts
71333
71737
  var CLOSE_GRACE_MS = 750;
71334
71738
  var KILL_GRACE_MS = 4500;
71335
- var delay2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
71739
+ async function within(work, ms) {
71740
+ let timer;
71741
+ try {
71742
+ await Promise.race([
71743
+ work,
71744
+ new Promise((resolve) => {
71745
+ timer = setTimeout(resolve, ms);
71746
+ })
71747
+ ]);
71748
+ } finally {
71749
+ if (timer !== undefined)
71750
+ clearTimeout(timer);
71751
+ }
71752
+ }
71336
71753
  function createMcpRuntime(options) {
71337
71754
  const dialling = new AbortController;
71338
71755
  const config = loadMcpServers({
@@ -71363,7 +71780,7 @@ function createMcpRuntime(options) {
71363
71780
  ];
71364
71781
  const kills = [];
71365
71782
  const env = options.env ?? process.env;
71366
- const connect = options.connect ?? ((server) => defaultConnect(server, env, dialling.signal, kills));
71783
+ const connect = options.connect ?? ((server) => defaultConnect(server, env, dialling.signal, kills, options.configDir));
71367
71784
  const settled = startServers(launchable, connect).then((result) => {
71368
71785
  catalog = result.catalog;
71369
71786
  states = [...heldStates, ...result.servers].sort((a, b) => a.name.localeCompare(b.name));
@@ -71377,6 +71794,7 @@ function createMcpRuntime(options) {
71377
71794
  }
71378
71795
  await closeServers(fresh);
71379
71796
  };
71797
+ let closing;
71380
71798
  return {
71381
71799
  catalog: () => catalog,
71382
71800
  servers: () => states,
@@ -71386,14 +71804,17 @@ function createMcpRuntime(options) {
71386
71804
  ready: async () => {
71387
71805
  await settled;
71388
71806
  },
71389
- close: async () => {
71807
+ close: () => closing ??= closeNow()
71808
+ };
71809
+ async function closeNow() {
71810
+ {
71390
71811
  dialling.abort();
71391
- await Promise.race([settled, delay2(CLOSE_GRACE_MS)]);
71812
+ await within(settled, CLOSE_GRACE_MS);
71392
71813
  await closeOnce(states);
71393
- await Promise.race([Promise.all(kills), delay2(KILL_GRACE_MS)]);
71814
+ await within(Promise.all(kills), KILL_GRACE_MS);
71394
71815
  settled.then(() => closeOnce(states)).catch(() => {});
71395
71816
  }
71396
- };
71817
+ }
71397
71818
  }
71398
71819
  function handshakeBudgetMs(server) {
71399
71820
  const configured = server.startup_timeout_sec;
@@ -71404,7 +71825,25 @@ function handshakeBudgetMs(server) {
71404
71825
  }
71405
71826
  var MAX_TIMEOUT_MS = 24 * 60 * 60 * 1000;
71406
71827
  var DEFAULT_HANDSHAKE_MS = 15000;
71407
- async function connectRemote(server, env, signal) {
71828
+ var SESSION_REDIRECT_URL = "http://127.0.0.1/keryx-session-never-listens";
71829
+ function sessionAuthProviderOptions(server, runtimeConfigDir, stored, now = Date.now()) {
71830
+ if (!usesOAuth(server.raw))
71831
+ return;
71832
+ const tokens = stored?.tokens;
71833
+ if (tokens === undefined)
71834
+ return;
71835
+ if (isExpired(tokens, now) && tokens.refresh_token === undefined)
71836
+ return;
71837
+ return {
71838
+ serverName: server.name,
71839
+ serverUrl: server.url,
71840
+ ...runtimeConfigDir === undefined ? {} : { configDir: runtimeConfigDir },
71841
+ redirectUrl: SESSION_REDIRECT_URL,
71842
+ interactive: false,
71843
+ ...server.oauth === false || server.oauth?.clientId === undefined ? {} : { clientId: server.oauth.clientId }
71844
+ };
71845
+ }
71846
+ async function connectRemote(server, env, signal, runtimeConfigDir) {
71408
71847
  const target = remoteTargetProblem(server.raw, env);
71409
71848
  if (target !== undefined) {
71410
71849
  throw new Error(`server "${server.name}": ${target}`);
@@ -71413,8 +71852,11 @@ async function connectRemote(server, env, signal) {
71413
71852
  if (!resolved.ok) {
71414
71853
  throw new Error(`server "${server.name}": ${describeHollow(resolved.hollow)}`);
71415
71854
  }
71855
+ const options = sessionAuthProviderOptions(server, runtimeConfigDir, server.url === undefined ? undefined : readCredential(server.name, server.url, runtimeConfigDir).record);
71856
+ const authProvider = options === undefined ? undefined : createOAuthProvider(options);
71416
71857
  return connectHttpMcpServer(server.url, {
71417
71858
  headers: resolved.headers,
71859
+ ...authProvider === undefined ? {} : { authProvider },
71418
71860
  handshakeTimeoutMs: handshakeBudgetMs(server),
71419
71861
  ...signal === undefined ? {} : { signal }
71420
71862
  });
@@ -71424,9 +71866,9 @@ function timeoutFor(server, rawName) {
71424
71866
  return;
71425
71867
  return server.tool_timeouts?.[rawName] ?? server.tool_timeout_sec;
71426
71868
  }
71427
- async function defaultConnect(server, env, signal, kills) {
71869
+ async function defaultConnect(server, env, signal, kills, configDir) {
71428
71870
  if (transportOf(server) === "http")
71429
- return connectRemote(server, env, signal);
71871
+ return connectRemote(server, env, signal, configDir);
71430
71872
  const command = server.command;
71431
71873
  if (command === undefined || command === "") {
71432
71874
  throw new Error(`server "${server.name}" sets neither command nor url`);
@@ -71439,22 +71881,22 @@ async function defaultConnect(server, env, signal, kills) {
71439
71881
  function defaultServerCwd(server, fallbackCwd = process.cwd()) {
71440
71882
  if (server.source !== "project")
71441
71883
  return fallbackCwd;
71442
- return path166.dirname(path166.dirname(server.file));
71884
+ return path167.dirname(path167.dirname(server.file));
71443
71885
  }
71444
71886
 
71445
71887
  // src/mcp-servers/store.ts
71446
71888
  init_config_dir();
71447
71889
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
71448
- import path167 from "path";
71890
+ import path168 from "path";
71449
71891
  var SERVER_NAME_PATTERN = /^[A-Za-z0-9_-]+$/;
71450
71892
  function userConfigFile(configDir) {
71451
- return path167.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers.json");
71893
+ return path168.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers.json");
71452
71894
  }
71453
71895
  function projectConfigFile(projectRoot) {
71454
- return path167.join(projectRoot, ".keryx", "mcp-servers.json");
71896
+ return path168.join(projectRoot, ".keryx", "mcp-servers.json");
71455
71897
  }
71456
71898
  function overlayFile(configDir) {
71457
- return path167.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers-disabled.json");
71899
+ return path168.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers-disabled.json");
71458
71900
  }
71459
71901
  function readForWrite(file) {
71460
71902
  const read = readConfigFile(file);
@@ -71493,7 +71935,7 @@ function writeDocument(file, doc, scope) {
71493
71935
  writeOwnerOnlyFileAtomic(file, body);
71494
71936
  return;
71495
71937
  }
71496
- mkdirSync6(path167.dirname(file), { recursive: true });
71938
+ mkdirSync6(path168.dirname(file), { recursive: true });
71497
71939
  writeFileSync7(file, body, { encoding: "utf8" });
71498
71940
  }
71499
71941
  function addServer(options) {
@@ -71597,7 +72039,9 @@ var MCP_CONSUMER_SUBCOMMANDS = [
71597
72039
  "disable",
71598
72040
  "trust",
71599
72041
  "untrust",
71600
- "doctor"
72042
+ "doctor",
72043
+ "auth",
72044
+ "logout"
71601
72045
  ];
71602
72046
  function isMcpConsumerSubcommand(value) {
71603
72047
  return MCP_CONSUMER_SUBCOMMANDS.includes(value ?? "");
@@ -71620,6 +72064,10 @@ async function runMcpConsumerCommand(subcommand, args, deps) {
71620
72064
  return trustCommand(args, deps, false);
71621
72065
  case "doctor":
71622
72066
  return doctorCommand(args, deps);
72067
+ case "auth":
72068
+ return authCommand(args, deps);
72069
+ case "logout":
72070
+ return logoutCommand(args, deps);
71623
72071
  }
71624
72072
  }
71625
72073
  function projectRootOf(deps) {
@@ -71761,6 +72209,178 @@ function addCommand(argv, deps) {
71761
72209
  deps.log(`Run \`keryx mcp doctor ${name}\` to check it connects.`);
71762
72210
  return 0;
71763
72211
  }
72212
+ function logoutCommand(args, deps) {
72213
+ const own = splitAtSeparator(args).own;
72214
+ const name = own[0];
72215
+ if (name === undefined || name.startsWith("-")) {
72216
+ deps.err("usage: keryx mcp logout <name>");
72217
+ return 1;
72218
+ }
72219
+ const config = load(deps);
72220
+ const server = config.servers.find((candidate) => candidate.name === name);
72221
+ const url = server?.url;
72222
+ if (url === undefined) {
72223
+ const removed = forgetByName(name, deps.configDir);
72224
+ if (removed.count === 0) {
72225
+ deps.err(`no stored credential for "${name}".`);
72226
+ return 1;
72227
+ }
72228
+ deps.log(`Forgot ${removed.count} stored credential(s) for "${name}".`);
72229
+ return 0;
72230
+ }
72231
+ const existing = readCredential(name, url, deps.configDir);
72232
+ if (existing.problem !== undefined) {
72233
+ deps.err(existing.problem);
72234
+ return 1;
72235
+ }
72236
+ if (existing.record === undefined) {
72237
+ deps.log(`"${name}" has no stored credential; nothing to forget.`);
72238
+ return 0;
72239
+ }
72240
+ const cleared = clearCredential(name, url, deps.configDir);
72241
+ if (!cleared.ok) {
72242
+ deps.err(cleared.error);
72243
+ return 1;
72244
+ }
72245
+ deps.log(`Forgot the stored credential for "${name}".`);
72246
+ deps.log(`Run \`keryx mcp auth ${name}\` to authorise again.`);
72247
+ return 0;
72248
+ }
72249
+ async function authCommand(args, deps) {
72250
+ const own = splitAtSeparator(args).own;
72251
+ const unknown = own.filter((arg, index, all) => isUnknownFlag(arg, index, all));
72252
+ if (unknown.length > 0) {
72253
+ deps.err(`unknown option ${unknown[0]}`);
72254
+ return 1;
72255
+ }
72256
+ const name = positional3(args);
72257
+ if (name === undefined) {
72258
+ deps.err("usage: keryx mcp auth <name>");
72259
+ return 1;
72260
+ }
72261
+ const config = load(deps);
72262
+ const server = config.servers.find((candidate) => candidate.name === name);
72263
+ if (server === undefined) {
72264
+ deps.err(config.servers.length === 0 ? `no server named "${name}" \u2014 no MCP servers are configured` : `no server named "${name}". Configured: ${config.servers.map((s) => s.name).join(", ")}`);
72265
+ return 1;
72266
+ }
72267
+ if (transportOf(server) !== "http") {
72268
+ deps.err(`"${name}" is a local (stdio) server; OAuth applies to remote ones.`);
72269
+ return 1;
72270
+ }
72271
+ if (requiresApproval(server, loadTrustStore(deps.configDir))) {
72272
+ deps.err(`"${name}" is a project server that has not been approved.`);
72273
+ deps.err(`Read what it connects to, then: keryx mcp trust ${name}`);
72274
+ return 1;
72275
+ }
72276
+ const env = deps.env ?? process.env;
72277
+ const targetProblem = remoteTargetProblem(server.raw, env);
72278
+ if (targetProblem !== undefined) {
72279
+ deps.err(`"${name}": ${targetProblem}`);
72280
+ return 1;
72281
+ }
72282
+ if (!usesOAuth(server.raw)) {
72283
+ deps.err(`"${name}" does not use OAuth \u2014 it authenticates with a header or bearer_token_env_var.`);
72284
+ deps.err("Nothing to do. Remove that credential first if you meant to switch to OAuth.");
72285
+ return 1;
72286
+ }
72287
+ const interactive = resolveInteractive(deps.interactive, process.stdin, process.stdout);
72288
+ if (!interactive) {
72289
+ deps.err(`"${name}" needs OAuth, and this process has no terminal to ask in.`);
72290
+ deps.err("Run `keryx mcp auth " + name + "` from an interactive shell.");
72291
+ return 1;
72292
+ }
72293
+ deps.log(`Authorising "${name}" at ${displayUrl(server.raw.url)}\u2026`);
72294
+ deps.log("A browser window will open. keryx never sees your password \u2014 only the token the server issues.");
72295
+ return await runOAuthFlow(server, deps);
72296
+ }
72297
+ async function runOAuthFlow(server, deps) {
72298
+ const configuredPort = server.oauth === false ? undefined : server.oauth?.callbackPort;
72299
+ const listener = startCallbackListener(configuredPort === undefined ? {} : { port: configuredPort });
72300
+ try {
72301
+ const provider = createOAuthProvider({
72302
+ serverName: server.name,
72303
+ serverUrl: server.url,
72304
+ ...deps.configDir === undefined ? {} : { configDir: deps.configDir },
72305
+ interactive: true,
72306
+ openBrowser: deps.openBrowser ?? ((url) => {
72307
+ openAuthorisationUrl(url, deps.log);
72308
+ }),
72309
+ redirectUrl: listener.redirectUrl,
72310
+ state: listener.state,
72311
+ ...server.oauth === false || server.oauth?.clientId === undefined ? {} : { clientId: server.oauth.clientId },
72312
+ ...server.oauth === false || server.oauth?.scopes === undefined ? {} : { scopes: server.oauth.scopes }
72313
+ });
72314
+ let opened = false;
72315
+ const legOne = await connectHttpMcpServer(server.url, {
72316
+ authProvider: {
72317
+ ...provider,
72318
+ redirectToAuthorization: async (url) => {
72319
+ opened = true;
72320
+ await provider.redirectToAuthorization(url);
72321
+ }
72322
+ },
72323
+ handshakeTimeoutMs: 30000
72324
+ }).then((connection) => ({ ok: true, connection }), (error) => ({ ok: false, error }));
72325
+ if (legOne.ok) {
72326
+ await legOne.connection.close();
72327
+ const existing = readCredential(server.name, server.url, deps.configDir).record;
72328
+ if (existing?.tokens === undefined) {
72329
+ deps.log(`"${server.name}" connects without authentication; there is nothing to authorise.`);
72330
+ } else {
72331
+ deps.log(`"${server.name}" is already authorised; nothing to do.`);
72332
+ }
72333
+ return 0;
72334
+ }
72335
+ if (!opened) {
72336
+ const message = legOne.error instanceof Error ? legOne.error.message : String(legOne.error);
72337
+ deps.err(`Could not start authorisation for "${server.name}": ${sanitiseForDisplay(message)}`);
72338
+ return 1;
72339
+ }
72340
+ const result = await listener.result;
72341
+ if (!result.ok) {
72342
+ deps.err(`Authorisation did not complete: ${result.reason}`);
72343
+ return 1;
72344
+ }
72345
+ const sdk = await loadAuthSdk();
72346
+ await sdk.auth(provider, { serverUrl: server.url, authorizationCode: result.code });
72347
+ const stored = readCredential(server.name, server.url, deps.configDir).record;
72348
+ if (stored?.tokens === undefined || stored.tokens.access_token === "") {
72349
+ deps.err("The exchange returned no token. Nothing was stored.");
72350
+ return 1;
72351
+ }
72352
+ deps.log(`Authorised "${server.name}" \u2014 ${describeCredential(stored, Date.now())}.`);
72353
+ deps.log(`Stored owner-only in ${credentialsFile(deps.configDir)}.`);
72354
+ return 0;
72355
+ } catch (error) {
72356
+ deps.err(`Authorisation failed: ${error instanceof Error ? error.message : String(error)}`);
72357
+ return 1;
72358
+ } finally {
72359
+ listener.close();
72360
+ }
72361
+ }
72362
+ async function loadAuthSdk() {
72363
+ const mod = await import("@modelcontextprotocol/sdk/client/auth.js");
72364
+ return mod;
72365
+ }
72366
+ function resolveInteractive(explicit, stdin, stdout) {
72367
+ return explicit ?? bothStreamsAreATerminal(stdin, stdout);
72368
+ }
72369
+ function bothStreamsAreATerminal(stdin, stdout) {
72370
+ return stdin.isTTY === true && stdout.isTTY === true;
72371
+ }
72372
+ function openAuthorisationUrl(url, log, platform = process.platform, env = process.env) {
72373
+ const plan = browserOpenPlan(url.toString(), platform, env);
72374
+ if (plan === undefined) {
72375
+ log("No graphical session detected, so nothing was opened.");
72376
+ log(`Open this URL yourself to continue:
72377
+
72378
+ ${url.toString()}
72379
+ `);
72380
+ return;
72381
+ }
72382
+ openVerificationUrl(url.toString());
72383
+ }
71764
72384
  function removeCommand(args, deps) {
71765
72385
  const own = splitAtSeparator(args).own;
71766
72386
  const unknown = own.filter((arg, index, all) => isUnknownFlag(arg, index, all));
@@ -71911,9 +72531,10 @@ async function doctorCommand(args, deps) {
71911
72531
  const env = deps.env ?? process.env;
71912
72532
  report = await runDoctor2(config, {
71913
72533
  only,
71914
- connect: deps.connect ?? ((server) => defaultConnect(server, env, dialling.signal, kills)),
72534
+ connect: deps.connect ?? ((server) => defaultConnect(server, env, dialling.signal, kills, deps.configDir)),
71915
72535
  heldForApproval: (server) => requiresApproval(server, approvals),
71916
- env
72536
+ env,
72537
+ configDir: deps.configDir
71917
72538
  });
71918
72539
  } finally {
71919
72540
  process.removeListener("SIGINT", onInt);
@@ -72020,12 +72641,12 @@ function positional3(args) {
72020
72641
 
72021
72642
  // src/commands/serve-mcp.ts
72022
72643
  init_args();
72023
- import path174 from "path";
72644
+ import path175 from "path";
72024
72645
 
72025
72646
  // src/mcp/discovery.ts
72026
72647
  init_fs();
72027
72648
  init_json();
72028
- import path168 from "path";
72649
+ import path169 from "path";
72029
72650
  var MODULE_MANIFEST_KEY = {
72030
72651
  gdgraph: "gdgraph",
72031
72652
  security: "security",
@@ -72087,7 +72708,7 @@ function buildDiscovery(manifest) {
72087
72708
  };
72088
72709
  }
72089
72710
  async function loadDiscovery(cwd) {
72090
- const manifestPath = path168.join(cwd, ".metaproject", "metaproject.json");
72711
+ const manifestPath = path169.join(cwd, ".metaproject", "metaproject.json");
72091
72712
  if (!await pathExists(manifestPath)) {
72092
72713
  return buildDiscovery({});
72093
72714
  }
@@ -72160,7 +72781,7 @@ init_store2();
72160
72781
  init_fs();
72161
72782
  import { createHash as createHash34, randomUUID as randomUUID17 } from "crypto";
72162
72783
  import { appendFile as appendFile7, mkdir as mkdir58, open as open2, readFile as readFile93, rename as rename7, rm as rm12, stat as stat16, writeFile as writeFile50 } from "fs/promises";
72163
- import path169 from "path";
72784
+ import path170 from "path";
72164
72785
 
72165
72786
  // src/sac/policy-experiment.ts
72166
72787
  import { createHash as createHash33, createHmac as createHmac2 } from "crypto";
@@ -72516,7 +73137,7 @@ function hasReportIntegrity(report) {
72516
73137
  return reportHashPattern.test(reportDigest) && sha2565(body) === reportDigest;
72517
73138
  }
72518
73139
  async function resolvePolicySelection(workspaceRoot, canonicalFallback) {
72519
- const configPath = path169.join(workspaceRoot, ...policyExperimentConfigPath);
73140
+ const configPath = path170.join(workspaceRoot, ...policyExperimentConfigPath);
72520
73141
  let configJson;
72521
73142
  try {
72522
73143
  const raw = await readFile93(configPath, "utf8").catch((error) => {
@@ -72618,7 +73239,7 @@ async function diagnosePolicyReadiness(workspaceRoot) {
72618
73239
  const integrityReady = steps.every((entry) => entry.step === "activation-flags" || entry.status === "pass");
72619
73240
  return Object.freeze({ configPresent, enabled, killSwitch, integrityReady, candidateWouldActivate: integrityReady && enabled && !killSwitch, steps: Object.freeze([...steps]) });
72620
73241
  };
72621
- const configPath = path169.join(workspaceRoot, ...policyExperimentConfigPath);
73242
+ const configPath = path170.join(workspaceRoot, ...policyExperimentConfigPath);
72622
73243
  let configJson;
72623
73244
  try {
72624
73245
  const raw = await readFile93(configPath, "utf8").catch((error) => {
@@ -72873,9 +73494,9 @@ class FwkReadService {
72873
73494
  const recordedAt = nowIso2(now);
72874
73495
  const id = `receipt-${randomUUID17().replace(/-/g, "").slice(0, 16)}`;
72875
73496
  const base = { schemaVersion: "1.0", id, workspaceId, actor, action, decision, recordedAt, cost: { tokens: 0, toolCalls: 1, elapsedMs: 0 }, contextAssembly: { traceRef: assembly.traceRef, configurationRevision: assembly.configurationRevision, selected: assembly.selected.map((id) => `./ids/${id}`), omittedOptional: assembly.omittedOptional.map((id) => `./ids/${id}`) }, policy: { ref: assembly.policyRef, revision: assembly.policyRevision }, ...action === "resource" ? { resourceRef: `./ids/${resourceId ?? "unknown"}` } : {} };
72876
- const ledger = path169.join(this.options.canonical.workspaceRoot, ".metaproject", "context-operations", "access-receipts.jsonl");
72877
- const checkpointPath = path169.join(path169.dirname(ledger), "access-receipts.checkpoint.json");
72878
- await mkdir58(path169.dirname(ledger), { recursive: true, mode: 448 });
73497
+ const ledger = path170.join(this.options.canonical.workspaceRoot, ".metaproject", "context-operations", "access-receipts.jsonl");
73498
+ const checkpointPath = path170.join(path170.dirname(ledger), "access-receipts.checkpoint.json");
73499
+ await mkdir58(path170.dirname(ledger), { recursive: true, mode: 448 });
72879
73500
  return withFileLock(`${ledger}.lock`, async () => {
72880
73501
  const state = await resolveLedgerState(ledger, checkpointPath, this.options.verifyReceiptLedger ?? verifyAccessReceiptLedger);
72881
73502
  const receipt = sealAccessReceipt(base, state.headHash);
@@ -72987,7 +73608,7 @@ init_workspace_service();
72987
73608
  init_sac();
72988
73609
  import { appendFile as appendFile8, mkdir as mkdir59, readFile as readFile94 } from "fs/promises";
72989
73610
  import { randomUUID as randomUUID18 } from "crypto";
72990
- import path170 from "path";
73611
+ import path171 from "path";
72991
73612
 
72992
73613
  class CollaborationServiceError extends Error {
72993
73614
  code;
@@ -73003,7 +73624,7 @@ class CollaborationService {
73003
73624
  this.input = input;
73004
73625
  }
73005
73626
  file(id) {
73006
- return path170.join(this.input.workspaceRoot, ".metaproject", "workspaces", id, "activity.jsonl");
73627
+ return path171.join(this.input.workspaceRoot, ".metaproject", "workspaces", id, "activity.jsonl");
73007
73628
  }
73008
73629
  async overview(input) {
73009
73630
  const manifest = await this.input.workspaces.show(input);
@@ -73040,7 +73661,7 @@ class CollaborationService {
73040
73661
  occurredAt: (this.input.now ?? (() => new Date))().toISOString(),
73041
73662
  ...suppliedHandoff ? { handoff: { ...suppliedHandoff, from: actor.subject } } : {}
73042
73663
  });
73043
- await mkdir59(path170.dirname(this.file(manifest.id)), { recursive: true, mode: 448 });
73664
+ await mkdir59(path171.dirname(this.file(manifest.id)), { recursive: true, mode: 448 });
73044
73665
  await appendFile8(this.file(manifest.id), `${JSON.stringify(activity)}
73045
73666
  `, { mode: 384 });
73046
73667
  return normalizeCollaborationResult(activity);
@@ -73095,9 +73716,9 @@ init_workspace_service();
73095
73716
  init_fs();
73096
73717
  init_machine_wrap_up();
73097
73718
  import { mkdir as mkdir60, readdir as readdir31, readFile as readFile95 } from "fs/promises";
73098
- import path171 from "path";
73719
+ import path172 from "path";
73099
73720
  function externalSlatesDir(cwd) {
73100
- return path171.join(cwd, ".keryx", "external-slates");
73721
+ return path172.join(cwd, ".keryx", "external-slates");
73101
73722
  }
73102
73723
  var EXTERNAL_SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
73103
73724
  function assertValidExternalSessionId(externalSessionId) {
@@ -73107,14 +73728,14 @@ function assertValidExternalSessionId(externalSessionId) {
73107
73728
  }
73108
73729
  function externalSlatePath(cwd, externalSessionId) {
73109
73730
  assertValidExternalSessionId(externalSessionId);
73110
- return path171.join(externalSlatesDir(cwd), `${externalSessionId}.json`);
73731
+ return path172.join(externalSlatesDir(cwd), `${externalSessionId}.json`);
73111
73732
  }
73112
73733
  function externalSlateLockPath(cwd, externalSessionId) {
73113
73734
  return `${externalSlatePath(cwd, externalSessionId)}.lock`;
73114
73735
  }
73115
73736
  function externalSlateEvidenceDir(cwd, externalSessionId) {
73116
73737
  assertValidExternalSessionId(externalSessionId);
73117
- return path171.join(externalSlatesDir(cwd), externalSessionId);
73738
+ return path172.join(externalSlatesDir(cwd), externalSessionId);
73118
73739
  }
73119
73740
  async function readExternalSlate(cwd, externalSessionId) {
73120
73741
  try {
@@ -73759,7 +74380,7 @@ function buildToolRegistry() {
73759
74380
  // src/mcp/resources.ts
73760
74381
  init_fs();
73761
74382
  init_contained_read();
73762
- import path172 from "path";
74383
+ import path173 from "path";
73763
74384
  import { lstat as lstat4, readdir as readdir32, realpath as realpath7, stat as stat17 } from "fs/promises";
73764
74385
  var URI_PREFIX = "metaproject://";
73765
74386
  function mimeForPath(filePath) {
@@ -73772,13 +74393,13 @@ function mimeForPath(filePath) {
73772
74393
  return "text/plain";
73773
74394
  }
73774
74395
  function dataRoot3(cwd) {
73775
- return path172.join(cwd, ".metaproject", "data");
74396
+ return path173.join(cwd, ".metaproject", "data");
73776
74397
  }
73777
74398
  function wikiRoot(cwd) {
73778
- return path172.join(cwd, ".metaproject", "wiki");
74399
+ return path173.join(cwd, ".metaproject", "wiki");
73779
74400
  }
73780
74401
  function memoryRoot2(cwd) {
73781
- return path172.join(cwd, ".metaproject", "memory");
74402
+ return path173.join(cwd, ".metaproject", "memory");
73782
74403
  }
73783
74404
  var MAX_RESOURCE_BYTES = 8 * 1024 * 1024;
73784
74405
  async function walkFiles(root, ownerRoot = root, ancestors = new Set) {
@@ -73804,7 +74425,7 @@ async function walkFiles(root, ownerRoot = root, ancestors = new Set) {
73804
74425
  return [];
73805
74426
  }
73806
74427
  for (const entry of entries) {
73807
- const full = path172.join(root, entry.name);
74428
+ const full = path173.join(root, entry.name);
73808
74429
  const info = await stat17(full).catch(() => null);
73809
74430
  if (!info || !isPathInside(ownerReal, await realpath7(full).catch(() => "")))
73810
74431
  continue;
@@ -73832,9 +74453,9 @@ async function listArtifacts(cwd) {
73832
74453
  if (!moduleEntry.isDirectory()) {
73833
74454
  continue;
73834
74455
  }
73835
- const artifactsDir = path172.join(base, moduleEntry.name, "artifacts");
74456
+ const artifactsDir = path173.join(base, moduleEntry.name, "artifacts");
73836
74457
  for (const file of await walkFiles(artifactsDir)) {
73837
- const rel = toPosix(path172.relative(artifactsDir, file));
74458
+ const rel = toPosix(path173.relative(artifactsDir, file));
73838
74459
  const relPath = `${moduleEntry.name}/${rel}`;
73839
74460
  listings.push({
73840
74461
  uri: `${URI_PREFIX}artifacts/${relPath}`,
@@ -73848,7 +74469,7 @@ async function listArtifacts(cwd) {
73848
74469
  async function listUnderRoot(cwd, cls, root) {
73849
74470
  const listings = [];
73850
74471
  for (const file of await walkFiles(root)) {
73851
- const rel = toPosix(path172.relative(root, file));
74472
+ const rel = toPosix(path173.relative(root, file));
73852
74473
  listings.push({
73853
74474
  uri: `${URI_PREFIX}${cls}/${rel}`,
73854
74475
  name: rel,
@@ -73898,12 +74519,12 @@ function resolveConfined(cwd, cls, relPath) {
73898
74519
  if (moduleName.includes("..") || moduleName.length === 0) {
73899
74520
  return null;
73900
74521
  }
73901
- const root = path172.join(dataRoot3(cwd), moduleName, "artifacts");
73902
- const absolute = path172.resolve(root, rest);
74522
+ const root = path173.join(dataRoot3(cwd), moduleName, "artifacts");
74523
+ const absolute = path173.resolve(root, rest);
73903
74524
  return isPathInside(root, absolute) ? { root, absolute } : null;
73904
74525
  }
73905
74526
  const root = cls === "wiki" ? wikiRoot(cwd) : memoryRoot2(cwd);
73906
- const absolute = path172.resolve(root, relPath);
74527
+ const absolute = path173.resolve(root, relPath);
73907
74528
  return isPathInside(root, absolute) ? { root, absolute } : null;
73908
74529
  }
73909
74530
  async function readResource(cwd, roots, uri) {
@@ -74114,7 +74735,7 @@ async function serveMcpCommand(args = [], cwd = process.cwd()) {
74114
74735
  return;
74115
74736
  }
74116
74737
  const http = args.includes("--http");
74117
- const projectRoot = path174.resolve(resolveServeRoot(optionValue(args, "--cwd"), cwd, process.env));
74738
+ const projectRoot = path175.resolve(resolveServeRoot(optionValue(args, "--cwd"), cwd, process.env));
74118
74739
  try {
74119
74740
  await serveMcp({ cwd: projectRoot, http });
74120
74741
  } catch (error) {
@@ -74190,7 +74811,9 @@ function printMcpHelp() {
74190
74811
  "keryx mcp add <name> --transport http <url> # a remote one",
74191
74812
  "keryx mcp remove|enable|disable <name>",
74192
74813
  "keryx mcp trust|untrust <name> # approve a project-scoped server",
74193
- "keryx mcp doctor [name] [--json] # dial it and say what is wrong"
74814
+ "keryx mcp doctor [name] [--json] # dial it and say what is wrong",
74815
+ "keryx mcp auth <name> # authorise a remote server in your browser",
74816
+ "keryx mcp logout <name> # forget its stored credential"
74194
74817
  ]);
74195
74818
  heading("Publishing keryx itself (the retired `keryx mcp` spellings)");
74196
74819
  helpUsage([
@@ -74216,14 +74839,14 @@ function printMcpHelp() {
74216
74839
  // src/commands/status.ts
74217
74840
  init_fs();
74218
74841
  init_json();
74219
- import path175 from "path";
74842
+ import path176 from "path";
74220
74843
  async function statusCommand(args = []) {
74221
74844
  if (args.includes("--help") || args.includes("-h")) {
74222
74845
  printHelp16();
74223
74846
  return;
74224
74847
  }
74225
- const root = path175.join(process.cwd(), ".metaproject");
74226
- const manifestPath = path175.join(root, "metaproject.json");
74848
+ const root = path176.join(process.cwd(), ".metaproject");
74849
+ const manifestPath = path176.join(root, "metaproject.json");
74227
74850
  if (!await pathExists(root)) {
74228
74851
  console.log("Metaproject: not initialized");
74229
74852
  console.log("Run: keryx init");
@@ -74263,7 +74886,7 @@ Reports the workspace root and one enabled/disabled line per module. Use
74263
74886
 
74264
74887
  // src/commands/harness.ts
74265
74888
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "fs";
74266
- import path178 from "path";
74889
+ import path179 from "path";
74267
74890
  import { randomUUID as randomUUID20 } from "crypto";
74268
74891
 
74269
74892
  // src/security/harness-scan.ts
@@ -75336,7 +75959,7 @@ ${command.argv.join(" ")}`);
75336
75959
  }
75337
75960
 
75338
75961
  // src/harness/process/sandbox/profile.ts
75339
- import path176 from "path";
75962
+ import path177 from "path";
75340
75963
  var DEFAULT_SECRET_SUBPATHS = [
75341
75964
  ".ssh",
75342
75965
  ".gnupg",
@@ -75361,7 +75984,7 @@ function defaultReadDenyList(home) {
75361
75984
  if (!home) {
75362
75985
  return [];
75363
75986
  }
75364
- return DEFAULT_SECRET_SUBPATHS.map((sub) => path176.join(home, sub));
75987
+ return DEFAULT_SECRET_SUBPATHS.map((sub) => path177.join(home, sub));
75365
75988
  }
75366
75989
  function defaultSandboxProfile(cwd, tmpDir, home) {
75367
75990
  return {
@@ -75381,10 +76004,10 @@ function dedupe3(values) {
75381
76004
  init_config_dir();
75382
76005
  init_shell_config();
75383
76006
  import { existsSync as existsSync26 } from "fs";
75384
- import path177 from "path";
76007
+ import path178 from "path";
75385
76008
  var SECRET_KEY_PATTERN2 = /api[_-]?key|secret|token|password|credential/i;
75386
76009
  function sandboxConfigPath(dir) {
75387
- return path177.join(path177.dirname(shellConfigPath(dir)), "sandbox.json");
76010
+ return path178.join(path178.dirname(shellConfigPath(dir)), "sandbox.json");
75388
76011
  }
75389
76012
  function isShellDefault(v) {
75390
76013
  return v === "off" || v === "workspace" || v === "strict" || v === "1";
@@ -76503,7 +77126,7 @@ async function harnessExec(args, deps) {
76503
77126
  env: commandEnv,
76504
77127
  cwd
76505
77128
  };
76506
- const worktreeRoot = path178.parse(path178.resolve(cwd, commandPath)).root || cwd;
77129
+ const worktreeRoot = path179.parse(path179.resolve(cwd, commandPath)).root || cwd;
76507
77130
  const budget = {
76508
77131
  reservationId: idSeq(),
76509
77132
  maxRuntimeMs: maxRuntimeMs ?? EXEC_DEFAULT_RUNTIME_MS
@@ -78449,7 +79072,7 @@ function workspaceReadTool(cwd) {
78449
79072
  // src/harness/tool/builtin/workspace-lifecycle-tool.ts
78450
79073
  import { randomUUID as randomUUID22 } from "crypto";
78451
79074
  import { writeFile as writeFile52 } from "fs/promises";
78452
- import path179 from "path";
79075
+ import path180 from "path";
78453
79076
  init_slate();
78454
79077
  init_store3();
78455
79078
  init_guard2();
@@ -78595,7 +79218,7 @@ function workspaceProposeTool(cwd, getSessionDir) {
78595
79218
  return { output: `workspace_propose: unrecognized 'kind' \u2014 expected one of: ${PROPOSAL_KINDS.join(", ")}`, isError: true };
78596
79219
  }
78597
79220
  const explicitSessionId = typeof input.sessionId === "string" && input.sessionId.length > 0 ? input.sessionId : undefined;
78598
- const sessionRef = explicitSessionId ?? path179.basename(getSessionDir() ?? "");
79221
+ const sessionRef = explicitSessionId ?? path180.basename(getSessionDir() ?? "");
78599
79222
  if (sessionRef.length === 0) {
78600
79223
  return { output: "workspace_propose: no 'sessionId' given and no active session in this run", isError: true };
78601
79224
  }
@@ -78767,7 +79390,7 @@ init_shell_config();
78767
79390
  init_command_risk();
78768
79391
  init_shell_syntax();
78769
79392
  import { existsSync as existsSync30 } from "fs";
78770
- import path180 from "path";
79393
+ import path181 from "path";
78771
79394
  import { createHash as createHash41 } from "crypto";
78772
79395
  var PREFIX_BANNED = new Set([
78773
79396
  "sh",
@@ -78972,7 +79595,7 @@ function emptyShellPermissions() {
78972
79595
  return { allow: [] };
78973
79596
  }
78974
79597
  function shellPermissionsPath(dir) {
78975
- return path180.join(path180.dirname(shellConfigPath(dir)), "permissions.json");
79598
+ return path181.join(path181.dirname(shellConfigPath(dir)), "permissions.json");
78976
79599
  }
78977
79600
  function loadShellPermissionsWithAudit(dir) {
78978
79601
  try {
@@ -79010,7 +79633,7 @@ function loadShellPermissions(dir) {
79010
79633
  function saveShellPermissions(perms, dir, options = {}) {
79011
79634
  try {
79012
79635
  const file = shellPermissionsPath(dir);
79013
- ensureKeryxConfigDir(path180.dirname(file));
79636
+ ensureKeryxConfigDir(path181.dirname(file));
79014
79637
  const cleaned = Array.from(new Set(perms.allow.map((p) => p.trim()).filter((p) => p.length > 0)));
79015
79638
  const body = {
79016
79639
  allow: options.skipValidation === true ? cleaned : cleaned.filter((p) => validateShellPattern(p).ok)
@@ -79449,12 +80072,12 @@ function remoteRequest(providerId, endpoint, query, key) {
79449
80072
  // src/lib/search-config.ts
79450
80073
  init_config_dir();
79451
80074
  import { existsSync as existsSync31 } from "fs";
79452
- import path181 from "path";
80075
+ import path182 from "path";
79453
80076
  function searchConfigPath(dir) {
79454
- return path181.join(keryxConfigDir(dir), "search-providers.json");
80077
+ return path182.join(keryxConfigDir(dir), "search-providers.json");
79455
80078
  }
79456
80079
  function searchCredentialPath(dir) {
79457
- return path181.join(keryxConfigDir(dir), "search-credentials.json");
80080
+ return path182.join(keryxConfigDir(dir), "search-credentials.json");
79458
80081
  }
79459
80082
  function readJson5(file) {
79460
80083
  try {
@@ -79583,7 +80206,7 @@ function createDefaultSearchProviderController(configDir) {
79583
80206
  import { createHash as createHash42, randomUUID as randomUUID24 } from "crypto";
79584
80207
  import { mkdtemp, rm as rm13 } from "fs/promises";
79585
80208
  import { tmpdir as tmpdir4 } from "os";
79586
- import path182 from "path";
80209
+ import path183 from "path";
79587
80210
  init_metaproject_adapter();
79588
80211
  init_ledger();
79589
80212
  init_orchestrate();
@@ -79846,8 +80469,8 @@ function createSpawnSubagentTool(deps) {
79846
80469
  let dispatchId = "";
79847
80470
  let closing = false;
79848
80471
  try {
79849
- ephemeralDir = await mkdtemp(path182.join(tmpdir4(), "keryx-subagent-slate-"));
79850
- dispatchId = path182.basename(ephemeralDir);
80472
+ ephemeralDir = await mkdtemp(path183.join(tmpdir4(), "keryx-subagent-slate-"));
80473
+ dispatchId = path183.basename(ephemeralDir);
79851
80474
  openedChildSlate = await openSlate({
79852
80475
  dir: ephemeralDir,
79853
80476
  cwd,
@@ -80090,7 +80713,7 @@ import { randomUUID as randomUUID25 } from "crypto";
80090
80713
  import { execFile as execFile4 } from "child_process";
80091
80714
  import { mkdir as mkdir61 } from "fs/promises";
80092
80715
  import { tmpdir as tmpdir6 } from "os";
80093
- import path184 from "path";
80716
+ import path185 from "path";
80094
80717
  import { promisify as promisify4 } from "util";
80095
80718
 
80096
80719
  // src/harness/child/git-worktree-port.ts
@@ -80342,7 +80965,7 @@ function readRuntimeBlock(dispatch) {
80342
80965
  // src/harness/external/runtime.ts
80343
80966
  import { mkdtemp as mkdtemp2, rm as rm14, writeFile as writeFile53 } from "fs/promises";
80344
80967
  import { tmpdir as tmpdir5 } from "os";
80345
- import path183 from "path";
80968
+ import path184 from "path";
80346
80969
 
80347
80970
  // src/harness/external/prompt.ts
80348
80971
  var EXTERNAL_RUNTIME_DIRECTIVE = [
@@ -81385,8 +82008,8 @@ async function prepareResultSchema() {
81385
82008
  try {
81386
82009
  const schema = await loadSchema("subagent-result");
81387
82010
  const schemaText = JSON.stringify(schema, null, 2);
81388
- schemaDir = await mkdtemp2(path183.join(tmpdir5(), "keryx-external-result-schema-"));
81389
- const schemaPath = path183.join(schemaDir, "subagent-result.schema.json");
82011
+ schemaDir = await mkdtemp2(path184.join(tmpdir5(), "keryx-external-result-schema-"));
82012
+ const schemaPath = path184.join(schemaDir, "subagent-result.schema.json");
81390
82013
  await writeFile53(schemaPath, schemaText, "utf8");
81391
82014
  return { ok: true, schema, schemaText, schemaPath, schemaDir };
81392
82015
  } catch (error) {
@@ -81643,7 +82266,7 @@ async function createRunExternal(options) {
81643
82266
  }
81644
82267
  const config = gate.config;
81645
82268
  const idSeq = options.idSeq ?? (() => randomUUID25());
81646
- const worktreesDir = options.worktreesDir ?? path184.join(tmpdir6(), "keryx-external-worktrees");
82269
+ const worktreesDir = options.worktreesDir ?? path185.join(tmpdir6(), "keryx-external-worktrees");
81647
82270
  const spawn = options.spawn ?? createBunSpawnPort();
81648
82271
  const worktree = options.worktree ?? ensuringParentDir(createGitWorktreePort({ repoRoot: options.cwd, worktreesDir }), worktreesDir);
81649
82272
  const readWorkingDiff = options.readWorkingDiff ?? (() => defaultWorkingDiff(options.cwd));
@@ -81953,7 +82576,7 @@ class LiveMarkdownBlock {
81953
82576
  // src/tui/theme.ts
81954
82577
  init_config_dir();
81955
82578
  import { existsSync as existsSync32 } from "fs";
81956
- import path185 from "path";
82579
+ import path186 from "path";
81957
82580
  var THEME_NAMES = ["groknight", "grokday", "tokyonight", "keryx"];
81958
82581
  var THEME_IDS = ["auto", ...THEME_NAMES];
81959
82582
  var DEFAULT_THEME_ID = "groknight";
@@ -82083,7 +82706,7 @@ function onThemeChange(listener) {
82083
82706
  };
82084
82707
  }
82085
82708
  function tuiConfigPath(dir) {
82086
- return path185.join(keryxConfigDir(dir), "tui.json");
82709
+ return path186.join(keryxConfigDir(dir), "tui.json");
82087
82710
  }
82088
82711
  function loadPersistedThemeId(dir) {
82089
82712
  try {
@@ -82140,7 +82763,7 @@ init_workspace_service();
82140
82763
  init_service8();
82141
82764
  init_store2();
82142
82765
  init_fs();
82143
- import path186 from "path";
82766
+ import path187 from "path";
82144
82767
  import { readFile as readFile97 } from "fs/promises";
82145
82768
  var POSITIVE_INTEGER = /^[1-9][0-9]*$/;
82146
82769
  var DEFAULT_AUTO_GOAL_ROUNDS = 8;
@@ -82216,7 +82839,7 @@ function getFlowService() {
82216
82839
  async function autoProvisionFlow(cwd, goalText) {
82217
82840
  const service = getFlowService();
82218
82841
  const result = await service.init({ cwd, title: goalText });
82219
- const acFile = path186.join(cwd, result.dir, "acceptance-criteria.md");
82842
+ const acFile = path187.join(cwd, result.dir, "acceptance-criteria.md");
82220
82843
  await writeFileAtomic(acFile, [
82221
82844
  "# Acceptance Criteria",
82222
82845
  "",
@@ -83767,7 +84390,7 @@ init_proposal_lifecycle();
83767
84390
  init_workspace_service();
83768
84391
  import { randomUUID as randomUUID27 } from "crypto";
83769
84392
  import { readdir as readdir34, stat as stat18 } from "fs/promises";
83770
- import path187 from "path";
84393
+ import path188 from "path";
83771
84394
 
83772
84395
  // src/sac/lifecycle-flag.ts
83773
84396
  init_store();
@@ -83926,7 +84549,7 @@ async function readExternalUnboundCandidates(cwd) {
83926
84549
  }
83927
84550
  return groups;
83928
84551
  }, []));
83929
- const metaPath = path187.join(extDir, `${id}.json`);
84552
+ const metaPath = path188.join(extDir, `${id}.json`);
83930
84553
  if (await pathExists(unboundDismissedReceiptPath(metaPath)))
83931
84554
  continue;
83932
84555
  candidates.push({
@@ -83940,7 +84563,7 @@ async function readExternalUnboundCandidates(cwd) {
83940
84563
  return candidates;
83941
84564
  }
83942
84565
  async function readNewestUnboundCandidateForExternal(cwd, externalSessionId) {
83943
- const evidenceDir = path187.join(externalSlatesDir(cwd), externalSessionId);
84566
+ const evidenceDir = path188.join(externalSlatesDir(cwd), externalSessionId);
83944
84567
  let entries;
83945
84568
  try {
83946
84569
  entries = (await readdir34(evidenceDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
@@ -83949,7 +84572,7 @@ async function readNewestUnboundCandidateForExternal(cwd, externalSessionId) {
83949
84572
  }
83950
84573
  entries.sort();
83951
84574
  for (let i = entries.length - 1;i >= 0; i--) {
83952
- const evidencePath = path187.join(evidenceDir, entries[i]);
84575
+ const evidencePath = path188.join(evidenceDir, entries[i]);
83953
84576
  if (await pathExists(unboundDismissedReceiptPath(evidencePath)))
83954
84577
  continue;
83955
84578
  const result = readConfigFile(evidencePath);
@@ -83992,7 +84615,7 @@ var SESSION_ATTRIBUTION_SLACK_MS = 5 * 60000;
83992
84615
  var MTIME_CLOCK_SKEW_TOLERANCE_MS = 5000;
83993
84616
  async function collectReceiptTargets(cwd, owner) {
83994
84617
  const targets = new Set;
83995
- const workspacesDir = path187.join(cwd, ".metaproject", "workspaces");
84618
+ const workspacesDir = path188.join(cwd, ".metaproject", "workspaces");
83996
84619
  let workspaceIds;
83997
84620
  try {
83998
84621
  workspaceIds = (await readdir34(workspacesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
@@ -84000,7 +84623,7 @@ async function collectReceiptTargets(cwd, owner) {
84000
84623
  return targets;
84001
84624
  }
84002
84625
  for (const workspaceId of workspaceIds) {
84003
- const receiptsDir = path187.join(workspacesDir, workspaceId, `${owner}-write-receipts`);
84626
+ const receiptsDir = path188.join(workspacesDir, workspaceId, `${owner}-write-receipts`);
84004
84627
  let files;
84005
84628
  try {
84006
84629
  files = (await readdir34(receiptsDir)).filter((name) => name.endsWith(".json"));
@@ -84008,7 +84631,7 @@ async function collectReceiptTargets(cwd, owner) {
84008
84631
  continue;
84009
84632
  }
84010
84633
  for (const file of files) {
84011
- const result = readConfigFile(path187.join(receiptsDir, file));
84634
+ const result = readConfigFile(path188.join(receiptsDir, file));
84012
84635
  if (!result.ok)
84013
84636
  continue;
84014
84637
  try {
@@ -84051,7 +84674,7 @@ async function findSkillFiles(dir) {
84051
84674
  }
84052
84675
  const found = [];
84053
84676
  for (const entry of entries) {
84054
- const full = path187.join(dir, entry.name);
84677
+ const full = path188.join(dir, entry.name);
84055
84678
  if (entry.isDirectory()) {
84056
84679
  found.push(...await findSkillFiles(full));
84057
84680
  } else if (entry.isFile() && entry.name === "SKILL.md") {
@@ -84110,10 +84733,10 @@ async function detectUnreviewedSacPathChanges(cwd, sessions) {
84110
84733
  changedAt: attribution.changedAt
84111
84734
  });
84112
84735
  }
84113
- const sacSkillsDir = path187.join(cwd, ".metaproject", "project-skills", "sac");
84736
+ const sacSkillsDir = path188.join(cwd, ".metaproject", "project-skills", "sac");
84114
84737
  const skillFiles = await findSkillFiles(sacSkillsDir);
84115
84738
  for (const absolutePath of skillFiles) {
84116
- const targetRef = path187.relative(path187.join(cwd, ".metaproject"), absolutePath).split(path187.sep).join("/");
84739
+ const targetRef = path188.relative(path188.join(cwd, ".metaproject"), absolutePath).split(path188.sep).join("/");
84117
84740
  if (skillReceipts.has(targetRef))
84118
84741
  continue;
84119
84742
  const attribution = await attributeToSession(absolutePath, scoped);
@@ -84131,12 +84754,12 @@ async function detectUnreviewedSacPathChanges(cwd, sessions) {
84131
84754
  return items;
84132
84755
  }
84133
84756
  async function isSlateEngaged(dir) {
84134
- if (await pathExists(path187.join(dir, "slate.json")))
84757
+ if (await pathExists(path188.join(dir, "slate.json")))
84135
84758
  return true;
84136
- if (await pathExists(path187.join(dir, "terminal-state.json")))
84759
+ if (await pathExists(path188.join(dir, "terminal-state.json")))
84137
84760
  return true;
84138
84761
  try {
84139
- const entries = await readdir34(path187.join(dir, "slate-archive"));
84762
+ const entries = await readdir34(path188.join(dir, "slate-archive"));
84140
84763
  return entries.length > 0;
84141
84764
  } catch {
84142
84765
  return false;
@@ -84150,7 +84773,7 @@ async function safeReadSlate(dir) {
84150
84773
  }
84151
84774
  }
84152
84775
  async function readTerminalState(dir) {
84153
- const result = readConfigFile(path187.join(dir, "terminal-state.json"));
84776
+ const result = readConfigFile(path188.join(dir, "terminal-state.json"));
84154
84777
  if (!result.ok) {
84155
84778
  return;
84156
84779
  }
@@ -84174,7 +84797,7 @@ async function resolveUnboundCandidateTarget(cwd, target) {
84174
84797
  if (session === undefined) {
84175
84798
  throw new Error(`No session or evidence path matches "${target}"`);
84176
84799
  }
84177
- const archiveDir = path187.join(sessionDir(session.projectPath, session.id), "slate-archive");
84800
+ const archiveDir = path188.join(sessionDir(session.projectPath, session.id), "slate-archive");
84178
84801
  let entries;
84179
84802
  try {
84180
84803
  entries = (await readdir34(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
@@ -84184,7 +84807,7 @@ async function resolveUnboundCandidateTarget(cwd, target) {
84184
84807
  if (entries.length === 0)
84185
84808
  throw new Error(`No unbound-candidate artifacts for session ${session.id}`);
84186
84809
  entries.sort();
84187
- return path187.join(archiveDir, entries[entries.length - 1]);
84810
+ return path188.join(archiveDir, entries[entries.length - 1]);
84188
84811
  }
84189
84812
  async function dismissUnboundByTarget(cwd, target, reason) {
84190
84813
  const evidencePath = await resolveUnboundCandidateTarget(cwd, target);
@@ -84210,7 +84833,7 @@ async function dismissUnboundCandidate(candidatePath, reason) {
84210
84833
  return { removed: candidatePath, receipt: receiptPath };
84211
84834
  }
84212
84835
  async function readNewestUnboundCandidate(dir) {
84213
- const archiveDir = path187.join(dir, "slate-archive");
84836
+ const archiveDir = path188.join(dir, "slate-archive");
84214
84837
  let entries;
84215
84838
  try {
84216
84839
  entries = (await readdir34(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
@@ -84219,7 +84842,7 @@ async function readNewestUnboundCandidate(dir) {
84219
84842
  }
84220
84843
  entries.sort();
84221
84844
  for (let i = entries.length - 1;i >= 0; i--) {
84222
- const evidencePath = path187.join(archiveDir, entries[i]);
84845
+ const evidencePath = path188.join(archiveDir, entries[i]);
84223
84846
  if (await pathExists(unboundDismissedReceiptPath(evidencePath)))
84224
84847
  continue;
84225
84848
  const result = readConfigFile(evidencePath);
@@ -84249,7 +84872,7 @@ function isFailureOutcome(g) {
84249
84872
  return g.outcome === "error" || g.outcome === "no_credential" || g.outcome === "conflict";
84250
84873
  }
84251
84874
  async function readNewestWrapUpOutcome(dir) {
84252
- const archiveDir = path187.join(dir, "slate-archive");
84875
+ const archiveDir = path188.join(dir, "slate-archive");
84253
84876
  let entries;
84254
84877
  try {
84255
84878
  entries = (await readdir34(archiveDir)).filter((name) => name.endsWith("-wrap-up-outcome.json"));
@@ -84258,7 +84881,7 @@ async function readNewestWrapUpOutcome(dir) {
84258
84881
  }
84259
84882
  entries.sort();
84260
84883
  for (let i = entries.length - 1;i >= 0; i--) {
84261
- const evidencePath = path187.join(archiveDir, entries[i]);
84884
+ const evidencePath = path188.join(archiveDir, entries[i]);
84262
84885
  const result = readConfigFile(evidencePath);
84263
84886
  if (!result.ok) {
84264
84887
  continue;
@@ -87739,41 +88362,6 @@ init_patch_risk();
87739
88362
  init_catalog();
87740
88363
  init_grants();
87741
88364
  init_login();
87742
-
87743
- // src/lib/oauth/open-url.ts
87744
- import { spawn as spawn5 } from "child_process";
87745
- function browserOpenPlan(url, platform = process.platform, env = process.env) {
87746
- if (url.length === 0) {
87747
- return;
87748
- }
87749
- if (platform === "darwin") {
87750
- return { cmd: "open", args: [url] };
87751
- }
87752
- if (platform === "win32") {
87753
- return { cmd: "cmd", args: ["/c", "start", "", url] };
87754
- }
87755
- if (typeof env.DISPLAY === "string" && env.DISPLAY.length > 0) {
87756
- return { cmd: "xdg-open", args: [url] };
87757
- }
87758
- if (typeof env.WAYLAND_DISPLAY === "string" && env.WAYLAND_DISPLAY.length > 0) {
87759
- return { cmd: "xdg-open", args: [url] };
87760
- }
87761
- return;
87762
- }
87763
- function openVerificationUrl(url, deps = {}) {
87764
- const plan = browserOpenPlan(url, deps.platform ?? process.platform, deps.env ?? process.env);
87765
- if (plan === undefined) {
87766
- return;
87767
- }
87768
- const spawnFn = deps.spawn ?? spawn5;
87769
- try {
87770
- const child = spawnFn(plan.cmd, plan.args, { stdio: "ignore", detached: true });
87771
- child.on("error", () => {});
87772
- child.unref();
87773
- } catch {}
87774
- }
87775
-
87776
- // src/tui/tui-shell.ts
87777
88365
  init_shell_config();
87778
88366
  init_provider_config();
87779
88367
 
@@ -87809,10 +88397,10 @@ function describeElicitationPrompt(tool, inputJson) {
87809
88397
  // src/lib/permission-mode-config.ts
87810
88398
  init_permission_mode();
87811
88399
  init_config_dir();
87812
- import path188 from "path";
88400
+ import path189 from "path";
87813
88401
  var EMPTY3 = { schemaVersion: 1, projects: {} };
87814
88402
  function permissionModeConfigPath(dir) {
87815
- return path188.join(keryxConfigDir(dir), "permission-mode.json");
88403
+ return path189.join(keryxConfigDir(dir), "permission-mode.json");
87816
88404
  }
87817
88405
  function withRegistryLock2(dir, fn) {
87818
88406
  return withFileLock2(`${permissionModeConfigPath(dir)}.lock`, fn, {
@@ -88330,7 +88918,7 @@ function showComposerChoice(otui, r, dock, request) {
88330
88918
  // src/lib/version-check.ts
88331
88919
  init_config_dir();
88332
88920
  init_fs();
88333
- import path189 from "path";
88921
+ import path190 from "path";
88334
88922
  var REGISTRY_URL = "https://registry.npmjs.org/@mrciphersmith%2Fkeryx/latest";
88335
88923
  var FIXED_INSTALL_COMMAND = "npm install -g @mrciphersmith/keryx@latest";
88336
88924
  var RESPONSE_BODY_LIMIT_BYTES = 64 * 1024;
@@ -88532,7 +89120,7 @@ async function checkVersion(options) {
88532
89120
  const now = options.now ?? Date.now;
88533
89121
  const timestamp = now();
88534
89122
  const configDir = ensureKeryxConfigDir(options.cacheDir);
88535
- const cacheFile = path189.join(configDir, "version-check.json");
89123
+ const cacheFile = path190.join(configDir, "version-check.json");
88536
89124
  const cache = parseCache(cacheFile);
88537
89125
  if (cache?.latestVersion !== undefined && cache.successAt !== undefined && timestamp - cache.successAt >= 0 && timestamp - cache.successAt < SUCCESS_CACHE_TTL_MS) {
88538
89126
  return resultFor(options.currentVersion, current, cache.latestVersion, "cache");
@@ -96960,6 +97548,9 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
96960
97548
  });
96961
97549
  }
96962
97550
  } finally {
97551
+ try {
97552
+ await readlineMcp?.close();
97553
+ } catch {}
96963
97554
  destroy();
96964
97555
  rl.close();
96965
97556
  }
@@ -97105,7 +97696,7 @@ Shell:
97105
97696
  init_fs();
97106
97697
  import { readFile as readFile98 } from "fs/promises";
97107
97698
  import { stdin } from "process";
97108
- import path190 from "path";
97699
+ import path191 from "path";
97109
97700
  var MODULES = [
97110
97701
  { name: "gdgraph", flag: "--no-gdgraph", desc: "code graph, symbols, affected context", defaultEnabled: true },
97111
97702
  { name: "gdctx", flag: "--no-gdctx", desc: "token-aware command/read output", defaultEnabled: true },
@@ -97151,8 +97742,8 @@ async function modulesCommand(args = []) {
97151
97742
  return;
97152
97743
  }
97153
97744
  const wantsJson = args.includes("--json") && (sub === undefined || sub === "status" || sub === "list" || sub === "--json");
97154
- const metaprojectRoot = path190.join(process.cwd(), ".metaproject");
97155
- const manifestPath = path190.join(metaprojectRoot, "metaproject.json");
97745
+ const metaprojectRoot = path191.join(process.cwd(), ".metaproject");
97746
+ const manifestPath = path191.join(metaprojectRoot, "metaproject.json");
97156
97747
  if (!await pathExists(manifestPath)) {
97157
97748
  if (wantsJson) {
97158
97749
  console.log(JSON.stringify({ schemaVersion: 1, error: "not-initialized", modules: [] }, null, 2));
@@ -97271,7 +97862,7 @@ init_serve_config();
97271
97862
 
97272
97863
  // src/lib/serve-credential.ts
97273
97864
  init_config_dir();
97274
- import { createHash as createHash43, randomBytes as randomBytes4, randomUUID as randomUUID30 } from "crypto";
97865
+ import { createHash as createHash43, randomBytes as randomBytes5, randomUUID as randomUUID30 } from "crypto";
97275
97866
  import {
97276
97867
  chmodSync as chmodSync4,
97277
97868
  closeSync as closeSync3,
@@ -97283,9 +97874,9 @@ import {
97283
97874
  unlinkSync as unlinkSync3,
97284
97875
  writeFileSync as writeFileSync9
97285
97876
  } from "fs";
97286
- import path191 from "path";
97877
+ import path192 from "path";
97287
97878
  function serveCredentialPath(dir) {
97288
- return path191.join(keryxConfigDir(dir), "serve-credentials.json");
97879
+ return path192.join(keryxConfigDir(dir), "serve-credentials.json");
97289
97880
  }
97290
97881
  function constantTimeEqual(a, b) {
97291
97882
  const width = Math.max(a.length, b.length);
@@ -97391,8 +97982,8 @@ function withServeCredentialLock(dir, fn, onWaiting) {
97391
97982
  });
97392
97983
  }
97393
97984
  function mintRecord(now) {
97394
- const token = randomBytes4(32).toString("base64url");
97395
- const salt = randomBytes4(32).toString("hex");
97985
+ const token = randomBytes5(32).toString("base64url");
97986
+ const salt = randomBytes5(32).toString("hex");
97396
97987
  return {
97397
97988
  token,
97398
97989
  record: { id: randomUUID30(), algorithm: "sha256", salt, hash: hashToken(salt, token), createdAt: now }
@@ -97519,22 +98110,22 @@ class AuthFailureThrottle {
97519
98110
  init_config_dir();
97520
98111
  import { createHash as createHash44 } from "crypto";
97521
98112
  import { existsSync as existsSync34, readdirSync as readdirSync3, rmSync as rmSync2 } from "fs";
97522
- import path192 from "path";
98113
+ import path193 from "path";
97523
98114
  var MAX_TURN_EVENTS = 1e4;
97524
98115
  function turnsRoot(dir) {
97525
- return path192.join(keryxConfigDir(dir), "turns");
98116
+ return path193.join(keryxConfigDir(dir), "turns");
97526
98117
  }
97527
98118
  function turnDir(turnId, dir) {
97528
- return path192.join(turnsRoot(dir), turnId);
98119
+ return path193.join(turnsRoot(dir), turnId);
97529
98120
  }
97530
98121
  function keyPath(project, idempotencyKey, dir) {
97531
98122
  const projectBytes = Buffer.byteLength(project, "utf8");
97532
98123
  const digest = createHash44("sha256").update(`${projectBytes}:${project}\x00${idempotencyKey}`, "utf8").digest("hex");
97533
- return path192.join(turnsRoot(dir), "keys", `${digest}.json`);
98124
+ return path193.join(turnsRoot(dir), "keys", `${digest}.json`);
97534
98125
  }
97535
98126
  function legacyKeyPath(idempotencyKey, dir) {
97536
98127
  const digest = createHash44("sha256").update(idempotencyKey, "utf8").digest("hex");
97537
- return path192.join(turnsRoot(dir), "keys", `${digest}.json`);
98128
+ return path193.join(turnsRoot(dir), "keys", `${digest}.json`);
97538
98129
  }
97539
98130
  function adoptLegacyClaim(project, idempotencyKey, dir) {
97540
98131
  const legacy = legacyKeyPath(idempotencyKey, dir);
@@ -97598,7 +98189,7 @@ function ensureTurnDir(turnId, dir) {
97598
98189
  }
97599
98190
  function createTurnRecord(record, dir) {
97600
98191
  ensureTurnDir(record.turnId, dir);
97601
- writeOwnerOnlyFile(path192.join(turnDir(record.turnId, dir), "turn.json"), `${JSON.stringify(record, null, 2)}
98192
+ writeOwnerOnlyFile(path193.join(turnDir(record.turnId, dir), "turn.json"), `${JSON.stringify(record, null, 2)}
97602
98193
  `);
97603
98194
  }
97604
98195
  function appendTurnEvent(event, dir, opts) {
@@ -97607,12 +98198,12 @@ function appendTurnEvent(event, dir, opts) {
97607
98198
  }
97608
98199
  const line = JSON.stringify(event);
97609
98200
  try {
97610
- appendOwnerOnlyLine(path192.join(turnDir(event.turnId, dir), "events.jsonl"), line);
98201
+ appendOwnerOnlyLine(path193.join(turnDir(event.turnId, dir), "events.jsonl"), line);
97611
98202
  } catch (error) {
97612
98203
  if (error?.code !== "ENOENT") {
97613
98204
  throw error;
97614
98205
  }
97615
- appendOwnerOnlyLine(path192.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
98206
+ appendOwnerOnlyLine(path193.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
97616
98207
  }
97617
98208
  return true;
97618
98209
  }
@@ -97620,7 +98211,7 @@ function readTurnEvents(turnId, after = -1, dir) {
97620
98211
  if (!isTurnId(turnId)) {
97621
98212
  return { ok: false, reason: "not-a-turn-id" };
97622
98213
  }
97623
- const read = readTurnFile(path192.join(turnDir(turnId, dir), "events.jsonl"));
98214
+ const read = readTurnFile(path193.join(turnDir(turnId, dir), "events.jsonl"));
97624
98215
  if (!read.ok) {
97625
98216
  if (isDefiniteAbsence2(read.reason)) {
97626
98217
  return { ok: true, value: [] };
@@ -97648,7 +98239,7 @@ function readTurnRecord(turnId, dir) {
97648
98239
  if (!isTurnId(turnId)) {
97649
98240
  return { ok: false, reason: "not-a-turn-id" };
97650
98241
  }
97651
- const read = readTurnFile(path192.join(turnDir(turnId, dir), "turn.json"));
98242
+ const read = readTurnFile(path193.join(turnDir(turnId, dir), "turn.json"));
97652
98243
  if (!read.ok) {
97653
98244
  return { ok: false, reason: read.reason };
97654
98245
  }
@@ -97667,7 +98258,7 @@ function finishTurn(turnId, result, dir) {
97667
98258
  if (!record.ok) {
97668
98259
  return false;
97669
98260
  }
97670
- writeOwnerOnlyFile(path192.join(turnDir(turnId, dir), "turn.json"), `${JSON.stringify({ ...record.value, result }, null, 2)}
98261
+ writeOwnerOnlyFile(path193.join(turnDir(turnId, dir), "turn.json"), `${JSON.stringify({ ...record.value, result }, null, 2)}
97671
98262
  `);
97672
98263
  return true;
97673
98264
  }
@@ -97713,7 +98304,7 @@ function releaseIdempotencyKey(project, idempotencyKey, turnId, dir) {
97713
98304
 
97714
98305
  // src/lib/serve-turn.ts
97715
98306
  import { randomUUID as randomUUID31 } from "crypto";
97716
- import path193 from "path";
98307
+ import path194 from "path";
97717
98308
  init_service();
97718
98309
  var REMOTE_ORIGIN = "remote:http";
97719
98310
  var MAX_PROMPT_CHARS = 32000;
@@ -97782,9 +98373,9 @@ function isUuid(value) {
97782
98373
  return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(value);
97783
98374
  }
97784
98375
  function resolveProject(declared, dir) {
97785
- const wanted = path193.resolve(declared);
98376
+ const wanted = path194.resolve(declared);
97786
98377
  for (const entry of listProjects(dir, () => {})) {
97787
- if (path193.resolve(entry.path) === wanted) {
98378
+ if (path194.resolve(entry.path) === wanted) {
97788
98379
  return { ok: true, project: entry.path };
97789
98380
  }
97790
98381
  }
@@ -98820,7 +99411,7 @@ function printHelp19() {
98820
99411
  import { spawn as spawn6 } from "child_process";
98821
99412
  import { chmod as chmod4, mkdir as mkdir62, readFile as readFile99, readdir as readdir35, writeFile as writeFile54 } from "fs/promises";
98822
99413
  import { access as access4, constants as constants2, existsSync as existsSync35 } from "fs";
98823
- import path194 from "path";
99414
+ import path195 from "path";
98824
99415
  import { fileURLToPath as fileURLToPath7 } from "url";
98825
99416
  init_config2();
98826
99417
  init_config();
@@ -98837,8 +99428,8 @@ async function updateCommand(args = []) {
98837
99428
  return;
98838
99429
  }
98839
99430
  const projectRoot = process.cwd();
98840
- const metaprojectRoot = path194.join(projectRoot, ".metaproject");
98841
- banner("keryx update", `Refreshing the .metaproject workspace in ${path194.basename(projectRoot)}/`);
99431
+ const metaprojectRoot = path195.join(projectRoot, ".metaproject");
99432
+ banner("keryx update", `Refreshing the .metaproject workspace in ${path195.basename(projectRoot)}/`);
98842
99433
  if (!await pathExists(metaprojectRoot)) {
98843
99434
  console.log(` ${style.red(symbols.cross)} Metaproject is not initialized.`);
98844
99435
  console.log(` ${style.cyan(symbols.arrow)} Run ${style.cyan("keryx init")} first.`);
@@ -98899,7 +99490,7 @@ async function updateCommand(args = []) {
98899
99490
  nextSteps(steps);
98900
99491
  }
98901
99492
  async function previewServiceFiles(projectRoot, options) {
98902
- const metaprojectRoot = path194.join(projectRoot, ".metaproject");
99493
+ const metaprojectRoot = path195.join(projectRoot, ".metaproject");
98903
99494
  const manifestState = await readManifest5(metaprojectRoot);
98904
99495
  const manifest = manifestState.manifest;
98905
99496
  const enableTasks = moduleEnabled2(manifest, "tasks") || !options.noTasks;
@@ -98928,12 +99519,12 @@ async function previewServiceFiles(projectRoot, options) {
98928
99519
  });
98929
99520
  }
98930
99521
  async function refreshServiceFiles(projectRoot, options) {
98931
- const metaprojectRoot = path194.join(projectRoot, ".metaproject");
99522
+ const metaprojectRoot = path195.join(projectRoot, ".metaproject");
98932
99523
  const manifestState = await readManifest5(metaprojectRoot);
98933
99524
  const manifest = manifestState.manifest;
98934
99525
  const recoveredManifest = !manifestState.exists || !manifestState.valid;
98935
99526
  if (manifestState.migrated) {
98936
- await writeFile54(path194.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
99527
+ await writeFile54(path195.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
98937
99528
  `, "utf8");
98938
99529
  }
98939
99530
  const enableGdgraph = moduleEnabled2(manifest, "gdgraph");
@@ -98970,9 +99561,9 @@ async function refreshServiceFiles(projectRoot, options) {
98970
99561
  enableSecurity,
98971
99562
  enableSac
98972
99563
  });
98973
- await writeTextIfChanged4(path194.join(metaprojectRoot, "core", "README.md"), renderMetaprojectCoreReadme());
98974
- await writeTextIfChanged4(path194.join(metaprojectRoot, "hooks", "README.md"), renderHooksReadme());
98975
- await writeTextIfChanged4(path194.join(metaprojectRoot, "skills", "project-rules", "README.md"), renderProjectRulesSkillReadme({ sources: ruleSources }));
99564
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "core", "README.md"), renderMetaprojectCoreReadme());
99565
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "hooks", "README.md"), renderHooksReadme());
99566
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "skills", "project-rules", "README.md"), renderProjectRulesSkillReadme({ sources: ruleSources }));
98976
99567
  await writeRoutingEntrypointPair(metaprojectRoot, {
98977
99568
  enableGdgraph,
98978
99569
  enableGdctx,
@@ -98995,7 +99586,7 @@ async function refreshServiceFiles(projectRoot, options) {
98995
99586
  }
98996
99587
  }
98997
99588
  });
98998
- await writeTextIfChanged4(path194.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
99589
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
98999
99590
  enableGdgraph,
99000
99591
  enableGdctx,
99001
99592
  enableGdwiki,
@@ -99007,7 +99598,7 @@ async function refreshServiceFiles(projectRoot, options) {
99007
99598
  enableSecurity,
99008
99599
  data: dashboardData
99009
99600
  }));
99010
- await writeTextIfMissing4(path194.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
99601
+ await writeTextIfMissing4(path195.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
99011
99602
  enableGdgraph,
99012
99603
  enableGdctx,
99013
99604
  enableGdwiki,
@@ -99020,31 +99611,31 @@ async function refreshServiceFiles(projectRoot, options) {
99020
99611
  }));
99021
99612
  if (enableGdgraph) {
99022
99613
  await installGdgraphCoreScripts2(metaprojectRoot);
99023
- await writeTextIfChanged4(path194.join(metaprojectRoot, "modules", "gdgraph.md"), renderGdgraphManifest());
99024
- await writeTextIfChanged4(path194.join(metaprojectRoot, "core", "gdgraph", "README.md"), renderGdgraphCoreReadme());
99025
- await writeTextIfChanged4(path194.join(metaprojectRoot, "skills", "gdgraph", "SKILL.md"), renderGdgraphSkillReadme());
99614
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "modules", "gdgraph.md"), renderGdgraphManifest());
99615
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "core", "gdgraph", "README.md"), renderGdgraphCoreReadme());
99616
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "skills", "gdgraph", "SKILL.md"), renderGdgraphSkillReadme());
99026
99617
  await seedAssetsLock(metaprojectRoot);
99027
99618
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
99028
99619
  await installManagedHook2(projectRoot, "post-commit", "gdgraph-post-commit", renderGdgraphPostCommitHook());
99029
99620
  }
99030
99621
  }
99031
99622
  if (enableGdctx) {
99032
- await writeTextIfMissing4(path194.join(metaprojectRoot, "gdctx.config.json"), renderGdctxConfig());
99033
- await writeTextIfChanged4(path194.join(metaprojectRoot, "modules", "gdctx.md"), renderGdctxManifest());
99034
- await writeTextIfChanged4(path194.join(metaprojectRoot, "core", "gdctx", "README.md"), renderGdctxCoreReadme());
99035
- await writeTextIfChanged4(path194.join(metaprojectRoot, "skills", "gdctx", "SKILL.md"), renderGdctxSkillReadme());
99623
+ await writeTextIfMissing4(path195.join(metaprojectRoot, "gdctx.config.json"), renderGdctxConfig());
99624
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "modules", "gdctx.md"), renderGdctxManifest());
99625
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "core", "gdctx", "README.md"), renderGdctxCoreReadme());
99626
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "skills", "gdctx", "SKILL.md"), renderGdctxSkillReadme());
99036
99627
  }
99037
99628
  if (enableGdwiki) {
99038
- await writeTextIfMissing4(path194.join(metaprojectRoot, "wiki", "templates", "page.md"), renderWikiPageTemplate());
99039
- await writeTextIfChanged4(path194.join(metaprojectRoot, "modules", "gdwiki.md"), renderGdwikiManifest());
99040
- await writeTextIfChanged4(path194.join(metaprojectRoot, "skills", "gdwiki", "SKILL.md"), renderGdwikiSkillReadme());
99629
+ await writeTextIfMissing4(path195.join(metaprojectRoot, "wiki", "templates", "page.md"), renderWikiPageTemplate());
99630
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "modules", "gdwiki.md"), renderGdwikiManifest());
99631
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "skills", "gdwiki", "SKILL.md"), renderGdwikiSkillReadme());
99041
99632
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
99042
99633
  await installManagedHook2(projectRoot, "post-commit", "gdwiki-post-commit", renderGdwikiPostCommitHook());
99043
99634
  }
99044
99635
  }
99045
99636
  if (enableSac) {
99046
- await writeTextIfMissing4(path194.join(metaprojectRoot, "modules", "sac.md"), renderSacManifest());
99047
- await writeTextIfChanged4(path194.join(metaprojectRoot, "skills", "sac", "SKILL.md"), renderSacSkillReadme());
99637
+ await writeTextIfMissing4(path195.join(metaprojectRoot, "modules", "sac.md"), renderSacManifest());
99638
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "skills", "sac", "SKILL.md"), renderSacSkillReadme());
99048
99639
  }
99049
99640
  if (enableGdskills) {
99050
99641
  await installGdskills(metaprojectRoot, gdskillsProfile, { createDataDirs: false });
@@ -99053,25 +99644,25 @@ async function refreshServiceFiles(projectRoot, options) {
99053
99644
  }
99054
99645
  }
99055
99646
  if (enableHealth) {
99056
- await writeTextIfMissing4(path194.join(metaprojectRoot, "health.config.json"), renderHealthConfig());
99057
- await writeTextIfChanged4(path194.join(metaprojectRoot, "modules", "health.md"), renderHealthManifest());
99058
- await writeTextIfChanged4(path194.join(metaprojectRoot, "core", "health", "README.md"), renderHealthCoreReadme());
99059
- await writeTextIfChanged4(path194.join(metaprojectRoot, "skills", "health", "SKILL.md"), renderHealthSkillReadme());
99647
+ await writeTextIfMissing4(path195.join(metaprojectRoot, "health.config.json"), renderHealthConfig());
99648
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "modules", "health.md"), renderHealthManifest());
99649
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "core", "health", "README.md"), renderHealthCoreReadme());
99650
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "skills", "health", "SKILL.md"), renderHealthSkillReadme());
99060
99651
  if (manifest.modules?.health?.hooks?.gitPostCommit) {
99061
99652
  await installManagedHook2(projectRoot, "post-commit", "health-post-commit", renderHealthPostCommitHook());
99062
99653
  }
99063
99654
  }
99064
99655
  if (enableTesting) {
99065
- await writeTextIfMissing4(path194.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
99656
+ await writeTextIfMissing4(path195.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
99066
99657
  postCommitRefresh: Boolean(manifest.modules?.testing?.hooks?.gitPostCommit),
99067
99658
  prePushGate: Boolean(manifest.modules?.testing?.hooks?.prePush)
99068
99659
  }));
99069
- await writeTextIfChanged4(path194.join(metaprojectRoot, "modules", "testing.md"), renderTestingManifest());
99070
- await writeTextIfChanged4(path194.join(metaprojectRoot, "core", "testing", "README.md"), renderTestingCoreReadme());
99071
- await writeTextIfChanged4(path194.join(metaprojectRoot, "skills", "testing", "SKILL.md"), renderTestingSkillReadme());
99660
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "modules", "testing.md"), renderTestingManifest());
99661
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "core", "testing", "README.md"), renderTestingCoreReadme());
99662
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "skills", "testing", "SKILL.md"), renderTestingSkillReadme());
99072
99663
  if (enableGdwiki) {
99073
- await writeTextIfMissing4(path194.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
99074
- await writeTextIfMissing4(path194.join(metaprojectRoot, "wiki", "testing", "conventions.md"), renderTestingWikiConventions());
99664
+ await writeTextIfMissing4(path195.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
99665
+ await writeTextIfMissing4(path195.join(metaprojectRoot, "wiki", "testing", "conventions.md"), renderTestingWikiConventions());
99075
99666
  }
99076
99667
  if (manifest.modules?.testing?.hooks?.gitPostCommit) {
99077
99668
  await installManagedHook2(projectRoot, "post-commit", "testing-post-commit", renderTestingPostCommitHook());
@@ -99084,24 +99675,24 @@ async function refreshServiceFiles(projectRoot, options) {
99084
99675
  await installManagedHook2(projectRoot, "post-commit", "metaproject-dashboard-post-commit", renderMetaprojectDashboardPostCommitHook());
99085
99676
  }
99086
99677
  if (enableMemory) {
99087
- await writeTextIfMissing4(path194.join(metaprojectRoot, "memory.config.json"), renderMemoryConfig());
99088
- await writeTextIfMissing4(path194.join(metaprojectRoot, "memory", "templates", "entry.md"), renderMemoryEntryTemplate());
99089
- await writeTextIfChanged4(path194.join(metaprojectRoot, "modules", "memory.md"), renderMemoryManifest());
99090
- await writeTextIfChanged4(path194.join(metaprojectRoot, "core", "memory", "README.md"), renderMemoryCoreReadme());
99091
- await writeTextIfChanged4(path194.join(metaprojectRoot, "skills", "memory", "SKILL.md"), renderMemorySkillReadme());
99678
+ await writeTextIfMissing4(path195.join(metaprojectRoot, "memory.config.json"), renderMemoryConfig());
99679
+ await writeTextIfMissing4(path195.join(metaprojectRoot, "memory", "templates", "entry.md"), renderMemoryEntryTemplate());
99680
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "modules", "memory.md"), renderMemoryManifest());
99681
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "core", "memory", "README.md"), renderMemoryCoreReadme());
99682
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "skills", "memory", "SKILL.md"), renderMemorySkillReadme());
99092
99683
  }
99093
99684
  if (enableTasks) {
99094
- await writeTextIfChanged4(path194.join(metaprojectRoot, "flows", "README.md"), renderFlowsReadme());
99095
- await writeTextIfChanged4(path194.join(metaprojectRoot, "modules", "tasks.md"), renderTasksManifest());
99096
- await writeTextIfChanged4(path194.join(metaprojectRoot, "skills", "flow", "SKILL.md"), renderFlowSkillRouter());
99097
- await writeTextIfChanged4(path194.join(metaprojectRoot, "skills", "flow", "init.md"), renderFlowInitSkill());
99098
- await writeTextIfChanged4(path194.join(metaprojectRoot, "skills", "flow", "manage.md"), renderFlowManageSkill());
99099
- await writeTextIfChanged4(path194.join(metaprojectRoot, "skills", "flow", "complete.md"), renderFlowCompleteSkill());
99685
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "flows", "README.md"), renderFlowsReadme());
99686
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "modules", "tasks.md"), renderTasksManifest());
99687
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "skills", "flow", "SKILL.md"), renderFlowSkillRouter());
99688
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "skills", "flow", "init.md"), renderFlowInitSkill());
99689
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "skills", "flow", "manage.md"), renderFlowManageSkill());
99690
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "skills", "flow", "complete.md"), renderFlowCompleteSkill());
99100
99691
  }
99101
99692
  if (enableSecurity) {
99102
- await writeTextIfMissing4(path194.join(metaprojectRoot, "security.config.json"), renderSecurityConfig());
99103
- await writeTextIfChanged4(path194.join(metaprojectRoot, "modules", "security.md"), renderSecurityManifest());
99104
- await writeTextIfChanged4(path194.join(metaprojectRoot, "core", "security", "README.md"), renderSecurityCoreReadme());
99693
+ await writeTextIfMissing4(path195.join(metaprojectRoot, "security.config.json"), renderSecurityConfig());
99694
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "modules", "security.md"), renderSecurityManifest());
99695
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "core", "security", "README.md"), renderSecurityCoreReadme());
99105
99696
  if (manifest.modules?.security?.hooks?.prePush) {
99106
99697
  await installManagedHook2(projectRoot, "pre-push", "security-pre-push", renderSecurityPrePushHook());
99107
99698
  }
@@ -99152,13 +99743,13 @@ async function refreshServiceFiles(projectRoot, options) {
99152
99743
  };
99153
99744
  }
99154
99745
  async function buildDashboard(projectRoot = process.cwd()) {
99155
- const metaprojectRoot = path194.join(projectRoot, ".metaproject");
99746
+ const metaprojectRoot = path195.join(projectRoot, ".metaproject");
99156
99747
  if (!await pathExists(metaprojectRoot)) {
99157
99748
  throw new Error("Metaproject is not initialized. Run: keryx init");
99158
99749
  }
99159
99750
  const manifest = (await readManifest5(metaprojectRoot)).manifest;
99160
99751
  const data = await collectDashboardData(metaprojectRoot);
99161
- const dashboardPath = path194.join(metaprojectRoot, "keryx-dashboard.html");
99752
+ const dashboardPath = path195.join(metaprojectRoot, "keryx-dashboard.html");
99162
99753
  await writeTextIfChanged4(dashboardPath, renderMetaprojectDashboardHtml({
99163
99754
  enableGdgraph: moduleEnabled2(manifest, "gdgraph"),
99164
99755
  enableGdctx: moduleEnabled2(manifest, "gdctx"),
@@ -99178,7 +99769,7 @@ async function shouldInstallDashboardPostCommitHook(projectRoot, manifest) {
99178
99769
  if (Object.values(modules).some((module) => Boolean(module.hooks?.gitPostCommit))) {
99179
99770
  return true;
99180
99771
  }
99181
- const hookPath = path194.join(projectRoot, ".git", "hooks", "post-commit");
99772
+ const hookPath = path195.join(projectRoot, ".git", "hooks", "post-commit");
99182
99773
  if (!await pathExists(hookPath)) {
99183
99774
  return false;
99184
99775
  }
@@ -99198,11 +99789,11 @@ async function collectDashboardData(metaprojectRoot) {
99198
99789
  if (testing) {
99199
99790
  data.testing = testing;
99200
99791
  }
99201
- const wiki = await collectMarkdownPages(path194.join(metaprojectRoot, "wiki"), "wiki");
99792
+ const wiki = await collectMarkdownPages(path195.join(metaprojectRoot, "wiki"), "wiki");
99202
99793
  if (wiki.length > 0) {
99203
99794
  data.wiki = { pages: wiki };
99204
99795
  }
99205
- const memory = await collectMarkdownPages(path194.join(metaprojectRoot, "memory"), "memory");
99796
+ const memory = await collectMarkdownPages(path195.join(metaprojectRoot, "memory"), "memory");
99206
99797
  if (memory.length > 0) {
99207
99798
  data.memory = { entries: memory };
99208
99799
  }
@@ -99217,7 +99808,7 @@ async function collectDashboardData(metaprojectRoot) {
99217
99808
  return data;
99218
99809
  }
99219
99810
  async function collectTasksDashboardData(metaprojectRoot) {
99220
- const flowsRoot = path194.join(metaprojectRoot, "flows");
99811
+ const flowsRoot = path195.join(metaprojectRoot, "flows");
99221
99812
  if (!await pathExists(flowsRoot)) {
99222
99813
  return null;
99223
99814
  }
@@ -99229,7 +99820,7 @@ async function collectTasksDashboardData(metaprojectRoot) {
99229
99820
  }
99230
99821
  const flows = [];
99231
99822
  for (const dir of dirEntries) {
99232
- const flowPath = path194.join(flowsRoot, dir, "flow.json");
99823
+ const flowPath = path195.join(flowsRoot, dir, "flow.json");
99233
99824
  if (!await pathExists(flowPath)) {
99234
99825
  continue;
99235
99826
  }
@@ -99237,7 +99828,7 @@ async function collectTasksDashboardData(metaprojectRoot) {
99237
99828
  const flow = JSON.parse(await readFile99(flowPath, "utf8"));
99238
99829
  const tasks = Array.isArray(flow.tasks) ? flow.tasks : [];
99239
99830
  let acTotal = 0;
99240
- const acPath = path194.join(flowsRoot, dir, "acceptance-criteria.md");
99831
+ const acPath = path195.join(flowsRoot, dir, "acceptance-criteria.md");
99241
99832
  if (await pathExists(acPath)) {
99242
99833
  const acContent = await readFile99(acPath, "utf8");
99243
99834
  acTotal = (acContent.match(/^- AC\d+:/gm) ?? []).length;
@@ -99291,7 +99882,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
99291
99882
  "data/testing/context.md"
99292
99883
  ];
99293
99884
  for (const href of staticHrefs) {
99294
- const filePath = path194.join(metaprojectRoot, ...href.split("/"));
99885
+ const filePath = path195.join(metaprojectRoot, ...href.split("/"));
99295
99886
  if (!await pathExists(filePath)) {
99296
99887
  continue;
99297
99888
  }
@@ -99308,7 +99899,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
99308
99899
  return docs;
99309
99900
  }
99310
99901
  async function collectHealthDashboardData(metaprojectRoot) {
99311
- const reportPath = path194.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
99902
+ const reportPath = path195.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
99312
99903
  if (!await pathExists(reportPath)) {
99313
99904
  return;
99314
99905
  }
@@ -99417,8 +100008,8 @@ function metricToScope(metric) {
99417
100008
  };
99418
100009
  }
99419
100010
  async function collectGraphDashboardData(metaprojectRoot) {
99420
- const nodesPath = path194.join(metaprojectRoot, "data", "gdgraph", "storage", "nodes.jsonl");
99421
- const edgesPath = path194.join(metaprojectRoot, "data", "gdgraph", "storage", "edges.jsonl");
100011
+ const nodesPath = path195.join(metaprojectRoot, "data", "gdgraph", "storage", "nodes.jsonl");
100012
+ const edgesPath = path195.join(metaprojectRoot, "data", "gdgraph", "storage", "edges.jsonl");
99422
100013
  if (!await pathExists(nodesPath) || !await pathExists(edgesPath)) {
99423
100014
  return;
99424
100015
  }
@@ -99469,8 +100060,8 @@ async function collectGraphDashboardData(metaprojectRoot) {
99469
100060
  };
99470
100061
  }
99471
100062
  async function collectTestingDashboardData(metaprojectRoot) {
99472
- const reportPath = path194.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
99473
- const contextPath = path194.join(metaprojectRoot, "data", "testing", "context.md");
100063
+ const reportPath = path195.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
100064
+ const contextPath = path195.join(metaprojectRoot, "data", "testing", "context.md");
99474
100065
  if (await pathExists(reportPath)) {
99475
100066
  const report = JSON.parse(await readFile99(reportPath, "utf8"));
99476
100067
  const totalTests = numberOrUndefined(report.total);
@@ -99499,7 +100090,7 @@ async function collectMarkdownPages(root, hrefPrefix) {
99499
100090
  const files = await listMarkdownFiles(root);
99500
100091
  const pages = [];
99501
100092
  for (const filePath of files.slice(0, 40)) {
99502
- const relativePath = path194.relative(root, filePath).split(path194.sep).join("/");
100093
+ const relativePath = path195.relative(root, filePath).split(path195.sep).join("/");
99503
100094
  if (relativePath === "index.md" || relativePath.startsWith("templates/")) {
99504
100095
  continue;
99505
100096
  }
@@ -99520,7 +100111,7 @@ async function listMarkdownFiles(root) {
99520
100111
  const entries = await readdir35(root, { withFileTypes: true });
99521
100112
  const files = [];
99522
100113
  for (const entry of entries) {
99523
- const fullPath = path194.join(root, entry.name);
100114
+ const fullPath = path195.join(root, entry.name);
99524
100115
  if (entry.isDirectory()) {
99525
100116
  files.push(...await listMarkdownFiles(fullPath));
99526
100117
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -99568,7 +100159,7 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
99568
100159
  const manifest = {
99569
100160
  schemaVersion: 1,
99570
100161
  standardVersion: STANDARD_VERSION,
99571
- name: `${path194.basename(path194.dirname(metaprojectRoot))}-metaproject`,
100162
+ name: `${path195.basename(path195.dirname(metaprojectRoot))}-metaproject`,
99572
100163
  createdBy: "keryx",
99573
100164
  profiles: computeProfiles(enabledModuleKeys),
99574
100165
  paths: {
@@ -99658,11 +100249,11 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
99658
100249
  metaproject: ".metaproject/index.md"
99659
100250
  }
99660
100251
  };
99661
- await writeFile54(path194.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
100252
+ await writeFile54(path195.join(metaprojectRoot, "metaproject.json"), `${JSON.stringify(manifest, null, 2)}
99662
100253
  `, "utf8");
99663
100254
  }
99664
100255
  async function enableTasksInManifest(metaprojectRoot) {
99665
- const manifestPath = path194.join(metaprojectRoot, "metaproject.json");
100256
+ const manifestPath = path195.join(metaprojectRoot, "metaproject.json");
99666
100257
  if (!await pathExists(manifestPath)) {
99667
100258
  return;
99668
100259
  }
@@ -99685,7 +100276,7 @@ async function enableTasksInManifest(metaprojectRoot) {
99685
100276
  `, "utf8");
99686
100277
  }
99687
100278
  async function updateManifestAgentEntrypoints(metaprojectRoot, ruleSources) {
99688
- const manifestPath = path194.join(metaprojectRoot, "metaproject.json");
100279
+ const manifestPath = path195.join(metaprojectRoot, "metaproject.json");
99689
100280
  if (!await pathExists(manifestPath)) {
99690
100281
  return;
99691
100282
  }
@@ -99721,72 +100312,72 @@ async function updateRuntime(projectRoot) {
99721
100312
  }
99722
100313
  }
99723
100314
  async function findRuntimeRoot(projectRoot) {
99724
- const projectRuntime = path194.join(projectRoot, ".metaproject", "runtime", "keryx");
99725
- if (await pathExists(path194.join(projectRuntime, ".git"))) {
100315
+ const projectRuntime = path195.join(projectRoot, ".metaproject", "runtime", "keryx");
100316
+ if (await pathExists(path195.join(projectRuntime, ".git"))) {
99726
100317
  return projectRuntime;
99727
100318
  }
99728
100319
  const home = process.env.HOME;
99729
100320
  if (!home) {
99730
100321
  return null;
99731
100322
  }
99732
- const globalRuntime = path194.join(home, ".keryx", "keryx");
99733
- if (await pathExists(path194.join(globalRuntime, ".git"))) {
100323
+ const globalRuntime = path195.join(home, ".keryx", "keryx");
100324
+ if (await pathExists(path195.join(globalRuntime, ".git"))) {
99734
100325
  return globalRuntime;
99735
100326
  }
99736
100327
  return null;
99737
100328
  }
99738
100329
  async function createServiceDirs(metaprojectRoot, modules) {
99739
100330
  const dirs = [
99740
- path194.join(metaprojectRoot, "core"),
99741
- path194.join(metaprojectRoot, "hooks", "post-update.d"),
99742
- path194.join(metaprojectRoot, "modules"),
99743
- path194.join(metaprojectRoot, "rules"),
99744
- path194.join(metaprojectRoot, "skills", "project-rules"),
100331
+ path195.join(metaprojectRoot, "core"),
100332
+ path195.join(metaprojectRoot, "hooks", "post-update.d"),
100333
+ path195.join(metaprojectRoot, "modules"),
100334
+ path195.join(metaprojectRoot, "rules"),
100335
+ path195.join(metaprojectRoot, "skills", "project-rules"),
99745
100336
  ...modules.enableGdgraph ? [
99746
- path194.join(metaprojectRoot, "core", "gdgraph"),
99747
- path194.join(metaprojectRoot, "skills", "gdgraph")
100337
+ path195.join(metaprojectRoot, "core", "gdgraph"),
100338
+ path195.join(metaprojectRoot, "skills", "gdgraph")
99748
100339
  ] : [],
99749
100340
  ...modules.enableGdctx ? [
99750
- path194.join(metaprojectRoot, "core", "gdctx"),
99751
- path194.join(metaprojectRoot, "skills", "gdctx")
100341
+ path195.join(metaprojectRoot, "core", "gdctx"),
100342
+ path195.join(metaprojectRoot, "skills", "gdctx")
99752
100343
  ] : [],
99753
100344
  ...modules.enableGdwiki ? [
99754
- path194.join(metaprojectRoot, "skills", "gdwiki"),
99755
- path194.join(metaprojectRoot, "wiki", "templates")
100345
+ path195.join(metaprojectRoot, "skills", "gdwiki"),
100346
+ path195.join(metaprojectRoot, "wiki", "templates")
99756
100347
  ] : [],
99757
100348
  ...modules.enableHealth ? [
99758
- path194.join(metaprojectRoot, "core", "health"),
99759
- path194.join(metaprojectRoot, "skills", "health")
100349
+ path195.join(metaprojectRoot, "core", "health"),
100350
+ path195.join(metaprojectRoot, "skills", "health")
99760
100351
  ] : [],
99761
100352
  ...modules.enableTesting ? [
99762
- path194.join(metaprojectRoot, "core", "testing"),
99763
- path194.join(metaprojectRoot, "skills", "testing")
100353
+ path195.join(metaprojectRoot, "core", "testing"),
100354
+ path195.join(metaprojectRoot, "skills", "testing")
99764
100355
  ] : [],
99765
100356
  ...modules.enableMemory ? [
99766
- path194.join(metaprojectRoot, "core", "memory"),
99767
- path194.join(metaprojectRoot, "skills", "memory"),
99768
- path194.join(metaprojectRoot, "memory", "templates")
100357
+ path195.join(metaprojectRoot, "core", "memory"),
100358
+ path195.join(metaprojectRoot, "skills", "memory"),
100359
+ path195.join(metaprojectRoot, "memory", "templates")
99769
100360
  ] : [],
99770
100361
  ...modules.enableTasks ? [
99771
- path194.join(metaprojectRoot, "flows"),
99772
- path194.join(metaprojectRoot, "skills", "flow")
100362
+ path195.join(metaprojectRoot, "flows"),
100363
+ path195.join(metaprojectRoot, "skills", "flow")
99773
100364
  ] : [],
99774
100365
  ...modules.enableSecurity ? [
99775
- path194.join(metaprojectRoot, "core", "security")
100366
+ path195.join(metaprojectRoot, "core", "security")
99776
100367
  ] : [],
99777
100368
  ...modules.enableSac ? [
99778
- path194.join(metaprojectRoot, "skills", "sac")
100369
+ path195.join(metaprojectRoot, "skills", "sac")
99779
100370
  ] : []
99780
100371
  ];
99781
100372
  await Promise.all(dirs.map((dir) => mkdir62(dir, { recursive: true })));
99782
100373
  }
99783
100374
  async function installGdgraphCoreScripts2(metaprojectRoot) {
99784
- const gdgraphCoreRoot = path194.join(metaprojectRoot, "core", "gdgraph");
100375
+ const gdgraphCoreRoot = path195.join(metaprojectRoot, "core", "gdgraph");
99785
100376
  await mkdir62(gdgraphCoreRoot, { recursive: true });
99786
100377
  for (const file of GDGRAPH_CORE_SOURCES) {
99787
- await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path194.join(gdgraphCoreRoot, file));
100378
+ await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path195.join(gdgraphCoreRoot, file));
99788
100379
  }
99789
- await writeTextIfChanged4(path194.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
100380
+ await writeTextIfChanged4(path195.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
99790
100381
  }
99791
100382
  async function installManagedHook2(projectRoot, hookName, blockId, content) {
99792
100383
  const hooksRoot = await resolveGitHooksRoot(projectRoot);
@@ -99794,7 +100385,7 @@ async function installManagedHook2(projectRoot, hookName, blockId, content) {
99794
100385
  return;
99795
100386
  }
99796
100387
  await mkdir62(hooksRoot, { recursive: true });
99797
- const hookPath = path194.join(hooksRoot, hookName);
100388
+ const hookPath = path195.join(hooksRoot, hookName);
99798
100389
  const blockStart = `# keryx:${blockId}:begin`;
99799
100390
  const blockEnd = `# keryx:${blockId}:end`;
99800
100391
  const managedBlock = `${blockStart}
@@ -99815,7 +100406,7 @@ async function removeManagedHook2(projectRoot, hookName, blockId) {
99815
100406
  if (!hooksRoot) {
99816
100407
  return;
99817
100408
  }
99818
- const hookPath = path194.join(hooksRoot, hookName);
100409
+ const hookPath = path195.join(hooksRoot, hookName);
99819
100410
  if (!await pathExists(hookPath)) {
99820
100411
  return;
99821
100412
  }
@@ -99837,7 +100428,7 @@ async function prePushHasSecurityBlock2(projectRoot) {
99837
100428
  if (!hooksRoot) {
99838
100429
  return false;
99839
100430
  }
99840
- const hookPath = path194.join(hooksRoot, "pre-push");
100431
+ const hookPath = path195.join(hooksRoot, "pre-push");
99841
100432
  if (!await pathExists(hookPath)) {
99842
100433
  return false;
99843
100434
  }
@@ -99852,7 +100443,7 @@ async function agentSettingsHasSecuritySentinel2(projectRoot) {
99852
100443
  return (await readFile99(file, "utf8")).includes(AGENT_HOOKS_SENTINEL);
99853
100444
  }
99854
100445
  async function readManifest5(metaprojectRoot) {
99855
- const manifestPath = path194.join(metaprojectRoot, "metaproject.json");
100446
+ const manifestPath = path195.join(metaprojectRoot, "metaproject.json");
99856
100447
  if (!await pathExists(manifestPath)) {
99857
100448
  return {
99858
100449
  exists: false,
@@ -99921,7 +100512,7 @@ async function inferManifestFromExistingMetaproject(metaprojectRoot) {
99921
100512
  }
99922
100513
  async function anyPathExists(root, candidates) {
99923
100514
  for (const candidate of candidates) {
99924
- if (await pathExists(path194.join(root, candidate))) {
100515
+ if (await pathExists(path195.join(root, candidate))) {
99925
100516
  return true;
99926
100517
  }
99927
100518
  }
@@ -99944,13 +100535,13 @@ function parseUpdateArgs(args) {
99944
100535
  };
99945
100536
  }
99946
100537
  async function runPostUpdateHooks(projectRoot) {
99947
- const hooksDir = path194.join(projectRoot, ".metaproject", "hooks", "post-update.d");
100538
+ const hooksDir = path195.join(projectRoot, ".metaproject", "hooks", "post-update.d");
99948
100539
  if (!await pathExists(hooksDir)) {
99949
100540
  return;
99950
100541
  }
99951
100542
  const entries = (await readdir35(hooksDir)).sort();
99952
100543
  for (const entry of entries) {
99953
- const hookPath = path194.join(hooksDir, entry);
100544
+ const hookPath = path195.join(hooksDir, entry);
99954
100545
  try {
99955
100546
  await accessExecutable(hookPath);
99956
100547
  } catch {
@@ -99991,14 +100582,14 @@ async function writeTextIfChanged4(filePath, content) {
99991
100582
  if (await pathExists(filePath) && await readFile99(filePath, "utf8") === content) {
99992
100583
  return;
99993
100584
  }
99994
- await mkdir62(path194.dirname(filePath), { recursive: true });
100585
+ await mkdir62(path195.dirname(filePath), { recursive: true });
99995
100586
  await writeFile54(filePath, content, "utf8");
99996
100587
  }
99997
100588
  async function writeTextIfMissing4(filePath, content) {
99998
100589
  if (await pathExists(filePath)) {
99999
100590
  return;
100000
100591
  }
100001
- await mkdir62(path194.dirname(filePath), { recursive: true });
100592
+ await mkdir62(path195.dirname(filePath), { recursive: true });
100002
100593
  await writeFile54(filePath, content, "utf8");
100003
100594
  }
100004
100595
  async function copyFileIfChanged2(from, to) {
@@ -100006,7 +100597,7 @@ async function copyFileIfChanged2(from, to) {
100006
100597
  if (await pathExists(to) && await readFile99(to, "utf8") === next) {
100007
100598
  return;
100008
100599
  }
100009
- await mkdir62(path194.dirname(to), { recursive: true });
100600
+ await mkdir62(path195.dirname(to), { recursive: true });
100010
100601
  await writeFile54(to, next, "utf8");
100011
100602
  }
100012
100603
  function runtimeSourcePath2(relativePath) {
@@ -100015,7 +100606,7 @@ function runtimeSourcePath2(relativePath) {
100015
100606
  return directPath;
100016
100607
  }
100017
100608
  if (relativePath.startsWith("../")) {
100018
- const packagedSourcePath = path194.join(path194.dirname(fileURLToPath7(import.meta.url)), "..", "src", relativePath.slice(3));
100609
+ const packagedSourcePath = path195.join(path195.dirname(fileURLToPath7(import.meta.url)), "..", "src", relativePath.slice(3));
100019
100610
  if (existsSync35(packagedSourcePath)) {
100020
100611
  return packagedSourcePath;
100021
100612
  }
@@ -100050,7 +100641,7 @@ function printHelp20() {
100050
100641
 
100051
100642
  // src/commands/dashboard.ts
100052
100643
  import { spawn as spawn7 } from "child_process";
100053
- import path195 from "path";
100644
+ import path196 from "path";
100054
100645
  init_args();
100055
100646
  async function dashboardCommand(args = []) {
100056
100647
  const options = parseOptions(args);
@@ -100061,7 +100652,7 @@ async function dashboardCommand(args = []) {
100061
100652
  }
100062
100653
  if (subcommand === "build") {
100063
100654
  const result = await buildDashboard();
100064
- const rel = path195.relative(process.cwd(), result.path);
100655
+ const rel = path196.relative(process.cwd(), result.path);
100065
100656
  console.log(` ${style.green(symbols.ok)} Dashboard built ${style.cyan(symbols.arrow)} ${style.cyan(rel)}`);
100066
100657
  note(`Open it: keryx dashboard open`);
100067
100658
  return;
@@ -100069,7 +100660,7 @@ async function dashboardCommand(args = []) {
100069
100660
  if (subcommand === "open") {
100070
100661
  const result = await buildDashboard();
100071
100662
  await openFile(result.path);
100072
- const rel = path195.relative(process.cwd(), result.path);
100663
+ const rel = path196.relative(process.cwd(), result.path);
100073
100664
  console.log(` ${style.green(symbols.ok)} Opened ${style.cyan(rel)}`);
100074
100665
  return;
100075
100666
  }
@@ -100118,7 +100709,7 @@ import { readFileSync as readFileSync8 } from "fs";
100118
100709
  // src/agents/bootstrap.ts
100119
100710
  import { mkdir as mkdir63, readFile as readFile100, writeFile as writeFile55 } from "fs/promises";
100120
100711
  import { homedir as homedir8 } from "os";
100121
- import path196 from "path";
100712
+ import path197 from "path";
100122
100713
  init_fs();
100123
100714
  var AGENT_BOOTSTRAP_START = "<!-- keryx:global-bootstrap -->";
100124
100715
  var AGENT_BOOTSTRAP_END = "<!-- /keryx:global-bootstrap -->";
@@ -100128,35 +100719,35 @@ var AGENT_BOOTSTRAP_RUNTIMES = [
100128
100719
  aliases: ["claude-code"],
100129
100720
  label: "Claude Code",
100130
100721
  fileName: "CLAUDE.md",
100131
- filePath: (homeRoot) => path196.join(homeRoot, ".claude", "CLAUDE.md")
100722
+ filePath: (homeRoot) => path197.join(homeRoot, ".claude", "CLAUDE.md")
100132
100723
  },
100133
100724
  {
100134
100725
  id: "opencode",
100135
100726
  aliases: ["open-code"],
100136
100727
  label: "OpenCode",
100137
100728
  fileName: "AGENTS.md",
100138
- filePath: (homeRoot) => path196.join(homeRoot, ".config", "opencode", "AGENTS.md")
100729
+ filePath: (homeRoot) => path197.join(homeRoot, ".config", "opencode", "AGENTS.md")
100139
100730
  },
100140
100731
  {
100141
100732
  id: "zcode",
100142
100733
  aliases: ["zed", "zed-code"],
100143
100734
  label: "ZCode",
100144
100735
  fileName: "AGENTS.md",
100145
- filePath: (homeRoot) => path196.join(homeRoot, ".zcode", "AGENTS.md")
100736
+ filePath: (homeRoot) => path197.join(homeRoot, ".zcode", "AGENTS.md")
100146
100737
  },
100147
100738
  {
100148
100739
  id: "codex",
100149
100740
  aliases: [],
100150
100741
  label: "Codex",
100151
100742
  fileName: "AGENTS.md",
100152
- filePath: (homeRoot) => path196.join(homeRoot, ".codex", "AGENTS.md")
100743
+ filePath: (homeRoot) => path197.join(homeRoot, ".codex", "AGENTS.md")
100153
100744
  },
100154
100745
  {
100155
100746
  id: "antigravity",
100156
100747
  aliases: ["antigravuty", "antigravity-code"],
100157
100748
  label: "Antigravity",
100158
100749
  fileName: "AGENTS.md",
100159
- filePath: (homeRoot) => path196.join(homeRoot, ".config", "antigravity", "AGENTS.md")
100750
+ filePath: (homeRoot) => path197.join(homeRoot, ".config", "antigravity", "AGENTS.md")
100160
100751
  }
100161
100752
  ];
100162
100753
  function agentBootstrapRuntimeIds() {
@@ -100204,7 +100795,7 @@ async function installAgentBootstrap(runtime, options = {}) {
100204
100795
  const dryRun = options.dryRun === true;
100205
100796
  const wrote = next !== current;
100206
100797
  if (wrote && !dryRun) {
100207
- await mkdir63(path196.dirname(filePath), { recursive: true });
100798
+ await mkdir63(path197.dirname(filePath), { recursive: true });
100208
100799
  await writeFile55(filePath, next, "utf8");
100209
100800
  }
100210
100801
  const status = dryRun ? statusFromContent(runtime, filePath, exists, next) : await agentBootstrapStatus(runtime, homeRoot);
@@ -100741,7 +101332,7 @@ function printBootstrapHelp() {
100741
101332
  // src/commands/metrics.ts
100742
101333
  init_args();
100743
101334
  import { readFile as readFile101 } from "fs/promises";
100744
- import path198 from "path";
101335
+ import path199 from "path";
100745
101336
 
100746
101337
  // src/metrics/benchmark.ts
100747
101338
  var RELIABILITIES2 = new Set(["exact", "estimated", "unknown"]);
@@ -101558,7 +102149,7 @@ function buildContainmentManifest(inputs, options = {}) {
101558
102149
 
101559
102150
  // src/metrics/oracle-runner.ts
101560
102151
  import { mkdir as mkdir64, writeFile as writeFile56 } from "fs/promises";
101561
- import path197 from "path";
102152
+ import path198 from "path";
101562
102153
 
101563
102154
  // src/metrics/ir.ts
101564
102155
  function toIdSet(ids) {
@@ -102004,9 +102595,9 @@ function buildEvidenceBundle(input, options = {}) {
102004
102595
  async function persistEvidenceBundle(outDir, bundle, ladder = "metastore") {
102005
102596
  const safeTarget = bundle.target.replace(/[^A-Za-z0-9._/-]/g, "_");
102006
102597
  const safeCase = bundle.caseId.replace(/[^A-Za-z0-9._-]/g, "_");
102007
- const dir = path197.join(outDir, "bench", ladder, safeTarget, safeCase, bundle.variant, String(bundle.seed));
102598
+ const dir = path198.join(outDir, "bench", ladder, safeTarget, safeCase, bundle.variant, String(bundle.seed));
102008
102599
  await mkdir64(dir, { recursive: true });
102009
- const write = (name, value) => writeFile56(path197.join(dir, name), `${JSON.stringify(value, null, 2)}
102600
+ const write = (name, value) => writeFile56(path198.join(dir, name), `${JSON.stringify(value, null, 2)}
102010
102601
  `, "utf8");
102011
102602
  await Promise.all([
102012
102603
  write("inputs.json", bundle.inputs),
@@ -102046,7 +102637,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
102046
102637
  console.log("# metrics status");
102047
102638
  console.log("");
102048
102639
  console.log(`root: ${root}`);
102049
- console.log(`enabled: ${await Bun.file(path198.join(projectRoot, ".metaproject", "metaproject.json")).exists() ? "yes" : "no"}`);
102640
+ console.log(`enabled: ${await Bun.file(path199.join(projectRoot, ".metaproject", "metaproject.json")).exists() ? "yes" : "no"}`);
102050
102641
  const latest = await readLatestPointer(root);
102051
102642
  console.log(`latest: ${latest.status}`);
102052
102643
  return;
@@ -102058,7 +102649,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
102058
102649
  process.exitCode = 1;
102059
102650
  return;
102060
102651
  }
102061
- const record = JSON.parse(await readFile101(path198.resolve(projectRoot, file), "utf8"));
102652
+ const record = JSON.parse(await readFile101(path199.resolve(projectRoot, file), "utf8"));
102062
102653
  const result = validateRunRecord(record);
102063
102654
  console.log(result.valid ? "valid: yes" : "valid: no");
102064
102655
  for (const error of result.errors)
@@ -102083,7 +102674,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
102083
102674
  process.exitCode = 1;
102084
102675
  return;
102085
102676
  }
102086
- const file = path198.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
102677
+ const file = path199.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
102087
102678
  if (!await Bun.file(file).exists()) {
102088
102679
  console.error(`Run not found: ${runId}`);
102089
102680
  process.exitCode = 1;
@@ -102100,8 +102691,8 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
102100
102691
  process.exitCode = 1;
102101
102692
  return;
102102
102693
  }
102103
- const a = JSON.parse(await readFile101(path198.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
102104
- const b = JSON.parse(await readFile101(path198.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
102694
+ const a = JSON.parse(await readFile101(path199.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
102695
+ const b = JSON.parse(await readFile101(path199.join(metricsRoot(projectRoot), "runs", `${runB}.json`), "utf8"));
102105
102696
  const comparison = compareExecutionRuns(a, b);
102106
102697
  console.log(stableJson(comparison));
102107
102698
  return;
@@ -102135,8 +102726,8 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
102135
102726
  return;
102136
102727
  }
102137
102728
  const template = createPairedBenchmarkTemplate(taskIds);
102138
- await Bun.write(path198.resolve(projectRoot, out), stableJson(template));
102139
- console.log(`manifest: ${path198.relative(projectRoot, path198.resolve(projectRoot, out))}`);
102729
+ await Bun.write(path199.resolve(projectRoot, out), stableJson(template));
102730
+ console.log(`manifest: ${path199.relative(projectRoot, path199.resolve(projectRoot, out))}`);
102140
102731
  return;
102141
102732
  }
102142
102733
  if (subcommand === "benchmark" && args[1] === "run") {
@@ -102150,7 +102741,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
102150
102741
  process.exitCode = 1;
102151
102742
  return;
102152
102743
  }
102153
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, file), "utf8"));
102744
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, file), "utf8"));
102154
102745
  const input = Array.isArray(raw) ? raw : raw.runs ?? [];
102155
102746
  const result = validatePairedBenchmark(input);
102156
102747
  console.log(stableJson(result));
@@ -102162,7 +102753,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
102162
102753
  process.exitCode = 1;
102163
102754
  }
102164
102755
  async function loadAffectedSets(projectRoot, file) {
102165
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, file), "utf8"));
102756
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, file), "utf8"));
102166
102757
  const map = new Map;
102167
102758
  for (const entry of raw.targets ?? []) {
102168
102759
  if (typeof entry.target === "string")
@@ -102233,7 +102824,7 @@ async function runHarnessLayer(projectRoot, args, ladder) {
102233
102824
  let tasks;
102234
102825
  let model;
102235
102826
  try {
102236
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, resultsPath), "utf8"));
102827
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, resultsPath), "utf8"));
102237
102828
  tasks = raw.tasks ?? [];
102238
102829
  model = raw.model;
102239
102830
  } catch (error) {
@@ -102264,7 +102855,7 @@ async function runSafetyCompletionHonestyLayer(projectRoot, args, ladder) {
102264
102855
  let cases;
102265
102856
  let model;
102266
102857
  try {
102267
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, resultsPath), "utf8"));
102858
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, resultsPath), "utf8"));
102268
102859
  cases = raw.cases ?? [];
102269
102860
  model = raw.model;
102270
102861
  } catch (error) {
@@ -102289,7 +102880,7 @@ async function runSafetyFalsePremiseLayer(projectRoot, args, ladder) {
102289
102880
  let cases;
102290
102881
  let model;
102291
102882
  try {
102292
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, resultsPath), "utf8"));
102883
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, resultsPath), "utf8"));
102293
102884
  cases = raw.cases ?? [];
102294
102885
  model = raw.model;
102295
102886
  } catch (error) {
@@ -102314,7 +102905,7 @@ async function runSafetyContainmentLayer(projectRoot, args, ladder, caseClass) {
102314
102905
  let cases;
102315
102906
  let model;
102316
102907
  try {
102317
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, resultsPath), "utf8"));
102908
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, resultsPath), "utf8"));
102318
102909
  cases = raw.cases ?? [];
102319
102910
  model = raw.model;
102320
102911
  } catch (error) {
@@ -102376,12 +102967,12 @@ async function runGdgraphLayer(projectRoot, args, ladder) {
102376
102967
  }
102377
102968
  const manifests = buildOracleManifestsByGold(inputs, { ladder });
102378
102969
  if (outDir) {
102379
- const resolvedOut = path198.resolve(projectRoot, outDir);
102970
+ const resolvedOut = path199.resolve(projectRoot, outDir);
102380
102971
  for (const input of inputs) {
102381
102972
  for (const named of input.golds) {
102382
102973
  const bundle = buildEvidenceBundle({ target: input.target, system: input.system, gold: named.gold }, { ladder, goldReference: goldPathFor(named.kind), timestamp: new Date().toISOString() });
102383
- const dir = await persistEvidenceBundle(path198.join(resolvedOut, named.kind), bundle, ladder);
102384
- console.error(`bundle[${named.kind}]: ${path198.relative(projectRoot, dir)}`);
102974
+ const dir = await persistEvidenceBundle(path199.join(resolvedOut, named.kind), bundle, ladder);
102975
+ console.error(`bundle[${named.kind}]: ${path199.relative(projectRoot, dir)}`);
102385
102976
  }
102386
102977
  }
102387
102978
  }
@@ -102405,7 +102996,7 @@ async function runGdgraphLayer(projectRoot, args, ladder) {
102405
102996
  return allValid;
102406
102997
  }
102407
102998
  async function loadCoverageMap2(projectRoot, file) {
102408
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, file), "utf8"));
102999
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, file), "utf8"));
102409
103000
  return raw.coverageMap ?? {};
102410
103001
  }
102411
103002
  async function runTestingLayer(projectRoot, args, ladder) {
@@ -102442,7 +103033,7 @@ async function runTestingLayer(projectRoot, args, ladder) {
102442
103033
  return result.valid;
102443
103034
  }
102444
103035
  async function loadMemoryGoldK(projectRoot, file) {
102445
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, file), "utf8"));
103036
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, file), "utf8"));
102446
103037
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 3;
102447
103038
  }
102448
103039
  async function runMemoryLayer(projectRoot, args, ladder) {
@@ -102479,11 +103070,11 @@ async function runMemoryLayer(projectRoot, args, ladder) {
102479
103070
  return result.valid;
102480
103071
  }
102481
103072
  async function loadWikiGoldK(projectRoot, file) {
102482
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, file), "utf8"));
103073
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, file), "utf8"));
102483
103074
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 5;
102484
103075
  }
102485
103076
  async function loadWikiGroundedness(projectRoot, file) {
102486
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, file), "utf8"));
103077
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, file), "utf8"));
102487
103078
  const map = new Map;
102488
103079
  for (const entry of raw.targets ?? []) {
102489
103080
  if (typeof entry.target !== "string" || !Array.isArray(entry.scores) || entry.scores.length !== 3)
@@ -102539,7 +103130,7 @@ async function runWikiLayer(projectRoot, args, ladder) {
102539
103130
  return result.valid;
102540
103131
  }
102541
103132
  async function loadGdctxFacts(projectRoot, file) {
102542
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, file), "utf8"));
103133
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, file), "utf8"));
102543
103134
  const inputs = [];
102544
103135
  for (const entry of raw.inputs ?? []) {
102545
103136
  if (typeof entry.input === "string") {
@@ -102577,7 +103168,7 @@ async function collect(projectRoot, args) {
102577
103168
  process.exitCode = 1;
102578
103169
  return;
102579
103170
  }
102580
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, eventFile), "utf8"));
103171
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, eventFile), "utf8"));
102581
103172
  const events = Array.isArray(raw) ? raw : raw.events;
102582
103173
  const startedAt = optionValue(args, "--started-at") ?? events[0]?.timestamp_utc ?? new Date().toISOString();
102583
103174
  const finishedAt = optionValue(args, "--finished-at") ?? events.at(-1)?.timestamp_utc ?? startedAt;
@@ -102593,11 +103184,11 @@ async function collect(projectRoot, args) {
102593
103184
  parentRunId: optionValue(args, "--parent-run-id") ?? null
102594
103185
  });
102595
103186
  const result = await writeRunArtifacts(metricsRoot(projectRoot), record, { cwd: projectRoot });
102596
- console.log(`json: ${path198.relative(projectRoot, result.jsonPath)}`);
102597
- console.log(`markdown: ${path198.relative(projectRoot, result.markdownPath)}`);
103187
+ console.log(`json: ${path199.relative(projectRoot, result.jsonPath)}`);
103188
+ console.log(`markdown: ${path199.relative(projectRoot, result.markdownPath)}`);
102598
103189
  }
102599
103190
  function metricsRoot(projectRoot) {
102600
- return path198.join(projectRoot, ".metaproject", "data", "metrics");
103191
+ return path199.join(projectRoot, ".metaproject", "data", "metrics");
102601
103192
  }
102602
103193
  function printMetricsHelp() {
102603
103194
  console.log(`keryx metrics
@@ -103070,7 +103661,7 @@ Providers with a sanctioned subscription login: grok (SuperGrok), openai (ChatGP
103070
103661
  Claude Pro/Max, Gemini Google-account login, and DeepSeek OAuth are not offered.
103071
103662
  `);
103072
103663
  }
103073
- async function authCommand(args) {
103664
+ async function authCommand2(args) {
103074
103665
  const command = args[0];
103075
103666
  if (!command || command === "--help" || command === "-h") {
103076
103667
  printAuthHelp();
@@ -103464,7 +104055,7 @@ var CLI_ROUTES = {
103464
104055
  modules: modulesCommand,
103465
104056
  projects: projectsCommand,
103466
104057
  providers: providersCommand,
103467
- auth: authCommand,
104058
+ auth: authCommand2,
103468
104059
  serve: serveCommand,
103469
104060
  update: updateCommand,
103470
104061
  dashboard: dashboardCommand,