@kontextmind/kxm 0.7.165 → 0.7.167

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 (41) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/docs/README.md +1 -0
  4. package/docs/architecture/hosted-direction.md +9 -4
  5. package/docs/architecture/inventory.md +1 -0
  6. package/docs/concepts/trust-model.md +1 -1
  7. package/docs/contributing/decisions.md +153 -0
  8. package/docs/contributing/learnings.md +54 -53
  9. package/docs/contributing/operating-rules.md +19 -6
  10. package/docs/glossary.md +2 -2
  11. package/docs/operations/troubleshooting.md +2 -2
  12. package/docs/operations.md +62 -8
  13. package/docs/reference/cli-reference.md +11 -11
  14. package/docs/reference/config-reference.md +36 -11
  15. package/docs/reference/configuration.md +2 -2
  16. package/docs/reference/tools.md +1 -1
  17. package/docs/start/quickstart-claude-code.md +2 -2
  18. package/package.json +1 -1
  19. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  20. package/plugins/kxm/README.md +3 -3
  21. package/plugins/kxm/dist/claude-hook.js +417 -66
  22. package/plugins/kxm/dist/cli.js +1399 -900
  23. package/plugins/kxm/dist/extension.js +399 -64
  24. package/plugins/kxm/dist/mcp-server.js +622 -135
  25. package/plugins/kxm/dist/runtime-supervisor.js +668 -166
  26. package/plugins/kxm/dist/runtime.js +713 -211
  27. package/plugins/kxm/dist/server.js +466 -58
  28. package/plugins/kxm/package.json +1 -1
  29. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +15 -7
  30. package/plugins/kxm/src/cli/hub.ts +80 -21
  31. package/plugins/kxm/src/cli/types.ts +8 -1
  32. package/plugins/kxm/src/cli.ts +41 -7
  33. package/plugins/kxm/src/config.ts +246 -3
  34. package/plugins/kxm/src/extension.ts +8 -7
  35. package/plugins/kxm/src/hub-autostart.ts +20 -0
  36. package/plugins/kxm/src/hub-env.ts +83 -31
  37. package/plugins/kxm/src/hub-identity.ts +337 -0
  38. package/plugins/kxm/src/mcp-server.ts +17 -15
  39. package/plugins/kxm/src/project-name.ts +6 -47
  40. package/plugins/kxm/src/server.ts +22 -4
  41. package/scripts/kxm-hub.mjs +5 -0
@@ -14523,7 +14523,7 @@ var require_dist2 = __commonJS({
14523
14523
  });
14524
14524
 
14525
14525
  // plugins/kxm/src/extension.ts
