@mrciphersmith/keryx 0.2.96 → 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 +1052 -481
  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.96",
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",
@@ -71376,7 +71780,7 @@ function createMcpRuntime(options) {
71376
71780
  ];
71377
71781
  const kills = [];
71378
71782
  const env = options.env ?? process.env;
71379
- 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));
71380
71784
  const settled = startServers(launchable, connect).then((result) => {
71381
71785
  catalog = result.catalog;
71382
71786
  states = [...heldStates, ...result.servers].sort((a, b) => a.name.localeCompare(b.name));
@@ -71421,7 +71825,25 @@ function handshakeBudgetMs(server) {
71421
71825
  }
71422
71826
  var MAX_TIMEOUT_MS = 24 * 60 * 60 * 1000;
71423
71827
  var DEFAULT_HANDSHAKE_MS = 15000;
71424
- 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) {
71425
71847
  const target = remoteTargetProblem(server.raw, env);
71426
71848
  if (target !== undefined) {
71427
71849
  throw new Error(`server "${server.name}": ${target}`);
@@ -71430,8 +71852,11 @@ async function connectRemote(server, env, signal) {
71430
71852
  if (!resolved.ok) {
71431
71853
  throw new Error(`server "${server.name}": ${describeHollow(resolved.hollow)}`);
71432
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);
71433
71857
  return connectHttpMcpServer(server.url, {
71434
71858
  headers: resolved.headers,
71859
+ ...authProvider === undefined ? {} : { authProvider },
71435
71860
  handshakeTimeoutMs: handshakeBudgetMs(server),
71436
71861
  ...signal === undefined ? {} : { signal }
71437
71862
  });
@@ -71441,9 +71866,9 @@ function timeoutFor(server, rawName) {
71441
71866
  return;
71442
71867
  return server.tool_timeouts?.[rawName] ?? server.tool_timeout_sec;
71443
71868
  }
71444
- async function defaultConnect(server, env, signal, kills) {
71869
+ async function defaultConnect(server, env, signal, kills, configDir) {
71445
71870
  if (transportOf(server) === "http")
71446
- return connectRemote(server, env, signal);
71871
+ return connectRemote(server, env, signal, configDir);
71447
71872
  const command = server.command;
71448
71873
  if (command === undefined || command === "") {
71449
71874
  throw new Error(`server "${server.name}" sets neither command nor url`);
@@ -71456,22 +71881,22 @@ async function defaultConnect(server, env, signal, kills) {
71456
71881
  function defaultServerCwd(server, fallbackCwd = process.cwd()) {
71457
71882
  if (server.source !== "project")
71458
71883
  return fallbackCwd;
71459
- return path166.dirname(path166.dirname(server.file));
71884
+ return path167.dirname(path167.dirname(server.file));
71460
71885
  }
71461
71886
 
71462
71887
  // src/mcp-servers/store.ts
71463
71888
  init_config_dir();
71464
71889
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
71465
- import path167 from "path";
71890
+ import path168 from "path";
71466
71891
  var SERVER_NAME_PATTERN = /^[A-Za-z0-9_-]+$/;
71467
71892
  function userConfigFile(configDir) {
71468
- return path167.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers.json");
71893
+ return path168.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers.json");
71469
71894
  }
71470
71895
  function projectConfigFile(projectRoot) {
71471
- return path167.join(projectRoot, ".keryx", "mcp-servers.json");
71896
+ return path168.join(projectRoot, ".keryx", "mcp-servers.json");
71472
71897
  }
71473
71898
  function overlayFile(configDir) {
71474
- return path167.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers-disabled.json");
71899
+ return path168.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers-disabled.json");
71475
71900
  }
71476
71901
  function readForWrite(file) {
71477
71902
  const read = readConfigFile(file);
@@ -71510,7 +71935,7 @@ function writeDocument(file, doc, scope) {
71510
71935
  writeOwnerOnlyFileAtomic(file, body);
71511
71936
  return;
71512
71937
  }
71513
- mkdirSync6(path167.dirname(file), { recursive: true });
71938
+ mkdirSync6(path168.dirname(file), { recursive: true });
71514
71939
  writeFileSync7(file, body, { encoding: "utf8" });
71515
71940
  }
71516
71941
  function addServer(options) {
@@ -71614,7 +72039,9 @@ var MCP_CONSUMER_SUBCOMMANDS = [
71614
72039
  "disable",
71615
72040
  "trust",
71616
72041
  "untrust",
71617
- "doctor"
72042
+ "doctor",
72043
+ "auth",
72044
+ "logout"
71618
72045
  ];
71619
72046
  function isMcpConsumerSubcommand(value) {
71620
72047
  return MCP_CONSUMER_SUBCOMMANDS.includes(value ?? "");
@@ -71637,6 +72064,10 @@ async function runMcpConsumerCommand(subcommand, args, deps) {
71637
72064
  return trustCommand(args, deps, false);
71638
72065
  case "doctor":
71639
72066
  return doctorCommand(args, deps);
72067
+ case "auth":
72068
+ return authCommand(args, deps);
72069
+ case "logout":
72070
+ return logoutCommand(args, deps);
71640
72071
  }
71641
72072
  }
71642
72073
  function projectRootOf(deps) {
@@ -71778,6 +72209,178 @@ function addCommand(argv, deps) {
71778
72209
  deps.log(`Run \`keryx mcp doctor ${name}\` to check it connects.`);
71779
72210
  return 0;
71780
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
+ }
71781
72384
  function removeCommand(args, deps) {
71782
72385
  const own = splitAtSeparator(args).own;
71783
72386
  const unknown = own.filter((arg, index, all) => isUnknownFlag(arg, index, all));
@@ -71928,9 +72531,10 @@ async function doctorCommand(args, deps) {
71928
72531
  const env = deps.env ?? process.env;
71929
72532
  report = await runDoctor2(config, {
71930
72533
  only,
71931
- connect: deps.connect ?? ((server) => defaultConnect(server, env, dialling.signal, kills)),
72534
+ connect: deps.connect ?? ((server) => defaultConnect(server, env, dialling.signal, kills, deps.configDir)),
71932
72535
  heldForApproval: (server) => requiresApproval(server, approvals),
71933
- env
72536
+ env,
72537
+ configDir: deps.configDir
71934
72538
  });
71935
72539
  } finally {
71936
72540
  process.removeListener("SIGINT", onInt);
@@ -72037,12 +72641,12 @@ function positional3(args) {
72037
72641
 
72038
72642
  // src/commands/serve-mcp.ts
72039
72643
  init_args();
72040
- import path174 from "path";
72644
+ import path175 from "path";
72041
72645
 
72042
72646
  // src/mcp/discovery.ts
72043
72647
  init_fs();
72044
72648
  init_json();
72045
- import path168 from "path";
72649
+ import path169 from "path";
72046
72650
  var MODULE_MANIFEST_KEY = {
72047
72651
  gdgraph: "gdgraph",
72048
72652
  security: "security",
@@ -72104,7 +72708,7 @@ function buildDiscovery(manifest) {
72104
72708
  };
72105
72709
  }
72106
72710
  async function loadDiscovery(cwd) {
72107
- const manifestPath = path168.join(cwd, ".metaproject", "metaproject.json");
72711
+ const manifestPath = path169.join(cwd, ".metaproject", "metaproject.json");
72108
72712
  if (!await pathExists(manifestPath)) {
72109
72713
  return buildDiscovery({});
72110
72714
  }
@@ -72177,7 +72781,7 @@ init_store2();
72177
72781
  init_fs();
72178
72782
  import { createHash as createHash34, randomUUID as randomUUID17 } from "crypto";
72179
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";
72180
- import path169 from "path";
72784
+ import path170 from "path";
72181
72785
 
72182
72786
  // src/sac/policy-experiment.ts
72183
72787
  import { createHash as createHash33, createHmac as createHmac2 } from "crypto";
@@ -72533,7 +73137,7 @@ function hasReportIntegrity(report) {
72533
73137
  return reportHashPattern.test(reportDigest) && sha2565(body) === reportDigest;
72534
73138
  }
72535
73139
  async function resolvePolicySelection(workspaceRoot, canonicalFallback) {
72536
- const configPath = path169.join(workspaceRoot, ...policyExperimentConfigPath);
73140
+ const configPath = path170.join(workspaceRoot, ...policyExperimentConfigPath);
72537
73141
  let configJson;
72538
73142
  try {
72539
73143
  const raw = await readFile93(configPath, "utf8").catch((error) => {
@@ -72635,7 +73239,7 @@ async function diagnosePolicyReadiness(workspaceRoot) {
72635
73239
  const integrityReady = steps.every((entry) => entry.step === "activation-flags" || entry.status === "pass");
72636
73240
  return Object.freeze({ configPresent, enabled, killSwitch, integrityReady, candidateWouldActivate: integrityReady && enabled && !killSwitch, steps: Object.freeze([...steps]) });
72637
73241
  };
72638
- const configPath = path169.join(workspaceRoot, ...policyExperimentConfigPath);
73242
+ const configPath = path170.join(workspaceRoot, ...policyExperimentConfigPath);
72639
73243
  let configJson;
72640
73244
  try {
72641
73245
  const raw = await readFile93(configPath, "utf8").catch((error) => {
@@ -72890,9 +73494,9 @@ class FwkReadService {
72890
73494
  const recordedAt = nowIso2(now);
72891
73495
  const id = `receipt-${randomUUID17().replace(/-/g, "").slice(0, 16)}`;
72892
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"}` } : {} };
72893
- const ledger = path169.join(this.options.canonical.workspaceRoot, ".metaproject", "context-operations", "access-receipts.jsonl");
72894
- const checkpointPath = path169.join(path169.dirname(ledger), "access-receipts.checkpoint.json");
72895
- 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 });
72896
73500
  return withFileLock(`${ledger}.lock`, async () => {
72897
73501
  const state = await resolveLedgerState(ledger, checkpointPath, this.options.verifyReceiptLedger ?? verifyAccessReceiptLedger);
72898
73502
  const receipt = sealAccessReceipt(base, state.headHash);
@@ -73004,7 +73608,7 @@ init_workspace_service();
73004
73608
  init_sac();
73005
73609
  import { appendFile as appendFile8, mkdir as mkdir59, readFile as readFile94 } from "fs/promises";
73006
73610
  import { randomUUID as randomUUID18 } from "crypto";
73007
- import path170 from "path";
73611
+ import path171 from "path";
73008
73612
 
73009
73613
  class CollaborationServiceError extends Error {
73010
73614
  code;
@@ -73020,7 +73624,7 @@ class CollaborationService {
73020
73624
  this.input = input;
73021
73625
  }
73022
73626
  file(id) {
73023
- return path170.join(this.input.workspaceRoot, ".metaproject", "workspaces", id, "activity.jsonl");
73627
+ return path171.join(this.input.workspaceRoot, ".metaproject", "workspaces", id, "activity.jsonl");
73024
73628
  }
73025
73629
  async overview(input) {
73026
73630
  const manifest = await this.input.workspaces.show(input);
@@ -73057,7 +73661,7 @@ class CollaborationService {
73057
73661
  occurredAt: (this.input.now ?? (() => new Date))().toISOString(),
73058
73662
  ...suppliedHandoff ? { handoff: { ...suppliedHandoff, from: actor.subject } } : {}
73059
73663
  });
73060
- 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 });
73061
73665
  await appendFile8(this.file(manifest.id), `${JSON.stringify(activity)}
73062
73666
  `, { mode: 384 });
73063
73667
  return normalizeCollaborationResult(activity);
@@ -73112,9 +73716,9 @@ init_workspace_service();
73112
73716
  init_fs();
73113
73717
  init_machine_wrap_up();
73114
73718
  import { mkdir as mkdir60, readdir as readdir31, readFile as readFile95 } from "fs/promises";
73115
- import path171 from "path";
73719
+ import path172 from "path";
73116
73720
  function externalSlatesDir(cwd) {
73117
- return path171.join(cwd, ".keryx", "external-slates");
73721
+ return path172.join(cwd, ".keryx", "external-slates");
73118
73722
  }
73119
73723
  var EXTERNAL_SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
73120
73724
  function assertValidExternalSessionId(externalSessionId) {
@@ -73124,14 +73728,14 @@ function assertValidExternalSessionId(externalSessionId) {
73124
73728
  }
73125
73729
  function externalSlatePath(cwd, externalSessionId) {
73126
73730
  assertValidExternalSessionId(externalSessionId);
73127
- return path171.join(externalSlatesDir(cwd), `${externalSessionId}.json`);
73731
+ return path172.join(externalSlatesDir(cwd), `${externalSessionId}.json`);
73128
73732
  }
73129
73733
  function externalSlateLockPath(cwd, externalSessionId) {
73130
73734
  return `${externalSlatePath(cwd, externalSessionId)}.lock`;
73131
73735
  }
73132
73736
  function externalSlateEvidenceDir(cwd, externalSessionId) {
73133
73737
  assertValidExternalSessionId(externalSessionId);
73134
- return path171.join(externalSlatesDir(cwd), externalSessionId);
73738
+ return path172.join(externalSlatesDir(cwd), externalSessionId);
73135
73739
  }
73136
73740
  async function readExternalSlate(cwd, externalSessionId) {
73137
73741
  try {
@@ -73776,7 +74380,7 @@ function buildToolRegistry() {
73776
74380
  // src/mcp/resources.ts
73777
74381
  init_fs();
73778
74382
  init_contained_read();
73779
- import path172 from "path";
74383
+ import path173 from "path";
73780
74384
  import { lstat as lstat4, readdir as readdir32, realpath as realpath7, stat as stat17 } from "fs/promises";
73781
74385
  var URI_PREFIX = "metaproject://";
73782
74386
  function mimeForPath(filePath) {
@@ -73789,13 +74393,13 @@ function mimeForPath(filePath) {
73789
74393
  return "text/plain";
73790
74394
  }
73791
74395
  function dataRoot3(cwd) {
73792
- return path172.join(cwd, ".metaproject", "data");
74396
+ return path173.join(cwd, ".metaproject", "data");
73793
74397
  }
73794
74398
  function wikiRoot(cwd) {
73795
- return path172.join(cwd, ".metaproject", "wiki");
74399
+ return path173.join(cwd, ".metaproject", "wiki");
73796
74400
  }
73797
74401
  function memoryRoot2(cwd) {
73798
- return path172.join(cwd, ".metaproject", "memory");
74402
+ return path173.join(cwd, ".metaproject", "memory");
73799
74403
  }
73800
74404
  var MAX_RESOURCE_BYTES = 8 * 1024 * 1024;
73801
74405
  async function walkFiles(root, ownerRoot = root, ancestors = new Set) {
@@ -73821,7 +74425,7 @@ async function walkFiles(root, ownerRoot = root, ancestors = new Set) {
73821
74425
  return [];
73822
74426
  }
73823
74427
  for (const entry of entries) {
73824
- const full = path172.join(root, entry.name);
74428
+ const full = path173.join(root, entry.name);
73825
74429
  const info = await stat17(full).catch(() => null);
73826
74430
  if (!info || !isPathInside(ownerReal, await realpath7(full).catch(() => "")))
73827
74431
  continue;
@@ -73849,9 +74453,9 @@ async function listArtifacts(cwd) {
73849
74453
  if (!moduleEntry.isDirectory()) {
73850
74454
  continue;
73851
74455
  }
73852
- const artifactsDir = path172.join(base, moduleEntry.name, "artifacts");
74456
+ const artifactsDir = path173.join(base, moduleEntry.name, "artifacts");
73853
74457
  for (const file of await walkFiles(artifactsDir)) {
73854
- const rel = toPosix(path172.relative(artifactsDir, file));
74458
+ const rel = toPosix(path173.relative(artifactsDir, file));
73855
74459
  const relPath = `${moduleEntry.name}/${rel}`;
73856
74460
  listings.push({
73857
74461
  uri: `${URI_PREFIX}artifacts/${relPath}`,
@@ -73865,7 +74469,7 @@ async function listArtifacts(cwd) {
73865
74469
  async function listUnderRoot(cwd, cls, root) {
73866
74470
  const listings = [];
73867
74471
  for (const file of await walkFiles(root)) {
73868
- const rel = toPosix(path172.relative(root, file));
74472
+ const rel = toPosix(path173.relative(root, file));
73869
74473
  listings.push({
73870
74474
  uri: `${URI_PREFIX}${cls}/${rel}`,
73871
74475
  name: rel,
@@ -73915,12 +74519,12 @@ function resolveConfined(cwd, cls, relPath) {
73915
74519
  if (moduleName.includes("..") || moduleName.length === 0) {
73916
74520
  return null;
73917
74521
  }
73918
- const root = path172.join(dataRoot3(cwd), moduleName, "artifacts");
73919
- const absolute = path172.resolve(root, rest);
74522
+ const root = path173.join(dataRoot3(cwd), moduleName, "artifacts");
74523
+ const absolute = path173.resolve(root, rest);
73920
74524
  return isPathInside(root, absolute) ? { root, absolute } : null;
73921
74525
  }
73922
74526
  const root = cls === "wiki" ? wikiRoot(cwd) : memoryRoot2(cwd);
73923
- const absolute = path172.resolve(root, relPath);
74527
+ const absolute = path173.resolve(root, relPath);
73924
74528
  return isPathInside(root, absolute) ? { root, absolute } : null;
73925
74529
  }
73926
74530
  async function readResource(cwd, roots, uri) {
@@ -74131,7 +74735,7 @@ async function serveMcpCommand(args = [], cwd = process.cwd()) {
74131
74735
  return;
74132
74736
  }
74133
74737
  const http = args.includes("--http");
74134
- const projectRoot = path174.resolve(resolveServeRoot(optionValue(args, "--cwd"), cwd, process.env));
74738
+ const projectRoot = path175.resolve(resolveServeRoot(optionValue(args, "--cwd"), cwd, process.env));
74135
74739
  try {
74136
74740
  await serveMcp({ cwd: projectRoot, http });
74137
74741
  } catch (error) {
@@ -74207,7 +74811,9 @@ function printMcpHelp() {
74207
74811
  "keryx mcp add <name> --transport http <url> # a remote one",
74208
74812
  "keryx mcp remove|enable|disable <name>",
74209
74813
  "keryx mcp trust|untrust <name> # approve a project-scoped server",
74210
- "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"
74211
74817
  ]);
74212
74818
  heading("Publishing keryx itself (the retired `keryx mcp` spellings)");
74213
74819
  helpUsage([
@@ -74233,14 +74839,14 @@ function printMcpHelp() {
74233
74839
  // src/commands/status.ts
74234
74840
  init_fs();
74235
74841
  init_json();
74236
- import path175 from "path";
74842
+ import path176 from "path";
74237
74843
  async function statusCommand(args = []) {
74238
74844
  if (args.includes("--help") || args.includes("-h")) {
74239
74845
  printHelp16();
74240
74846
  return;
74241
74847
  }
74242
- const root = path175.join(process.cwd(), ".metaproject");
74243
- const manifestPath = path175.join(root, "metaproject.json");
74848
+ const root = path176.join(process.cwd(), ".metaproject");
74849
+ const manifestPath = path176.join(root, "metaproject.json");
74244
74850
  if (!await pathExists(root)) {
74245
74851
  console.log("Metaproject: not initialized");
74246
74852
  console.log("Run: keryx init");
@@ -74280,7 +74886,7 @@ Reports the workspace root and one enabled/disabled line per module. Use
74280
74886
 
74281
74887
  // src/commands/harness.ts
74282
74888
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "fs";
74283
- import path178 from "path";
74889
+ import path179 from "path";
74284
74890
  import { randomUUID as randomUUID20 } from "crypto";
74285
74891
 
74286
74892
  // src/security/harness-scan.ts
@@ -75353,7 +75959,7 @@ ${command.argv.join(" ")}`);
75353
75959
  }
75354
75960
 
75355
75961
  // src/harness/process/sandbox/profile.ts
75356
- import path176 from "path";
75962
+ import path177 from "path";
75357
75963
  var DEFAULT_SECRET_SUBPATHS = [
75358
75964
  ".ssh",
75359
75965
  ".gnupg",
@@ -75378,7 +75984,7 @@ function defaultReadDenyList(home) {
75378
75984
  if (!home) {
75379
75985
  return [];
75380
75986
  }
75381
- return DEFAULT_SECRET_SUBPATHS.map((sub) => path176.join(home, sub));
75987
+ return DEFAULT_SECRET_SUBPATHS.map((sub) => path177.join(home, sub));
75382
75988
  }
75383
75989
  function defaultSandboxProfile(cwd, tmpDir, home) {
75384
75990
  return {
@@ -75398,10 +76004,10 @@ function dedupe3(values) {
75398
76004
  init_config_dir();
75399
76005
  init_shell_config();
75400
76006
  import { existsSync as existsSync26 } from "fs";
75401
- import path177 from "path";
76007
+ import path178 from "path";
75402
76008
  var SECRET_KEY_PATTERN2 = /api[_-]?key|secret|token|password|credential/i;
75403
76009
  function sandboxConfigPath(dir) {
75404
- return path177.join(path177.dirname(shellConfigPath(dir)), "sandbox.json");
76010
+ return path178.join(path178.dirname(shellConfigPath(dir)), "sandbox.json");
75405
76011
  }
75406
76012
  function isShellDefault(v) {
75407
76013
  return v === "off" || v === "workspace" || v === "strict" || v === "1";
@@ -76520,7 +77126,7 @@ async function harnessExec(args, deps) {
76520
77126
  env: commandEnv,
76521
77127
  cwd
76522
77128
  };
76523
- const worktreeRoot = path178.parse(path178.resolve(cwd, commandPath)).root || cwd;
77129
+ const worktreeRoot = path179.parse(path179.resolve(cwd, commandPath)).root || cwd;
76524
77130
  const budget = {
76525
77131
  reservationId: idSeq(),
76526
77132
  maxRuntimeMs: maxRuntimeMs ?? EXEC_DEFAULT_RUNTIME_MS
@@ -78466,7 +79072,7 @@ function workspaceReadTool(cwd) {
78466
79072
  // src/harness/tool/builtin/workspace-lifecycle-tool.ts
78467
79073
  import { randomUUID as randomUUID22 } from "crypto";
78468
79074
  import { writeFile as writeFile52 } from "fs/promises";
78469
- import path179 from "path";
79075
+ import path180 from "path";
78470
79076
  init_slate();
78471
79077
  init_store3();
78472
79078
  init_guard2();
@@ -78612,7 +79218,7 @@ function workspaceProposeTool(cwd, getSessionDir) {
78612
79218
  return { output: `workspace_propose: unrecognized 'kind' \u2014 expected one of: ${PROPOSAL_KINDS.join(", ")}`, isError: true };
78613
79219
  }
78614
79220
  const explicitSessionId = typeof input.sessionId === "string" && input.sessionId.length > 0 ? input.sessionId : undefined;
78615
- const sessionRef = explicitSessionId ?? path179.basename(getSessionDir() ?? "");
79221
+ const sessionRef = explicitSessionId ?? path180.basename(getSessionDir() ?? "");
78616
79222
  if (sessionRef.length === 0) {
78617
79223
  return { output: "workspace_propose: no 'sessionId' given and no active session in this run", isError: true };
78618
79224
  }
@@ -78784,7 +79390,7 @@ init_shell_config();
78784
79390
  init_command_risk();
78785
79391
  init_shell_syntax();
78786
79392
  import { existsSync as existsSync30 } from "fs";
78787
- import path180 from "path";
79393
+ import path181 from "path";
78788
79394
  import { createHash as createHash41 } from "crypto";
78789
79395
  var PREFIX_BANNED = new Set([
78790
79396
  "sh",
@@ -78989,7 +79595,7 @@ function emptyShellPermissions() {
78989
79595
  return { allow: [] };
78990
79596
  }
78991
79597
  function shellPermissionsPath(dir) {
78992
- return path180.join(path180.dirname(shellConfigPath(dir)), "permissions.json");
79598
+ return path181.join(path181.dirname(shellConfigPath(dir)), "permissions.json");
78993
79599
  }
78994
79600
  function loadShellPermissionsWithAudit(dir) {
78995
79601
  try {
@@ -79027,7 +79633,7 @@ function loadShellPermissions(dir) {
79027
79633
  function saveShellPermissions(perms, dir, options = {}) {
79028
79634
  try {
79029
79635
  const file = shellPermissionsPath(dir);
79030
- ensureKeryxConfigDir(path180.dirname(file));
79636
+ ensureKeryxConfigDir(path181.dirname(file));
79031
79637
  const cleaned = Array.from(new Set(perms.allow.map((p) => p.trim()).filter((p) => p.length > 0)));
79032
79638
  const body = {
79033
79639
  allow: options.skipValidation === true ? cleaned : cleaned.filter((p) => validateShellPattern(p).ok)
@@ -79466,12 +80072,12 @@ function remoteRequest(providerId, endpoint, query, key) {
79466
80072
  // src/lib/search-config.ts
79467
80073
  init_config_dir();
79468
80074
  import { existsSync as existsSync31 } from "fs";
79469
- import path181 from "path";
80075
+ import path182 from "path";
79470
80076
  function searchConfigPath(dir) {
79471
- return path181.join(keryxConfigDir(dir), "search-providers.json");
80077
+ return path182.join(keryxConfigDir(dir), "search-providers.json");
79472
80078
  }
79473
80079
  function searchCredentialPath(dir) {
79474
- return path181.join(keryxConfigDir(dir), "search-credentials.json");
80080
+ return path182.join(keryxConfigDir(dir), "search-credentials.json");
79475
80081
  }
79476
80082
  function readJson5(file) {
79477
80083
  try {
@@ -79600,7 +80206,7 @@ function createDefaultSearchProviderController(configDir) {
79600
80206
  import { createHash as createHash42, randomUUID as randomUUID24 } from "crypto";
79601
80207
  import { mkdtemp, rm as rm13 } from "fs/promises";
79602
80208
  import { tmpdir as tmpdir4 } from "os";
79603
- import path182 from "path";
80209
+ import path183 from "path";
79604
80210
  init_metaproject_adapter();
79605
80211
  init_ledger();
79606
80212
  init_orchestrate();
@@ -79863,8 +80469,8 @@ function createSpawnSubagentTool(deps) {
79863
80469
  let dispatchId = "";
79864
80470
  let closing = false;
79865
80471
  try {
79866
- ephemeralDir = await mkdtemp(path182.join(tmpdir4(), "keryx-subagent-slate-"));
79867
- dispatchId = path182.basename(ephemeralDir);
80472
+ ephemeralDir = await mkdtemp(path183.join(tmpdir4(), "keryx-subagent-slate-"));
80473
+ dispatchId = path183.basename(ephemeralDir);
79868
80474
  openedChildSlate = await openSlate({
79869
80475
  dir: ephemeralDir,
79870
80476
  cwd,
@@ -80107,7 +80713,7 @@ import { randomUUID as randomUUID25 } from "crypto";
80107
80713
  import { execFile as execFile4 } from "child_process";
80108
80714
  import { mkdir as mkdir61 } from "fs/promises";
80109
80715
  import { tmpdir as tmpdir6 } from "os";
80110
- import path184 from "path";
80716
+ import path185 from "path";
80111
80717
  import { promisify as promisify4 } from "util";
80112
80718
 
80113
80719
  // src/harness/child/git-worktree-port.ts
@@ -80359,7 +80965,7 @@ function readRuntimeBlock(dispatch) {
80359
80965
  // src/harness/external/runtime.ts
80360
80966
  import { mkdtemp as mkdtemp2, rm as rm14, writeFile as writeFile53 } from "fs/promises";
80361
80967
  import { tmpdir as tmpdir5 } from "os";
80362
- import path183 from "path";
80968
+ import path184 from "path";
80363
80969
 
80364
80970
  // src/harness/external/prompt.ts
80365
80971
  var EXTERNAL_RUNTIME_DIRECTIVE = [
@@ -81402,8 +82008,8 @@ async function prepareResultSchema() {
81402
82008
  try {
81403
82009
  const schema = await loadSchema("subagent-result");
81404
82010
  const schemaText = JSON.stringify(schema, null, 2);
81405
- schemaDir = await mkdtemp2(path183.join(tmpdir5(), "keryx-external-result-schema-"));
81406
- 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");
81407
82013
  await writeFile53(schemaPath, schemaText, "utf8");
81408
82014
  return { ok: true, schema, schemaText, schemaPath, schemaDir };
81409
82015
  } catch (error) {
@@ -81660,7 +82266,7 @@ async function createRunExternal(options) {
81660
82266
  }
81661
82267
  const config = gate.config;
81662
82268
  const idSeq = options.idSeq ?? (() => randomUUID25());
81663
- const worktreesDir = options.worktreesDir ?? path184.join(tmpdir6(), "keryx-external-worktrees");
82269
+ const worktreesDir = options.worktreesDir ?? path185.join(tmpdir6(), "keryx-external-worktrees");
81664
82270
  const spawn = options.spawn ?? createBunSpawnPort();
81665
82271
  const worktree = options.worktree ?? ensuringParentDir(createGitWorktreePort({ repoRoot: options.cwd, worktreesDir }), worktreesDir);
81666
82272
  const readWorkingDiff = options.readWorkingDiff ?? (() => defaultWorkingDiff(options.cwd));
@@ -81970,7 +82576,7 @@ class LiveMarkdownBlock {
81970
82576
  // src/tui/theme.ts
81971
82577
  init_config_dir();
81972
82578
  import { existsSync as existsSync32 } from "fs";
81973
- import path185 from "path";
82579
+ import path186 from "path";
81974
82580
  var THEME_NAMES = ["groknight", "grokday", "tokyonight", "keryx"];
81975
82581
  var THEME_IDS = ["auto", ...THEME_NAMES];
81976
82582
  var DEFAULT_THEME_ID = "groknight";
@@ -82100,7 +82706,7 @@ function onThemeChange(listener) {
82100
82706
  };
82101
82707
  }
82102
82708
  function tuiConfigPath(dir) {
82103
- return path185.join(keryxConfigDir(dir), "tui.json");
82709
+ return path186.join(keryxConfigDir(dir), "tui.json");
82104
82710
  }
82105
82711
  function loadPersistedThemeId(dir) {
82106
82712
  try {
@@ -82157,7 +82763,7 @@ init_workspace_service();
82157
82763
  init_service8();
82158
82764
  init_store2();
82159
82765
  init_fs();
82160
- import path186 from "path";
82766
+ import path187 from "path";
82161
82767
  import { readFile as readFile97 } from "fs/promises";
82162
82768
  var POSITIVE_INTEGER = /^[1-9][0-9]*$/;
82163
82769
  var DEFAULT_AUTO_GOAL_ROUNDS = 8;
@@ -82233,7 +82839,7 @@ function getFlowService() {
82233
82839
  async function autoProvisionFlow(cwd, goalText) {
82234
82840
  const service = getFlowService();
82235
82841
  const result = await service.init({ cwd, title: goalText });
82236
- const acFile = path186.join(cwd, result.dir, "acceptance-criteria.md");
82842
+ const acFile = path187.join(cwd, result.dir, "acceptance-criteria.md");
82237
82843
  await writeFileAtomic(acFile, [
82238
82844
  "# Acceptance Criteria",
82239
82845
  "",
@@ -83784,7 +84390,7 @@ init_proposal_lifecycle();
83784
84390
  init_workspace_service();
83785
84391
  import { randomUUID as randomUUID27 } from "crypto";
83786
84392
  import { readdir as readdir34, stat as stat18 } from "fs/promises";
83787
- import path187 from "path";
84393
+ import path188 from "path";
83788
84394
 
83789
84395
  // src/sac/lifecycle-flag.ts
83790
84396
  init_store();
@@ -83943,7 +84549,7 @@ async function readExternalUnboundCandidates(cwd) {
83943
84549
  }
83944
84550
  return groups;
83945
84551
  }, []));
83946
- const metaPath = path187.join(extDir, `${id}.json`);
84552
+ const metaPath = path188.join(extDir, `${id}.json`);
83947
84553
  if (await pathExists(unboundDismissedReceiptPath(metaPath)))
83948
84554
  continue;
83949
84555
  candidates.push({
@@ -83957,7 +84563,7 @@ async function readExternalUnboundCandidates(cwd) {
83957
84563
  return candidates;
83958
84564
  }
83959
84565
  async function readNewestUnboundCandidateForExternal(cwd, externalSessionId) {
83960
- const evidenceDir = path187.join(externalSlatesDir(cwd), externalSessionId);
84566
+ const evidenceDir = path188.join(externalSlatesDir(cwd), externalSessionId);
83961
84567
  let entries;
83962
84568
  try {
83963
84569
  entries = (await readdir34(evidenceDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
@@ -83966,7 +84572,7 @@ async function readNewestUnboundCandidateForExternal(cwd, externalSessionId) {
83966
84572
  }
83967
84573
  entries.sort();
83968
84574
  for (let i = entries.length - 1;i >= 0; i--) {
83969
- const evidencePath = path187.join(evidenceDir, entries[i]);
84575
+ const evidencePath = path188.join(evidenceDir, entries[i]);
83970
84576
  if (await pathExists(unboundDismissedReceiptPath(evidencePath)))
83971
84577
  continue;
83972
84578
  const result = readConfigFile(evidencePath);
@@ -84009,7 +84615,7 @@ var SESSION_ATTRIBUTION_SLACK_MS = 5 * 60000;
84009
84615
  var MTIME_CLOCK_SKEW_TOLERANCE_MS = 5000;
84010
84616
  async function collectReceiptTargets(cwd, owner) {
84011
84617
  const targets = new Set;
84012
- const workspacesDir = path187.join(cwd, ".metaproject", "workspaces");
84618
+ const workspacesDir = path188.join(cwd, ".metaproject", "workspaces");
84013
84619
  let workspaceIds;
84014
84620
  try {
84015
84621
  workspaceIds = (await readdir34(workspacesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
@@ -84017,7 +84623,7 @@ async function collectReceiptTargets(cwd, owner) {
84017
84623
  return targets;
84018
84624
  }
84019
84625
  for (const workspaceId of workspaceIds) {
84020
- const receiptsDir = path187.join(workspacesDir, workspaceId, `${owner}-write-receipts`);
84626
+ const receiptsDir = path188.join(workspacesDir, workspaceId, `${owner}-write-receipts`);
84021
84627
  let files;
84022
84628
  try {
84023
84629
  files = (await readdir34(receiptsDir)).filter((name) => name.endsWith(".json"));
@@ -84025,7 +84631,7 @@ async function collectReceiptTargets(cwd, owner) {
84025
84631
  continue;
84026
84632
  }
84027
84633
  for (const file of files) {
84028
- const result = readConfigFile(path187.join(receiptsDir, file));
84634
+ const result = readConfigFile(path188.join(receiptsDir, file));
84029
84635
  if (!result.ok)
84030
84636
  continue;
84031
84637
  try {
@@ -84068,7 +84674,7 @@ async function findSkillFiles(dir) {
84068
84674
  }
84069
84675
  const found = [];
84070
84676
  for (const entry of entries) {
84071
- const full = path187.join(dir, entry.name);
84677
+ const full = path188.join(dir, entry.name);
84072
84678
  if (entry.isDirectory()) {
84073
84679
  found.push(...await findSkillFiles(full));
84074
84680
  } else if (entry.isFile() && entry.name === "SKILL.md") {
@@ -84127,10 +84733,10 @@ async function detectUnreviewedSacPathChanges(cwd, sessions) {
84127
84733
  changedAt: attribution.changedAt
84128
84734
  });
84129
84735
  }
84130
- const sacSkillsDir = path187.join(cwd, ".metaproject", "project-skills", "sac");
84736
+ const sacSkillsDir = path188.join(cwd, ".metaproject", "project-skills", "sac");
84131
84737
  const skillFiles = await findSkillFiles(sacSkillsDir);
84132
84738
  for (const absolutePath of skillFiles) {
84133
- 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("/");
84134
84740
  if (skillReceipts.has(targetRef))
84135
84741
  continue;
84136
84742
  const attribution = await attributeToSession(absolutePath, scoped);
@@ -84148,12 +84754,12 @@ async function detectUnreviewedSacPathChanges(cwd, sessions) {
84148
84754
  return items;
84149
84755
  }
84150
84756
  async function isSlateEngaged(dir) {
84151
- if (await pathExists(path187.join(dir, "slate.json")))
84757
+ if (await pathExists(path188.join(dir, "slate.json")))
84152
84758
  return true;
84153
- if (await pathExists(path187.join(dir, "terminal-state.json")))
84759
+ if (await pathExists(path188.join(dir, "terminal-state.json")))
84154
84760
  return true;
84155
84761
  try {
84156
- const entries = await readdir34(path187.join(dir, "slate-archive"));
84762
+ const entries = await readdir34(path188.join(dir, "slate-archive"));
84157
84763
  return entries.length > 0;
84158
84764
  } catch {
84159
84765
  return false;
@@ -84167,7 +84773,7 @@ async function safeReadSlate(dir) {
84167
84773
  }
84168
84774
  }
84169
84775
  async function readTerminalState(dir) {
84170
- const result = readConfigFile(path187.join(dir, "terminal-state.json"));
84776
+ const result = readConfigFile(path188.join(dir, "terminal-state.json"));
84171
84777
  if (!result.ok) {
84172
84778
  return;
84173
84779
  }
@@ -84191,7 +84797,7 @@ async function resolveUnboundCandidateTarget(cwd, target) {
84191
84797
  if (session === undefined) {
84192
84798
  throw new Error(`No session or evidence path matches "${target}"`);
84193
84799
  }
84194
- const archiveDir = path187.join(sessionDir(session.projectPath, session.id), "slate-archive");
84800
+ const archiveDir = path188.join(sessionDir(session.projectPath, session.id), "slate-archive");
84195
84801
  let entries;
84196
84802
  try {
84197
84803
  entries = (await readdir34(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
@@ -84201,7 +84807,7 @@ async function resolveUnboundCandidateTarget(cwd, target) {
84201
84807
  if (entries.length === 0)
84202
84808
  throw new Error(`No unbound-candidate artifacts for session ${session.id}`);
84203
84809
  entries.sort();
84204
- return path187.join(archiveDir, entries[entries.length - 1]);
84810
+ return path188.join(archiveDir, entries[entries.length - 1]);
84205
84811
  }
84206
84812
  async function dismissUnboundByTarget(cwd, target, reason) {
84207
84813
  const evidencePath = await resolveUnboundCandidateTarget(cwd, target);
@@ -84227,7 +84833,7 @@ async function dismissUnboundCandidate(candidatePath, reason) {
84227
84833
  return { removed: candidatePath, receipt: receiptPath };
84228
84834
  }
84229
84835
  async function readNewestUnboundCandidate(dir) {
84230
- const archiveDir = path187.join(dir, "slate-archive");
84836
+ const archiveDir = path188.join(dir, "slate-archive");
84231
84837
  let entries;
84232
84838
  try {
84233
84839
  entries = (await readdir34(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
@@ -84236,7 +84842,7 @@ async function readNewestUnboundCandidate(dir) {
84236
84842
  }
84237
84843
  entries.sort();
84238
84844
  for (let i = entries.length - 1;i >= 0; i--) {
84239
- const evidencePath = path187.join(archiveDir, entries[i]);
84845
+ const evidencePath = path188.join(archiveDir, entries[i]);
84240
84846
  if (await pathExists(unboundDismissedReceiptPath(evidencePath)))
84241
84847
  continue;
84242
84848
  const result = readConfigFile(evidencePath);
@@ -84266,7 +84872,7 @@ function isFailureOutcome(g) {
84266
84872
  return g.outcome === "error" || g.outcome === "no_credential" || g.outcome === "conflict";
84267
84873
  }
84268
84874
  async function readNewestWrapUpOutcome(dir) {
84269
- const archiveDir = path187.join(dir, "slate-archive");
84875
+ const archiveDir = path188.join(dir, "slate-archive");
84270
84876
  let entries;
84271
84877
  try {
84272
84878
  entries = (await readdir34(archiveDir)).filter((name) => name.endsWith("-wrap-up-outcome.json"));
@@ -84275,7 +84881,7 @@ async function readNewestWrapUpOutcome(dir) {
84275
84881
  }
84276
84882
  entries.sort();
84277
84883
  for (let i = entries.length - 1;i >= 0; i--) {
84278
- const evidencePath = path187.join(archiveDir, entries[i]);
84884
+ const evidencePath = path188.join(archiveDir, entries[i]);
84279
84885
  const result = readConfigFile(evidencePath);
84280
84886
  if (!result.ok) {
84281
84887
  continue;
@@ -87756,41 +88362,6 @@ init_patch_risk();
87756
88362
  init_catalog();
87757
88363
  init_grants();
87758
88364
  init_login();
87759
-
87760
- // src/lib/oauth/open-url.ts
87761
- import { spawn as spawn5 } from "child_process";
87762
- function browserOpenPlan(url, platform = process.platform, env = process.env) {
87763
- if (url.length === 0) {
87764
- return;
87765
- }
87766
- if (platform === "darwin") {
87767
- return { cmd: "open", args: [url] };
87768
- }
87769
- if (platform === "win32") {
87770
- return { cmd: "cmd", args: ["/c", "start", "", url] };
87771
- }
87772
- if (typeof env.DISPLAY === "string" && env.DISPLAY.length > 0) {
87773
- return { cmd: "xdg-open", args: [url] };
87774
- }
87775
- if (typeof env.WAYLAND_DISPLAY === "string" && env.WAYLAND_DISPLAY.length > 0) {
87776
- return { cmd: "xdg-open", args: [url] };
87777
- }
87778
- return;
87779
- }
87780
- function openVerificationUrl(url, deps = {}) {
87781
- const plan = browserOpenPlan(url, deps.platform ?? process.platform, deps.env ?? process.env);
87782
- if (plan === undefined) {
87783
- return;
87784
- }
87785
- const spawnFn = deps.spawn ?? spawn5;
87786
- try {
87787
- const child = spawnFn(plan.cmd, plan.args, { stdio: "ignore", detached: true });
87788
- child.on("error", () => {});
87789
- child.unref();
87790
- } catch {}
87791
- }
87792
-
87793
- // src/tui/tui-shell.ts
87794
88365
  init_shell_config();
87795
88366
  init_provider_config();
87796
88367
 
@@ -87826,10 +88397,10 @@ function describeElicitationPrompt(tool, inputJson) {
87826
88397
  // src/lib/permission-mode-config.ts
87827
88398
  init_permission_mode();
87828
88399
  init_config_dir();
87829
- import path188 from "path";
88400
+ import path189 from "path";
87830
88401
  var EMPTY3 = { schemaVersion: 1, projects: {} };
87831
88402
  function permissionModeConfigPath(dir) {
87832
- return path188.join(keryxConfigDir(dir), "permission-mode.json");
88403
+ return path189.join(keryxConfigDir(dir), "permission-mode.json");
87833
88404
  }
87834
88405
  function withRegistryLock2(dir, fn) {
87835
88406
  return withFileLock2(`${permissionModeConfigPath(dir)}.lock`, fn, {
@@ -88347,7 +88918,7 @@ function showComposerChoice(otui, r, dock, request) {
88347
88918
  // src/lib/version-check.ts
88348
88919
  init_config_dir();
88349
88920
  init_fs();
88350
- import path189 from "path";
88921
+ import path190 from "path";
88351
88922
  var REGISTRY_URL = "https://registry.npmjs.org/@mrciphersmith%2Fkeryx/latest";
88352
88923
  var FIXED_INSTALL_COMMAND = "npm install -g @mrciphersmith/keryx@latest";
88353
88924
  var RESPONSE_BODY_LIMIT_BYTES = 64 * 1024;
@@ -88549,7 +89120,7 @@ async function checkVersion(options) {
88549
89120
  const now = options.now ?? Date.now;
88550
89121
  const timestamp = now();
88551
89122
  const configDir = ensureKeryxConfigDir(options.cacheDir);
88552
- const cacheFile = path189.join(configDir, "version-check.json");
89123
+ const cacheFile = path190.join(configDir, "version-check.json");
88553
89124
  const cache = parseCache(cacheFile);
88554
89125
  if (cache?.latestVersion !== undefined && cache.successAt !== undefined && timestamp - cache.successAt >= 0 && timestamp - cache.successAt < SUCCESS_CACHE_TTL_MS) {
88555
89126
  return resultFor(options.currentVersion, current, cache.latestVersion, "cache");
@@ -97125,7 +97696,7 @@ Shell:
97125
97696
  init_fs();
97126
97697
  import { readFile as readFile98 } from "fs/promises";
97127
97698
  import { stdin } from "process";
97128
- import path190 from "path";
97699
+ import path191 from "path";
97129
97700
  var MODULES = [
97130
97701
  { name: "gdgraph", flag: "--no-gdgraph", desc: "code graph, symbols, affected context", defaultEnabled: true },
97131
97702
  { name: "gdctx", flag: "--no-gdctx", desc: "token-aware command/read output", defaultEnabled: true },
@@ -97171,8 +97742,8 @@ async function modulesCommand(args = []) {
97171
97742
  return;
97172
97743
  }
97173
97744
  const wantsJson = args.includes("--json") && (sub === undefined || sub === "status" || sub === "list" || sub === "--json");
97174
- const metaprojectRoot = path190.join(process.cwd(), ".metaproject");
97175
- const manifestPath = path190.join(metaprojectRoot, "metaproject.json");
97745
+ const metaprojectRoot = path191.join(process.cwd(), ".metaproject");
97746
+ const manifestPath = path191.join(metaprojectRoot, "metaproject.json");
97176
97747
  if (!await pathExists(manifestPath)) {
97177
97748
  if (wantsJson) {
97178
97749
  console.log(JSON.stringify({ schemaVersion: 1, error: "not-initialized", modules: [] }, null, 2));
@@ -97291,7 +97862,7 @@ init_serve_config();
97291
97862
 
97292
97863
  // src/lib/serve-credential.ts
97293
97864
  init_config_dir();
97294
- 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";
97295
97866
  import {
97296
97867
  chmodSync as chmodSync4,
97297
97868
  closeSync as closeSync3,
@@ -97303,9 +97874,9 @@ import {
97303
97874
  unlinkSync as unlinkSync3,
97304
97875
  writeFileSync as writeFileSync9
97305
97876
  } from "fs";
97306
- import path191 from "path";
97877
+ import path192 from "path";
97307
97878
  function serveCredentialPath(dir) {
97308
- return path191.join(keryxConfigDir(dir), "serve-credentials.json");
97879
+ return path192.join(keryxConfigDir(dir), "serve-credentials.json");
97309
97880
  }
97310
97881
  function constantTimeEqual(a, b) {
97311
97882
  const width = Math.max(a.length, b.length);
@@ -97411,8 +97982,8 @@ function withServeCredentialLock(dir, fn, onWaiting) {
97411
97982
  });
97412
97983
  }
97413
97984
  function mintRecord(now) {
97414
- const token = randomBytes4(32).toString("base64url");
97415
- const salt = randomBytes4(32).toString("hex");
97985
+ const token = randomBytes5(32).toString("base64url");
97986
+ const salt = randomBytes5(32).toString("hex");
97416
97987
  return {
97417
97988
  token,
97418
97989
  record: { id: randomUUID30(), algorithm: "sha256", salt, hash: hashToken(salt, token), createdAt: now }
@@ -97539,22 +98110,22 @@ class AuthFailureThrottle {
97539
98110
  init_config_dir();
97540
98111
  import { createHash as createHash44 } from "crypto";
97541
98112
  import { existsSync as existsSync34, readdirSync as readdirSync3, rmSync as rmSync2 } from "fs";
97542
- import path192 from "path";
98113
+ import path193 from "path";
97543
98114
  var MAX_TURN_EVENTS = 1e4;
97544
98115
  function turnsRoot(dir) {
97545
- return path192.join(keryxConfigDir(dir), "turns");
98116
+ return path193.join(keryxConfigDir(dir), "turns");
97546
98117
  }
97547
98118
  function turnDir(turnId, dir) {
97548
- return path192.join(turnsRoot(dir), turnId);
98119
+ return path193.join(turnsRoot(dir), turnId);
97549
98120
  }
97550
98121
  function keyPath(project, idempotencyKey, dir) {
97551
98122
  const projectBytes = Buffer.byteLength(project, "utf8");
97552
98123
  const digest = createHash44("sha256").update(`${projectBytes}:${project}\x00${idempotencyKey}`, "utf8").digest("hex");
97553
- return path192.join(turnsRoot(dir), "keys", `${digest}.json`);
98124
+ return path193.join(turnsRoot(dir), "keys", `${digest}.json`);
97554
98125
  }
97555
98126
  function legacyKeyPath(idempotencyKey, dir) {
97556
98127
  const digest = createHash44("sha256").update(idempotencyKey, "utf8").digest("hex");
97557
- return path192.join(turnsRoot(dir), "keys", `${digest}.json`);
98128
+ return path193.join(turnsRoot(dir), "keys", `${digest}.json`);
97558
98129
  }
97559
98130
  function adoptLegacyClaim(project, idempotencyKey, dir) {
97560
98131
  const legacy = legacyKeyPath(idempotencyKey, dir);
@@ -97618,7 +98189,7 @@ function ensureTurnDir(turnId, dir) {
97618
98189
  }
97619
98190
  function createTurnRecord(record, dir) {
97620
98191
  ensureTurnDir(record.turnId, dir);
97621
- 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)}
97622
98193
  `);
97623
98194
  }
97624
98195
  function appendTurnEvent(event, dir, opts) {
@@ -97627,12 +98198,12 @@ function appendTurnEvent(event, dir, opts) {
97627
98198
  }
97628
98199
  const line = JSON.stringify(event);
97629
98200
  try {
97630
- appendOwnerOnlyLine(path192.join(turnDir(event.turnId, dir), "events.jsonl"), line);
98201
+ appendOwnerOnlyLine(path193.join(turnDir(event.turnId, dir), "events.jsonl"), line);
97631
98202
  } catch (error) {
97632
98203
  if (error?.code !== "ENOENT") {
97633
98204
  throw error;
97634
98205
  }
97635
- appendOwnerOnlyLine(path192.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
98206
+ appendOwnerOnlyLine(path193.join(ensureTurnDir(event.turnId, dir), "events.jsonl"), line);
97636
98207
  }
97637
98208
  return true;
97638
98209
  }
@@ -97640,7 +98211,7 @@ function readTurnEvents(turnId, after = -1, dir) {
97640
98211
  if (!isTurnId(turnId)) {
97641
98212
  return { ok: false, reason: "not-a-turn-id" };
97642
98213
  }
97643
- const read = readTurnFile(path192.join(turnDir(turnId, dir), "events.jsonl"));
98214
+ const read = readTurnFile(path193.join(turnDir(turnId, dir), "events.jsonl"));
97644
98215
  if (!read.ok) {
97645
98216
  if (isDefiniteAbsence2(read.reason)) {
97646
98217
  return { ok: true, value: [] };
@@ -97668,7 +98239,7 @@ function readTurnRecord(turnId, dir) {
97668
98239
  if (!isTurnId(turnId)) {
97669
98240
  return { ok: false, reason: "not-a-turn-id" };
97670
98241
  }
97671
- const read = readTurnFile(path192.join(turnDir(turnId, dir), "turn.json"));
98242
+ const read = readTurnFile(path193.join(turnDir(turnId, dir), "turn.json"));
97672
98243
  if (!read.ok) {
97673
98244
  return { ok: false, reason: read.reason };
97674
98245
  }
@@ -97687,7 +98258,7 @@ function finishTurn(turnId, result, dir) {
97687
98258
  if (!record.ok) {
97688
98259
  return false;
97689
98260
  }
97690
- 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)}
97691
98262
  `);
97692
98263
  return true;
97693
98264
  }
@@ -97733,7 +98304,7 @@ function releaseIdempotencyKey(project, idempotencyKey, turnId, dir) {
97733
98304
 
97734
98305
  // src/lib/serve-turn.ts
97735
98306
  import { randomUUID as randomUUID31 } from "crypto";
97736
- import path193 from "path";
98307
+ import path194 from "path";
97737
98308
  init_service();
97738
98309
  var REMOTE_ORIGIN = "remote:http";
97739
98310
  var MAX_PROMPT_CHARS = 32000;
@@ -97802,9 +98373,9 @@ function isUuid(value) {
97802
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);
97803
98374
  }
97804
98375
  function resolveProject(declared, dir) {
97805
- const wanted = path193.resolve(declared);
98376
+ const wanted = path194.resolve(declared);
97806
98377
  for (const entry of listProjects(dir, () => {})) {
97807
- if (path193.resolve(entry.path) === wanted) {
98378
+ if (path194.resolve(entry.path) === wanted) {
97808
98379
  return { ok: true, project: entry.path };
97809
98380
  }
97810
98381
  }
@@ -98840,7 +99411,7 @@ function printHelp19() {
98840
99411
  import { spawn as spawn6 } from "child_process";
98841
99412
  import { chmod as chmod4, mkdir as mkdir62, readFile as readFile99, readdir as readdir35, writeFile as writeFile54 } from "fs/promises";
98842
99413
  import { access as access4, constants as constants2, existsSync as existsSync35 } from "fs";
98843
- import path194 from "path";
99414
+ import path195 from "path";
98844
99415
  import { fileURLToPath as fileURLToPath7 } from "url";
98845
99416
  init_config2();
98846
99417
  init_config();
@@ -98857,8 +99428,8 @@ async function updateCommand(args = []) {
98857
99428
  return;
98858
99429
  }
98859
99430
  const projectRoot = process.cwd();
98860
- const metaprojectRoot = path194.join(projectRoot, ".metaproject");
98861
- 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)}/`);
98862
99433
  if (!await pathExists(metaprojectRoot)) {
98863
99434
  console.log(` ${style.red(symbols.cross)} Metaproject is not initialized.`);
98864
99435
  console.log(` ${style.cyan(symbols.arrow)} Run ${style.cyan("keryx init")} first.`);
@@ -98919,7 +99490,7 @@ async function updateCommand(args = []) {
98919
99490
  nextSteps(steps);
98920
99491
  }
98921
99492
  async function previewServiceFiles(projectRoot, options) {
98922
- const metaprojectRoot = path194.join(projectRoot, ".metaproject");
99493
+ const metaprojectRoot = path195.join(projectRoot, ".metaproject");
98923
99494
  const manifestState = await readManifest5(metaprojectRoot);
98924
99495
  const manifest = manifestState.manifest;
98925
99496
  const enableTasks = moduleEnabled2(manifest, "tasks") || !options.noTasks;
@@ -98948,12 +99519,12 @@ async function previewServiceFiles(projectRoot, options) {
98948
99519
  });
98949
99520
  }
98950
99521
  async function refreshServiceFiles(projectRoot, options) {
98951
- const metaprojectRoot = path194.join(projectRoot, ".metaproject");
99522
+ const metaprojectRoot = path195.join(projectRoot, ".metaproject");
98952
99523
  const manifestState = await readManifest5(metaprojectRoot);
98953
99524
  const manifest = manifestState.manifest;
98954
99525
  const recoveredManifest = !manifestState.exists || !manifestState.valid;
98955
99526
  if (manifestState.migrated) {
98956
- 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)}
98957
99528
  `, "utf8");
98958
99529
  }
98959
99530
  const enableGdgraph = moduleEnabled2(manifest, "gdgraph");
@@ -98990,9 +99561,9 @@ async function refreshServiceFiles(projectRoot, options) {
98990
99561
  enableSecurity,
98991
99562
  enableSac
98992
99563
  });
98993
- await writeTextIfChanged4(path194.join(metaprojectRoot, "core", "README.md"), renderMetaprojectCoreReadme());
98994
- await writeTextIfChanged4(path194.join(metaprojectRoot, "hooks", "README.md"), renderHooksReadme());
98995
- 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 }));
98996
99567
  await writeRoutingEntrypointPair(metaprojectRoot, {
98997
99568
  enableGdgraph,
98998
99569
  enableGdctx,
@@ -99015,7 +99586,7 @@ async function refreshServiceFiles(projectRoot, options) {
99015
99586
  }
99016
99587
  }
99017
99588
  });
99018
- await writeTextIfChanged4(path194.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
99589
+ await writeTextIfChanged4(path195.join(metaprojectRoot, "keryx-dashboard.html"), renderMetaprojectDashboardHtml({
99019
99590
  enableGdgraph,
99020
99591
  enableGdctx,
99021
99592
  enableGdwiki,
@@ -99027,7 +99598,7 @@ async function refreshServiceFiles(projectRoot, options) {
99027
99598
  enableSecurity,
99028
99599
  data: dashboardData
99029
99600
  }));
99030
- await writeTextIfMissing4(path194.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
99601
+ await writeTextIfMissing4(path195.join(metaprojectRoot, "README.md"), renderMetaprojectReadme({
99031
99602
  enableGdgraph,
99032
99603
  enableGdctx,
99033
99604
  enableGdwiki,
@@ -99040,31 +99611,31 @@ async function refreshServiceFiles(projectRoot, options) {
99040
99611
  }));
99041
99612
  if (enableGdgraph) {
99042
99613
  await installGdgraphCoreScripts2(metaprojectRoot);
99043
- await writeTextIfChanged4(path194.join(metaprojectRoot, "modules", "gdgraph.md"), renderGdgraphManifest());
99044
- await writeTextIfChanged4(path194.join(metaprojectRoot, "core", "gdgraph", "README.md"), renderGdgraphCoreReadme());
99045
- 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());
99046
99617
  await seedAssetsLock(metaprojectRoot);
99047
99618
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
99048
99619
  await installManagedHook2(projectRoot, "post-commit", "gdgraph-post-commit", renderGdgraphPostCommitHook());
99049
99620
  }
99050
99621
  }
99051
99622
  if (enableGdctx) {
99052
- await writeTextIfMissing4(path194.join(metaprojectRoot, "gdctx.config.json"), renderGdctxConfig());
99053
- await writeTextIfChanged4(path194.join(metaprojectRoot, "modules", "gdctx.md"), renderGdctxManifest());
99054
- await writeTextIfChanged4(path194.join(metaprojectRoot, "core", "gdctx", "README.md"), renderGdctxCoreReadme());
99055
- 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());
99056
99627
  }
99057
99628
  if (enableGdwiki) {
99058
- await writeTextIfMissing4(path194.join(metaprojectRoot, "wiki", "templates", "page.md"), renderWikiPageTemplate());
99059
- await writeTextIfChanged4(path194.join(metaprojectRoot, "modules", "gdwiki.md"), renderGdwikiManifest());
99060
- 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());
99061
99632
  if (manifest.modules?.gdgraph?.hooks?.gitPostCommit) {
99062
99633
  await installManagedHook2(projectRoot, "post-commit", "gdwiki-post-commit", renderGdwikiPostCommitHook());
99063
99634
  }
99064
99635
  }
99065
99636
  if (enableSac) {
99066
- await writeTextIfMissing4(path194.join(metaprojectRoot, "modules", "sac.md"), renderSacManifest());
99067
- 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());
99068
99639
  }
99069
99640
  if (enableGdskills) {
99070
99641
  await installGdskills(metaprojectRoot, gdskillsProfile, { createDataDirs: false });
@@ -99073,25 +99644,25 @@ async function refreshServiceFiles(projectRoot, options) {
99073
99644
  }
99074
99645
  }
99075
99646
  if (enableHealth) {
99076
- await writeTextIfMissing4(path194.join(metaprojectRoot, "health.config.json"), renderHealthConfig());
99077
- await writeTextIfChanged4(path194.join(metaprojectRoot, "modules", "health.md"), renderHealthManifest());
99078
- await writeTextIfChanged4(path194.join(metaprojectRoot, "core", "health", "README.md"), renderHealthCoreReadme());
99079
- 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());
99080
99651
  if (manifest.modules?.health?.hooks?.gitPostCommit) {
99081
99652
  await installManagedHook2(projectRoot, "post-commit", "health-post-commit", renderHealthPostCommitHook());
99082
99653
  }
99083
99654
  }
99084
99655
  if (enableTesting) {
99085
- await writeTextIfMissing4(path194.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
99656
+ await writeTextIfMissing4(path195.join(metaprojectRoot, "testing.config.json"), renderTestingConfig({
99086
99657
  postCommitRefresh: Boolean(manifest.modules?.testing?.hooks?.gitPostCommit),
99087
99658
  prePushGate: Boolean(manifest.modules?.testing?.hooks?.prePush)
99088
99659
  }));
99089
- await writeTextIfChanged4(path194.join(metaprojectRoot, "modules", "testing.md"), renderTestingManifest());
99090
- await writeTextIfChanged4(path194.join(metaprojectRoot, "core", "testing", "README.md"), renderTestingCoreReadme());
99091
- 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());
99092
99663
  if (enableGdwiki) {
99093
- await writeTextIfMissing4(path194.join(metaprojectRoot, "wiki", "testing", "README.md"), renderTestingWikiReadme());
99094
- 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());
99095
99666
  }
99096
99667
  if (manifest.modules?.testing?.hooks?.gitPostCommit) {
99097
99668
  await installManagedHook2(projectRoot, "post-commit", "testing-post-commit", renderTestingPostCommitHook());
@@ -99104,24 +99675,24 @@ async function refreshServiceFiles(projectRoot, options) {
99104
99675
  await installManagedHook2(projectRoot, "post-commit", "metaproject-dashboard-post-commit", renderMetaprojectDashboardPostCommitHook());
99105
99676
  }
99106
99677
  if (enableMemory) {
99107
- await writeTextIfMissing4(path194.join(metaprojectRoot, "memory.config.json"), renderMemoryConfig());
99108
- await writeTextIfMissing4(path194.join(metaprojectRoot, "memory", "templates", "entry.md"), renderMemoryEntryTemplate());
99109
- await writeTextIfChanged4(path194.join(metaprojectRoot, "modules", "memory.md"), renderMemoryManifest());
99110
- await writeTextIfChanged4(path194.join(metaprojectRoot, "core", "memory", "README.md"), renderMemoryCoreReadme());
99111
- 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());
99112
99683
  }
99113
99684
  if (enableTasks) {
99114
- await writeTextIfChanged4(path194.join(metaprojectRoot, "flows", "README.md"), renderFlowsReadme());
99115
- await writeTextIfChanged4(path194.join(metaprojectRoot, "modules", "tasks.md"), renderTasksManifest());
99116
- await writeTextIfChanged4(path194.join(metaprojectRoot, "skills", "flow", "SKILL.md"), renderFlowSkillRouter());
99117
- await writeTextIfChanged4(path194.join(metaprojectRoot, "skills", "flow", "init.md"), renderFlowInitSkill());
99118
- await writeTextIfChanged4(path194.join(metaprojectRoot, "skills", "flow", "manage.md"), renderFlowManageSkill());
99119
- 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());
99120
99691
  }
99121
99692
  if (enableSecurity) {
99122
- await writeTextIfMissing4(path194.join(metaprojectRoot, "security.config.json"), renderSecurityConfig());
99123
- await writeTextIfChanged4(path194.join(metaprojectRoot, "modules", "security.md"), renderSecurityManifest());
99124
- 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());
99125
99696
  if (manifest.modules?.security?.hooks?.prePush) {
99126
99697
  await installManagedHook2(projectRoot, "pre-push", "security-pre-push", renderSecurityPrePushHook());
99127
99698
  }
@@ -99172,13 +99743,13 @@ async function refreshServiceFiles(projectRoot, options) {
99172
99743
  };
99173
99744
  }
99174
99745
  async function buildDashboard(projectRoot = process.cwd()) {
99175
- const metaprojectRoot = path194.join(projectRoot, ".metaproject");
99746
+ const metaprojectRoot = path195.join(projectRoot, ".metaproject");
99176
99747
  if (!await pathExists(metaprojectRoot)) {
99177
99748
  throw new Error("Metaproject is not initialized. Run: keryx init");
99178
99749
  }
99179
99750
  const manifest = (await readManifest5(metaprojectRoot)).manifest;
99180
99751
  const data = await collectDashboardData(metaprojectRoot);
99181
- const dashboardPath = path194.join(metaprojectRoot, "keryx-dashboard.html");
99752
+ const dashboardPath = path195.join(metaprojectRoot, "keryx-dashboard.html");
99182
99753
  await writeTextIfChanged4(dashboardPath, renderMetaprojectDashboardHtml({
99183
99754
  enableGdgraph: moduleEnabled2(manifest, "gdgraph"),
99184
99755
  enableGdctx: moduleEnabled2(manifest, "gdctx"),
@@ -99198,7 +99769,7 @@ async function shouldInstallDashboardPostCommitHook(projectRoot, manifest) {
99198
99769
  if (Object.values(modules).some((module) => Boolean(module.hooks?.gitPostCommit))) {
99199
99770
  return true;
99200
99771
  }
99201
- const hookPath = path194.join(projectRoot, ".git", "hooks", "post-commit");
99772
+ const hookPath = path195.join(projectRoot, ".git", "hooks", "post-commit");
99202
99773
  if (!await pathExists(hookPath)) {
99203
99774
  return false;
99204
99775
  }
@@ -99218,11 +99789,11 @@ async function collectDashboardData(metaprojectRoot) {
99218
99789
  if (testing) {
99219
99790
  data.testing = testing;
99220
99791
  }
99221
- const wiki = await collectMarkdownPages(path194.join(metaprojectRoot, "wiki"), "wiki");
99792
+ const wiki = await collectMarkdownPages(path195.join(metaprojectRoot, "wiki"), "wiki");
99222
99793
  if (wiki.length > 0) {
99223
99794
  data.wiki = { pages: wiki };
99224
99795
  }
99225
- const memory = await collectMarkdownPages(path194.join(metaprojectRoot, "memory"), "memory");
99796
+ const memory = await collectMarkdownPages(path195.join(metaprojectRoot, "memory"), "memory");
99226
99797
  if (memory.length > 0) {
99227
99798
  data.memory = { entries: memory };
99228
99799
  }
@@ -99237,7 +99808,7 @@ async function collectDashboardData(metaprojectRoot) {
99237
99808
  return data;
99238
99809
  }
99239
99810
  async function collectTasksDashboardData(metaprojectRoot) {
99240
- const flowsRoot = path194.join(metaprojectRoot, "flows");
99811
+ const flowsRoot = path195.join(metaprojectRoot, "flows");
99241
99812
  if (!await pathExists(flowsRoot)) {
99242
99813
  return null;
99243
99814
  }
@@ -99249,7 +99820,7 @@ async function collectTasksDashboardData(metaprojectRoot) {
99249
99820
  }
99250
99821
  const flows = [];
99251
99822
  for (const dir of dirEntries) {
99252
- const flowPath = path194.join(flowsRoot, dir, "flow.json");
99823
+ const flowPath = path195.join(flowsRoot, dir, "flow.json");
99253
99824
  if (!await pathExists(flowPath)) {
99254
99825
  continue;
99255
99826
  }
@@ -99257,7 +99828,7 @@ async function collectTasksDashboardData(metaprojectRoot) {
99257
99828
  const flow = JSON.parse(await readFile99(flowPath, "utf8"));
99258
99829
  const tasks = Array.isArray(flow.tasks) ? flow.tasks : [];
99259
99830
  let acTotal = 0;
99260
- const acPath = path194.join(flowsRoot, dir, "acceptance-criteria.md");
99831
+ const acPath = path195.join(flowsRoot, dir, "acceptance-criteria.md");
99261
99832
  if (await pathExists(acPath)) {
99262
99833
  const acContent = await readFile99(acPath, "utf8");
99263
99834
  acTotal = (acContent.match(/^- AC\d+:/gm) ?? []).length;
@@ -99311,7 +99882,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
99311
99882
  "data/testing/context.md"
99312
99883
  ];
99313
99884
  for (const href of staticHrefs) {
99314
- const filePath = path194.join(metaprojectRoot, ...href.split("/"));
99885
+ const filePath = path195.join(metaprojectRoot, ...href.split("/"));
99315
99886
  if (!await pathExists(filePath)) {
99316
99887
  continue;
99317
99888
  }
@@ -99328,7 +99899,7 @@ async function collectDashboardDocs(metaprojectRoot, wiki, memory) {
99328
99899
  return docs;
99329
99900
  }
99330
99901
  async function collectHealthDashboardData(metaprojectRoot) {
99331
- const reportPath = path194.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
99902
+ const reportPath = path195.join(metaprojectRoot, "data", "health", "artifacts", "latest.json");
99332
99903
  if (!await pathExists(reportPath)) {
99333
99904
  return;
99334
99905
  }
@@ -99437,8 +100008,8 @@ function metricToScope(metric) {
99437
100008
  };
99438
100009
  }
99439
100010
  async function collectGraphDashboardData(metaprojectRoot) {
99440
- const nodesPath = path194.join(metaprojectRoot, "data", "gdgraph", "storage", "nodes.jsonl");
99441
- 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");
99442
100013
  if (!await pathExists(nodesPath) || !await pathExists(edgesPath)) {
99443
100014
  return;
99444
100015
  }
@@ -99489,8 +100060,8 @@ async function collectGraphDashboardData(metaprojectRoot) {
99489
100060
  };
99490
100061
  }
99491
100062
  async function collectTestingDashboardData(metaprojectRoot) {
99492
- const reportPath = path194.join(metaprojectRoot, "data", "testing", "artifacts", "latest.json");
99493
- 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");
99494
100065
  if (await pathExists(reportPath)) {
99495
100066
  const report = JSON.parse(await readFile99(reportPath, "utf8"));
99496
100067
  const totalTests = numberOrUndefined(report.total);
@@ -99519,7 +100090,7 @@ async function collectMarkdownPages(root, hrefPrefix) {
99519
100090
  const files = await listMarkdownFiles(root);
99520
100091
  const pages = [];
99521
100092
  for (const filePath of files.slice(0, 40)) {
99522
- const relativePath = path194.relative(root, filePath).split(path194.sep).join("/");
100093
+ const relativePath = path195.relative(root, filePath).split(path195.sep).join("/");
99523
100094
  if (relativePath === "index.md" || relativePath.startsWith("templates/")) {
99524
100095
  continue;
99525
100096
  }
@@ -99540,7 +100111,7 @@ async function listMarkdownFiles(root) {
99540
100111
  const entries = await readdir35(root, { withFileTypes: true });
99541
100112
  const files = [];
99542
100113
  for (const entry of entries) {
99543
- const fullPath = path194.join(root, entry.name);
100114
+ const fullPath = path195.join(root, entry.name);
99544
100115
  if (entry.isDirectory()) {
99545
100116
  files.push(...await listMarkdownFiles(fullPath));
99546
100117
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -99588,7 +100159,7 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
99588
100159
  const manifest = {
99589
100160
  schemaVersion: 1,
99590
100161
  standardVersion: STANDARD_VERSION,
99591
- name: `${path194.basename(path194.dirname(metaprojectRoot))}-metaproject`,
100162
+ name: `${path195.basename(path195.dirname(metaprojectRoot))}-metaproject`,
99592
100163
  createdBy: "keryx",
99593
100164
  profiles: computeProfiles(enabledModuleKeys),
99594
100165
  paths: {
@@ -99678,11 +100249,11 @@ async function writeRecoveredManifest(metaprojectRoot, modules) {
99678
100249
  metaproject: ".metaproject/index.md"
99679
100250
  }
99680
100251
  };
99681
- 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)}
99682
100253
  `, "utf8");
99683
100254
  }
99684
100255
  async function enableTasksInManifest(metaprojectRoot) {
99685
- const manifestPath = path194.join(metaprojectRoot, "metaproject.json");
100256
+ const manifestPath = path195.join(metaprojectRoot, "metaproject.json");
99686
100257
  if (!await pathExists(manifestPath)) {
99687
100258
  return;
99688
100259
  }
@@ -99705,7 +100276,7 @@ async function enableTasksInManifest(metaprojectRoot) {
99705
100276
  `, "utf8");
99706
100277
  }
99707
100278
  async function updateManifestAgentEntrypoints(metaprojectRoot, ruleSources) {
99708
- const manifestPath = path194.join(metaprojectRoot, "metaproject.json");
100279
+ const manifestPath = path195.join(metaprojectRoot, "metaproject.json");
99709
100280
  if (!await pathExists(manifestPath)) {
99710
100281
  return;
99711
100282
  }
@@ -99741,72 +100312,72 @@ async function updateRuntime(projectRoot) {
99741
100312
  }
99742
100313
  }
99743
100314
  async function findRuntimeRoot(projectRoot) {
99744
- const projectRuntime = path194.join(projectRoot, ".metaproject", "runtime", "keryx");
99745
- if (await pathExists(path194.join(projectRuntime, ".git"))) {
100315
+ const projectRuntime = path195.join(projectRoot, ".metaproject", "runtime", "keryx");
100316
+ if (await pathExists(path195.join(projectRuntime, ".git"))) {
99746
100317
  return projectRuntime;
99747
100318
  }
99748
100319
  const home = process.env.HOME;
99749
100320
  if (!home) {
99750
100321
  return null;
99751
100322
  }
99752
- const globalRuntime = path194.join(home, ".keryx", "keryx");
99753
- if (await pathExists(path194.join(globalRuntime, ".git"))) {
100323
+ const globalRuntime = path195.join(home, ".keryx", "keryx");
100324
+ if (await pathExists(path195.join(globalRuntime, ".git"))) {
99754
100325
  return globalRuntime;
99755
100326
  }
99756
100327
  return null;
99757
100328
  }
99758
100329
  async function createServiceDirs(metaprojectRoot, modules) {
99759
100330
  const dirs = [
99760
- path194.join(metaprojectRoot, "core"),
99761
- path194.join(metaprojectRoot, "hooks", "post-update.d"),
99762
- path194.join(metaprojectRoot, "modules"),
99763
- path194.join(metaprojectRoot, "rules"),
99764
- 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"),
99765
100336
  ...modules.enableGdgraph ? [
99766
- path194.join(metaprojectRoot, "core", "gdgraph"),
99767
- path194.join(metaprojectRoot, "skills", "gdgraph")
100337
+ path195.join(metaprojectRoot, "core", "gdgraph"),
100338
+ path195.join(metaprojectRoot, "skills", "gdgraph")
99768
100339
  ] : [],
99769
100340
  ...modules.enableGdctx ? [
99770
- path194.join(metaprojectRoot, "core", "gdctx"),
99771
- path194.join(metaprojectRoot, "skills", "gdctx")
100341
+ path195.join(metaprojectRoot, "core", "gdctx"),
100342
+ path195.join(metaprojectRoot, "skills", "gdctx")
99772
100343
  ] : [],
99773
100344
  ...modules.enableGdwiki ? [
99774
- path194.join(metaprojectRoot, "skills", "gdwiki"),
99775
- path194.join(metaprojectRoot, "wiki", "templates")
100345
+ path195.join(metaprojectRoot, "skills", "gdwiki"),
100346
+ path195.join(metaprojectRoot, "wiki", "templates")
99776
100347
  ] : [],
99777
100348
  ...modules.enableHealth ? [
99778
- path194.join(metaprojectRoot, "core", "health"),
99779
- path194.join(metaprojectRoot, "skills", "health")
100349
+ path195.join(metaprojectRoot, "core", "health"),
100350
+ path195.join(metaprojectRoot, "skills", "health")
99780
100351
  ] : [],
99781
100352
  ...modules.enableTesting ? [
99782
- path194.join(metaprojectRoot, "core", "testing"),
99783
- path194.join(metaprojectRoot, "skills", "testing")
100353
+ path195.join(metaprojectRoot, "core", "testing"),
100354
+ path195.join(metaprojectRoot, "skills", "testing")
99784
100355
  ] : [],
99785
100356
  ...modules.enableMemory ? [
99786
- path194.join(metaprojectRoot, "core", "memory"),
99787
- path194.join(metaprojectRoot, "skills", "memory"),
99788
- path194.join(metaprojectRoot, "memory", "templates")
100357
+ path195.join(metaprojectRoot, "core", "memory"),
100358
+ path195.join(metaprojectRoot, "skills", "memory"),
100359
+ path195.join(metaprojectRoot, "memory", "templates")
99789
100360
  ] : [],
99790
100361
  ...modules.enableTasks ? [
99791
- path194.join(metaprojectRoot, "flows"),
99792
- path194.join(metaprojectRoot, "skills", "flow")
100362
+ path195.join(metaprojectRoot, "flows"),
100363
+ path195.join(metaprojectRoot, "skills", "flow")
99793
100364
  ] : [],
99794
100365
  ...modules.enableSecurity ? [
99795
- path194.join(metaprojectRoot, "core", "security")
100366
+ path195.join(metaprojectRoot, "core", "security")
99796
100367
  ] : [],
99797
100368
  ...modules.enableSac ? [
99798
- path194.join(metaprojectRoot, "skills", "sac")
100369
+ path195.join(metaprojectRoot, "skills", "sac")
99799
100370
  ] : []
99800
100371
  ];
99801
100372
  await Promise.all(dirs.map((dir) => mkdir62(dir, { recursive: true })));
99802
100373
  }
99803
100374
  async function installGdgraphCoreScripts2(metaprojectRoot) {
99804
- const gdgraphCoreRoot = path194.join(metaprojectRoot, "core", "gdgraph");
100375
+ const gdgraphCoreRoot = path195.join(metaprojectRoot, "core", "gdgraph");
99805
100376
  await mkdir62(gdgraphCoreRoot, { recursive: true });
99806
100377
  for (const file of GDGRAPH_CORE_SOURCES) {
99807
- await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path194.join(gdgraphCoreRoot, file));
100378
+ await copyFileIfChanged2(runtimeSourcePath2(`../gdgraph/${file}`), path195.join(gdgraphCoreRoot, file));
99808
100379
  }
99809
- await writeTextIfChanged4(path194.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
100380
+ await writeTextIfChanged4(path195.join(gdgraphCoreRoot, "cli.ts"), renderGdgraphCoreCli());
99810
100381
  }
99811
100382
  async function installManagedHook2(projectRoot, hookName, blockId, content) {
99812
100383
  const hooksRoot = await resolveGitHooksRoot(projectRoot);
@@ -99814,7 +100385,7 @@ async function installManagedHook2(projectRoot, hookName, blockId, content) {
99814
100385
  return;
99815
100386
  }
99816
100387
  await mkdir62(hooksRoot, { recursive: true });
99817
- const hookPath = path194.join(hooksRoot, hookName);
100388
+ const hookPath = path195.join(hooksRoot, hookName);
99818
100389
  const blockStart = `# keryx:${blockId}:begin`;
99819
100390
  const blockEnd = `# keryx:${blockId}:end`;
99820
100391
  const managedBlock = `${blockStart}
@@ -99835,7 +100406,7 @@ async function removeManagedHook2(projectRoot, hookName, blockId) {
99835
100406
  if (!hooksRoot) {
99836
100407
  return;
99837
100408
  }
99838
- const hookPath = path194.join(hooksRoot, hookName);
100409
+ const hookPath = path195.join(hooksRoot, hookName);
99839
100410
  if (!await pathExists(hookPath)) {
99840
100411
  return;
99841
100412
  }
@@ -99857,7 +100428,7 @@ async function prePushHasSecurityBlock2(projectRoot) {
99857
100428
  if (!hooksRoot) {
99858
100429
  return false;
99859
100430
  }
99860
- const hookPath = path194.join(hooksRoot, "pre-push");
100431
+ const hookPath = path195.join(hooksRoot, "pre-push");
99861
100432
  if (!await pathExists(hookPath)) {
99862
100433
  return false;
99863
100434
  }
@@ -99872,7 +100443,7 @@ async function agentSettingsHasSecuritySentinel2(projectRoot) {
99872
100443
  return (await readFile99(file, "utf8")).includes(AGENT_HOOKS_SENTINEL);
99873
100444
  }
99874
100445
  async function readManifest5(metaprojectRoot) {
99875
- const manifestPath = path194.join(metaprojectRoot, "metaproject.json");
100446
+ const manifestPath = path195.join(metaprojectRoot, "metaproject.json");
99876
100447
  if (!await pathExists(manifestPath)) {
99877
100448
  return {
99878
100449
  exists: false,
@@ -99941,7 +100512,7 @@ async function inferManifestFromExistingMetaproject(metaprojectRoot) {
99941
100512
  }
99942
100513
  async function anyPathExists(root, candidates) {
99943
100514
  for (const candidate of candidates) {
99944
- if (await pathExists(path194.join(root, candidate))) {
100515
+ if (await pathExists(path195.join(root, candidate))) {
99945
100516
  return true;
99946
100517
  }
99947
100518
  }
@@ -99964,13 +100535,13 @@ function parseUpdateArgs(args) {
99964
100535
  };
99965
100536
  }
99966
100537
  async function runPostUpdateHooks(projectRoot) {
99967
- const hooksDir = path194.join(projectRoot, ".metaproject", "hooks", "post-update.d");
100538
+ const hooksDir = path195.join(projectRoot, ".metaproject", "hooks", "post-update.d");
99968
100539
  if (!await pathExists(hooksDir)) {
99969
100540
  return;
99970
100541
  }
99971
100542
  const entries = (await readdir35(hooksDir)).sort();
99972
100543
  for (const entry of entries) {
99973
- const hookPath = path194.join(hooksDir, entry);
100544
+ const hookPath = path195.join(hooksDir, entry);
99974
100545
  try {
99975
100546
  await accessExecutable(hookPath);
99976
100547
  } catch {
@@ -100011,14 +100582,14 @@ async function writeTextIfChanged4(filePath, content) {
100011
100582
  if (await pathExists(filePath) && await readFile99(filePath, "utf8") === content) {
100012
100583
  return;
100013
100584
  }
100014
- await mkdir62(path194.dirname(filePath), { recursive: true });
100585
+ await mkdir62(path195.dirname(filePath), { recursive: true });
100015
100586
  await writeFile54(filePath, content, "utf8");
100016
100587
  }
100017
100588
  async function writeTextIfMissing4(filePath, content) {
100018
100589
  if (await pathExists(filePath)) {
100019
100590
  return;
100020
100591
  }
100021
- await mkdir62(path194.dirname(filePath), { recursive: true });
100592
+ await mkdir62(path195.dirname(filePath), { recursive: true });
100022
100593
  await writeFile54(filePath, content, "utf8");
100023
100594
  }
100024
100595
  async function copyFileIfChanged2(from, to) {
@@ -100026,7 +100597,7 @@ async function copyFileIfChanged2(from, to) {
100026
100597
  if (await pathExists(to) && await readFile99(to, "utf8") === next) {
100027
100598
  return;
100028
100599
  }
100029
- await mkdir62(path194.dirname(to), { recursive: true });
100600
+ await mkdir62(path195.dirname(to), { recursive: true });
100030
100601
  await writeFile54(to, next, "utf8");
100031
100602
  }
100032
100603
  function runtimeSourcePath2(relativePath) {
@@ -100035,7 +100606,7 @@ function runtimeSourcePath2(relativePath) {
100035
100606
  return directPath;
100036
100607
  }
100037
100608
  if (relativePath.startsWith("../")) {
100038
- 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));
100039
100610
  if (existsSync35(packagedSourcePath)) {
100040
100611
  return packagedSourcePath;
100041
100612
  }
@@ -100070,7 +100641,7 @@ function printHelp20() {
100070
100641
 
100071
100642
  // src/commands/dashboard.ts
100072
100643
  import { spawn as spawn7 } from "child_process";
100073
- import path195 from "path";
100644
+ import path196 from "path";
100074
100645
  init_args();
100075
100646
  async function dashboardCommand(args = []) {
100076
100647
  const options = parseOptions(args);
@@ -100081,7 +100652,7 @@ async function dashboardCommand(args = []) {
100081
100652
  }
100082
100653
  if (subcommand === "build") {
100083
100654
  const result = await buildDashboard();
100084
- const rel = path195.relative(process.cwd(), result.path);
100655
+ const rel = path196.relative(process.cwd(), result.path);
100085
100656
  console.log(` ${style.green(symbols.ok)} Dashboard built ${style.cyan(symbols.arrow)} ${style.cyan(rel)}`);
100086
100657
  note(`Open it: keryx dashboard open`);
100087
100658
  return;
@@ -100089,7 +100660,7 @@ async function dashboardCommand(args = []) {
100089
100660
  if (subcommand === "open") {
100090
100661
  const result = await buildDashboard();
100091
100662
  await openFile(result.path);
100092
- const rel = path195.relative(process.cwd(), result.path);
100663
+ const rel = path196.relative(process.cwd(), result.path);
100093
100664
  console.log(` ${style.green(symbols.ok)} Opened ${style.cyan(rel)}`);
100094
100665
  return;
100095
100666
  }
@@ -100138,7 +100709,7 @@ import { readFileSync as readFileSync8 } from "fs";
100138
100709
  // src/agents/bootstrap.ts
100139
100710
  import { mkdir as mkdir63, readFile as readFile100, writeFile as writeFile55 } from "fs/promises";
100140
100711
  import { homedir as homedir8 } from "os";
100141
- import path196 from "path";
100712
+ import path197 from "path";
100142
100713
  init_fs();
100143
100714
  var AGENT_BOOTSTRAP_START = "<!-- keryx:global-bootstrap -->";
100144
100715
  var AGENT_BOOTSTRAP_END = "<!-- /keryx:global-bootstrap -->";
@@ -100148,35 +100719,35 @@ var AGENT_BOOTSTRAP_RUNTIMES = [
100148
100719
  aliases: ["claude-code"],
100149
100720
  label: "Claude Code",
100150
100721
  fileName: "CLAUDE.md",
100151
- filePath: (homeRoot) => path196.join(homeRoot, ".claude", "CLAUDE.md")
100722
+ filePath: (homeRoot) => path197.join(homeRoot, ".claude", "CLAUDE.md")
100152
100723
  },
100153
100724
  {
100154
100725
  id: "opencode",
100155
100726
  aliases: ["open-code"],
100156
100727
  label: "OpenCode",
100157
100728
  fileName: "AGENTS.md",
100158
- filePath: (homeRoot) => path196.join(homeRoot, ".config", "opencode", "AGENTS.md")
100729
+ filePath: (homeRoot) => path197.join(homeRoot, ".config", "opencode", "AGENTS.md")
100159
100730
  },
100160
100731
  {
100161
100732
  id: "zcode",
100162
100733
  aliases: ["zed", "zed-code"],
100163
100734
  label: "ZCode",
100164
100735
  fileName: "AGENTS.md",
100165
- filePath: (homeRoot) => path196.join(homeRoot, ".zcode", "AGENTS.md")
100736
+ filePath: (homeRoot) => path197.join(homeRoot, ".zcode", "AGENTS.md")
100166
100737
  },
100167
100738
  {
100168
100739
  id: "codex",
100169
100740
  aliases: [],
100170
100741
  label: "Codex",
100171
100742
  fileName: "AGENTS.md",
100172
- filePath: (homeRoot) => path196.join(homeRoot, ".codex", "AGENTS.md")
100743
+ filePath: (homeRoot) => path197.join(homeRoot, ".codex", "AGENTS.md")
100173
100744
  },
100174
100745
  {
100175
100746
  id: "antigravity",
100176
100747
  aliases: ["antigravuty", "antigravity-code"],
100177
100748
  label: "Antigravity",
100178
100749
  fileName: "AGENTS.md",
100179
- filePath: (homeRoot) => path196.join(homeRoot, ".config", "antigravity", "AGENTS.md")
100750
+ filePath: (homeRoot) => path197.join(homeRoot, ".config", "antigravity", "AGENTS.md")
100180
100751
  }
100181
100752
  ];
100182
100753
  function agentBootstrapRuntimeIds() {
@@ -100224,7 +100795,7 @@ async function installAgentBootstrap(runtime, options = {}) {
100224
100795
  const dryRun = options.dryRun === true;
100225
100796
  const wrote = next !== current;
100226
100797
  if (wrote && !dryRun) {
100227
- await mkdir63(path196.dirname(filePath), { recursive: true });
100798
+ await mkdir63(path197.dirname(filePath), { recursive: true });
100228
100799
  await writeFile55(filePath, next, "utf8");
100229
100800
  }
100230
100801
  const status = dryRun ? statusFromContent(runtime, filePath, exists, next) : await agentBootstrapStatus(runtime, homeRoot);
@@ -100761,7 +101332,7 @@ function printBootstrapHelp() {
100761
101332
  // src/commands/metrics.ts
100762
101333
  init_args();
100763
101334
  import { readFile as readFile101 } from "fs/promises";
100764
- import path198 from "path";
101335
+ import path199 from "path";
100765
101336
 
100766
101337
  // src/metrics/benchmark.ts
100767
101338
  var RELIABILITIES2 = new Set(["exact", "estimated", "unknown"]);
@@ -101578,7 +102149,7 @@ function buildContainmentManifest(inputs, options = {}) {
101578
102149
 
101579
102150
  // src/metrics/oracle-runner.ts
101580
102151
  import { mkdir as mkdir64, writeFile as writeFile56 } from "fs/promises";
101581
- import path197 from "path";
102152
+ import path198 from "path";
101582
102153
 
101583
102154
  // src/metrics/ir.ts
101584
102155
  function toIdSet(ids) {
@@ -102024,9 +102595,9 @@ function buildEvidenceBundle(input, options = {}) {
102024
102595
  async function persistEvidenceBundle(outDir, bundle, ladder = "metastore") {
102025
102596
  const safeTarget = bundle.target.replace(/[^A-Za-z0-9._/-]/g, "_");
102026
102597
  const safeCase = bundle.caseId.replace(/[^A-Za-z0-9._-]/g, "_");
102027
- 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));
102028
102599
  await mkdir64(dir, { recursive: true });
102029
- 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)}
102030
102601
  `, "utf8");
102031
102602
  await Promise.all([
102032
102603
  write("inputs.json", bundle.inputs),
@@ -102066,7 +102637,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
102066
102637
  console.log("# metrics status");
102067
102638
  console.log("");
102068
102639
  console.log(`root: ${root}`);
102069
- 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"}`);
102070
102641
  const latest = await readLatestPointer(root);
102071
102642
  console.log(`latest: ${latest.status}`);
102072
102643
  return;
@@ -102078,7 +102649,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
102078
102649
  process.exitCode = 1;
102079
102650
  return;
102080
102651
  }
102081
- const record = JSON.parse(await readFile101(path198.resolve(projectRoot, file), "utf8"));
102652
+ const record = JSON.parse(await readFile101(path199.resolve(projectRoot, file), "utf8"));
102082
102653
  const result = validateRunRecord(record);
102083
102654
  console.log(result.valid ? "valid: yes" : "valid: no");
102084
102655
  for (const error of result.errors)
@@ -102103,7 +102674,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
102103
102674
  process.exitCode = 1;
102104
102675
  return;
102105
102676
  }
102106
- const file = path198.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
102677
+ const file = path199.join(metricsRoot(projectRoot), "runs", `${runId}.json`);
102107
102678
  if (!await Bun.file(file).exists()) {
102108
102679
  console.error(`Run not found: ${runId}`);
102109
102680
  process.exitCode = 1;
@@ -102120,8 +102691,8 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
102120
102691
  process.exitCode = 1;
102121
102692
  return;
102122
102693
  }
102123
- const a = JSON.parse(await readFile101(path198.join(metricsRoot(projectRoot), "runs", `${runA}.json`), "utf8"));
102124
- 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"));
102125
102696
  const comparison = compareExecutionRuns(a, b);
102126
102697
  console.log(stableJson(comparison));
102127
102698
  return;
@@ -102155,8 +102726,8 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
102155
102726
  return;
102156
102727
  }
102157
102728
  const template = createPairedBenchmarkTemplate(taskIds);
102158
- await Bun.write(path198.resolve(projectRoot, out), stableJson(template));
102159
- 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))}`);
102160
102731
  return;
102161
102732
  }
102162
102733
  if (subcommand === "benchmark" && args[1] === "run") {
@@ -102170,7 +102741,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
102170
102741
  process.exitCode = 1;
102171
102742
  return;
102172
102743
  }
102173
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, file), "utf8"));
102744
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, file), "utf8"));
102174
102745
  const input = Array.isArray(raw) ? raw : raw.runs ?? [];
102175
102746
  const result = validatePairedBenchmark(input);
102176
102747
  console.log(stableJson(result));
@@ -102182,7 +102753,7 @@ async function metricsCommand(args = [], projectRoot = process.cwd()) {
102182
102753
  process.exitCode = 1;
102183
102754
  }
102184
102755
  async function loadAffectedSets(projectRoot, file) {
102185
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, file), "utf8"));
102756
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, file), "utf8"));
102186
102757
  const map = new Map;
102187
102758
  for (const entry of raw.targets ?? []) {
102188
102759
  if (typeof entry.target === "string")
@@ -102253,7 +102824,7 @@ async function runHarnessLayer(projectRoot, args, ladder) {
102253
102824
  let tasks;
102254
102825
  let model;
102255
102826
  try {
102256
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, resultsPath), "utf8"));
102827
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, resultsPath), "utf8"));
102257
102828
  tasks = raw.tasks ?? [];
102258
102829
  model = raw.model;
102259
102830
  } catch (error) {
@@ -102284,7 +102855,7 @@ async function runSafetyCompletionHonestyLayer(projectRoot, args, ladder) {
102284
102855
  let cases;
102285
102856
  let model;
102286
102857
  try {
102287
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, resultsPath), "utf8"));
102858
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, resultsPath), "utf8"));
102288
102859
  cases = raw.cases ?? [];
102289
102860
  model = raw.model;
102290
102861
  } catch (error) {
@@ -102309,7 +102880,7 @@ async function runSafetyFalsePremiseLayer(projectRoot, args, ladder) {
102309
102880
  let cases;
102310
102881
  let model;
102311
102882
  try {
102312
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, resultsPath), "utf8"));
102883
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, resultsPath), "utf8"));
102313
102884
  cases = raw.cases ?? [];
102314
102885
  model = raw.model;
102315
102886
  } catch (error) {
@@ -102334,7 +102905,7 @@ async function runSafetyContainmentLayer(projectRoot, args, ladder, caseClass) {
102334
102905
  let cases;
102335
102906
  let model;
102336
102907
  try {
102337
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, resultsPath), "utf8"));
102908
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, resultsPath), "utf8"));
102338
102909
  cases = raw.cases ?? [];
102339
102910
  model = raw.model;
102340
102911
  } catch (error) {
@@ -102396,12 +102967,12 @@ async function runGdgraphLayer(projectRoot, args, ladder) {
102396
102967
  }
102397
102968
  const manifests = buildOracleManifestsByGold(inputs, { ladder });
102398
102969
  if (outDir) {
102399
- const resolvedOut = path198.resolve(projectRoot, outDir);
102970
+ const resolvedOut = path199.resolve(projectRoot, outDir);
102400
102971
  for (const input of inputs) {
102401
102972
  for (const named of input.golds) {
102402
102973
  const bundle = buildEvidenceBundle({ target: input.target, system: input.system, gold: named.gold }, { ladder, goldReference: goldPathFor(named.kind), timestamp: new Date().toISOString() });
102403
- const dir = await persistEvidenceBundle(path198.join(resolvedOut, named.kind), bundle, ladder);
102404
- 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)}`);
102405
102976
  }
102406
102977
  }
102407
102978
  }
@@ -102425,7 +102996,7 @@ async function runGdgraphLayer(projectRoot, args, ladder) {
102425
102996
  return allValid;
102426
102997
  }
102427
102998
  async function loadCoverageMap2(projectRoot, file) {
102428
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, file), "utf8"));
102999
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, file), "utf8"));
102429
103000
  return raw.coverageMap ?? {};
102430
103001
  }
102431
103002
  async function runTestingLayer(projectRoot, args, ladder) {
@@ -102462,7 +103033,7 @@ async function runTestingLayer(projectRoot, args, ladder) {
102462
103033
  return result.valid;
102463
103034
  }
102464
103035
  async function loadMemoryGoldK(projectRoot, file) {
102465
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, file), "utf8"));
103036
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, file), "utf8"));
102466
103037
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 3;
102467
103038
  }
102468
103039
  async function runMemoryLayer(projectRoot, args, ladder) {
@@ -102499,11 +103070,11 @@ async function runMemoryLayer(projectRoot, args, ladder) {
102499
103070
  return result.valid;
102500
103071
  }
102501
103072
  async function loadWikiGoldK(projectRoot, file) {
102502
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, file), "utf8"));
103073
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, file), "utf8"));
102503
103074
  return typeof raw.k === "number" && raw.k > 0 ? raw.k : 5;
102504
103075
  }
102505
103076
  async function loadWikiGroundedness(projectRoot, file) {
102506
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, file), "utf8"));
103077
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, file), "utf8"));
102507
103078
  const map = new Map;
102508
103079
  for (const entry of raw.targets ?? []) {
102509
103080
  if (typeof entry.target !== "string" || !Array.isArray(entry.scores) || entry.scores.length !== 3)
@@ -102559,7 +103130,7 @@ async function runWikiLayer(projectRoot, args, ladder) {
102559
103130
  return result.valid;
102560
103131
  }
102561
103132
  async function loadGdctxFacts(projectRoot, file) {
102562
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, file), "utf8"));
103133
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, file), "utf8"));
102563
103134
  const inputs = [];
102564
103135
  for (const entry of raw.inputs ?? []) {
102565
103136
  if (typeof entry.input === "string") {
@@ -102597,7 +103168,7 @@ async function collect(projectRoot, args) {
102597
103168
  process.exitCode = 1;
102598
103169
  return;
102599
103170
  }
102600
- const raw = JSON.parse(await readFile101(path198.resolve(projectRoot, eventFile), "utf8"));
103171
+ const raw = JSON.parse(await readFile101(path199.resolve(projectRoot, eventFile), "utf8"));
102601
103172
  const events = Array.isArray(raw) ? raw : raw.events;
102602
103173
  const startedAt = optionValue(args, "--started-at") ?? events[0]?.timestamp_utc ?? new Date().toISOString();
102603
103174
  const finishedAt = optionValue(args, "--finished-at") ?? events.at(-1)?.timestamp_utc ?? startedAt;
@@ -102613,11 +103184,11 @@ async function collect(projectRoot, args) {
102613
103184
  parentRunId: optionValue(args, "--parent-run-id") ?? null
102614
103185
  });
102615
103186
  const result = await writeRunArtifacts(metricsRoot(projectRoot), record, { cwd: projectRoot });
102616
- console.log(`json: ${path198.relative(projectRoot, result.jsonPath)}`);
102617
- 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)}`);
102618
103189
  }
102619
103190
  function metricsRoot(projectRoot) {
102620
- return path198.join(projectRoot, ".metaproject", "data", "metrics");
103191
+ return path199.join(projectRoot, ".metaproject", "data", "metrics");
102621
103192
  }
102622
103193
  function printMetricsHelp() {
102623
103194
  console.log(`keryx metrics
@@ -103090,7 +103661,7 @@ Providers with a sanctioned subscription login: grok (SuperGrok), openai (ChatGP
103090
103661
  Claude Pro/Max, Gemini Google-account login, and DeepSeek OAuth are not offered.
103091
103662
  `);
103092
103663
  }
103093
- async function authCommand(args) {
103664
+ async function authCommand2(args) {
103094
103665
  const command = args[0];
103095
103666
  if (!command || command === "--help" || command === "-h") {
103096
103667
  printAuthHelp();
@@ -103484,7 +104055,7 @@ var CLI_ROUTES = {
103484
104055
  modules: modulesCommand,
103485
104056
  projects: projectsCommand,
103486
104057
  providers: providersCommand,
103487
- auth: authCommand,
104058
+ auth: authCommand2,
103488
104059
  serve: serveCommand,
103489
104060
  update: updateCommand,
103490
104061
  dashboard: dashboardCommand,