@kontextmind/kxm 0.7.163 → 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 (38) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +22 -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/contributing/harness-routing-internals.md +28 -27
  7. package/docs/glossary.md +3 -3
  8. package/docs/operations/troubleshooting.md +3 -0
  9. package/docs/operations.md +154 -0
  10. package/docs/reference/cli-reference.md +4 -3
  11. package/docs/reference/configuration.md +11 -1
  12. package/docs/reference/harness-routing.md +17 -15
  13. package/package.json +1 -1
  14. package/plugins/kxm/.claude-plugin/plugin.json +2 -2
  15. package/plugins/kxm/README.md +2 -2
  16. package/plugins/kxm/dist/claude-hook.js +120 -16
  17. package/plugins/kxm/dist/cli.js +14929 -14542
  18. package/plugins/kxm/dist/extension.js +299 -122
  19. package/plugins/kxm/dist/mcp-server.js +7690 -106
  20. package/plugins/kxm/dist/runtime-supervisor.js +151 -4
  21. package/plugins/kxm/dist/runtime.js +158 -11
  22. package/plugins/kxm/package.json +1 -1
  23. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +5 -1
  24. package/plugins/kxm/src/cli/context-skills.ts +64 -10
  25. package/plugins/kxm/src/cli/hub.ts +128 -17
  26. package/plugins/kxm/src/cli/project.ts +7 -3
  27. package/plugins/kxm/src/cli/types.ts +6 -0
  28. package/plugins/kxm/src/cli/workflows.ts +12 -1
  29. package/plugins/kxm/src/cli.ts +23 -5
  30. package/plugins/kxm/src/extension.ts +7 -3
  31. package/plugins/kxm/src/hub-autostart.ts +3 -0
  32. package/plugins/kxm/src/hub-binding.ts +182 -2
  33. package/plugins/kxm/src/hub-env.ts +71 -0
  34. package/plugins/kxm/src/mcp-server.ts +3 -2
  35. package/plugins/kxm/src/project-name.ts +34 -7
  36. package/plugins/kxm/src/runtime-supervisor.ts +9 -3
  37. package/plugins/kxm/src/tenant-status.ts +9 -2
  38. package/scripts/build-runtime.mjs +5 -0
@@ -568,6 +568,47 @@ function validateHubUrl(raw) {
568
568
  }
569
569
  return parsed.href.replace(/\/$/, "");
570
570
  }