14526
- import { spawnSync as spawnSync3 } from "node:child_process";
14526
+ import { spawnSync as spawnSync4 } from "node:child_process";
14527
14527
  import { existsSync as existsSync13, mkdirSync as mkdirSync9, readFileSync as readFileSync14, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "node:fs";
14528
14528
  import { dirname as dirname8, join as join15 } from "node:path";
14529
14529
 
@@ -15995,9 +15995,25 @@ function enforceToolPolicy(commandName, env = process.env, options) {
15995
15995
  // plugins/kxm/src/config.ts
15996
15996
  var import_yaml = __toESM(require_dist(), 1);
15997
15997
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
15998
- import { homedir as homedir2 } from "node:os";
15998
+ import { tmpdir, homedir as homedir2 } from "node:os";
15999
15999
  import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
16000
16000
  var KXM_CONFIG_SCHEMA = "kxm.config.v1";
16001
+ var KxmHubConfigError = class extends Error {
16002
+ code = "hub_config_invalid";
16003
+ constructor(message) {
16004
+ super(message);
16005
+ this.name = "KxmHubConfigError";
16006
+ }
16007
+ };
16008
+ var HUB_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
16009
+ var HUB_OP_REF = /^op:\/\/\S+\/\S+\/\S+$/;
16010
+ var LITERAL_SECRET_KEY = /^(token|secret|password|authToken|auth_token|apiKey|api_key|api_token|bearer|credential|access_token)$/i;
16011
+ function isHubKeyEnvName(value) {
16012
+ return HUB_ENV_NAME.test(value);
16013
+ }
16014
+ function isHubOpReference(value) {
16015
+ return HUB_OP_REF.test(value);
16016
+ }
16001
16017
  var IMPROVEMENT_PROMOTION_POLICIES = ["manual_pr", "critic_quorum", "auto_threshold"];
16002
16018
  var DEFAULT_KXM_CONFIG = {
16003
16019
  schema: KXM_CONFIG_SCHEMA,
@@ -16068,9 +16084,151 @@ function deepMerge(target, source) {
16068
16084
  }
16069
16085
  return result2;
16070
16086
  }
16087
+ function literalSecretMessage(path) {
16088
+ return `refusing literal hub token at ${path}: store an op:// reference (key.op) or an environment variable name (key.env), never the token`;
16089
+ }
16090
+ function rejectLiteralHubSecrets(value, path) {
16091
+ if (Array.isArray(value)) {
16092
+ value.forEach((item, index) => rejectLiteralHubSecrets(item, `${path}[${index}]`));
16093
+ return;
16094
+ }
16095
+ if (!value || typeof value !== "object") return;
16096
+ for (const [key, child] of Object.entries(value)) {
16097
+ const childPath = `${path}.${key}`;
16098
+ if (typeof child === "string" && (key === "key" || LITERAL_SECRET_KEY.test(key))) {
16099
+ throw new KxmHubConfigError(literalSecretMessage(childPath));
16100
+ }
16101
+ if (child && typeof child === "object") rejectLiteralHubSecrets(child, childPath);
16102
+ }
16103
+ }
16104
+ function parseKeyRef(raw, path) {
16105
+ if (raw === void 0) return void 0;
16106
+ if (typeof raw === "string") throw new KxmHubConfigError(literalSecretMessage(path));
16107
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
16108
+ throw new KxmHubConfigError(`${path} must be a key reference with op and/or env, never a token`);
16109
+ }
16110
+ const row = raw;
16111
+ for (const key of Object.keys(row)) {
16112
+ if (key !== "op" && key !== "env") {
16113
+ throw new KxmHubConfigError(literalSecretMessage(`${path}.${key}`));
16114
+ }
16115
+ }
16116
+ const ref = {};
16117
+ if (row.op !== void 0) {
16118
+ if (typeof row.op !== "string" || !isHubOpReference(row.op)) {
16119
+ throw new KxmHubConfigError(`${path}.op must be an op://vault/item/field reference, not a token`);
16120
+ }
16121
+ ref.op = row.op;
16122
+ }
16123
+ if (row.env !== void 0) {
16124
+ if (typeof row.env !== "string" || !isHubKeyEnvName(row.env)) {
16125
+ throw new KxmHubConfigError(`${path}.env must be an environment variable name, not a token`);
16126
+ }
16127
+ ref.env = row.env;
16128
+ }
16129
+ if (!ref.op && !ref.env) throw new KxmHubConfigError(`${path} needs key.op, key.env, or both`);
16130
+ return ref;
16131
+ }
16132
+ function parseEndpoint(raw, path) {
16133
+ if (raw === void 0) return void 0;
16134
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
16135
+ throw new KxmHubConfigError(`${path} must be an object with url, project, and key`);
16136
+ }
16137
+ const row = raw;
16138
+ const endpoint = {};
16139
+ if (row.url !== void 0) {
16140
+ if (typeof row.url !== "string" || !isHubUrl(row.url)) {
16141
+ throw new KxmHubConfigError(`${path}.url must be an http or https URL without credentials, query, or fragment`);
16142
+ }
16143
+ endpoint.url = row.url.replace(/\/$/, "");
16144
+ }
16145
+ if (row.project !== void 0) {
16146
+ if (typeof row.project !== "string" || !isHubProjectId(row.project)) {
16147
+ throw new KxmHubConfigError(`${path}.project must be a single-line project id, not a token`);
16148
+ }
16149
+ endpoint.project = row.project.trim();
16150
+ }
16151
+ const key = parseKeyRef(row.key, `${path}.key`);
16152
+ if (key) endpoint.key = key;
16153
+ for (const name of Object.keys(row)) {
16154
+ if (name !== "url" && name !== "project" && name !== "key") {
16155
+ if (typeof row[name] === "string") throw new KxmHubConfigError(literalSecretMessage(`${path}.${name}`));
16156
+ throw new KxmHubConfigError(`${path}.${name} is not a hub endpoint field`);
16157
+ }
16158
+ }
16159
+ return endpoint;
16160
+ }
16161
+ function isHubUrl(raw) {
16162
+ try {
16163
+ const parsed = new URL(raw);
16164
+ return (parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.username === "" && parsed.password === "" && parsed.search === "" && parsed.hash === "" && !raw.includes("?") && !raw.includes("#");
16165
+ } catch {
16166
+ return false;
16167
+ }
16168
+ }
16169
+ function isHubProjectId(raw) {
16170
+ const value = raw.trim();
16171
+ return value.length > 0 && value.length <= 200 && !/[\s\r\n]/.test(value);
16172
+ }
16173
+ function parseProjectMap(raw) {
16174
+ if (raw === void 0) return void 0;
16175
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
16176
+ throw new KxmHubConfigError("hub.projects must be a map of project id to key reference, never a token");
16177
+ }
16178
+ const projects = {};
16179
+ for (const [project, value] of Object.entries(raw)) {
16180
+ if (!isHubProjectId(project)) {
16181
+ throw new KxmHubConfigError(`hub.projects key ${JSON.stringify(project)} must be a project id, not a token`);
16182
+ }
16183
+ const ref = parseKeyRef(value, `hub.projects.${project}`);
16184
+ if (!ref) throw new KxmHubConfigError(`hub.projects.${project} needs an op:// reference or an environment variable name`);
16185
+ projects[project.trim()] = ref;
16186
+ }
16187
+ return projects;
16188
+ }
16071
16189
  function normalizeHubConfig(raw) {
16072
16190
  const autoStart = raw?.autoStart;
16073
- return { autoStart: autoStart === "off" || autoStart === "background" ? autoStart : DEFAULT_KXM_CONFIG.hub.autoStart };
16191
+ const hub = {
16192
+ autoStart: autoStart === "off" || autoStart === "background" ? autoStart : DEFAULT_KXM_CONFIG.hub.autoStart
16193
+ };
16194
+ if (raw === void 0) return hub;
16195
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
16196
+ throw new KxmHubConfigError("hub must be an object");
16197
+ }
16198
+ rejectLiteralHubSecrets(raw, "hub");
16199
+ const row = raw;
16200
+ if (row.mode !== void 0) {
16201
+ if (row.mode !== "local" && row.mode !== "cloud") {
16202
+ throw new KxmHubConfigError("hub.mode must be local or cloud");
16203
+ }
16204
+ hub.mode = row.mode;
16205
+ }
16206
+ const local = parseEndpoint(row.local, "hub.local");
16207
+ const cloud = parseEndpoint(row.cloud, "hub.cloud");
16208
+ const projects = parseProjectMap(row.projects);
16209
+ if (local) hub.local = local;
16210
+ if (cloud) hub.cloud = cloud;
16211
+ if (projects && Object.keys(projects).length > 0) hub.projects = projects;
16212
+ return hub;
16213
+ }
16214
+ function findConfigRoot(start) {
16215
+ let dir = resolve2(start);
16216
+ for (; ; ) {
16217
+ if (existsSync2(join2(dir, ".kxm", "project.yaml")) || existsSync2(join2(dir, ".kxm", "config.yaml"))) return dir;
16218
+ const parent = dirname2(dir);
16219
+ if (parent === dir) return resolve2(start);
16220
+ dir = parent;
16221
+ }
16222
+ }
16223
+ function userConfigDirForEnv(env) {
16224
+ const explicit = env.KXM_USER_CONFIG_DIR?.trim();
16225
+ if (explicit) return explicit;
16226
+ if (env === process.env) return void 0;
16227
+ return join2(tmpdir(), "kxm-no-user-config");
16228
+ }
16229
+ function loadHubSettings(start, env = process.env) {
16230
+ const userConfigDir = userConfigDirForEnv(env);
16231
+ return loadKxmConfig(findConfigRoot(start), userConfigDir === void 0 ? {} : { userConfigDir }).hub;
16074
16232
  }
