@kontextmind/kxm 0.7.164 → 0.7.166

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 (45) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +25 -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/concepts/trust-model.md +1 -1
  7. package/docs/glossary.md +4 -4
  8. package/docs/operations/troubleshooting.md +4 -1
  9. package/docs/operations.md +208 -0
  10. package/docs/reference/cli-reference.md +12 -11
  11. package/docs/reference/config-reference.md +36 -11
  12. package/docs/reference/configuration.md +11 -1
  13. package/docs/reference/tools.md +1 -1
  14. package/docs/start/quickstart-claude-code.md +2 -2
  15. package/package.json +1 -1
  16. package/plugins/kxm/.claude-plugin/plugin.json +2 -2
  17. package/plugins/kxm/README.md +4 -4
  18. package/plugins/kxm/dist/claude-hook.js +511 -56
  19. package/plugins/kxm/dist/cli.js +15661 -14775
  20. package/plugins/kxm/dist/extension.js +645 -133
  21. package/plugins/kxm/dist/mcp-server.js +8152 -81
  22. package/plugins/kxm/dist/runtime-supervisor.js +804 -155
  23. package/plugins/kxm/dist/runtime.js +851 -202
  24. package/plugins/kxm/dist/server.js +466 -58
  25. package/plugins/kxm/package.json +1 -1
  26. package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +17 -5
  27. package/plugins/kxm/src/cli/context-skills.ts +64 -10
  28. package/plugins/kxm/src/cli/hub.ts +192 -22
  29. package/plugins/kxm/src/cli/project.ts +7 -3
  30. package/plugins/kxm/src/cli/types.ts +14 -1
  31. package/plugins/kxm/src/cli/workflows.ts +12 -1
  32. package/plugins/kxm/src/cli.ts +63 -11
  33. package/plugins/kxm/src/config.ts +246 -3
  34. package/plugins/kxm/src/extension.ts +12 -7
  35. package/plugins/kxm/src/hub-autostart.ts +23 -0
  36. package/plugins/kxm/src/hub-binding.ts +182 -2
  37. package/plugins/kxm/src/hub-env.ts +132 -9
  38. package/plugins/kxm/src/hub-identity.ts +337 -0
  39. package/plugins/kxm/src/mcp-server.ts +17 -14
  40. package/plugins/kxm/src/project-name.ts +6 -20
  41. package/plugins/kxm/src/runtime-supervisor.ts +9 -3
  42. package/plugins/kxm/src/server.ts +22 -4
  43. package/plugins/kxm/src/tenant-status.ts +9 -2
  44. package/scripts/build-runtime.mjs +5 -0
  45. package/scripts/kxm-hub.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);
