@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +9 -0
- package/README.md +2 -0
- package/docs/README.md +1 -0
- package/docs/architecture/inventory.md +4 -3
- 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/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
|
@@ -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
|
-
|
|
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
|
-
|
|
32045
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32793
|
-
|
|
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
|
|
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,
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 ??
|
|
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);
|
package/plugins/kxm/package.json
CHANGED
|
@@ -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
|
-
...(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|