16075
16233
  function recordOf(raw) {
16076
16234
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
@@ -16244,17 +16402,6 @@ function splitTokenCommand(raw) {
16244
16402
  function isCloudTokenEnvName(value) {
16245
16403
  return TOKEN_ENV_NAME.test(value);
16246
16404
  }
16247
- function resolveHubServerUrl(env = process.env) {
16248
- const fromEnv = env.KXM_SERVER_URL?.trim();
16249
- if (fromEnv) return fromEnv.replace(/\/$/, "");
16250
- try {
16251
- const binding = readHubBinding(env);
16252
- if (binding?.url) return binding.url;
16253
- } catch (error2) {
16254
- if (!(error2 instanceof HubBindingError)) throw error2;
16255
- }
16256
- return "http://127.0.0.1:7331";
16257
- }
16258
16405
  function resolveCloudHubToken(binding, env = process.env) {
16259
16406
  if (!binding.cloud) {
16260
16407
  throw new CloudTokenError("cloud_token_missing", "cloud hub token is missing; the local hub-env token was not used");
@@ -16296,6 +16443,17 @@ function runTokenCommand(command, env) {
16296
16443
  }
16297
16444
  return stdout.trim();
16298
16445
  }
16446
+ function hubBindingScope(url) {
16447
+ let host;
16448
+ try {
16449
+ host = new URL(url).hostname.toLowerCase();
16450
+ } catch {
16451
+ return "remote";
16452
+ }
16453
+ if (host === "localhost" || host === "::1" || host === "[::1]" || host.endsWith(".localhost")) return "loopback";
16454
+ const v4 = /^127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/.exec(host);
16455
+ return v4 && [v4[1], v4[2], v4[3]].every((part) => Number(part) <= 255) ? "loopback" : "remote";
16456
+ }
16299
16457
  function isIsoTimestamp(value) {
16300
16458
  if (Number.isNaN(Date.parse(value))) return false;
16301
16459
  return value === new Date(value).toISOString();
@@ -16383,31 +16541,21 @@ async function probeHubHealth(url, fetchImpl, timeoutMs = HUB_HEALTH_PROBE_MS) {
16383
16541
  }
16384
16542
  }
16385
16543
 
16386
- // plugins/kxm/src/hub-env.ts
16387
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
16388
- import { homedir as homedir4 } from "node:os";
16389
- import { randomBytes } from "node:crypto";
16390
- import { dirname as dirname5, isAbsolute as isAbsolute2, join as join5, resolve as resolve5 } from "node:path";
16391
-
16392
- // plugins/kxm/src/project-name.ts
16544
+ // plugins/kxm/src/hub-identity.ts
16393
16545
  var import_yaml2 = __toESM(require_dist(), 1);
16546
+ import { spawnSync as spawnSync2 } from "node:child_process";
16394
16547
  import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
16395
16548
  import { basename, dirname as dirname4, join as join4, resolve as resolve4 } from "node:path";
16396
- function defaultProjectName(cwd, env = process.env, explicit) {
16397
- const fromExplicit = explicit?.trim();
16398
- if (fromExplicit) return fromExplicit;
16399
- const fromEnv = env.KXM_PROJECT?.trim();
16400
- if (fromEnv) return fromEnv;
16401
- const fromProject = readProjectYamlId(cwd);
16402
- if (fromProject) return fromProject;
16549
+ var OP_TIMEOUT_MS = 15e3;
16550
+ var OP_MAX_BUFFER = 4096;
16551
+ function normalizeUrl(raw) {
16403
16552
  try {
16404
- const pkg = JSON.parse(readFileSync4(join4(cwd, "package.json"), "utf8"));
16405
- if (typeof pkg.name === "string" && pkg.name.trim().length > 0) return pkg.name.trim();
16553
+ return validateHubUrl(raw);
16406
16554
  } catch {
16555
+ return void 0;
16407
16556
  }
16408
- return basename(cwd);
16409
16557
  }
16410
- function readProjectYamlId(start) {
16558
+ function projectYamlId(start) {
16411
16559
  let dir = resolve4(start);
16412
16560
  for (; ; ) {
16413
16561
  const file = join4(dir, ".kxm", "project.yaml");
@@ -16426,8 +16574,174 @@ function readProjectYamlId(start) {
16426
16574
  dir = parent;
16427
16575
  }
16428
16576
  }
16577
+ function packageName(cwd) {
16578
+ try {
16579
+ const pkg = JSON.parse(readFileSync4(join4(cwd, "package.json"), "utf8"));
16580
+ if (typeof pkg.name === "string" && pkg.name.trim().length > 0) return pkg.name.trim();
16581
+ } catch {
16582
+ }
16583
+ return void 0;
16584
+ }
16585
+ function activeBinding(env) {
16586
+ try {
16587
+ return readHubBinding(env);
16588
+ } catch (error2) {
16589
+ if (error2 instanceof HubBindingError) return void 0;
16590
+ throw error2;
16591
+ }
16592
+ }
16593
+ function endpointFor(mode, settings) {
16594
+ return mode === "cloud" ? settings.cloud : settings.local;
16595
+ }
16596
+ function resolveProjectIdentity(cwd, env = process.env, explicit) {
16597
+ const fromExplicit = explicit?.trim();
16598
+ if (fromExplicit) return { project: fromExplicit, source: "flag", sourceLabel: "--project" };
16599
+ const fromEnv = env.KXM_PROJECT?.trim();
16600
+ if (fromEnv) return { project: fromEnv, source: "env", sourceLabel: "KXM_PROJECT" };
16601
+ const settings = loadHubSettings(cwd, env);
16602
+ const mode = settings.mode ?? (activeBinding(env)?.cloud ? "cloud" : "local");
16603
+ const fromConfig = endpointFor(mode, settings)?.project?.trim();
16604
+ if (fromConfig) {
16605
+ return { project: fromConfig, source: "config", sourceLabel: `.kxm/config.yaml hub.${mode}.project` };
16606
+ }
16607
+ const fromProject = projectYamlId(cwd);
16608
+ if (fromProject) return { project: fromProject, source: "project.yaml", sourceLabel: ".kxm/project.yaml id" };
16609
+ const fromPackage = packageName(cwd);
16610
+ if (fromPackage) return { project: fromPackage, source: "package.json", sourceLabel: "package.json name" };
16611
+ return { project: basename(cwd), source: "directory", sourceLabel: "directory name" };
16612
+ }
16613
+ function opReferenceFromTokenCommand(command) {
16614
+ try {
16615
+ const args = splitTokenCommand(command);
16616
+ if (args[0] === "op" && args[1] === "read" && args[2] && isHubOpReference(args[2])) return args[2];
16617
+ } catch {
16618
+ return void 0;
16619
+ }
16620
+ return void 0;
16621
+ }
16622
+ function defaultOpRead(reference, env) {
16623
+ if (!isHubOpReference(reference)) {
16624
+ throw new CloudTokenError("cloud_token_command_invalid", `refusing to resolve ${reference}; an op:// reference is required`);
16625
+ }
16626
+ const result2 = spawnSync2("op", ["read", reference], {
16627
+ encoding: "utf8",
16628
+ shell: false,
16629
+ timeout: OP_TIMEOUT_MS,
16630
+ maxBuffer: OP_MAX_BUFFER,
16631
+ env,
16632
+ windowsHide: true
16633
+ });
16634
+ if (result2.error || result2.status !== 0) {
16635
+ throw new CloudTokenError(
16636
+ "cloud_token_command_failed",
16637
+ `op read failed for ${reference}; the token was not saved`
16638
+ );
16639
+ }
16640
+ const line = (result2.stdout ?? "").trim();
16641
+ if (!line || line.split(/\r?\n/).length !== 1) {
16642
+ throw new CloudTokenError(
16643
+ "cloud_token_command_failed",
16644
+ `op read did not print a single token for ${reference}; the token was not saved`
16645
+ );
16646
+ }
16647
+ return line;
16648
+ }
16649
+ function resolveKeyReference(ref, env, opRead = defaultOpRead) {
16650
+ if (!ref) return void 0;
16651
+ if (ref.env) {
16652
+ const value = env[ref.env]?.trim();
16653
+ if (value) return { value, source: `env:${ref.env}` };
16654
+ }
16655
+ if (ref.op) {
16656
+ const value = opRead(ref.op, env).trim();
16657
+ if (!value || value.split(/\r?\n/).length !== 1) {
16658
+ throw new CloudTokenError("cloud_token_command_failed", `op read did not print a single token for ${ref.op}`);
16659
+ }
16660
+ return { value, source: `op:${ref.op}` };
16661
+ }
16662
+ return void 0;
16663
+ }
16664
+ function keySourceFor(input) {
16665
+ const ref = input.endpoint?.key;
16666
+ if (input.mode === "local" && input.env.KXM_AUTH_TOKEN?.trim()) return "env:KXM_AUTH_TOKEN";
16667
+ if (ref?.env && input.env[ref.env]?.trim()) return `env:${ref.env}`;
16668
+ if (ref?.op) return `op:${ref.op}`;
16669
+ if (ref?.env) return `env:${ref.env}`;
16670
+ if (input.mode === "cloud") {
16671
+ if (input.binding?.tokenEnv) return `env:${input.binding.tokenEnv}`;
16672
+ if (input.binding?.tokenCommand) {
16673
+ const op = opReferenceFromTokenCommand(input.binding.tokenCommand);
16674
+ return op ? `op:${op}` : "command";
16675
+ }
16676
+ return "missing";
16677
+ }
16678
+ if (input.hasSavedProjectToken?.(input.project)) return "hub-env";
16679
+ return "missing";
16680
+ }
16681
+ function describeHubConnection(cwd, env = process.env, hooks = {}) {
16682
+ const settings = loadHubSettings(cwd, env);
16683
+ const binding = activeBinding(env);
16684
+ const mode = settings.mode ?? (binding?.cloud ? "cloud" : "local");
16685
+ const modeSource = settings.mode ? "config" : binding?.cloud ? "binding" : "default";
16686
+ const endpoint = endpointFor(mode, settings);
16687
+ const fromEnv = env.KXM_SERVER_URL?.trim();
16688
+ const fromConfig = endpoint?.url?.trim();
16689
+ const fromBinding = binding?.url;
16690
+ let url = "http://127.0.0.1:7331";
16691
+ let urlSource = "default";
16692
+ if (fromEnv) {
16693
+ url = fromEnv.replace(/\/$/, "");
16694
+ urlSource = "env";
16695
+ } else if (fromConfig) {
16696
+ url = fromConfig.replace(/\/$/, "");
16697
+ urlSource = "config";
16698
+ } else if (fromBinding) {
16699
+ url = fromBinding;
16700
+ urlSource = "binding";
16701
+ }
16702
+ const identity = resolveProjectIdentity(cwd, env, hooks.explicitProject);
16703
+ const configuredCloudUrl = settings.cloud?.url ?? (binding?.cloud ? binding.url : void 0);
16704
+ let scope;
16705
+ if (mode === "cloud") {
16706
+ const override = fromEnv ? normalizeUrl(fromEnv) : void 0;
16707
+ const named = configuredCloudUrl ? normalizeUrl(configuredCloudUrl) : void 0;
16708
+ scope = override && named && override !== named ? hubBindingScope(url) : "remote";
16709
+ } else {
16710
+ scope = hubBindingScope(url);
16711
+ }
16712
+ return {
16713
+ ...identity,
16714
+ mode,
16715
+ modeSource,
16716
+ url,
16717
+ urlSource,
16718
+ scope,
16719
+ ...endpoint ? { endpoint } : {},
16720
+ ...binding ? { binding } : {},
16721
+ keySource: keySourceFor({
16722
+ mode,
16723
+ env,
16724
+ ...endpoint ? { endpoint } : {},
16725
+ ...binding ? { binding } : {},
16726
+ project: identity.project,
16727
+ ...hooks.hasSavedProjectToken ? { hasSavedProjectToken: hooks.hasSavedProjectToken } : {}
16728
+ })
16729
+ };
16730
+ }
16731
+ function projectTokenMissingMessage(identity) {
16732
+ return [
16733
+ `kxm has no project token for project ${identity.project} (from ${identity.sourceLabel}).`,
16734
+ `Ask the user to set this project's token. Set KXM_AUTH_TOKEN, or set hub.local.project or hub.cloud.project in .kxm/config.yaml to the project key the hub already knows, or add a key reference under hub.projects.${identity.project} (key.op is an op:// reference, key.env is an environment variable name, never the token) and restart the hub.`,
16735
+ "KXM_PROJECT or --project overrides the id for one command.",
16736
+ "An agent never uses the hub admin token."
16737
+ ].join(" ");
16738
+ }
16429
16739
 
16430
16740
  // plugins/kxm/src/hub-env.ts
16741
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
16742
+ import { homedir as homedir4 } from "node:os";
16743
+ import { randomBytes } from "node:crypto";
16744
+ import { dirname as dirname5, isAbsolute as isAbsolute2, join as join5, resolve as resolve5 } from "node:path";
16431
16745
  var HUB_ENV_SCHEMA = "kxm.hub-env.v1";
16432
16746
  var HubEnvError = class extends Error {
16433
16747
  constructor(message) {
@@ -16567,24 +16881,24 @@ function resolveHubCredentials(options = {}) {
16567
16881
  written: needsPersist === true
16568
16882
  };
16569
16883
  }
16570
- function activeCloudBinding(env) {
16571
- try {
16572
- const binding = readHubBinding(env);
16573
- return binding?.cloud ? binding : void 0;
16574
- } catch (error2) {
16575
- if (error2 instanceof HubBindingError) {
16576
- throw new HubEnvError(
16577
- `${error2.message}; refusing to guess a credential \u2014 repair or remove ${hubBindingFile(env)}`
16578
- );
16579
- }
16580
- throw error2;
16581
- }
16884
+ function cloudModeToken(cwd, env, explicitProject, opRead) {
16885
+ const connection = describeHubConnection(cwd, env, explicitProject === void 0 ? {} : { explicitProject });
16886
+ if (connection.mode !== "cloud") return void 0;
16887
+ const fromConfig = resolveKeyReference(connection.endpoint?.key, env, opRead);
16888
+ if (fromConfig) return fromConfig.value;
16889
+ if (connection.binding?.cloud) return resolveCloudHubToken(connection.binding, env);
16890
+ throw new CloudTokenError(
16891
+ "cloud_token_missing",
16892
+ "cloud hub token is missing; the local hub-env token was not used"
16893
+ );
16582
16894
  }
16583
- function resolveAgentHubAuthToken(env, project) {
16584
- const cloud = activeCloudBinding(env);
16585
- if (cloud) return resolveCloudHubToken(cloud, env);
16895
+ function resolveAgentHubAuthToken(env, project, cwd = process.cwd(), opRead) {
16896
+ const cloud = cloudModeToken(cwd, env, void 0, opRead);
16897
+ if (cloud) return cloud;
16586
16898
  const envToken = env.KXM_AUTH_TOKEN?.trim();
16587
16899
  if (envToken) return envToken;
16900
+ const fromConfig = resolveKeyReference(describeHubConnection(cwd, env).endpoint?.key, env, opRead);
16901
+ if (fromConfig) return fromConfig.value;
16588
16902
  const tokens = readHubEnvRecord(env)?.projectTokens;
16589
16903
  if (!tokens || !Object.hasOwn(tokens, project)) return void 0;
16590
16904
  return tokens[project]?.trim() || void 0;
@@ -16592,12 +16906,16 @@ function resolveAgentHubAuthToken(env, project) {
16592
16906
  var AgentProjectTokenMissingError = class extends Error {
16593
16907
  code = "project_token_missing";
16594
16908
  project;
16595
- constructor(project) {
16596
- super(
16597
- `kxm has no project token for project ${project} on this machine. Set KXM_AUTH_TOKEN to that project's token, or add ${project} to the hub KXM_PROJECT_TOKENS (list every existing project too, because that variable replaces the saved map). An agent never uses the hub admin token.`
16598
- );
16909
+ projectSource;
16910
+ hint;
16911
+ constructor(projectOrIdentity) {
16912
+ const identity = typeof projectOrIdentity === "string" ? { project: projectOrIdentity, source: "env", sourceLabel: "the project id sent to the hub" } : projectOrIdentity;
16913
+ const message = projectTokenMissingMessage(identity);
16914
+ super(message);
16599
16915
  this.name = "AgentProjectTokenMissingError";
16600
- this.project = project;
16916
+ this.project = identity.project;
16917
+ this.projectSource = typeof projectOrIdentity === "string" ? "unknown" : identity.source;
16918
+ this.hint = message;
16601
16919
  }
16602
16920
  };
16603
16921
 
@@ -16683,6 +17001,22 @@ async function ensureHubRunning(options) {
16683
17001
  if (health === "on") return { status: "bound-healthy", url: binding.url };
16684
17002
  if (binding.cloud) return { status: "failed", reason: "cloud_hub_unreachable", logPath: "" };
16685
17003
  }
17004
+ if (!binding?.cloud) {
17005
+ try {
17006
+ const connection = describeHubConnection(options.cwd, env);
17007
+ if (connection.mode === "cloud") {
17008
+ const { health } = await probeHubHealth(connection.url, fetchImpl);
17009
+ if (health === "on") return { status: "bound-healthy", url: connection.url };
17010
+ return { status: "failed", reason: "cloud_hub_unreachable", logPath: "" };
17011
+ }
17012
+ } catch (error2) {
17013
+ return {
17014
+ status: "failed",
17015
+ reason: error2 instanceof Error ? error2.message : "hub_config_invalid",
17016
+ logPath: ""
17017
+ };
17018
+ }
17019
+ }
16686
17020
  const workdir = resolve6(env.KXM_WORKDIR?.trim() || options.cwd);
16687
17021
  const workspaceDir = resolve6(workdir, env.KXM_WORKSPACE_DIR?.trim() || ".kxm");
16688
17022
  const stateDir = resolve6(workdir, env.KXM_STATE_DIR?.trim() || join7(workspaceDir, "state"));
@@ -36777,7 +37111,7 @@ async function consumeWorkerRecoveryEnvelope(client, stateDir, agentName, projec
36777
37111
  }
36778
37112
 
36779
37113
  // plugins/kxm/src/session-work.ts
36780
- import { spawnSync as spawnSync2 } from "node:child_process";
37114
+ import { spawnSync as spawnSync3 } from "node:child_process";
36781
37115
  import { randomUUID } from "node:crypto";
36782
37116
  import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync12, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "node:fs";
36783
37117
  import { join as join13 } from "node:path";
@@ -37275,10 +37609,10 @@ function formatShipLine(ship) {
37275
37609
  }
37276
37610
  function readGitShip(cwd) {
37277
37611
  try {
37278
- const dirty = spawnSync2("git", ["--no-optional-locks", "-C", cwd, "status", "--porcelain"], { encoding: "utf8", windowsHide: true });
37612
+ const dirty = spawnSync3("git", ["--no-optional-locks", "-C", cwd, "status", "--porcelain"], { encoding: "utf8", windowsHide: true });
37279
37613
  if (dirty.status !== 0) return void 0;
37280
37614
  const isDirty2 = dirty.stdout.trim().length > 0;
37281
- const upstream = spawnSync2("git", ["-C", cwd, "rev-list", "--count", "@{u}..HEAD"], { encoding: "utf8", windowsHide: true });
37615
+ const upstream = spawnSync3("git", ["-C", cwd, "rev-list", "--count", "@{u}..HEAD"], { encoding: "utf8", windowsHide: true });
37282
37616
  if (upstream.status === 0) {
37283
37617
  return {
37284
37618
  dirty: isDirty2,
@@ -37286,9 +37620,9 @@ function readGitShip(cwd) {
37286
37620
  };
37287
37621
  }
37288
37622
  for (const baseRef of ["origin/HEAD", "main", "origin/main", "master", "origin/master"]) {
37289
- const mb = spawnSync2("git", ["-C", cwd, "merge-base", baseRef, "HEAD"], { encoding: "utf8", windowsHide: true });
37623
+ const mb = spawnSync3("git", ["-C", cwd, "merge-base", baseRef, "HEAD"], { encoding: "utf8", windowsHide: true });
37290
37624
  if (mb.status === 0 && mb.stdout.trim()) {
37291
- const count = spawnSync2("git", ["-C", cwd, "rev-list", "--count", `${mb.stdout.trim()}..HEAD`], { encoding: "utf8", windowsHide: true });
37625
+ const count = spawnSync3("git", ["-C", cwd, "rev-list", "--count", `${mb.stdout.trim()}..HEAD`], { encoding: "utf8", windowsHide: true });
37292
37626
  if (count.status === 0) {
37293
37627
  return {
37294
37628
  dirty: isDirty2,
@@ -37994,7 +38328,7 @@ function piMeshExtension(pi) {
37994
38328
  ctx2.ui.setEditorText?.(item.prompt);
37995
38329
  }
37996
38330
  async function showKxmHub(ctx2) {
37997
- const url = resolveHubServerUrl(process.env);
38331
+ const url = describeHubConnection(process.cwd(), process.env).url;
37998
38332
  let health = "unreachable";
37999
38333
  try {
38000
38334
  const response = await fetch(`${url}/health`);
@@ -38284,8 +38618,9 @@ function piMeshExtension(pi) {
38284
38618
  return;
38285
38619
  }
38286
38620
  currentPiSessionId = ctx2.sessionManager?.getSessionId();
38287
- const serverUrl = resolveHubServerUrl(process.env);
38288
- const project = defaultProjectName(ctx2.cwd, process.env);
38621
+ const identity = resolveProjectIdentity(ctx2.cwd, process.env);
38622
+ const serverUrl = describeHubConnection(ctx2.cwd, process.env).url;
38623
+ const project = identity.project;
38289
38624
  const name = process.env.KXM_AGENT_NAME ?? pi.getSessionName() ?? `pi-${process.pid}`;
38290
38625
  agentName = name;
38291
38626
  projectName = project;
@@ -38311,7 +38646,7 @@ function piMeshExtension(pi) {
38311
38646
  }
38312
38647
  let hubAuthToken;
38313
38648
  try {
38314
- hubAuthToken = resolveAgentHubAuthToken(process.env, project);
38649
+ hubAuthToken = resolveAgentHubAuthToken(process.env, project, ctx2.cwd);
38315
38650
  } catch (error2) {
38316
38651
  const detail = error2 instanceof CloudTokenError ? error2.message : `kxm could not read persisted hub credentials: ${error2 instanceof Error ? error2.message : String(error2)}`;
38317
38652
  ctx2.ui.notify(detail, "error");
@@ -38319,7 +38654,7 @@ function piMeshExtension(pi) {
38319
38654
  return;
38320
38655
  }
38321
38656
  if (!hubAuthToken) {
38322
- ctx2.ui.notify(new AgentProjectTokenMissingError(project).message, "error");
38657
+ ctx2.ui.notify(new AgentProjectTokenMissingError(identity).message, "error");
38323
38658
  await applySessionChrome(ctx2, event, true);
38324
38659
  return;
38325
38660
  }
@@ -38533,12 +38868,12 @@ function piMeshExtension(pi) {
38533
38868
  if (command === "memory") {
38534
38869
  const cwd = typeof ctx2.cwd === "string" ? ctx2.cwd : process.cwd();
38535
38870
  const kxmBin = process.env.KXM_BIN || "kxm";
38536
- const res = spawnSync3(kxmBin, ["memory", "brief"], { cwd, encoding: "utf8", windowsHide: true });
38871
+ const res = spawnSync4(kxmBin, ["memory", "brief"], { cwd, encoding: "utf8", windowsHide: true });
38537
38872
  let memText = res.status === 0 && res.stdout ? res.stdout.trim() : "";
38538
38873
  if (!memText) {
38539
38874
  const script = join15(cwd, "scripts", "kxm.mjs");
38540
38875
  if (existsSync13(script)) {
38541
- const scriptRes = spawnSync3(process.execPath, [script, "memory", "brief"], { cwd, encoding: "utf8", windowsHide: true });
38876
+ const scriptRes = spawnSync4(process.execPath, [script, "memory", "brief"], { cwd, encoding: "utf8", windowsHide: true });
38542
38877
  if (scriptRes.status === 0 && scriptRes.stdout) memText = scriptRes.stdout.trim();
38543
38878
  }
38544
38879
  }