@@ -2556,7 +2635,7 @@ var require_stringify = __commonJS({
2556
2635
  props.push(doc.directives.tagString(tag));
2557
2636
  return props.join(" ");
2558
2637
  }
2559
- function stringify2(item, ctx, onComment, onChompKeep) {
2638
+ function stringify3(item, ctx, onComment, onChompKeep) {
2560
2639
  if (identity.isPair(item))
2561
2640
  return item.toString(ctx, onComment, onChompKeep);
2562
2641
  if (identity.isAlias(item)) {
@@ -2585,7 +2664,7 @@ var require_stringify = __commonJS({
2585
2664
  ${ctx.indent}${str}`;
2586
2665
  }
2587
2666
  exports.createStringifyContext = createStringifyContext;
2588
- exports.stringify = stringify2;
2667
+ exports.stringify = stringify3;
2589
2668
  }
2590
2669
  });
2591
2670
 
@@ -2595,7 +2674,7 @@ var require_stringifyPair = __commonJS({
2595
2674
  "use strict";
2596
2675
  var identity = require_identity();
2597
2676
  var Scalar = require_Scalar();
2598
- var stringify2 = require_stringify();
2677
+ var stringify3 = require_stringify();
2599
2678
  var stringifyComment = require_stringifyComment();
2600
2679
  function stringifyPair({ key, value }, ctx, onComment, onChompKeep) {
2601
2680
  const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx;
@@ -2617,7 +2696,7 @@ var require_stringifyPair = __commonJS({
2617
2696
  });
2618
2697
  let keyCommentDone = false;
2619
2698
  let chompKeep = false;
2620
- let str = stringify2.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
2699
+ let str = stringify3.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
2621
2700
  if (!explicitKey && !ctx.inFlow && str.length > 1024) {
2622
2701
  if (simpleKeys)
2623
2702
  throw new Error("With simple keys, single line scalar must not span more than 1024 characters");
@@ -2669,7 +2748,7 @@ ${indent}:`;
2669
2748
  ctx.indent = ctx.indent.substring(2);
2670
2749
  }
2671
2750
  let valueCommentDone = false;
2672
- const valueStr = stringify2.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true);
2751
+ const valueStr = stringify3.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true);
2673
2752
  let ws = " ";
2674
2753
  if (keyComment || vsb || vcb) {
2675
2754
  ws = vsb ? "\n" : "";
@@ -2810,7 +2889,7 @@ var require_addPairToJSMap = __commonJS({
2810
2889
  "use strict";
2811
2890
  var log = require_log();
2812
2891
  var merge = require_merge();
2813
- var stringify2 = require_stringify();
2892
+ var stringify3 = require_stringify();
2814
2893
  var identity = require_identity();
2815
2894
  var toJS = require_toJS();
2816
2895
  function addPairToJSMap(ctx, map, { key, value }) {
@@ -2846,7 +2925,7 @@ var require_addPairToJSMap = __commonJS({
2846
2925
  if (typeof jsKey !== "object")
2847
2926
  return String(jsKey);
2848
2927
  if (identity.isNode(key) && ctx?.doc) {
2849
- const strCtx = stringify2.createStringifyContext(ctx.doc, {});
2928
+ const strCtx = stringify3.createStringifyContext(ctx.doc, {});
2850
2929
  strCtx.anchors = /* @__PURE__ */ new Set();
2851
2930
  for (const node of ctx.anchors.keys())
2852
2931
  strCtx.anchors.add(node.anchor);
@@ -2913,12 +2992,12 @@ var require_stringifyCollection = __commonJS({
2913
2992
  "node_modules/yaml/dist/stringify/stringifyCollection.js"(exports) {
2914
2993
  "use strict";
2915
2994
  var identity = require_identity();
2916
- var stringify2 = require_stringify();
2995
+ var stringify3 = require_stringify();
2917
2996
  var stringifyComment = require_stringifyComment();
2918
2997
  function stringifyCollection(collection, ctx, options) {
2919
2998
  const flow = ctx.inFlow ?? collection.flow;
2920
- const stringify3 = flow ? stringifyFlowCollection : stringifyBlockCollection;
2921
- return stringify3(collection, ctx, options);
2999
+ const stringify4 = flow ? stringifyFlowCollection : stringifyBlockCollection;
3000
+ return stringify4(collection, ctx, options);
2922
3001
  }
2923
3002
  function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) {
2924
3003
  const { indent, options: { commentString } } = ctx;
@@ -2943,7 +3022,7 @@ var require_stringifyCollection = __commonJS({
2943
3022
  }
2944
3023
  }
2945
3024
  chompKeep = false;
2946
- let str2 = stringify2.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
3025
+ let str2 = stringify3.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
2947
3026
  if (comment2)
2948
3027
  str2 += stringifyComment.lineComment(str2, itemIndent, commentString(comment2));
2949
3028
  if (chompKeep && comment2)
@@ -3010,7 +3089,7 @@ ${indent}${line}` : "\n";
3010
3089
  }
3011
3090
  if (comment)
3012
3091
  reqNewline = true;
3013
- let str = stringify2.stringify(item, itemCtx, () => comment = null);
3092
+ let str = stringify3.stringify(item, itemCtx, () => comment = null);
3014
3093
  reqNewline || (reqNewline = lines.length > linesAtValue || str.includes("\n"));
3015
3094
  if (i < items.length - 1) {
3016
3095
  str += ",";
@@ -4371,7 +4450,7 @@ var require_stringifyDocument = __commonJS({
4371
4450
  "node_modules/yaml/dist/stringify/stringifyDocument.js"(exports) {
4372
4451
  "use strict";
4373
4452
  var identity = require_identity();
4374
- var stringify2 = require_stringify();
4453
+ var stringify3 = require_stringify();
4375
4454
  var stringifyComment = require_stringifyComment();
4376
4455
  function stringifyDocument(doc, options) {
4377
4456
  const lines = [];
@@ -4386,7 +4465,7 @@ var require_stringifyDocument = __commonJS({
4386
4465
  }
4387
4466
  if (hasDirectives)
4388
4467
  lines.push("---");
4389
- const ctx = stringify2.createStringifyContext(doc, options);
4468
+ const ctx = stringify3.createStringifyContext(doc, options);
4390
4469
  const { commentString } = ctx.options;
4391
4470
  if (doc.commentBefore) {
4392
4471
  if (lines.length !== 1)
@@ -4408,7 +4487,7 @@ var require_stringifyDocument = __commonJS({
4408
4487
  contentComment = doc.contents.comment;
4409
4488
  }
4410
4489
  const onChompKeep = contentComment ? void 0 : () => chompKeep = true;
4411
- let body = stringify2.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep);
4490
+ let body = stringify3.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep);
4412
4491
  if (contentComment)
4413
4492
  body += stringifyComment.lineComment(body, "", commentString(contentComment));
4414
4493
  if ((body[0] === "|" || body[0] === ">") && lines[lines.length - 1] === "---") {
@@ -4416,7 +4495,7 @@ var require_stringifyDocument = __commonJS({
4416
4495
  } else
4417
4496
  lines.push(body);
4418
4497
  } else {
4419
- lines.push(stringify2.stringify(doc.contents, ctx));
4498
+ lines.push(stringify3.stringify(doc.contents, ctx));
4420
4499
  }
4421
4500
  if (doc.directives?.docEnd) {
4422
4501
  if (doc.comment) {
@@ -6552,7 +6631,7 @@ var require_cst_scalar = __commonJS({
6552
6631
  var require_cst_stringify = __commonJS({
6553
6632
  "node_modules/yaml/dist/parse/cst-stringify.js"(exports) {
6554
6633
  "use strict";
6555
- var stringify2 = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst);
6634
+ var stringify3 = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst);
6556
6635
  function stringifyToken(token) {
6557
6636
  switch (token.type) {
6558
6637
  case "block-scalar": {
@@ -6605,7 +6684,7 @@ var require_cst_stringify = __commonJS({
6605
6684
  res += stringifyToken(value);
6606
6685
  return res;
6607
6686
  }
6608
- exports.stringify = stringify2;
6687
+ exports.stringify = stringify3;
6609
6688
  }
6610
6689
  });
6611
6690
 
@@ -8316,7 +8395,7 @@ var require_public_api = __commonJS({
8316
8395
  }
8317
8396
  return doc;
8318
8397
  }
8319
- function parse2(src, reviver, options) {
8398
+ function parse3(src, reviver, options) {
8320
8399
  let _reviver = void 0;
8321
8400
  if (typeof reviver === "function") {
8322
8401
  _reviver = reviver;
@@ -8335,7 +8414,7 @@ var require_public_api = __commonJS({
8335
8414
  }
8336
8415
  return doc.toJS(Object.assign({ reviver: _reviver }, options));
8337
8416
  }
8338
- function stringify2(value, replacer, options) {
8417
+ function stringify3(value, replacer, options) {
8339
8418
  let _replacer = null;
8340
8419
  if (typeof replacer === "function" || Array.isArray(replacer)) {
8341
8420
  _replacer = replacer;
@@ -8357,10 +8436,10 @@ var require_public_api = __commonJS({
8357
8436
  return value.toString(options);
8358
8437
  return new Document.Document(value, _replacer, options).toString(options);
8359
8438
  }
8360
- exports.parse = parse2;
8439
+ exports.parse = parse3;
8361
8440
  exports.parseAllDocuments = parseAllDocuments;
8362
8441
  exports.parseDocument = parseDocument;
8363
- exports.stringify = stringify2;
8442
+ exports.stringify = stringify3;
8364
8443
  }
8365
8444
  });
8366
8445
 
@@ -8765,26 +8844,402 @@ var init_memory = __esm({
8765
8844
  }
8766
8845
  });
8767
8846
 
8768
- // plugins/kxm/src/project-name.ts
8769
- var project_name_exports = {};
8770
- __export(project_name_exports, {
8771
- defaultProjectName: () => defaultProjectName
8847
+ // plugins/kxm/src/config.ts
8848
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
8849
+ import { tmpdir, homedir as homedir3 } from "node:os";
8850
+ import { dirname as dirname3, join as join6, resolve as resolve5 } from "node:path";
8851
+ function isHubKeyEnvName(value) {
8852
+ return HUB_ENV_NAME.test(value);
8853
+ }
8854
+ function isHubOpReference(value) {
8855
+ return HUB_OP_REF.test(value);
8856
+ }
8857
+ function userConfigDirectory(overrideDir) {
8858
+ if (overrideDir) return resolve5(overrideDir);
8859
+ return resolve5(process.env.KXM_USER_CONFIG_DIR?.trim() || join6(homedir3(), ".config", "kxm"));
8860
+ }
8861
+ function repoConfigDirectory(repoRoot) {
8862
+ return resolve5(repoRoot, ".kxm");
8863
+ }
8864
+ function deepMerge(target, source) {
8865
+ const result = { ...target };
8866
+ for (const [key, val] of Object.entries(source)) {
8867
+ if (val && typeof val === "object" && !Array.isArray(val)) {
8868
+ const existing = result[key] && typeof result[key] === "object" && !Array.isArray(result[key]) ? result[key] : {};
8869
+ result[key] = deepMerge(existing, val);
8870
+ } else if (val !== void 0) {
8871
+ result[key] = val;
8872
+ }
8873
+ }
8874
+ return result;
8875
+ }
8876
+ function literalSecretMessage(path) {
8877
+ return `refusing literal hub token at ${path}: store an op:// reference (key.op) or an environment variable name (key.env), never the token`;
8878
+ }
8879
+ function rejectLiteralHubSecrets(value, path) {
8880
+ if (Array.isArray(value)) {
8881
+ value.forEach((item, index) => rejectLiteralHubSecrets(item, `${path}[${index}]`));
8882
+ return;
8883
+ }
8884
+ if (!value || typeof value !== "object") return;
8885
+ for (const [key, child] of Object.entries(value)) {
8886
+ const childPath = `${path}.${key}`;
8887
+ if (typeof child === "string" && (key === "key" || LITERAL_SECRET_KEY.test(key))) {
8888
+ throw new KxmHubConfigError(literalSecretMessage(childPath));
8889
+ }
8890
+ if (child && typeof child === "object") rejectLiteralHubSecrets(child, childPath);
8891
+ }
8892
+ }
8893
+ function parseKeyRef(raw, path) {
8894
+ if (raw === void 0) return void 0;
8895
+ if (typeof raw === "string") throw new KxmHubConfigError(literalSecretMessage(path));
8896
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
8897
+ throw new KxmHubConfigError(`${path} must be a key reference with op and/or env, never a token`);
8898
+ }
8899
+ const row = raw;
8900
+ for (const key of Object.keys(row)) {
8901
+ if (key !== "op" && key !== "env") {
8902
+ throw new KxmHubConfigError(literalSecretMessage(`${path}.${key}`));
8903
+ }
8904
+ }
8905
+ const ref = {};
8906
+ if (row.op !== void 0) {
8907
+ if (typeof row.op !== "string" || !isHubOpReference(row.op)) {
8908
+ throw new KxmHubConfigError(`${path}.op must be an op://vault/item/field reference, not a token`);
8909
+ }
8910
+ ref.op = row.op;
8911
+ }
8912
+ if (row.env !== void 0) {
8913
+ if (typeof row.env !== "string" || !isHubKeyEnvName(row.env)) {
8914
+ throw new KxmHubConfigError(`${path}.env must be an environment variable name, not a token`);
8915
+ }
8916
+ ref.env = row.env;
8917
+ }
8918
+ if (!ref.op && !ref.env) throw new KxmHubConfigError(`${path} needs key.op, key.env, or both`);
8919
+ return ref;
8920
+ }
8921
+ function parseEndpoint(raw, path) {
8922
+ if (raw === void 0) return void 0;
8923
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
8924
+ throw new KxmHubConfigError(`${path} must be an object with url, project, and key`);
8925
+ }
8926
+ const row = raw;
8927
+ const endpoint = {};
8928
+ if (row.url !== void 0) {
8929
+ if (typeof row.url !== "string" || !isHubUrl(row.url)) {
8930
+ throw new KxmHubConfigError(`${path}.url must be an http or https URL without credentials, query, or fragment`);
8931
+ }
8932
+ endpoint.url = row.url.replace(/\/$/, "");
8933
+ }
8934
+ if (row.project !== void 0) {
8935
+ if (typeof row.project !== "string" || !isHubProjectId(row.project)) {
8936
+ throw new KxmHubConfigError(`${path}.project must be a single-line project id, not a token`);
8937
+ }
8938
+ endpoint.project = row.project.trim();
8939
+ }
8940
+ const key = parseKeyRef(row.key, `${path}.key`);
8941
+ if (key) endpoint.key = key;
8942
+ for (const name of Object.keys(row)) {
8943
+ if (name !== "url" && name !== "project" && name !== "key") {
8944
+ if (typeof row[name] === "string") throw new KxmHubConfigError(literalSecretMessage(`${path}.${name}`));
8945
+ throw new KxmHubConfigError(`${path}.${name} is not a hub endpoint field`);
8946
+ }
8947
+ }
8948
+ return endpoint;
8949
+ }
8950
+ function isHubUrl(raw) {
8951
+ try {
8952
+ const parsed = new URL(raw);
8953
+ return (parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.username === "" && parsed.password === "" && parsed.search === "" && parsed.hash === "" && !raw.includes("?") && !raw.includes("#");
8954
+ } catch {
8955
+ return false;
8956
+ }
8957
+ }
8958
+ function isHubProjectId(raw) {
8959
+ const value = raw.trim();
8960
+ return value.length > 0 && value.length <= 200 && !/[\s\r\n]/.test(value);
8961
+ }
8962
+ function parseProjectMap(raw) {
8963
+ if (raw === void 0) return void 0;
8964
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
8965
+ throw new KxmHubConfigError("hub.projects must be a map of project id to key reference, never a token");
8966
+ }
8967
+ const projects = {};
8968
+ for (const [project, value] of Object.entries(raw)) {
8969
+ if (!isHubProjectId(project)) {
8970
+ throw new KxmHubConfigError(`hub.projects key ${JSON.stringify(project)} must be a project id, not a token`);
8971
+ }
8972
+ const ref = parseKeyRef(value, `hub.projects.${project}`);
8973
+ if (!ref) throw new KxmHubConfigError(`hub.projects.${project} needs an op:// reference or an environment variable name`);
8974
+ projects[project.trim()] = ref;
8975
+ }
8976
+ return projects;
8977
+ }
8978
+ function normalizeHubConfig(raw) {
8979
+ const autoStart = raw?.autoStart;
8980
+ const hub = {
8981
+ autoStart: autoStart === "off" || autoStart === "background" ? autoStart : DEFAULT_KXM_CONFIG.hub.autoStart
8982
+ };
8983
+ if (raw === void 0) return hub;
8984
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
8985
+ throw new KxmHubConfigError("hub must be an object");
8986
+ }
8987
+ rejectLiteralHubSecrets(raw, "hub");
8988
+ const row = raw;
8989
+ if (row.mode !== void 0) {
8990
+ if (row.mode !== "local" && row.mode !== "cloud") {
8991
+ throw new KxmHubConfigError("hub.mode must be local or cloud");
8992
+ }
8993
+ hub.mode = row.mode;
8994
+ }
8995
+ const local = parseEndpoint(row.local, "hub.local");
8996
+ const cloud = parseEndpoint(row.cloud, "hub.cloud");
8997
+ const projects = parseProjectMap(row.projects);
8998
+ if (local) hub.local = local;
8999
+ if (cloud) hub.cloud = cloud;
9000
+ if (projects && Object.keys(projects).length > 0) hub.projects = projects;
9001
+ return hub;
9002
+ }
9003
+ function findConfigRoot(start) {
9004
+ let dir = resolve5(start);
9005
+ for (; ; ) {
9006
+ if (existsSync7(join6(dir, ".kxm", "project.yaml")) || existsSync7(join6(dir, ".kxm", "config.yaml"))) return dir;
9007
+ const parent = dirname3(dir);
9008
+ if (parent === dir) return resolve5(start);
9009
+ dir = parent;
9010
+ }
9011
+ }
9012
+ function userConfigDirForEnv(env) {
9013
+ const explicit = env.KXM_USER_CONFIG_DIR?.trim();
9014
+ if (explicit) return explicit;
9015
+ if (env === process.env) return void 0;
9016
+ return join6(tmpdir(), "kxm-no-user-config");
9017
+ }
9018
+ function loadHubSettings(start, env = process.env) {
9019
+ const userConfigDir = userConfigDirForEnv(env);
9020
+ return loadKxmConfig(findConfigRoot(start), userConfigDir === void 0 ? {} : { userConfigDir }).hub;
9021
+ }
9022
+ function recordOf(raw) {
9023
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
9024
+ }
9025
+ function finiteNumber(value) {
9026
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
9027
+ }
9028
+ function normalizeImprovementConfig(raw) {
9029
+ const value = recordOf(raw);
9030
+ const threshold = recordOf(value.autoThreshold);
9031
+ const policy = value.promotionPolicy;
9032
+ const halfLife = finiteNumber(value.telemetryHalfLifeDays);
9033
+ const minRuns = finiteNumber(threshold.minRuns);
9034
+ const minPassRate = finiteNumber(threshold.minPassRate);
9035
+ const minCostSavings = finiteNumber(threshold.minCostSavings);
9036
+ return {
9037
+ promotionPolicy: IMPROVEMENT_PROMOTION_POLICIES.includes(policy) ? policy : "manual_pr",
9038
+ telemetryHalfLifeDays: halfLife !== void 0 && halfLife > 0 && halfLife <= 3650 ? halfLife : 14,
9039
+ autoThreshold: {
9040
+ minRuns: minRuns !== void 0 && Number.isInteger(minRuns) && minRuns >= 1 && minRuns <= 1e6 ? minRuns : 10,
9041
+ minPassRate: minPassRate !== void 0 && minPassRate >= 0 && minPassRate <= 1 ? minPassRate : 0.95,
9042
+ minCostSavings: minCostSavings !== void 0 && minCostSavings >= 0 ? minCostSavings : 0.5
9043
+ }
9044
+ };
9045
+ }
9046
+ function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
9047
+ const userDir = userConfigDirectory(options.userConfigDir);
9048
+ const userConfigFile = join6(userDir, "config.yaml");
9049
+ const repoDir = repoConfigDirectory(repoRoot);
9050
+ const repoConfigFile = join6(repoDir, "config.yaml");
9051
+ let userRaw = {};
9052
+ let userLoadedPath;
9053
+ if (existsSync7(userConfigFile)) {
9054
+ try {
9055
+ const text = readFileSync7(userConfigFile, "utf8");
9056
+ userRaw = (0, import_yaml2.parse)(text) ?? {};
9057
+ userLoadedPath = userConfigFile;
9058
+ } catch (error) {
9059
+ throw new Error(`invalid user config YAML at ${userConfigFile}`, { cause: error });
9060
+ }
9061
+ }
9062
+ let repoRaw = {};
9063
+ let repoLoadedPath;
9064
+ if (existsSync7(repoConfigFile)) {
9065
+ try {
9066
+ const text = readFileSync7(repoConfigFile, "utf8");
9067
+ repoRaw = (0, import_yaml2.parse)(text) ?? {};
9068
+ repoLoadedPath = repoConfigFile;
9069
+ } catch (error) {
9070
+ throw new Error(`invalid project config YAML at ${repoConfigFile}`, { cause: error });
9071
+ }
9072
+ }
9073
+ const baseCopy = JSON.parse(JSON.stringify(DEFAULT_KXM_CONFIG));
9074
+ const mergedUser = deepMerge(baseCopy, userRaw);
9075
+ const mergedAll = deepMerge(mergedUser, repoRaw);
9076
+ return {
9077
+ schema: KXM_CONFIG_SCHEMA,
9078
+ user: mergedAll.user ?? {},
9079
+ defaults: mergedAll.defaults ?? {},
9080
+ dash: mergedAll.dash ?? {},
9081
+ sync: mergedAll.sync ?? {},
9082
+ hub: normalizeHubConfig(mergedAll.hub),
9083
+ improvement: normalizeImprovementConfig(mergedAll.improvement),
9084
+ routing: mergedAll.routing ?? DEFAULT_KXM_CONFIG.routing,
9085
+ telemetry: mergedAll.telemetry ?? DEFAULT_KXM_CONFIG.telemetry,
9086
+ loadedFrom: {
9087
+ userConfigPath: userLoadedPath,
9088
+ repoConfigPath: repoLoadedPath
9089
+ }
9090
+ };
9091
+ }
9092
+ var import_yaml2, KXM_CONFIG_SCHEMA, KxmHubConfigError, HUB_ENV_NAME, HUB_OP_REF, LITERAL_SECRET_KEY, IMPROVEMENT_PROMOTION_POLICIES, DEFAULT_KXM_CONFIG;
9093
+ var init_config = __esm({
9094
+ "plugins/kxm/src/config.ts"() {
9095
+ "use strict";
9096
+ import_yaml2 = __toESM(require_dist(), 1);
9097
+ KXM_CONFIG_SCHEMA = "kxm.config.v1";
9098
+ KxmHubConfigError = class extends Error {
9099
+ code = "hub_config_invalid";
9100
+ constructor(message) {
9101
+ super(message);
9102
+ this.name = "KxmHubConfigError";
9103
+ }
9104
+ };
9105
+ HUB_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
9106
+ HUB_OP_REF = /^op:\/\/\S+\/\S+\/\S+$/;
9107
+ LITERAL_SECRET_KEY = /^(token|secret|password|authToken|auth_token|apiKey|api_key|api_token|bearer|credential|access_token)$/i;
9108
+ IMPROVEMENT_PROMOTION_POLICIES = ["manual_pr", "critic_quorum", "auto_threshold"];
9109
+ DEFAULT_KXM_CONFIG = {
9110
+ schema: KXM_CONFIG_SCHEMA,
9111
+ user: {
9112
+ theme: "dark",
9113
+ preferredCritics: ["reviewer-arch", "reviewer-cli"],
9114
+ tokenBudget: 16e3
9115
+ },
9116
+ defaults: {
9117
+ workflow: "software-engineering/feature-implementation",
9118
+ harness: "pi"
9119
+ },
9120
+ dash: {
9121
+ defaultScreen: "agents",
9122
+ refreshIntervalMs: 1e3,
9123
+ autoOpen: false
9124
+ },
9125
+ sync: {
9126
+ defaultTracker: "none"
9127
+ },
9128
+ hub: {
9129
+ autoStart: "background"
9130
+ },
9131
+ improvement: {
9132
+ promotionPolicy: "manual_pr",
9133
+ telemetryHalfLifeDays: 14,
9134
+ autoThreshold: {
9135
+ minRuns: 10,
9136
+ minPassRate: 0.95,
9137
+ minCostSavings: 0.5
9138
+ }
9139
+ },
9140
+ routing: {
9141
+ shadowExecution: {
9142
+ enabled: false,
9143
+ sampleRate: 0.05,
9144
+ candidateModels: []
9145
+ },
9146
+ circuitBreaker: {
9147
+ mode: "soft_demotion",
9148
+ failureThreshold: 3,
9149
+ windowSeconds: 3600,
9150
+ cooldownSeconds: 1800,
9151
+ penaltyMultiplier: 5
9152
+ }
9153
+ },
9154
+ telemetry: {
9155
+ federated: true,
9156
+ anonymize: true
9157
+ }
9158
+ };
9159
+ }
8772
9160
  });
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) {
8776
- const fromEnv = env.KXM_PROJECT?.trim();
8777
- if (fromEnv) return fromEnv;
9161
+
9162
+ // plugins/kxm/src/hub-identity.ts
9163
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "node:fs";
9164
+ import { basename as basename2, dirname as dirname4, join as join7, resolve as resolve6 } from "node:path";
9165
+ function projectYamlId(start) {
9166
+ let dir = resolve6(start);
9167
+ for (; ; ) {
9168
+ const file = join7(dir, ".kxm", "project.yaml");
9169
+ if (existsSync8(file)) {
9170
+ try {
9171
+ const doc = (0, import_yaml3.parse)(readFileSync8(file, "utf8"));
9172
+ if (doc && typeof doc === "object" && !Array.isArray(doc) && typeof doc.id === "string" && doc.id.trim()) {
9173
+ return doc.id.trim();
9174
+ }
9175
+ } catch {
9176
+ }
9177
+ return void 0;
9178
+ }
9179
+ const parent = dirname4(dir);
9180
+ if (parent === dir) return void 0;
9181
+ dir = parent;
9182
+ }
9183
+ }
9184
+ function packageName(cwd) {
8778
9185
  try {
8779
- const pkg = JSON.parse(readFileSync7(join6(cwd, "package.json"), "utf8"));
9186
+ const pkg = JSON.parse(readFileSync8(join7(cwd, "package.json"), "utf8"));
8780
9187
  if (typeof pkg.name === "string" && pkg.name.trim().length > 0) return pkg.name.trim();
8781
9188
  } catch {
8782
9189
  }
8783
- return basename2(cwd);
9190
+ return void 0;
9191
+ }
9192
+ function activeBinding(env) {
9193
+ try {
9194
+ return readHubBinding(env);
9195
+ } catch (error) {
9196
+ if (error instanceof HubBindingError) return void 0;
9197
+ throw error;
9198
+ }
8784
9199
  }
9200
+ function endpointFor(mode, settings) {
9201
+ return mode === "cloud" ? settings.cloud : settings.local;
9202
+ }
9203
+ function resolveProjectIdentity(cwd, env = process.env, explicit) {
9204
+ const fromExplicit = explicit?.trim();
9205
+ if (fromExplicit) return { project: fromExplicit, source: "flag", sourceLabel: "--project" };
9206
+ const fromEnv = env.KXM_PROJECT?.trim();
9207
+ if (fromEnv) return { project: fromEnv, source: "env", sourceLabel: "KXM_PROJECT" };
9208
+ const settings = loadHubSettings(cwd, env);
9209
+ const mode = settings.mode ?? (activeBinding(env)?.cloud ? "cloud" : "local");
9210
+ const fromConfig = endpointFor(mode, settings)?.project?.trim();
9211
+ if (fromConfig) {
9212
+ return { project: fromConfig, source: "config", sourceLabel: `.kxm/config.yaml hub.${mode}.project` };
9213
+ }
9214
+ const fromProject = projectYamlId(cwd);
9215
+ if (fromProject) return { project: fromProject, source: "project.yaml", sourceLabel: ".kxm/project.yaml id" };
9216
+ const fromPackage = packageName(cwd);
9217
+ if (fromPackage) return { project: fromPackage, source: "package.json", sourceLabel: "package.json name" };
9218
+ return { project: basename2(cwd), source: "directory", sourceLabel: "directory name" };
9219
+ }
9220
+ function defaultProjectName(cwd, env = process.env, explicit) {
9221
+ return resolveProjectIdentity(cwd, env, explicit).project;
9222
+ }
9223
+ var import_yaml3;
9224
+ var init_hub_identity = __esm({
9225
+ "plugins/kxm/src/hub-identity.ts"() {
9226
+ "use strict";
9227
+ import_yaml3 = __toESM(require_dist(), 1);
9228
+ init_config();
9229
+ init_hub_binding();
9230
+ }
9231
+ });
9232
+
9233
+ // plugins/kxm/src/project-name.ts
9234
+ var project_name_exports = {};
9235
+ __export(project_name_exports, {
9236
+ defaultProjectName: () => defaultProjectName,
9237
+ resolveProjectIdentity: () => resolveProjectIdentity
9238
+ });
8785
9239
  var init_project_name = __esm({
8786
9240
  "plugins/kxm/src/project-name.ts"() {
8787
9241
  "use strict";
9242
+ init_hub_identity();
8788
9243
  }
8789
9244
  });
8790
9245
 
@@ -8930,9 +9385,9 @@ __export(commands_exports, {
8930
9385
  timingSafeStringCompare: () => timingSafeStringCompare
8931
9386
  });
8932
9387
  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";
8934
- import { homedir as homedir3 } from "node:os";
8935
- import { dirname as dirname3, join as join7, resolve as resolve5 } from "node:path";
9388
+ import { chmodSync, existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
9389
+ import { homedir as homedir4 } from "node:os";
9390
+ import { dirname as dirname5, join as join8, resolve as resolve7 } from "node:path";
8936
9391
  function forwardedHops(handling) {
8937
9392
  if (!handling?.length) return void 0;
8938
9393
  return {
@@ -9114,18 +9569,18 @@ function parseSessionToken(token) {
9114
9569
  return void 0;
9115
9570
  }
9116
9571
  function resolveUserConfigDirectory(overrideDir) {
9117
- if (overrideDir) return resolve5(overrideDir);
9118
- return resolve5(process.env.KXM_USER_CONFIG_DIR?.trim() || join7(homedir3(), ".config", "kxm"));
9572
+ if (overrideDir) return resolve7(overrideDir);
9573
+ return resolve7(process.env.KXM_USER_CONFIG_DIR?.trim() || join8(homedir4(), ".config", "kxm"));
9119
9574
  }
9120
9575
  function sessionTokenPath(userConfigDir) {
9121
- return join7(resolveUserConfigDirectory(userConfigDir), "session.token");
9576
+ return join8(resolveUserConfigDirectory(userConfigDir), "session.token");
9122
9577
  }
9123
9578
  function persistSessionTokenToDisk(token, options) {
9124
9579
  const filePath = sessionTokenPath(options?.userConfigDir);
9125
- const dir = dirname3(filePath);
9126
- mkdirSync6(dir, { recursive: true, mode: 448 });
9580
+ const dir = dirname5(filePath);
9581
+ mkdirSync7(dir, { recursive: true, mode: 448 });
9127
9582
  const mode = options?.mode ?? 384;
9128
- writeFileSync5(filePath, `${token.trim()}
9583
+ writeFileSync6(filePath, `${token.trim()}
9129
9584
  `, { encoding: "utf8", mode });
9130
9585
  try {
9131
9586
  chmodSync(filePath, mode);
@@ -9135,9 +9590,9 @@ function persistSessionTokenToDisk(token, options) {
9135
9590
  }
9136
9591
  function readSessionTokenFromDisk(options) {
9137
9592
  const filePath = sessionTokenPath(options?.userConfigDir);
9138
- if (!existsSync7(filePath)) return void 0;
9593
+ if (!existsSync9(filePath)) return void 0;
9139
9594
  try {
9140
- const token = readFileSync8(filePath, "utf8").trim();
9595
+ const token = readFileSync9(filePath, "utf8").trim();
9141
9596
  if (!token) return void 0;
9142
9597
  const payload = parseSessionToken(token);
9143
9598
  if (!payload) return void 0;
@@ -9148,7 +9603,7 @@ function readSessionTokenFromDisk(options) {
9148
9603
  }
9149
9604
  function clearSessionTokenFromDisk(options) {
9150
9605
  const filePath = sessionTokenPath(options?.userConfigDir);
9151
- if (!existsSync7(filePath)) return false;
9606
+ if (!existsSync9(filePath)) return false;
9152
9607
  try {
9153
9608
  unlinkSync(filePath);
9154
9609
  return true;
@@ -9247,10 +9702,10 @@ function enforceToolPolicy(commandName, env = process.env, options) {
9247
9702
  return { allowed: true };
9248
9703
  }
9249
9704
  const tokenFile = sessionTokenPath(env.KXM_USER_CONFIG_DIR);
9250
- if (existsSync7(tokenFile)) {
9705
+ if (existsSync9(tokenFile)) {
9251
9706
  let tokenRaw;
9252
9707
  try {
9253
- tokenRaw = readFileSync8(tokenFile, "utf8").trim();
9708
+ tokenRaw = readFileSync9(tokenFile, "utf8").trim();
9254
9709
  } catch {
9255
9710
  return { allowed: false, error: "session_token_invalid", detail: "Session token file on disk could not be read" };
9256
9711
  }
@@ -9936,8 +10391,8 @@ var init_commands = __esm({
9936
10391
  });
9937
10392
 
9938
10393
  // plugins/kxm/src/claude-hook.ts
9939
- import { readFileSync as readFileSync9, statSync } from "node:fs";
9940
- import { join as join8, resolve as resolve6 } from "node:path";
10394
+ import { readFileSync as readFileSync10, statSync } from "node:fs";
10395
+ import { join as join9, resolve as resolve8 } from "node:path";
9941
10396
 
9942
10397
  // plugins/kxm/src/session-token-hint.ts
9943
10398
  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,15 +10465,15 @@ function truncate2(text, max) {
10010
10465
  }
10011
10466
  async function sessionStartContext(input, env) {
10012
10467
  const inputCwd = typeof input.cwd === "string" ? input.cwd.trim() : "";
10013
- const root = resolve6(env.CLAUDE_PROJECT_DIR?.trim() || inputCwd || process.cwd());
10014
- if (!isDirectory(join8(root, ".kxm"))) return void 0;
10468
+ const root = resolve8(env.CLAUDE_PROJECT_DIR?.trim() || inputCwd || process.cwd());
10469
+ if (!isDirectory(join9(root, ".kxm"))) return void 0;
10015
10470
  const [
10016
10471
  { resolveSessionHubStatus: resolveSessionHubStatus2 },
10017
10472
  { loadLocalMeshSnapshot: loadLocalMeshSnapshot2, resolveKxmSnapshotPaths: resolveKxmSnapshotPaths2 },
10018
10473
  { formatMemoryBriefText: formatMemoryBriefText2, generateMemoryBrief: generateMemoryBrief2 },
10019
10474
  { defaultProjectName: defaultProjectName2 },
10020
10475
  { enforceToolPolicy: enforceToolPolicy2 },
10021
- { parse: parse2 }
10476
+ { parse: parse3 }
10022
10477
  ] = await Promise.all([
10023
10478
  Promise.resolve().then(() => (init_session_work(), session_work_exports)),
10024
10479
  Promise.resolve().then(() => (init_local_snapshot(), local_snapshot_exports)),
@@ -10032,7 +10487,7 @@ async function sessionStartContext(input, env) {
10032
10487
  const url = (env.CLAUDE_PLUGIN_OPTION_SERVER_URL?.trim() || DEFAULT_SERVER_URL).replace(/\/$/, "");
10033
10488
  let runtimeProjectId;
10034
10489
  try {
10035
- const project = parse2(readFileSync9(join8(root, ".kxm", "project.yaml"), "utf8"));
10490
+ const project = parse3(readFileSync10(join9(root, ".kxm", "project.yaml"), "utf8"));
10036
10491
  if (typeof project?.id === "string" && project.id.trim()) runtimeProjectId = project.id.trim();
10037
10492
  } catch {
10038
10493
  }