@kontextmind/kxm 0.7.164 → 0.7.165

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 (36) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +9 -0
  3. package/README.md +2 -0
  4. package/docs/README.md +1 -0
  5. package/docs/architecture/inventory.md +4 -3
  6. package/docs/glossary.md +3 -3
  7. package/docs/operations/troubleshooting.md +3 -0
  8. package/docs/operations.md +154 -0
  9. package/docs/reference/cli-reference.md +4 -3
  10. package/docs/reference/configuration.md +11 -1
  11. package/package.json +1 -1
  12. package/plugins/kxm/.claude-plugin/plugin.json +2 -2
  13. package/plugins/kxm/README.md +2 -2
  14. package/plugins/kxm/dist/claude-hook.js +120 -16
  15. package/plugins/kxm/dist/cli.js +14929 -14542
  16. package/plugins/kxm/dist/extension.js +299 -122
  17. package/plugins/kxm/dist/mcp-server.js +7690 -106
  18. package/plugins/kxm/dist/runtime-supervisor.js +151 -4
  19. package/plugins/kxm/dist/runtime.js +158 -11
  20. package/plugins/kxm/package.json +1 -1
  21. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +5 -1
  22. package/plugins/kxm/src/cli/context-skills.ts +64 -10
  23. package/plugins/kxm/src/cli/hub.ts +128 -17
  24. package/plugins/kxm/src/cli/project.ts +7 -3
  25. package/plugins/kxm/src/cli/types.ts +6 -0
  26. package/plugins/kxm/src/cli/workflows.ts +12 -1
  27. package/plugins/kxm/src/cli.ts +23 -5
  28. package/plugins/kxm/src/extension.ts +7 -3
  29. package/plugins/kxm/src/hub-autostart.ts +3 -0
  30. package/plugins/kxm/src/hub-binding.ts +182 -2
  31. package/plugins/kxm/src/hub-env.ts +71 -0
  32. package/plugins/kxm/src/mcp-server.ts +3 -2
  33. package/plugins/kxm/src/project-name.ts +34 -7
  34. package/plugins/kxm/src/runtime-supervisor.ts +9 -3
  35. package/plugins/kxm/src/tenant-status.ts +9 -2
  36. package/scripts/build-runtime.mjs +5 -0
@@ -31321,10 +31321,22 @@ var RuntimeHubClient = class {
31321
31321
  };
31322
31322
 
31323
31323
  // plugins/kxm/src/hub-binding.ts
31324
+ import { spawnSync as spawnSync6 } from "node:child_process";
31324
31325
  import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync13, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
31325
31326
  import { homedir as homedir2 } from "node:os";
31326
31327
  import { dirname as dirname8, isAbsolute as isAbsolute6, join as join18, resolve as resolve9 } from "node:path";
31327
31328
  var HUB_BINDING_SCHEMA = "kxm.hub-binding.v1";
