@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +22 -0
- package/README.md +2 -0
- package/docs/README.md +1 -0
- package/docs/architecture/inventory.md +4 -3
- package/docs/contributing/harness-routing-internals.md +28 -27
- package/docs/glossary.md +3 -3
- package/docs/operations/troubleshooting.md +3 -0
- package/docs/operations.md +154 -0
- package/docs/reference/cli-reference.md +4 -3
- package/docs/reference/configuration.md +11 -1
- package/docs/reference/harness-routing.md +17 -15
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +2 -2
- package/plugins/kxm/README.md +2 -2
- package/plugins/kxm/dist/claude-hook.js +120 -16
- package/plugins/kxm/dist/cli.js +14929 -14542
- package/plugins/kxm/dist/extension.js +299 -122
- package/plugins/kxm/dist/mcp-server.js +7690 -106
- package/plugins/kxm/dist/runtime-supervisor.js +151 -4
- package/plugins/kxm/dist/runtime.js +158 -11
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +5 -1
- package/plugins/kxm/src/cli/context-skills.ts +64 -10
- package/plugins/kxm/src/cli/hub.ts +128 -17
- package/plugins/kxm/src/cli/project.ts +7 -3
- package/plugins/kxm/src/cli/types.ts +6 -0
- package/plugins/kxm/src/cli/workflows.ts +12 -1
- package/plugins/kxm/src/cli.ts +23 -5
- package/plugins/kxm/src/extension.ts +7 -3
- package/plugins/kxm/src/hub-autostart.ts +3 -0
- package/plugins/kxm/src/hub-binding.ts +182 -2
- package/plugins/kxm/src/hub-env.ts +71 -0
- package/plugins/kxm/src/mcp-server.ts +3 -2
- package/plugins/kxm/src/project-name.ts +34 -7
- package/plugins/kxm/src/runtime-supervisor.ts +9 -3
- package/plugins/kxm/src/tenant-status.ts +9 -2
- 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
|
-
|
|
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
|
|
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
|
|
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
|
|
9118
|
-
return
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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
|
|
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 =
|
|
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 },
|