571
+ function splitTokenCommand(raw) {
572
+ if (/[|&;<>$`\n\r]/.test(raw)) {
573
+ throw new CloudTokenError(
574
+ "cloud_token_command_invalid",
575
+ "token command must be a program and arguments, not a shell pipeline"
576
+ );
577
+ }
578
+ const args = [];
579
+ let current = "";
580
+ let quote;
581
+ for (const ch of raw) {
582
+ if (quote) {
583
+ if (ch === quote) quote = void 0;
584
+ else current += ch;
585
+ continue;
586
+ }
587
+ if (ch === "'" || ch === '"') {
588
+ quote = ch;
589
+ continue;
590
+ }
591
+ if (ch === " " || ch === " ") {
592
+ if (current.length > 0) {
593
+ args.push(current);
594
+ current = "";
595
+ }
596
+ continue;
597
+ }
598
+ current += ch;
599
+ }
600
+ if (quote) {
601
+ throw new CloudTokenError("cloud_token_command_invalid", "token command has an unclosed quote");
602
+ }
603
+ if (current.length > 0) args.push(current);
604
+ if (args.length === 0) {
605
+ throw new CloudTokenError("cloud_token_command_invalid", "token command is empty");
606
+ }
607
+ return args;
608
+ }
609
+ function isCloudTokenEnvName(value) {
610
+ return TOKEN_ENV_NAME.test(value);
611
+ }
571
612
  function isIsoTimestamp(value) {
572
613
  if (Number.isNaN(Date.parse(value))) return false;
573
614
  return value === new Date(value).toISOString();
@@ -591,7 +632,9 @@ function readHubBinding(env = process.env) {
591
632
  }
592
633
  const row = parsed;
593
634
  const keys = Object.keys(row);
594
- if (keys.length !== 3 || row.schema !== HUB_BINDING_SCHEMA || typeof row.url !== "string" || typeof row.boundAt !== "string" || !isIsoTimestamp(row.boundAt)) {
635
+ const allowed = /* @__PURE__ */ new Set(["schema", "url", "boundAt", "cloud", "tokenEnv", "tokenCommand"]);
636
+ const cloudish = keys.some((key) => key === "cloud" || key === "tokenEnv" || key === "tokenCommand");
637
+ 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)) {
595
638
  throw new HubBindingError(`malformed hub binding at ${file}`);
596
639
  }
597
640
  let url;
@@ -600,7 +643,34 @@ function readHubBinding(env = process.env) {
600
643
  } catch {
601
644
  throw new HubBindingError(`malformed hub binding at ${file}`);
602
645
  }
603
- return { schema: HUB_BINDING_SCHEMA, url, boundAt: row.boundAt };
646
+ if (!cloudish) return { schema: HUB_BINDING_SCHEMA, url, boundAt: row.boundAt };
647
+ if (row.cloud !== true) throw new HubBindingError(`malformed hub binding at ${file}`);
648
+ let tokenEnv;
649
+ let tokenCommand;
650
+ if (row.tokenEnv !== void 0) {
651
+ if (typeof row.tokenEnv !== "string" || !isCloudTokenEnvName(row.tokenEnv)) {
652
+ throw new HubBindingError(`malformed hub binding at ${file}`);
653
+ }
654
+ tokenEnv = row.tokenEnv;
655
+ }
656
+ if (row.tokenCommand !== void 0) {
657
+ if (typeof row.tokenCommand !== "string") throw new HubBindingError(`malformed hub binding at ${file}`);
658
+ try {
659
+ splitTokenCommand(row.tokenCommand);
660
+ } catch {
661
+ throw new HubBindingError(`malformed hub binding at ${file}`);
662
+ }
663
+ tokenCommand = row.tokenCommand;
664
+ }
665
+ if (!tokenEnv && !tokenCommand) throw new HubBindingError(`malformed hub binding at ${file}`);
666
+ return {
667
+ schema: HUB_BINDING_SCHEMA,
668
+ url,
669
+ boundAt: row.boundAt,
670
+ cloud: true,
671
+ ...tokenEnv ? { tokenEnv } : {},
672
+ ...tokenCommand ? { tokenCommand } : {}
673
+ };
604
674
  }
605
675
  async function probeHubHealth(url, fetchImpl, timeoutMs = HUB_HEALTH_PROBE_MS) {
606
676
  const started = Date.now();
@@ -625,12 +695,21 @@ async function probeHubHealth(url, fetchImpl, timeoutMs = HUB_HEALTH_PROBE_MS) {
625
695
  clearTimeout(timer);
626
696
  }
627
697
  }
628
- var HUB_BINDING_SCHEMA, HUB_HEALTH_PROBE_MS, HubBindingError;
698
+ var HUB_BINDING_SCHEMA, HUB_HEALTH_PROBE_MS, CloudTokenError, TOKEN_ENV_NAME, HubBindingError;
629
699
  var init_hub_binding = __esm({
630
700
  "plugins/kxm/src/hub-binding.ts"() {
631
701
  "use strict";
632
702
  HUB_BINDING_SCHEMA = "kxm.hub-binding.v1";
633
703
  HUB_HEALTH_PROBE_MS = 300;
704
+ CloudTokenError = class extends Error {
705
+ code;
706
+ constructor(code, message) {
707
+ super(message);
708
+ this.name = "CloudTokenError";
709
+ this.code = code;
710
+ }
711
+ };
712
+ TOKEN_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
634
713
  HubBindingError = class extends Error {
635
714
  constructor(message) {
636
715
  super(message);
@@ -8770,11 +8849,15 @@ var project_name_exports = {};
8770
8849
  __export(project_name_exports, {
8771
8850
  defaultProjectName: () => defaultProjectName
8772
8851
  });
8773
- import { readFileSync as readFileSync7 } from "node:fs";
8774
- import { basename as basename2, join as join6 } from "node:path";
8775
- function defaultProjectName(cwd, env = process.env) {
8852
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
8853
+ import { basename as basename2, dirname as dirname3, join as join6, resolve as resolve5 } from "node:path";
8854
+ function defaultProjectName(cwd, env = process.env, explicit) {
8855
+ const fromExplicit = explicit?.trim();
8856
+ if (fromExplicit) return fromExplicit;
8776
8857
  const fromEnv = env.KXM_PROJECT?.trim();
8777
8858
  if (fromEnv) return fromEnv;
8859
+ const fromProject = readProjectYamlId(cwd);
8860
+ if (fromProject) return fromProject;
8778
8861
  try {
8779
8862
  const pkg = JSON.parse(readFileSync7(join6(cwd, "package.json"), "utf8"));
8780
8863
  if (typeof pkg.name === "string" && pkg.name.trim().length > 0) return pkg.name.trim();
@@ -8782,9 +8865,30 @@ function defaultProjectName(cwd, env = process.env) {
8782
8865
  }
8783
8866
  return basename2(cwd);
8784
8867
  }
8868
+ function readProjectYamlId(start) {
8869
+ let dir = resolve5(start);
8870
+ for (; ; ) {
8871
+ const file = join6(dir, ".kxm", "project.yaml");
8872
+ if (existsSync7(file)) {
8873
+ try {
8874
+ const doc = (0, import_yaml2.parse)(readFileSync7(file, "utf8"));
8875
+ if (doc && typeof doc === "object" && !Array.isArray(doc) && typeof doc.id === "string" && doc.id.trim()) {
8876
+ return doc.id.trim();
8877
+ }
8878
+ } catch {
8879
+ }
8880
+ return void 0;
8881
+ }
8882
+ const parent = dirname3(dir);
8883
+ if (parent === dir) return void 0;
8884
+ dir = parent;
8885
+ }
8886
+ }
8887
+ var import_yaml2;
8785
8888
  var init_project_name = __esm({
8786
8889
  "plugins/kxm/src/project-name.ts"() {
8787
8890
  "use strict";
8891
+ import_yaml2 = __toESM(require_dist(), 1);
8788
8892
  }
8789
8893
  });
8790
8894
 
@@ -8930,9 +9034,9 @@ __export(commands_exports, {
8930
9034
  timingSafeStringCompare: () => timingSafeStringCompare
8931
9035
  });
8932
9036
  import { createHash, randomUUID as randomUUID3, timingSafeEqual } from "node:crypto";
8933
- import { chmodSync, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync8, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
9037
+ import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync8, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
8934
9038
  import { homedir as homedir3 } from "node:os";
8935
- import { dirname as dirname3, join as join7, resolve as resolve5 } from "node:path";
9039
+ import { dirname as dirname4, join as join7, resolve as resolve6 } from "node:path";
8936
9040
  function forwardedHops(handling) {
8937
9041
  if (!handling?.length) return void 0;
8938
9042
  return {
@@ -9114,15 +9218,15 @@ function parseSessionToken(token) {
9114
9218
  return void 0;
9115
9219
  }
9116
9220
  function resolveUserConfigDirectory(overrideDir) {
9117
- if (overrideDir) return resolve5(overrideDir);
9118
- return resolve5(process.env.KXM_USER_CONFIG_DIR?.trim() || join7(homedir3(), ".config", "kxm"));
9221
+ if (overrideDir) return resolve6(overrideDir);
9222
+ return resolve6(process.env.KXM_USER_CONFIG_DIR?.trim() || join7(homedir3(), ".config", "kxm"));
9119
9223
  }
9120
9224
  function sessionTokenPath(userConfigDir) {
9121
9225
  return join7(resolveUserConfigDirectory(userConfigDir), "session.token");
9122
9226
  }
9123
9227
  function persistSessionTokenToDisk(token, options) {
9124
9228
  const filePath = sessionTokenPath(options?.userConfigDir);
9125
- const dir = dirname3(filePath);
9229
+ const dir = dirname4(filePath);
9126
9230
  mkdirSync6(dir, { recursive: true, mode: 448 });
9127
9231
  const mode = options?.mode ?? 384;
9128
9232
  writeFileSync5(filePath, `${token.trim()}
@@ -9135,7 +9239,7 @@ function persistSessionTokenToDisk(token, options) {
9135
9239
  }
9136
9240
  function readSessionTokenFromDisk(options) {
9137
9241
  const filePath = sessionTokenPath(options?.userConfigDir);
9138
- if (!existsSync7(filePath)) return void 0;
9242
+ if (!existsSync8(filePath)) return void 0;
9139
9243
  try {
9140
9244
  const token = readFileSync8(filePath, "utf8").trim();
9141
9245
  if (!token) return void 0;
@@ -9148,7 +9252,7 @@ function readSessionTokenFromDisk(options) {
9148
9252
  }
9149
9253
  function clearSessionTokenFromDisk(options) {
9150
9254
  const filePath = sessionTokenPath(options?.userConfigDir);
9151
- if (!existsSync7(filePath)) return false;
9255
+ if (!existsSync8(filePath)) return false;
9152
9256
  try {
9153
9257
  unlinkSync(filePath);
9154
9258
  return true;
@@ -9247,7 +9351,7 @@ function enforceToolPolicy(commandName, env = process.env, options) {
9247
9351
  return { allowed: true };
9248
9352
  }
9249
9353
  const tokenFile = sessionTokenPath(env.KXM_USER_CONFIG_DIR);
9250
- if (existsSync7(tokenFile)) {
9354
+ if (existsSync8(tokenFile)) {
9251
9355
  let tokenRaw;
9252
9356
  try {
9253
9357
  tokenRaw = readFileSync8(tokenFile, "utf8").trim();
@@ -9937,7 +10041,7 @@ var init_commands = __esm({
9937
10041
 
9938
10042
  // plugins/kxm/src/claude-hook.ts
9939
10043
  import { readFileSync as readFileSync9, statSync } from "node:fs";
9940
- import { join as join8, resolve as resolve6 } from "node:path";
10044
+ import { join as join8, resolve as resolve7 } from "node:path";
9941
10045
 
9942
10046
  // plugins/kxm/src/session-token-hint.ts
9943
10047
  var ENV_TEXT = "KXM_SESSION_TOKEN in the environment Claude Code was launched from is malformed or expired, so every kxm_* tool fails with tool_policy_denied. Ask the user to unset or replace KXM_SESSION_TOKEN in the environment Claude Code was launched from, then restart Claude Code.";
@@ -10010,7 +10114,7 @@ function truncate2(text, max) {
10010
10114
  }
10011
10115
  async function sessionStartContext(input, env) {
10012
10116
  const inputCwd = typeof input.cwd === "string" ? input.cwd.trim() : "";
10013
- const root = resolve6(env.CLAUDE_PROJECT_DIR?.trim() || inputCwd || process.cwd());
10117
+ const root = resolve7(env.CLAUDE_PROJECT_DIR?.trim() || inputCwd || process.cwd());
10014
10118
  if (!isDirectory(join8(root, ".kxm"))) return void 0;
10015
10119
  const [
10016
10120
  { resolveSessionHubStatus: resolveSessionHubStatus2 },