31329
+ var CloudTokenError = class extends Error {
31330
+ code;
31331
+ constructor(code, message) {
31332
+ super(message);
31333
+ this.name = "CloudTokenError";
31334
+ this.code = code;
31335
+ }
31336
+ };
31337
+ var TOKEN_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
31338
+ var TOKEN_COMMAND_TIMEOUT_MS = 15e3;
31339
+ var TOKEN_COMMAND_MAX_BUFFER = 4096;
31328
31340
  var HubBindingError = class extends Error {
31329
31341
  constructor(message) {
31330
31342
  super(message);
@@ -31362,6 +31374,88 @@ function validateHubUrl(raw) {
31362
31374
  }
31363
31375
  return parsed.href.replace(/\/$/, "");
31364
31376
  }
31377
+ function splitTokenCommand(raw) {
31378
+ if (/[|&;<>$`\n\r]/.test(raw)) {
31379
+ throw new CloudTokenError(
31380
+ "cloud_token_command_invalid",
31381
+ "token command must be a program and arguments, not a shell pipeline"
31382
+ );
31383
+ }
31384
+ const args = [];
31385
+ let current = "";
31386
+ let quote;
31387
+ for (const ch of raw) {
31388
+ if (quote) {
31389
+ if (ch === quote) quote = void 0;
31390
+ else current += ch;
31391
+ continue;
31392
+ }
31393
+ if (ch === "'" || ch === '"') {
31394
+ quote = ch;
31395
+ continue;
31396
+ }
31397
+ if (ch === " " || ch === " ") {
31398
+ if (current.length > 0) {
31399
+ args.push(current);
31400
+ current = "";
31401
+ }
31402
+ continue;
31403
+ }
31404
+ current += ch;
31405
+ }
31406
+ if (quote) {
31407
+ throw new CloudTokenError("cloud_token_command_invalid", "token command has an unclosed quote");
31408
+ }
31409
+ if (current.length > 0) args.push(current);
31410
+ if (args.length === 0) {
31411
+ throw new CloudTokenError("cloud_token_command_invalid", "token command is empty");
31412
+ }
31413
+ return args;
31414
+ }
31415
+ function isCloudTokenEnvName(value) {
31416
+ return TOKEN_ENV_NAME.test(value);
31417
+ }
31418
+ function resolveCloudHubToken(binding, env = process.env) {
31419
+ if (!binding.cloud) {
31420
+ throw new CloudTokenError("cloud_token_missing", "cloud hub token is missing; the local hub-env token was not used");
31421
+ }
31422
+ if (binding.tokenEnv) {
31423
+ const value = env[binding.tokenEnv]?.trim();
31424
+ if (value) return value;
31425
+ }
31426
+ if (binding.tokenCommand) return runTokenCommand(binding.tokenCommand, env);
31427
+ const source = binding.tokenEnv ? `${binding.tokenEnv} is unset or empty` : "no token source is configured";
31428
+ throw new CloudTokenError(
31429
+ "cloud_token_missing",
31430
+ `cloud hub token is missing: ${source}; the local hub-env token was not used`
31431
+ );
31432
+ }
31433
+ function runTokenCommand(command, env) {
31434
+ const argv = splitTokenCommand(command);
31435
+ const [program, ...args] = argv;
31436
+ const result = spawnSync6(program, args, {
31437
+ encoding: "utf8",
31438
+ shell: false,
31439
+ timeout: TOKEN_COMMAND_TIMEOUT_MS,
31440
+ maxBuffer: TOKEN_COMMAND_MAX_BUFFER,
31441
+ env,
31442
+ windowsHide: true
31443
+ });
31444
+ if (result.error || result.status !== 0) {
31445
+ throw new CloudTokenError(
31446
+ "cloud_token_command_failed",
31447
+ "token command failed; the local hub-env token was not used"
31448
+ );
31449
+ }
31450
+ const stdout = result.stdout ?? "";
31451
+ if (stdout.trim().length === 0 || stdout.trim() !== stdout.trim().split(/\r?\n/)[0]) {
31452
+ throw new CloudTokenError(
31453
+ "cloud_token_command_failed",
31454
+ "token command did not print a single token; the local hub-env token was not used"
31455
+ );
31456
+ }
31457
+ return stdout.trim();
31458
+ }
31365
31459
  function isIsoTimestamp(value) {
31366
31460
  if (Number.isNaN(Date.parse(value))) return false;
31367
31461
  return value === new Date(value).toISOString();
@@ -31380,7 +31474,9 @@ function readHubBinding(env = process.env) {
31380
31474
  }
31381
31475
  const row = parsed;
31382
31476
  const keys = Object.keys(row);
31383
- if (keys.length !== 3 || row.schema !== HUB_BINDING_SCHEMA || typeof row.url !== "string" || typeof row.boundAt !== "string" || !isIsoTimestamp(row.boundAt)) {
31477
+ const allowed = /* @__PURE__ */ new Set(["schema", "url", "boundAt", "cloud", "tokenEnv", "tokenCommand"]);
31478
+ const cloudish = keys.some((key) => key === "cloud" || key === "tokenEnv" || key === "tokenCommand");
31479
+ if (keys.some((key) => !allowed.has(key)) || !cloudish && keys.length !== 3 || row.schema !== HUB_BINDING_SCHEMA || typeof row.url !== "string" || typeof row.boundAt !== "string" || !isIsoTimestamp(row.boundAt)) {
31384
31480
  throw new HubBindingError(`malformed hub binding at ${file}`);
31385
31481
  }
31386
31482
  let url;
@@ -31389,13 +31485,45 @@ function readHubBinding(env = process.env) {
31389
31485
  } catch {
31390
31486
  throw new HubBindingError(`malformed hub binding at ${file}`);
31391
31487
  }
31392
- return { schema: HUB_BINDING_SCHEMA, url, boundAt: row.boundAt };
31488
+ if (!cloudish) return { schema: HUB_BINDING_SCHEMA, url, boundAt: row.boundAt };
31489
+ if (row.cloud !== true) throw new HubBindingError(`malformed hub binding at ${file}`);
31490
+ let tokenEnv;
31491
+ let tokenCommand;
31492
+ if (row.tokenEnv !== void 0) {
31493
+ if (typeof row.tokenEnv !== "string" || !isCloudTokenEnvName(row.tokenEnv)) {
31494
+ throw new HubBindingError(`malformed hub binding at ${file}`);
31495
+ }
31496
+ tokenEnv = row.tokenEnv;
31497
+ }
31498
+ if (row.tokenCommand !== void 0) {
31499
+ if (typeof row.tokenCommand !== "string") throw new HubBindingError(`malformed hub binding at ${file}`);
31500
+ try {
31501
+ splitTokenCommand(row.tokenCommand);
31502
+ } catch {
31503
+ throw new HubBindingError(`malformed hub binding at ${file}`);
31504
+ }
31505
+ tokenCommand = row.tokenCommand;
31506
+ }
31507
+ if (!tokenEnv && !tokenCommand) throw new HubBindingError(`malformed hub binding at ${file}`);
31508
+ return {
31509
+ schema: HUB_BINDING_SCHEMA,
31510
+ url,
31511
+ boundAt: row.boundAt,
31512
+ cloud: true,
31513
+ ...tokenEnv ? { tokenEnv } : {},
31514
+ ...tokenCommand ? { tokenCommand } : {}
31515
+ };
31393
31516
  }
31394
31517
 
31395
31518
  // plugins/kxm/src/hub-env.ts
31396
31519
  import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync14, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "node:fs";
31397
31520
  import { homedir as homedir3 } from "node:os";
31398
31521
  import { dirname as dirname9, isAbsolute as isAbsolute7, join as join19, resolve as resolve10 } from "node:path";
31522
+
31523
+ // plugins/kxm/src/project-name.ts
31524
+ var import_yaml12 = __toESM(require_dist(), 1);
31525
+
31526
+ // plugins/kxm/src/hub-env.ts
31399
31527
  var HUB_ENV_SCHEMA = "kxm.hub-env.v1";
31400
31528
  var HubEnvError = class extends Error {
31401
31529
  constructor(message) {
@@ -31459,7 +31587,22 @@ function readHubEnvRecord(env = process.env) {
31459
31587
  ...record2.projectTokens !== void 0 ? { projectTokens: record2.projectTokens } : {}
31460
31588
  };
31461
31589
  }
31590
+ function activeCloudBinding(env) {
31591
+ try {
31592
+ const binding = readHubBinding(env);
31593
+ return binding?.cloud ? binding : void 0;
31594
+ } catch (error) {
31595
+ if (error instanceof HubBindingError) {
31596
+ throw new HubEnvError(
31597
+ `${error.message}; refusing to guess a credential \u2014 repair or remove ${hubBindingFile(env)}`
31598
+ );
31599
+ }
31600
+ throw error;
31601
+ }
31602
+ }
31462
31603
  function resolveClientHubAuthToken(env, project) {
31604
+ const cloud = activeCloudBinding(env);
31605
+ if (cloud) return resolveCloudHubToken(cloud, env);
31463
31606
  const envToken = env.KXM_AUTH_TOKEN?.trim();
31464
31607
  if (envToken) return envToken;
31465
31608
  const record2 = readHubEnvRecord(env);
@@ -32041,8 +32184,12 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
32041
32184
  const contexts = /* @__PURE__ */ new Map();
32042
32185
  const syncStatuses = /* @__PURE__ */ new Map();
32043
32186
  const registerSyncCredentials = (context) => {
32044
- const hubToken = resolveClientHubAuthToken(env, context.projectId);
32045
- if (hubToken) context.eventStore.syncRedactor.register(hubToken);
32187
+ try {
32188
+ const hubToken = resolveClientHubAuthToken(env, context.projectId);
32189
+ if (hubToken) context.eventStore.syncRedactor.register(hubToken);
32190
+ } catch (error) {
32191
+ if (!(error instanceof CloudTokenError)) throw error;
32192
+ }
32046
32193
  for (const key of Object.keys(env)) {
32047
32194
  if (key.startsWith("KXM_") && (key.endsWith("_TOKEN") || key.endsWith("_KEY")) || key.endsWith("_API_KEY") || key.endsWith("_SECRET")) {
32048
32195
  const value = env[key]?.trim();
@@ -32069,10 +32069,22 @@ var RuntimeHubClient = class {
32069
32069
  };
32070
32070
 
32071
32071
  // plugins/kxm/src/hub-binding.ts
32072
+ import { spawnSync as spawnSync6 } from "node:child_process";
32072
32073
  import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync13, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
32073
32074
  import { homedir as homedir2 } from "node:os";
32074
32075
  import { dirname as dirname8, isAbsolute as isAbsolute6, join as join18, resolve as resolve9 } from "node:path";
32075
32076
  var HUB_BINDING_SCHEMA = "kxm.hub-binding.v1";
32077
+ var CloudTokenError = class extends Error {
32078
+ code;
32079
+ constructor(code, message) {
32080
+ super(message);
32081
+ this.name = "CloudTokenError";
32082
+ this.code = code;
32083
+ }
32084
+ };
32085
+ var TOKEN_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
32086
+ var TOKEN_COMMAND_TIMEOUT_MS = 15e3;
32087
+ var TOKEN_COMMAND_MAX_BUFFER = 4096;
32076
32088
  var HubBindingError = class extends Error {
32077
32089
  constructor(message) {
32078
32090
  super(message);
@@ -32110,6 +32122,88 @@ function validateHubUrl(raw) {
32110
32122
  }
32111
32123
  return parsed.href.replace(/\/$/, "");
32112
32124
  }
32125
+ function splitTokenCommand(raw) {
32126
+ if (/[|&;<>$`\n\r]/.test(raw)) {
32127
+ throw new CloudTokenError(
32128
+ "cloud_token_command_invalid",
32129
+ "token command must be a program and arguments, not a shell pipeline"
32130
+ );
32131
+ }
32132
+ const args = [];
32133
+ let current = "";
32134
+ let quote;
32135
+ for (const ch of raw) {
32136
+ if (quote) {
32137
+ if (ch === quote) quote = void 0;
32138
+ else current += ch;
32139
+ continue;
32140
+ }
32141
+ if (ch === "'" || ch === '"') {
32142
+ quote = ch;
32143
+ continue;
32144
+ }
32145
+ if (ch === " " || ch === " ") {
32146
+ if (current.length > 0) {
32147
+ args.push(current);
32148
+ current = "";
32149
+ }
32150
+ continue;
32151
+ }
32152
+ current += ch;
32153
+ }
32154
+ if (quote) {
32155
+ throw new CloudTokenError("cloud_token_command_invalid", "token command has an unclosed quote");
32156
+ }
32157
+ if (current.length > 0) args.push(current);
32158
+ if (args.length === 0) {
32159
+ throw new CloudTokenError("cloud_token_command_invalid", "token command is empty");
32160
+ }
32161
+ return args;
32162
+ }
32163
+ function isCloudTokenEnvName(value) {
32164
+ return TOKEN_ENV_NAME.test(value);
32165
+ }
32166
+ function resolveCloudHubToken(binding, env = process.env) {
32167
+ if (!binding.cloud) {
32168
+ throw new CloudTokenError("cloud_token_missing", "cloud hub token is missing; the local hub-env token was not used");
32169
+ }
32170
+ if (binding.tokenEnv) {
32171
+ const value = env[binding.tokenEnv]?.trim();
32172
+ if (value) return value;
32173
+ }
32174
+ if (binding.tokenCommand) return runTokenCommand(binding.tokenCommand, env);
32175
+ const source = binding.tokenEnv ? `${binding.tokenEnv} is unset or empty` : "no token source is configured";
32176
+ throw new CloudTokenError(
32177
+ "cloud_token_missing",
32178
+ `cloud hub token is missing: ${source}; the local hub-env token was not used`
32179
+ );
32180
+ }
32181
+ function runTokenCommand(command, env) {
32182
+ const argv = splitTokenCommand(command);
32183
+ const [program, ...args] = argv;
32184
+ const result = spawnSync6(program, args, {
32185
+ encoding: "utf8",
32186
+ shell: false,
32187
+ timeout: TOKEN_COMMAND_TIMEOUT_MS,
32188
+ maxBuffer: TOKEN_COMMAND_MAX_BUFFER,
32189
+ env,
32190
+ windowsHide: true
32191
+ });
32192
+ if (result.error || result.status !== 0) {
32193
+ throw new CloudTokenError(
32194
+ "cloud_token_command_failed",
32195
+ "token command failed; the local hub-env token was not used"
32196
+ );
32197
+ }
32198
+ const stdout = result.stdout ?? "";
32199
+ if (stdout.trim().length === 0 || stdout.trim() !== stdout.trim().split(/\r?\n/)[0]) {
32200
+ throw new CloudTokenError(
32201
+ "cloud_token_command_failed",
32202
+ "token command did not print a single token; the local hub-env token was not used"
32203
+ );
32204
+ }
32205
+ return stdout.trim();
32206
+ }
32113
32207
  function isIsoTimestamp(value) {
32114
32208
  if (Number.isNaN(Date.parse(value))) return false;
32115
32209
  return value === new Date(value).toISOString();
@@ -32128,7 +32222,9 @@ function readHubBinding(env = process.env) {
32128
32222
  }
32129
32223
  const row = parsed;
32130
32224
  const keys = Object.keys(row);
32131
- if (keys.length !== 3 || row.schema !== HUB_BINDING_SCHEMA || typeof row.url !== "string" || typeof row.boundAt !== "string" || !isIsoTimestamp(row.boundAt)) {
32225
+ const allowed = /* @__PURE__ */ new Set(["schema", "url", "boundAt", "cloud", "tokenEnv", "tokenCommand"]);
32226
+ const cloudish = keys.some((key) => key === "cloud" || key === "tokenEnv" || key === "tokenCommand");
32227
+ if (keys.some((key) => !allowed.has(key)) || !cloudish && keys.length !== 3 || row.schema !== HUB_BINDING_SCHEMA || typeof row.url !== "string" || typeof row.boundAt !== "string" || !isIsoTimestamp(row.boundAt)) {
32132
32228
  throw new HubBindingError(`malformed hub binding at ${file}`);
32133
32229
  }
32134
32230
  let url;
@@ -32137,13 +32233,45 @@ function readHubBinding(env = process.env) {
32137
32233
  } catch {
32138
32234
  throw new HubBindingError(`malformed hub binding at ${file}`);
32139
32235
  }
32140
- return { schema: HUB_BINDING_SCHEMA, url, boundAt: row.boundAt };
32236
+ if (!cloudish) return { schema: HUB_BINDING_SCHEMA, url, boundAt: row.boundAt };
32237
+ if (row.cloud !== true) throw new HubBindingError(`malformed hub binding at ${file}`);
32238
+ let tokenEnv;
32239
+ let tokenCommand;
32240
+ if (row.tokenEnv !== void 0) {
32241
+ if (typeof row.tokenEnv !== "string" || !isCloudTokenEnvName(row.tokenEnv)) {
32242
+ throw new HubBindingError(`malformed hub binding at ${file}`);
32243
+ }
32244
+ tokenEnv = row.tokenEnv;
32245
+ }
32246
+ if (row.tokenCommand !== void 0) {
32247
+ if (typeof row.tokenCommand !== "string") throw new HubBindingError(`malformed hub binding at ${file}`);
32248
+ try {
32249
+ splitTokenCommand(row.tokenCommand);
32250
+ } catch {
32251
+ throw new HubBindingError(`malformed hub binding at ${file}`);
32252
+ }
32253
+ tokenCommand = row.tokenCommand;
32254
+ }
32255
+ if (!tokenEnv && !tokenCommand) throw new HubBindingError(`malformed hub binding at ${file}`);
32256
+ return {
32257
+ schema: HUB_BINDING_SCHEMA,
32258
+ url,
32259
+ boundAt: row.boundAt,
32260
+ cloud: true,
32261
+ ...tokenEnv ? { tokenEnv } : {},
32262
+ ...tokenCommand ? { tokenCommand } : {}
32263
+ };
32141
32264
  }
32142
32265
 
32143
32266
  // plugins/kxm/src/hub-env.ts
32144
32267
  import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync14, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "node:fs";
32145
32268
  import { homedir as homedir3 } from "node:os";
32146
32269
  import { dirname as dirname9, isAbsolute as isAbsolute7, join as join19, resolve as resolve10 } from "node:path";
32270
+
32271
+ // plugins/kxm/src/project-name.ts
32272
+ var import_yaml12 = __toESM(require_dist(), 1);
32273
+
32274
+ // plugins/kxm/src/hub-env.ts
32147
32275
  var HUB_ENV_SCHEMA = "kxm.hub-env.v1";
32148
32276
  var HubEnvError = class extends Error {
32149
32277
  constructor(message) {
@@ -32207,7 +32335,22 @@ function readHubEnvRecord(env = process.env) {
32207
32335
  ...record2.projectTokens !== void 0 ? { projectTokens: record2.projectTokens } : {}
32208
32336
  };
32209
32337
  }
32338
+ function activeCloudBinding(env) {
32339
+ try {
32340
+ const binding = readHubBinding(env);
32341
+ return binding?.cloud ? binding : void 0;
32342
+ } catch (error) {
32343
+ if (error instanceof HubBindingError) {
32344
+ throw new HubEnvError(
32345
+ `${error.message}; refusing to guess a credential \u2014 repair or remove ${hubBindingFile(env)}`
32346
+ );
32347
+ }
32348
+ throw error;
32349
+ }
32350
+ }
32210
32351
  function resolveClientHubAuthToken(env, project) {
32352
+ const cloud = activeCloudBinding(env);
32353
+ if (cloud) return resolveCloudHubToken(cloud, env);
32211
32354
  const envToken = env.KXM_AUTH_TOKEN?.trim();
32212
32355
  if (envToken) return envToken;
32213
32356
  const record2 = readHubEnvRecord(env);
@@ -32789,8 +32932,12 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
32789
32932
  const contexts = /* @__PURE__ */ new Map();
32790
32933
  const syncStatuses = /* @__PURE__ */ new Map();
32791
32934
  const registerSyncCredentials = (context) => {
32792
- const hubToken = resolveClientHubAuthToken(env, context.projectId);
32793
- if (hubToken) context.eventStore.syncRedactor.register(hubToken);
32935
+ try {
32936
+ const hubToken = resolveClientHubAuthToken(env, context.projectId);
32937
+ if (hubToken) context.eventStore.syncRedactor.register(hubToken);
32938
+ } catch (error) {
32939
+ if (!(error instanceof CloudTokenError)) throw error;
32940
+ }
32794
32941
  for (const key of Object.keys(env)) {
32795
32942
  if (key.startsWith("KXM_") && (key.endsWith("_TOKEN") || key.endsWith("_KEY")) || key.endsWith("_API_KEY") || key.endsWith("_SECRET")) {
32796
32943
  const value = env[key]?.trim();
@@ -34899,7 +35046,7 @@ Instructions for Operator:
34899
35046
  };
34900
35047
 
34901
35048
  // plugins/kxm/src/modes.ts
34902
- var import_yaml12 = __toESM(require_dist(), 1);
35049
+ var import_yaml13 = __toESM(require_dist(), 1);
34903
35050
  import { existsSync as existsSync20, readFileSync as readFileSync17 } from "node:fs";
34904
35051
  import { join as join23 } from "node:path";
34905
35052
  var DEFAULT_MODES_CONFIG = Object.freeze({
@@ -34970,7 +35117,7 @@ function loadModesConfig(projectRoot) {
34970
35117
  }
34971
35118
  try {
34972
35119
  const raw = readFileSync17(modesPath, "utf8");
34973
- const parsed = (0, import_yaml12.parse)(raw);
35120
+ const parsed = (0, import_yaml13.parse)(raw);
34974
35121
  if (parsed && parsed.schema === "kxm.modes.v1" && parsed.majorModes) {
34975
35122
  return {
34976
35123
  schema: "kxm.modes.v1",
@@ -35190,7 +35337,7 @@ ${divider}
35190
35337
  }
35191
35338
 
35192
35339
  // plugins/kxm/src/ssh-remote.ts
35193
- import { spawnSync as spawnSync6 } from "node:child_process";
35340
+ import { spawnSync as spawnSync7 } from "node:child_process";
35194
35341
  import { existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync18, readdirSync as readdirSync11, rmSync as rmSync5, statSync as statSync6 } from "node:fs";
35195
35342
  import { homedir as homedir4 } from "node:os";
35196
35343
  import { join as join24, resolve as resolve12 } from "node:path";
@@ -35266,7 +35413,7 @@ function parseSshConfig(configPath) {
35266
35413
  return [];
35267
35414
  }
35268
35415
  }
35269
- function resolveSshHostG(host, execFn = spawnSync6) {
35416
+ function resolveSshHostG(host, execFn = spawnSync7) {
35270
35417
  try {
35271
35418
  const result = execFn("ssh", ["-G", host], { encoding: "utf-8" });
35272
35419
  if (result.status !== 0 || !result.stdout) {
@@ -35332,7 +35479,7 @@ function buildSshArgs(options) {
35332
35479
  args.push(options.host);
35333
35480
  return args;
35334
35481
  }
35335
- function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync6) {
35482
+ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync7) {
35336
35483
  const resolvedDir = ensureSocketDir(socketDir);
35337
35484
  const controlPath = join24(resolvedDir, "%C");
35338
35485
  try {
@@ -35344,7 +35491,7 @@ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawn
35344
35491
  return false;
35345
35492
  }
35346
35493
  }
35347
- function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync6) {
35494
+ function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync7) {
35348
35495
  const resolvedDir = ensureSocketDir(socketDir);
35349
35496
  const controlPath = join24(resolvedDir, "%C");
35350
35497
  try {
@@ -35378,7 +35525,7 @@ function pruneSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
35378
35525
  }
35379
35526
  function executeSshRun(params) {
35380
35527
  const startTime = Date.now();
35381
- const execSyncFn = params.execFn ?? spawnSync6;
35528
+ const execSyncFn = params.execFn ?? spawnSync7;
35382
35529
  if (params.action === "info") {
35383
35530
  if (params.host) {
35384
35531
  const hostInfo = resolveSshHostG(params.host, execSyncFn);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-plugin",
3
- "version": "0.7.164",
3
+ "version": "0.7.165",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -46,7 +46,7 @@ the hub admin token or a project token.
46
46
  |---|---|---|
47
47
  | `kxm hub start` | Start the hub in the foreground | `--json` |
48
48
  | `kxm hub stop` | Request managed hub and worker shutdown | `--wait-ms <ms>` |
49
- | `kxm hub bind <url>` | Bind this machine to a running hub | http or https URL |
49
+ | `kxm hub bind <url>` | Bind this machine to a running hub | http or https URL. `--cloud` with `--token-env` or `--token-command` for an SSH forward to loopback. The token is not stored |
50
50
  | `kxm hub unbind` | Remove this machine's hub binding | `--json` |
51
51
  | `kxm restore <manifest>` | Replace this project's live stores from a verified backup manifest | `--all-projects`, `--json` |
52
52
 
@@ -69,6 +69,10 @@ kxm restore .kxm/backups/pre-upgrade/manifest.json
69
69
  - `kxm hub bind` to a remote (non-loopback) URL fails closed with
70
70
  `hub_bind_unauthenticated` unless a credential for the current project
71
71
  resolves from `KXM_AUTH_TOKEN` or the persisted `hub-env.json`.
72
+ - `kxm hub bind --cloud` is the bind for a loopback SSH forward of a remote
73
+ hub. It never falls back to this machine's hub-env token. Pass
74
+ `--token-env KXMD_HUB_TOKEN` or `--token-command "op read op://..."`.
75
+ See docs/operations.md, section "Cross-box peer attach".
72
76
  - Hub PID claims record the wrapper and server child PID. A dead wrapper's
73
77
  claim is reclaimed automatically, an orphaned server is terminated first,
74
78
  and `kxm hub stop` recovers such orphans directly.
@@ -10,6 +10,8 @@ import {
10
10
  } from "../memory.ts";
11
11
  import { SkillLifecycle, type SkillEvaluationKind, type SkillState } from "../skills.ts";
12
12
  import { writeCompiledWiki } from "../wiki.ts";
13
+ import { CloudTokenError } from "../hub-binding.ts";
14
+ import { resolveControlPlaneAuthToken } from "../hub-env.ts";
13
15
  import { print, printPlan, type Runtime } from "./types.ts";
14
16
 
15
17
  /** Authenticated hub POST for context operations. The CLI operates as the
@@ -19,13 +21,33 @@ export async function hubContextPost(input: {
19
21
  path: string;
20
22
  body: Record<string, unknown>;
21
23
  authToken?: string | undefined;
24
+ /** When set, a cloud binding's token source replaces the explicit env token. */
25
+ env?: NodeJS.ProcessEnv | undefined;
26
+ command?: string | undefined;
27
+ io?: Runtime["io"] | undefined;
28
+ json?: boolean | undefined;
22
29
  fetchImpl: typeof fetch;
23
- }): Promise<{ ok: boolean; status: number; body: unknown }> {
30
+ }): Promise<{ ok: boolean; status: number; body: unknown; exitCode?: number }> {
31
+ let authToken = input.authToken;
32
+ if (input.env) {
33
+ try {
34
+ authToken = resolveControlPlaneAuthToken(input.env);
35
+ } catch (error) {
36
+ if (error instanceof CloudTokenError) {
37
+ const command = input.command ?? "context";
38
+ if (input.io) {
39
+ print(input.io, Boolean(input.json), { ok: false, command, error: error.code, detail: error.message }, error.message);
40
+ }
41
+ return { ok: false, status: 0, body: { error: error.code, detail: error.message }, exitCode: 2 };
42
+ }
43
+ throw error;
44
+ }
45
+ }
24
46
  const response = await input.fetchImpl(`${input.serverUrl.replace(/\/$/, "")}${input.path}`, {
25
47
  method: "POST",
26
48
  headers: {
27
49
  "content-type": "application/json",
28
- ...(input.authToken ? { authorization: `Bearer ${input.authToken}` } : {}),
50
+ ...(authToken ? { authorization: `Bearer ${authToken}` } : {}),
29
51
  },
30
52
  body: JSON.stringify(input.body),
31
53
  });
@@ -64,9 +86,13 @@ export async function cmdContextGet(runtime: Runtime, project: string, options:
64
86
  serverUrl: runtime.serverUrl,
65
87
  path: "/v1/context/get",
66
88
  body,
67
- ...(runtime.env.KXM_AUTH_TOKEN?.trim() ? { authToken: runtime.env.KXM_AUTH_TOKEN.trim() } : {}),
89
+ env: runtime.env,
90
+ command: "context",
91
+ io: runtime.io,
92
+ json: runtime.json,
68
93
  fetchImpl: runtime.fetchImpl,
69
94
  });
95
+ if (response.exitCode) return response.exitCode;
70
96
  print(runtime.io, runtime.json, { ok: response.ok, command: "context get", status: response.status, ...(response.body as object) }, `context get ${response.ok ? "assembled" : `failed (${response.status})`}`);
71
97
  return response.ok ? 0 : 1;
72
98
  }
@@ -81,9 +107,13 @@ export async function cmdContextRecall(runtime: Runtime, project: string, option
81
107
  serverUrl: runtime.serverUrl,
82
108
  path: "/v1/context/recall",
83
109
  body,
84
- ...(runtime.env.KXM_AUTH_TOKEN?.trim() ? { authToken: runtime.env.KXM_AUTH_TOKEN.trim() } : {}),
110
+ env: runtime.env,
111
+ command: "context",
112
+ io: runtime.io,
113
+ json: runtime.json,
85
114
  fetchImpl: runtime.fetchImpl,
86
115
  });
116
+ if (response.exitCode) return response.exitCode;
87
117
  print(runtime.io, runtime.json, { ok: response.ok, command: "context recall", status: response.status, ...(response.body as object) }, `context recall ${response.ok ? "complete" : `failed (${response.status})`}`);
88
118
  return response.ok ? 0 : 1;
89
119
  }
@@ -95,9 +125,13 @@ export async function cmdContextState(runtime: Runtime, project: string, key: st
95
125
  serverUrl: runtime.serverUrl,
96
126
  path: "/v1/context/state",
97
127
  body,
98
- ...(runtime.env.KXM_AUTH_TOKEN?.trim() ? { authToken: runtime.env.KXM_AUTH_TOKEN.trim() } : {}),
128
+ env: runtime.env,
129
+ command: "context",
130
+ io: runtime.io,
131
+ json: runtime.json,
99
132
  fetchImpl: runtime.fetchImpl,
100
133
  });
134
+ if (response.exitCode) return response.exitCode;
101
135
  print(runtime.io, runtime.json, { ok: response.ok, command: "context state", status: response.status, ...(response.body as object) }, `context state ${response.ok ? "resolved" : `failed (${response.status})`}`);
102
136
  return response.ok ? 0 : 1;
103
137
  }
@@ -109,9 +143,13 @@ export async function cmdContextEpisode(runtime: Runtime, project: string, optio
109
143
  serverUrl: runtime.serverUrl,
110
144
  path: "/v1/context/episode",
111
145
  body,
112
- ...(runtime.env.KXM_AUTH_TOKEN?.trim() ? { authToken: runtime.env.KXM_AUTH_TOKEN.trim() } : {}),
146
+ env: runtime.env,
147
+ command: "context",
148
+ io: runtime.io,
149
+ json: runtime.json,
113
150
  fetchImpl: runtime.fetchImpl,
114
151
  });
152
+ if (response.exitCode) return response.exitCode;
115
153
  print(runtime.io, runtime.json, { ok: response.ok, command: "context episode", status: response.status, ...(response.body as object) }, `context episode ${response.ok ? "complete" : `failed (${response.status})`}`);
116
154
  return response.ok ? 0 : 1;
117
155
  }
@@ -135,9 +173,13 @@ export async function cmdContextPromote(runtime: Runtime, project: string, propo
135
173
  serverUrl: runtime.serverUrl,
136
174
  path: "/v1/context/state/promote",
137
175
  body: { project, proposalId, evidence },
138
- ...(runtime.env.KXM_AUTH_TOKEN?.trim() ? { authToken: runtime.env.KXM_AUTH_TOKEN.trim() } : {}),
176
+ env: runtime.env,
177
+ command: "context",
178
+ io: runtime.io,
179
+ json: runtime.json,
139
180
  fetchImpl: runtime.fetchImpl,
140
181
  });
182
+ if (response.exitCode) return response.exitCode;
141
183
  print(runtime.io, runtime.json, { ok: response.ok, command: "context promote", status: response.status, ...(response.body as object) }, `context promote ${response.ok ? "recorded" : `failed (${response.status})`}`);
142
184
  return response.ok ? 0 : 1;
143
185
  }
@@ -147,9 +189,13 @@ export async function cmdContextExplain(runtime: Runtime, project: string, itemI
147
189
  serverUrl: runtime.serverUrl,
148
190
  path: "/v1/context/explain",
149
191
  body: { project, id: itemId },
150
- ...(runtime.env.KXM_AUTH_TOKEN?.trim() ? { authToken: runtime.env.KXM_AUTH_TOKEN.trim() } : {}),
192
+ env: runtime.env,
193
+ command: "context",
194
+ io: runtime.io,
195
+ json: runtime.json,
151
196
  fetchImpl: runtime.fetchImpl,
152
197
  });
198
+ if (response.exitCode) return response.exitCode;
153
199
  print(runtime.io, runtime.json, { ok: response.ok, command: "context explain", status: response.status, ...(response.body as object) }, `context explain ${response.ok ? "complete" : `failed (${response.status})`}`);
154
200
  return response.ok ? 0 : 1;
155
201
  }
@@ -159,9 +205,13 @@ export async function cmdContextWikiCompile(runtime: Runtime, project: string, o
159
205
  serverUrl: runtime.serverUrl,
160
206
  path: "/v1/context/wiki/compile",
161
207
  body: { project },
162
- ...(runtime.env.KXM_AUTH_TOKEN?.trim() ? { authToken: runtime.env.KXM_AUTH_TOKEN.trim() } : {}),
208
+ env: runtime.env,
209
+ command: "context",
210
+ io: runtime.io,
211
+ json: runtime.json,
163
212
  fetchImpl: runtime.fetchImpl,
164
213
  });
214
+ if (response.exitCode) return response.exitCode;
165
215
  if (!response.ok) {
166
216
  print(runtime.io, runtime.json, { ok: false, command: "context wiki-compile", status: response.status, body: response.body }, `wiki compile failed (${response.status})`);
167
217
  return 1;
@@ -191,9 +241,13 @@ export async function cmdContextWikiLint(runtime: Runtime, project: string): Pro
191
241
  serverUrl: runtime.serverUrl,
192
242
  path: "/v1/context/wiki/compile",
193
243
  body: { project },
194
- ...(runtime.env.KXM_AUTH_TOKEN?.trim() ? { authToken: runtime.env.KXM_AUTH_TOKEN.trim() } : {}),
244
+ env: runtime.env,
245
+ command: "context",
246
+ io: runtime.io,
247
+ json: runtime.json,
195
248
  fetchImpl: runtime.fetchImpl,
196
249
  });
250
+ if (response.exitCode) return response.exitCode;
197
251
  if (!response.ok) {
198
252
  print(runtime.io, runtime.json, { ok: false, command: "context wiki-lint", status: response.status, body: response.body }, `wiki lint failed (${response.status})`);
199
253
  return 1;