@kontextmind/kxm 0.7.177 → 0.7.179
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 +21 -0
- package/docs/architecture/index.md +2 -1
- package/docs/architecture/inventory.md +8 -6
- package/docs/contributing/decisions.md +27 -1
- package/docs/contributing/development.md +4 -2
- package/docs/contributing/test-matrix.md +1 -0
- package/docs/contributing/tui-components.md +22 -4
- package/docs/reference/cli-reference.md +63 -6
- package/docs/reference/config-reference.md +17 -3
- package/docs/reference/tools.md +6 -4
- package/package.json +2 -2
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/claude-hook.js +40 -48
- package/plugins/kxm/dist/cli.js +2118 -276
- package/plugins/kxm/dist/extension.js +11252 -1492
- package/plugins/kxm/dist/mcp-server.js +3 -2
- package/plugins/kxm/dist/runtime-supervisor.js +2 -1
- package/plugins/kxm/dist/runtime.js +2 -1
- package/plugins/kxm/dist/server.js +2 -1
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/autocomplete.ts +3 -3
- package/plugins/kxm/src/cli/plugins.ts +193 -25
- package/plugins/kxm/src/cli/system.ts +37 -0
- package/plugins/kxm/src/cli.ts +9 -2
- package/plugins/kxm/src/config-editor.ts +479 -0
- package/plugins/kxm/src/config.ts +3 -2
- package/plugins/kxm/src/extension.ts +9 -2
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/session-work.ts +36 -51
package/plugins/kxm/dist/cli.js
CHANGED
|
@@ -8880,7 +8880,7 @@ var require_public_api = __commonJS({
|
|
|
8880
8880
|
}
|
|
8881
8881
|
return doc;
|
|
8882
8882
|
}
|
|
8883
|
-
function
|
|
8883
|
+
function parse13(src, reviver, options) {
|
|
8884
8884
|
let _reviver = void 0;
|
|
8885
8885
|
if (typeof reviver === "function") {
|
|
8886
8886
|
_reviver = reviver;
|
|
@@ -8921,7 +8921,7 @@ var require_public_api = __commonJS({
|
|
|
8921
8921
|
return value.toString(options);
|
|
8922
8922
|
return new Document.Document(value, _replacer, options).toString(options);
|
|
8923
8923
|
}
|
|
8924
|
-
exports.parse =
|
|
8924
|
+
exports.parse = parse13;
|
|
8925
8925
|
exports.parseAllDocuments = parseAllDocuments;
|
|
8926
8926
|
exports.parseDocument = parseDocument3;
|
|
8927
8927
|
exports.stringify = stringify12;
|
|
@@ -9184,6 +9184,7 @@ function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
|
|
|
9184
9184
|
const userConfigFile = join3(userDir, "config.yaml");
|
|
9185
9185
|
const repoDir = repoConfigDirectory(repoRoot);
|
|
9186
9186
|
const repoConfigFile = join3(repoDir, "config.yaml");
|
|
9187
|
+
const includeProject = options.layers !== "user";
|
|
9187
9188
|
let userRaw = {};
|
|
9188
9189
|
let userLoadedPath;
|
|
9189
9190
|
if (existsSync3(userConfigFile)) {
|
|
@@ -9197,7 +9198,7 @@ function loadKxmConfig(repoRoot = process.cwd(), options = {}) {
|
|
|
9197
9198
|
}
|
|
9198
9199
|
let repoRaw = {};
|
|
9199
9200
|
let repoLoadedPath;
|
|
9200
|
-
if (existsSync3(repoConfigFile)) {
|
|
9201
|
+
if (includeProject && existsSync3(repoConfigFile)) {
|
|
9201
9202
|
try {
|
|
9202
9203
|
const text = readFileSync2(repoConfigFile, "utf8");
|
|
9203
9204
|
repoRaw = (0, import_yaml.parse)(text) ?? {};
|
|
@@ -14043,7 +14044,7 @@ var require_fast_uri = __commonJS({
|
|
|
14043
14044
|
normalizeString(uri, options);
|
|
14044
14045
|
} else if (typeof uri === "object") {
|
|
14045
14046
|
uri = /** @type {T} */
|
|
14046
|
-
|
|
14047
|
+
parse13(serialize(uri, options), options);
|
|
14047
14048
|
}
|
|
14048
14049
|
return uri;
|
|
14049
14050
|
}
|
|
@@ -14083,8 +14084,8 @@ var require_fast_uri = __commonJS({
|
|
|
14083
14084
|
function resolveComponent(base, relative8, options, skipNormalization) {
|
|
14084
14085
|
const target = {};
|
|
14085
14086
|
if (!skipNormalization) {
|
|
14086
|
-
base =
|
|
14087
|
-
relative8 =
|
|
14087
|
+
base = parse13(serialize(base, options), options);
|
|
14088
|
+
relative8 = parse13(serialize(relative8, options), options);
|
|
14088
14089
|
}
|
|
14089
14090
|
options = options || {};
|
|
14090
14091
|
if (!options.tolerant && relative8.scheme) {
|
|
@@ -14376,7 +14377,7 @@ var require_fast_uri = __commonJS({
|
|
|
14376
14377
|
}
|
|
14377
14378
|
return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
|
|
14378
14379
|
}
|
|
14379
|
-
function
|
|
14380
|
+
function parse13(uri, opts) {
|
|
14380
14381
|
return parseWithStatus(uri, opts).parsed;
|
|
14381
14382
|
}
|
|
14382
14383
|
function normalizeString(uri, opts) {
|
|
@@ -14413,7 +14414,7 @@ var require_fast_uri = __commonJS({
|
|
|
14413
14414
|
resolveComponent,
|
|
14414
14415
|
equal,
|
|
14415
14416
|
serialize,
|
|
14416
|
-
parse:
|
|
14417
|
+
parse: parse13
|
|
14417
14418
|
};
|
|
14418
14419
|
module.exports = fastUri;
|
|
14419
14420
|
module.exports.default = fastUri;
|
|
@@ -19547,7 +19548,7 @@ function resolveDispatchStatus(entry, detected, authenticated, issues) {
|
|
|
19547
19548
|
return { status: "no", supported: false, reason: "not_authenticated" };
|
|
19548
19549
|
}
|
|
19549
19550
|
if (authenticated === null) {
|
|
19550
|
-
const authIssue = issues.find((
|
|
19551
|
+
const authIssue = issues.find((issue5) => issue5 === "auth_context_required" || issue5 === "auth_unknown" || issue5 === "auth_unparsed" || issue5.startsWith("auth_"));
|
|
19551
19552
|
return { status: "no", supported: false, reason: authIssue ?? issues[0] ?? "auth_unknown" };
|
|
19552
19553
|
}
|
|
19553
19554
|
return { status: "yes", supported: true };
|
|
@@ -20759,8 +20760,8 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
20759
20760
|
const writable = Object.values(objectValue(step.repositories) ?? {}).filter((access) => access === "write").length;
|
|
20760
20761
|
if (maxWriteRepositories > writable) issues.push(issue3("semantic", "write_repository_bound_invalid", file, `${stepId} maxWriteRepositories exceeds writable repository scope`));
|
|
20761
20762
|
}
|
|
20762
|
-
const
|
|
20763
|
-
const minimumPassed =
|
|
20763
|
+
const join60 = objectValue(step.join);
|
|
20764
|
+
const minimumPassed = join60 && typeof join60.minimumPassed === "number" ? join60.minimumPassed : void 0;
|
|
20764
20765
|
if (minimumPassed !== void 0 && minimumPassed > maximum) issues.push(issue3("semantic", "join_impossible", file, `${stepId} minimumPassed exceeds assignment maximum`));
|
|
20765
20766
|
const distinctBy = names(assignment?.distinctBy);
|
|
20766
20767
|
if (distinctBy.length > 0) {
|
|
@@ -21473,7 +21474,7 @@ var init_project_config = __esm({
|
|
|
21473
21474
|
issues;
|
|
21474
21475
|
constructor(issues) {
|
|
21475
21476
|
const sorted = sortIssues3(issues);
|
|
21476
|
-
super(sorted.map((
|
|
21477
|
+
super(sorted.map((issue5) => `${issue5.file}: ${issue5.code}: ${issue5.message}`).join("\n"));
|
|
21477
21478
|
this.name = "KxmConfigError";
|
|
21478
21479
|
this.issues = sorted;
|
|
21479
21480
|
}
|
|
@@ -22356,8 +22357,8 @@ import {
|
|
|
22356
22357
|
} from "node:fs";
|
|
22357
22358
|
import { basename as basename3, dirname as dirname9, isAbsolute as isAbsolute5, join as join12, relative as relative3, resolve as resolve10, sep as sep3 } from "node:path";
|
|
22358
22359
|
function databaseError(code, file, message) {
|
|
22359
|
-
const
|
|
22360
|
-
return new KxmConfigError([
|
|
22360
|
+
const issue5 = { phase: "semantic", code, file, message };
|
|
22361
|
+
return new KxmConfigError([issue5]);
|
|
22361
22362
|
}
|
|
22362
22363
|
function checkedParent(path4, description) {
|
|
22363
22364
|
const parent = dirname9(path4);
|
|
@@ -25100,7 +25101,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
|
|
|
25100
25101
|
const maxAttempts = compileCountField(step.maxAttempts, 1, `${id}.maxAttempts`, id, sink);
|
|
25101
25102
|
const timeoutMs = compileOptionalDuration(step.timeoutMs, `${id}.timeoutMs`, id, sink);
|
|
25102
25103
|
const assignments = compileAssignments(step, id, agent, sink);
|
|
25103
|
-
const
|
|
25104
|
+
const join60 = compileJoin(step, id, sink);
|
|
25104
25105
|
const requiredEvidence = compileEvidence(step, id, sink);
|
|
25105
25106
|
const transitions = compileTransitions(step, id, index, stepIndex, sink);
|
|
25106
25107
|
const outcomes = Object.keys(transitions).sort(compareCodeUnits5);
|
|
@@ -25128,7 +25129,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
|
|
|
25128
25129
|
transitions: orderedTransitions,
|
|
25129
25130
|
requiresPlanHash: requirePlanHash.includes(id),
|
|
25130
25131
|
assignments,
|
|
25131
|
-
join:
|
|
25132
|
+
join: join60
|
|
25132
25133
|
};
|
|
25133
25134
|
if (kind === "agent" || kind === "moa") {
|
|
25134
25135
|
if (!agent) return void 0;
|
|
@@ -25178,15 +25179,15 @@ function compileAssignments(step, stepId, primaryAgentId, sink) {
|
|
|
25178
25179
|
};
|
|
25179
25180
|
}
|
|
25180
25181
|
function compileJoin(step, stepId, sink) {
|
|
25181
|
-
const
|
|
25182
|
-
if (!
|
|
25183
|
-
const declared = stringValue2(
|
|
25182
|
+
const join60 = objectValue2(step.join);
|
|
25183
|
+
if (!join60) return { strategy: "all" };
|
|
25184
|
+
const declared = stringValue2(join60.strategy);
|
|
25184
25185
|
const strategy = declared && JOIN_STRATEGIES.has(declared) ? declared : "all";
|
|
25185
|
-
const minimumPassed = compileOptionalCount(
|
|
25186
|
+
const minimumPassed = compileOptionalCount(join60.minimumPassed, `${stepId}.join.minimumPassed`, stepId, sink);
|
|
25186
25187
|
const compiled = {
|
|
25187
25188
|
strategy,
|
|
25188
25189
|
...minimumPassed !== void 0 ? { minimumPassed } : {},
|
|
25189
|
-
...typeof
|
|
25190
|
+
...typeof join60.cancelRemaining === "boolean" ? { cancelRemaining: join60.cancelRemaining } : {}
|
|
25190
25191
|
};
|
|
25191
25192
|
return compiled;
|
|
25192
25193
|
}
|
|
@@ -25451,8 +25452,8 @@ function sortIssues4(issues) {
|
|
|
25451
25452
|
return compareCodeUnits5(left.code, right.code);
|
|
25452
25453
|
});
|
|
25453
25454
|
}
|
|
25454
|
-
function pushIssue(sink,
|
|
25455
|
-
sink.issues.push({ workflowId: sink.workflowId, ...
|
|
25455
|
+
function pushIssue(sink, issue5) {
|
|
25456
|
+
sink.issues.push({ workflowId: sink.workflowId, ...issue5 });
|
|
25456
25457
|
}
|
|
25457
25458
|
var KXM_COMPILED_WORKFLOW_SCHEMA, WORKFLOW_SCHEMA, STEP_ID_PATTERN, SUPPORTED_STEP_KINDS, TERMINAL_STATUSES, JOIN_STRATEGIES, EVIDENCE_KINDS, REPOSITORY_ACCESS, DISTINCT_BY, BANNED_RUNTIME_HINT, KxmEngineCompileError;
|
|
25458
25459
|
var init_engine_compile = __esm({
|
|
@@ -25472,7 +25473,7 @@ var init_engine_compile = __esm({
|
|
|
25472
25473
|
issues;
|
|
25473
25474
|
constructor(issues) {
|
|
25474
25475
|
const sorted = sortIssues4(issues);
|
|
25475
|
-
super(sorted.map((
|
|
25476
|
+
super(sorted.map((issue5) => `${issue5.workflowId}: ${issue5.code}: ${issue5.message}`).join("\n"));
|
|
25476
25477
|
this.name = "KxmEngineCompileError";
|
|
25477
25478
|
this.issues = sorted;
|
|
25478
25479
|
}
|
|
@@ -31226,7 +31227,7 @@ async function cmdKxmInit(runtime, options, postHooks) {
|
|
|
31226
31227
|
return finishInit(0, `init plan: ${initialized.plan.mode}`);
|
|
31227
31228
|
}
|
|
31228
31229
|
const next = initialized.plan.mode === "legacy" ? "legacy state is not migrated by this build: initialise a fresh project directory and copy the YAML definitions you want to keep" : initialized.repairPlan?.issues.length ? "managed-template repair is blocked by conflicts or authority changes; local files were preserved" : "partial or provenance-free KXM state requires explicit repair; no files were overwritten";
|
|
31229
|
-
return finishInit(1, [next, ...initialized.plan.issues.map((
|
|
31230
|
+
return finishInit(1, [next, ...initialized.plan.issues.map((issue5) => `${issue5.file}: ${issue5.code}: ${issue5.message}`)].join("\n"));
|
|
31230
31231
|
} catch (error) {
|
|
31231
31232
|
if (error instanceof KxmConfigError) {
|
|
31232
31233
|
print(runtime.io, runtime.json, {
|
|
@@ -40186,7 +40187,7 @@ async function cmdContextWikiLint(runtime, project) {
|
|
|
40186
40187
|
const compiled = response.body;
|
|
40187
40188
|
const issues = compiled.lint;
|
|
40188
40189
|
print(runtime.io, runtime.json, { ok: issues.length === 0, command: "context wiki-lint", project, issues, audit: compiled.audit }, issues.length === 0 ? "wiki lint clean" : `wiki lint found ${issues.length} issue(s)`);
|
|
40189
|
-
return issues.every((
|
|
40190
|
+
return issues.every((issue5) => issue5.severity !== "error") ? 0 : 1;
|
|
40190
40191
|
}
|
|
40191
40192
|
function skillStateFromFlag(value) {
|
|
40192
40193
|
if (value === "candidate" || value === "promoted" || value === "quarantined" || value === "rejected") return value;
|
|
@@ -40402,11 +40403,114 @@ ${lines.join("\n")}`
|
|
|
40402
40403
|
init_project();
|
|
40403
40404
|
|
|
40404
40405
|
// plugins/kxm/src/cli/plugins.ts
|
|
40406
|
+
var import_yaml19 = __toESM(require_dist(), 1);
|
|
40405
40407
|
init_repo_root();
|
|
40406
40408
|
init_harness();
|
|
40407
40409
|
init_types();
|
|
40408
40410
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
40411
|
+
import { existsSync as existsSync32, readFileSync as readFileSync30 } from "node:fs";
|
|
40412
|
+
import { isAbsolute as isAbsolute10, join as join36 } from "node:path";
|
|
40409
40413
|
var PLUGIN_SUPPORTED_HARNESSES = Object.freeze(["pi", "omp", "claude"]);
|
|
40414
|
+
var OMP_STATUS_SETTINGS = [
|
|
40415
|
+
{ key: "statusLine.leftSegments", configKey: "leftSegments", value: ["model", "mode", "git", "pr", "subagents"] },
|
|
40416
|
+
{ key: "statusLine.rightSegments", configKey: "rightSegments", value: ["usage", "cost", "context_pct"] },
|
|
40417
|
+
{ key: "statusLine.preset", configKey: "preset", value: "custom" }
|
|
40418
|
+
];
|
|
40419
|
+
function spawnHarness(runtime, command, args) {
|
|
40420
|
+
return runtime.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync9(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
|
|
40421
|
+
}
|
|
40422
|
+
function spawnFailed(run) {
|
|
40423
|
+
return Boolean(run.error) || run.status !== 0;
|
|
40424
|
+
}
|
|
40425
|
+
function spawnFailureDetail(run, fallback) {
|
|
40426
|
+
return (run.stderr || run.stdout || run.error?.message || fallback).trim();
|
|
40427
|
+
}
|
|
40428
|
+
function ompConfigValueArg(value) {
|
|
40429
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
40430
|
+
}
|
|
40431
|
+
function readOmpGlobalStatusSettings(runtime, command) {
|
|
40432
|
+
const args = ["config", "path"];
|
|
40433
|
+
const run = spawnHarness(runtime, command, args);
|
|
40434
|
+
if (spawnFailed(run)) return { args, run, rollbackAttempted: false, rollbackFailed: false };
|
|
40435
|
+
const agentDir = run.stdout.trim();
|
|
40436
|
+
if (!agentDir || !isAbsolute10(agentDir) || !existsSync32(agentDir)) {
|
|
40437
|
+
return {
|
|
40438
|
+
args,
|
|
40439
|
+
run: { status: 1, stdout: run.stdout, stderr: "omp config path did not return an existing absolute directory" },
|
|
40440
|
+
rollbackAttempted: false,
|
|
40441
|
+
rollbackFailed: false
|
|
40442
|
+
};
|
|
40443
|
+
}
|
|
40444
|
+
const configPath = ["config.yml", "config.yaml"].map((name) => join36(agentDir, name)).find(existsSync32);
|
|
40445
|
+
let document = {};
|
|
40446
|
+
try {
|
|
40447
|
+
if (configPath) document = (0, import_yaml19.parse)(readFileSync30(configPath, "utf8"));
|
|
40448
|
+
} catch {
|
|
40449
|
+
return {
|
|
40450
|
+
args,
|
|
40451
|
+
run: { status: 1, stdout: run.stdout, stderr: `could not read OMP global config at ${configPath}` },
|
|
40452
|
+
rollbackAttempted: false,
|
|
40453
|
+
rollbackFailed: false
|
|
40454
|
+
};
|
|
40455
|
+
}
|
|
40456
|
+
const root = document && typeof document === "object" && !Array.isArray(document) ? document : {};
|
|
40457
|
+
const rawStatusLine = "statusLine" in root ? root.statusLine : void 0;
|
|
40458
|
+
const statusLine = rawStatusLine && typeof rawStatusLine === "object" && !Array.isArray(rawStatusLine) ? rawStatusLine : {};
|
|
40459
|
+
const previous = {};
|
|
40460
|
+
for (const setting of OMP_STATUS_SETTINGS) {
|
|
40461
|
+
const value = statusLine[setting.configKey];
|
|
40462
|
+
previous[setting.key] = Object.hasOwn(statusLine, setting.configKey) && value !== null && value !== void 0 ? { present: true, value } : { present: false };
|
|
40463
|
+
}
|
|
40464
|
+
return { previous };
|
|
40465
|
+
}
|
|
40466
|
+
function rollbackOmpStatusBand(runtime, command, settings, previous) {
|
|
40467
|
+
let failed2 = false;
|
|
40468
|
+
for (const setting of [...settings].reverse()) {
|
|
40469
|
+
const snapshot = previous[setting.key];
|
|
40470
|
+
const args = snapshot?.present ? ["config", "set", setting.key, ompConfigValueArg(snapshot.value), "--json"] : ["config", "reset", setting.key, "--json"];
|
|
40471
|
+
if (spawnFailed(spawnHarness(runtime, command, args))) failed2 = true;
|
|
40472
|
+
}
|
|
40473
|
+
return failed2;
|
|
40474
|
+
}
|
|
40475
|
+
function configureOmpStatusBand(runtime, command) {
|
|
40476
|
+
const snapshot = readOmpGlobalStatusSettings(runtime, command);
|
|
40477
|
+
if ("run" in snapshot) return snapshot;
|
|
40478
|
+
const written = [];
|
|
40479
|
+
for (const setting of OMP_STATUS_SETTINGS) {
|
|
40480
|
+
const args = ["config", "set", setting.key, ompConfigValueArg(setting.value), "--json"];
|
|
40481
|
+
written.push(setting);
|
|
40482
|
+
const run = spawnHarness(runtime, command, args);
|
|
40483
|
+
if (spawnFailed(run)) {
|
|
40484
|
+
return {
|
|
40485
|
+
args,
|
|
40486
|
+
run,
|
|
40487
|
+
rollbackAttempted: true,
|
|
40488
|
+
rollbackFailed: rollbackOmpStatusBand(runtime, command, written, snapshot.previous)
|
|
40489
|
+
};
|
|
40490
|
+
}
|
|
40491
|
+
}
|
|
40492
|
+
for (const setting of OMP_STATUS_SETTINGS) {
|
|
40493
|
+
const args = ["config", "get", setting.key, "--json"];
|
|
40494
|
+
const run = spawnHarness(runtime, command, args);
|
|
40495
|
+
let actual;
|
|
40496
|
+
try {
|
|
40497
|
+
const parsed = JSON.parse(run.stdout);
|
|
40498
|
+
actual = parsed && typeof parsed === "object" && "value" in parsed ? parsed.value : void 0;
|
|
40499
|
+
} catch {
|
|
40500
|
+
actual = void 0;
|
|
40501
|
+
}
|
|
40502
|
+
if (spawnFailed(run) || JSON.stringify(actual) !== JSON.stringify(setting.value)) {
|
|
40503
|
+
const mismatch = spawnFailed(run) ? run : { status: 1, stdout: run.stdout, stderr: `effective ${setting.key} is shadowed or did not persist` };
|
|
40504
|
+
return {
|
|
40505
|
+
args,
|
|
40506
|
+
run: mismatch,
|
|
40507
|
+
rollbackAttempted: true,
|
|
40508
|
+
rollbackFailed: rollbackOmpStatusBand(runtime, command, written, snapshot.previous)
|
|
40509
|
+
};
|
|
40510
|
+
}
|
|
40511
|
+
}
|
|
40512
|
+
return void 0;
|
|
40513
|
+
}
|
|
40410
40514
|
async function cmdPluginInstall(runtime, options) {
|
|
40411
40515
|
const repoRoot = findKxmRepoRoot(import.meta.url);
|
|
40412
40516
|
const env = runtime.env.PATH ? runtime.env : { ...process.env, ...runtime.env };
|
|
@@ -40417,12 +40521,22 @@ async function cmdPluginInstall(runtime, options) {
|
|
|
40417
40521
|
if (options.omp) requestedHarnesses.push("omp");
|
|
40418
40522
|
if (options.claude) requestedHarnesses.push("claude");
|
|
40419
40523
|
const targetHarnesses = isExplicit ? requestedHarnesses : PLUGIN_SUPPORTED_HARNESSES;
|
|
40524
|
+
if (options.statusBand && isExplicit && !options.omp) {
|
|
40525
|
+
const detail = "--status-band requires OMP to be among the selected harnesses";
|
|
40526
|
+
print(
|
|
40527
|
+
runtime.io,
|
|
40528
|
+
runtime.json,
|
|
40529
|
+
{ ok: false, command: "plugin install", error: "omp_status_band_requires_omp", results: [] },
|
|
40530
|
+
detail
|
|
40531
|
+
);
|
|
40532
|
+
return 1;
|
|
40533
|
+
}
|
|
40420
40534
|
const results = [];
|
|
40421
40535
|
for (const harnessId of targetHarnesses) {
|
|
40422
40536
|
const status = inventory.harnesses.find((h) => h.id === harnessId);
|
|
40423
40537
|
const detected = Boolean(status?.detected && status?.command);
|
|
40424
40538
|
if (!detected) {
|
|
40425
|
-
if (isExplicit) {
|
|
40539
|
+
if (isExplicit || options.statusBand && harnessId === "omp") {
|
|
40426
40540
|
results.push({
|
|
40427
40541
|
harness: harnessId,
|
|
40428
40542
|
status: "failed",
|
|
@@ -40444,15 +40558,15 @@ async function cmdPluginInstall(runtime, options) {
|
|
|
40444
40558
|
detail: `would install kxm plugin into pi: ${command} ${args.join(" ")}`
|
|
40445
40559
|
});
|
|
40446
40560
|
} else {
|
|
40447
|
-
const run = runtime
|
|
40448
|
-
if (run
|
|
40561
|
+
const run = spawnHarness(runtime, command, args);
|
|
40562
|
+
if (spawnFailed(run)) {
|
|
40449
40563
|
results.push({
|
|
40450
40564
|
harness: "pi",
|
|
40451
40565
|
status: "failed",
|
|
40452
40566
|
command,
|
|
40453
40567
|
args,
|
|
40454
40568
|
error: run.error?.message || "install_failed",
|
|
40455
|
-
detail: (run
|
|
40569
|
+
detail: spawnFailureDetail(run, "pi install exited with error")
|
|
40456
40570
|
});
|
|
40457
40571
|
} else {
|
|
40458
40572
|
results.push({
|
|
@@ -40472,27 +40586,39 @@ async function cmdPluginInstall(runtime, options) {
|
|
|
40472
40586
|
status: "would",
|
|
40473
40587
|
command,
|
|
40474
40588
|
args,
|
|
40475
|
-
detail: `would install kxm plugin into omp: ${command} ${args.join(" ")}`
|
|
40589
|
+
detail: options.statusBand ? `would install kxm plugin into omp and configure the focused subscription status band: ${command} ${args.join(" ")}` : `would install kxm plugin into omp: ${command} ${args.join(" ")}`
|
|
40476
40590
|
});
|
|
40477
40591
|
} else {
|
|
40478
|
-
const run = runtime
|
|
40479
|
-
if (run
|
|
40592
|
+
const run = spawnHarness(runtime, command, args);
|
|
40593
|
+
if (spawnFailed(run)) {
|
|
40480
40594
|
results.push({
|
|
40481
40595
|
harness: "omp",
|
|
40482
40596
|
status: "failed",
|
|
40483
40597
|
command,
|
|
40484
40598
|
args,
|
|
40485
40599
|
error: run.error?.message || "install_failed",
|
|
40486
|
-
detail: (run
|
|
40600
|
+
detail: spawnFailureDetail(run, "omp plugin install exited with error")
|
|
40487
40601
|
});
|
|
40488
40602
|
} else {
|
|
40489
|
-
|
|
40490
|
-
|
|
40491
|
-
|
|
40492
|
-
|
|
40493
|
-
|
|
40494
|
-
|
|
40495
|
-
|
|
40603
|
+
const configFailure = options.statusBand ? configureOmpStatusBand(runtime, command) : void 0;
|
|
40604
|
+
if (configFailure) {
|
|
40605
|
+
results.push({
|
|
40606
|
+
harness: "omp",
|
|
40607
|
+
status: "failed",
|
|
40608
|
+
command,
|
|
40609
|
+
args: configFailure.args,
|
|
40610
|
+
error: "status_band_config_failed",
|
|
40611
|
+
detail: `installed kxm plugin into omp, but ${configFailure.args.slice(0, 3).join(" ")} failed: ${spawnFailureDetail(configFailure.run, "omp status band configuration exited with error")}${configFailure.rollbackAttempted ? configFailure.rollbackFailed ? "; previous settings could not be fully restored" : "; previous settings restored" : ""}`
|
|
40612
|
+
});
|
|
40613
|
+
} else {
|
|
40614
|
+
results.push({
|
|
40615
|
+
harness: "omp",
|
|
40616
|
+
status: "passed",
|
|
40617
|
+
command,
|
|
40618
|
+
args,
|
|
40619
|
+
detail: options.statusBand ? "installed kxm plugin into omp and configured the focused subscription status band; restart omp" : "installed kxm plugin into omp"
|
|
40620
|
+
});
|
|
40621
|
+
}
|
|
40496
40622
|
}
|
|
40497
40623
|
}
|
|
40498
40624
|
} else if (harnessId === "claude") {
|
|
@@ -40512,15 +40638,15 @@ async function cmdPluginInstall(runtime, options) {
|
|
|
40512
40638
|
} else {
|
|
40513
40639
|
spawnSync9(command, marketplaceArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
|
|
40514
40640
|
}
|
|
40515
|
-
const run = runtime
|
|
40516
|
-
if (run
|
|
40641
|
+
const run = spawnHarness(runtime, command, installArgs);
|
|
40642
|
+
if (spawnFailed(run)) {
|
|
40517
40643
|
results.push({
|
|
40518
40644
|
harness: "claude",
|
|
40519
40645
|
status: "failed",
|
|
40520
40646
|
command,
|
|
40521
40647
|
args: installArgs,
|
|
40522
40648
|
error: run.error?.message || "install_failed",
|
|
40523
|
-
detail: (run
|
|
40649
|
+
detail: spawnFailureDetail(run, "claude install exited with error")
|
|
40524
40650
|
});
|
|
40525
40651
|
} else {
|
|
40526
40652
|
results.push({
|
|
@@ -40564,8 +40690,8 @@ async function cmdPluginInstall(runtime, options) {
|
|
|
40564
40690
|
init_project_config();
|
|
40565
40691
|
init_types();
|
|
40566
40692
|
import { spawn as spawn4 } from "node:child_process";
|
|
40567
|
-
import { existsSync as
|
|
40568
|
-
import { join as
|
|
40693
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
40694
|
+
import { join as join37 } from "node:path";
|
|
40569
40695
|
var GENERATOR = "plans/kxm-roadmap/update-dashboard.mjs";
|
|
40570
40696
|
var SERVER = "ops/docs-site/serve.py";
|
|
40571
40697
|
function requireProject(runtime, command) {
|
|
@@ -40582,8 +40708,8 @@ function requireProject(runtime, command) {
|
|
|
40582
40708
|
return root;
|
|
40583
40709
|
}
|
|
40584
40710
|
function requireFile(runtime, command, root, relative8, error) {
|
|
40585
|
-
const file =
|
|
40586
|
-
if (
|
|
40711
|
+
const file = join37(root, relative8);
|
|
40712
|
+
if (existsSync33(file)) return file;
|
|
40587
40713
|
print(
|
|
40588
40714
|
runtime.io,
|
|
40589
40715
|
runtime.json,
|
|
@@ -40637,15 +40763,15 @@ init_runtime_store();
|
|
|
40637
40763
|
init_runtime_supervisor();
|
|
40638
40764
|
init_types();
|
|
40639
40765
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
40640
|
-
import { chmodSync as chmodSync6, existsSync as
|
|
40641
|
-
import { basename as basename8, dirname as dirname18, join as
|
|
40766
|
+
import { chmodSync as chmodSync6, existsSync as existsSync34, mkdirSync as mkdirSync24, readFileSync as readFileSync31, writeFileSync as writeFileSync22 } from "node:fs";
|
|
40767
|
+
import { basename as basename8, dirname as dirname18, join as join38, resolve as resolve25 } from "node:path";
|
|
40642
40768
|
var LANE_SCHEMA = "kxm.lanes.v1";
|
|
40643
40769
|
var SHA_RE = /^[a-f0-9]{40,64}$/;
|
|
40644
40770
|
var TIMESTAMP_RE = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{1,9})?Z$/;
|
|
40645
40771
|
var SETTLED_RUN = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
40646
40772
|
var LANE_RUN_LIST_LIMIT = 1e4;
|
|
40647
40773
|
function lanesFile(runtime) {
|
|
40648
|
-
return
|
|
40774
|
+
return join38(runtime.dirs.state, "lanes.json");
|
|
40649
40775
|
}
|
|
40650
40776
|
function gitEnv2() {
|
|
40651
40777
|
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")));
|
|
@@ -40693,9 +40819,9 @@ function isLaneFile(value) {
|
|
|
40693
40819
|
}
|
|
40694
40820
|
function loadLanes(runtime, command) {
|
|
40695
40821
|
const file = lanesFile(runtime);
|
|
40696
|
-
if (!
|
|
40822
|
+
if (!existsSync34(file)) return { schema: LANE_SCHEMA, lanes: {} };
|
|
40697
40823
|
try {
|
|
40698
|
-
const parsed = JSON.parse(
|
|
40824
|
+
const parsed = JSON.parse(readFileSync31(file, "utf8"));
|
|
40699
40825
|
if (!isLaneFile(parsed)) return refuse(runtime, command, "lanes_unreadable", `lane record ${file} is malformed`);
|
|
40700
40826
|
return parsed;
|
|
40701
40827
|
} catch {
|
|
@@ -40752,7 +40878,7 @@ function withLaneCwd(runtime, unit, command = "lane") {
|
|
|
40752
40878
|
const loaded2 = loadLanes(runtime, command);
|
|
40753
40879
|
if (typeof loaded2 === "number") return loaded2;
|
|
40754
40880
|
const lane = loaded2.lanes[unit];
|
|
40755
|
-
if (!lane || !
|
|
40881
|
+
if (!lane || !existsSync34(lane.path)) {
|
|
40756
40882
|
return refuse(runtime, command, "lane_missing", `lane ${unit} is not recorded or its worktree is absent`, { unit });
|
|
40757
40883
|
}
|
|
40758
40884
|
return {
|
|
@@ -40806,7 +40932,7 @@ async function cmdKxmRunCli(runtime, workflow, promptParts, options = {}) {
|
|
|
40806
40932
|
}
|
|
40807
40933
|
function readRunBrief(runtime, file, command) {
|
|
40808
40934
|
try {
|
|
40809
|
-
return { text:
|
|
40935
|
+
return { text: readFileSync31(resolve25(runtime.cwd, file), "utf8").trim() };
|
|
40810
40936
|
} catch {
|
|
40811
40937
|
return refuse(runtime, command, "brief_unreadable", `cannot read brief ${file}`, { brief: file });
|
|
40812
40938
|
}
|
|
@@ -40823,7 +40949,7 @@ function aheadBehind(path4, baseSha) {
|
|
|
40823
40949
|
return { behind: Number(match[1]), ahead: Number(match[2]) };
|
|
40824
40950
|
}
|
|
40825
40951
|
function laneView(unit, record) {
|
|
40826
|
-
const exists =
|
|
40952
|
+
const exists = existsSync34(record.path);
|
|
40827
40953
|
const counts = exists ? aheadBehind(record.path, record.baseSha) : { ahead: null, behind: null };
|
|
40828
40954
|
return {
|
|
40829
40955
|
unit,
|
|
@@ -40900,7 +41026,7 @@ async function laneStoreRuns(runtime, lanePath) {
|
|
|
40900
41026
|
}
|
|
40901
41027
|
}
|
|
40902
41028
|
const eventsPath = kxmProjectRunEventsPath(lanePath, runtime.env);
|
|
40903
|
-
if (!
|
|
41029
|
+
if (!existsSync34(eventsPath)) return void 0;
|
|
40904
41030
|
const store = new KxmRunEventStore(eventsPath);
|
|
40905
41031
|
try {
|
|
40906
41032
|
return store.runsForProject(projectId(), LANE_RUN_LIST_LIMIT).map((run) => ({ runId: run.runId, status: run.status }));
|
|
@@ -40969,7 +41095,7 @@ async function cmdLaneCreate(runtime, unit, options = {}) {
|
|
|
40969
41095
|
const loaded2 = loadLanes(runtime, command);
|
|
40970
41096
|
if (typeof loaded2 === "number") return loaded2;
|
|
40971
41097
|
const path4 = laneWorktreePath(root, unit);
|
|
40972
|
-
if (loaded2.lanes[unit] ||
|
|
41098
|
+
if (loaded2.lanes[unit] || existsSync34(path4) || branchExists(root, unit)) {
|
|
40973
41099
|
return refuse(runtime, command, "lane_exists", `lane ${unit} already exists`, { unit });
|
|
40974
41100
|
}
|
|
40975
41101
|
const baseSha = resolveBase(root, base);
|
|
@@ -40994,7 +41120,7 @@ async function cmdLaneCreate(runtime, unit, options = {}) {
|
|
|
40994
41120
|
return refuse(runtime, command, "lane_git_failed", `git worktree add failed: ${added.stderr.trim().slice(0, 300)}`, { unit });
|
|
40995
41121
|
}
|
|
40996
41122
|
const recordedPath = discoverKxmProjectRoot(path4) ?? path4;
|
|
40997
|
-
if (!
|
|
41123
|
+
if (!existsSync34(join38(recordedPath, ".kxm", "project.yaml"))) {
|
|
40998
41124
|
git2(root, ["worktree", "remove", "--force", path4]);
|
|
40999
41125
|
return refuse(runtime, command, "lane_not_project", `lane ${recordedPath} has no .kxm/project.yaml; the worktree was removed`, { unit, path: recordedPath });
|
|
41000
41126
|
}
|
|
@@ -41060,7 +41186,7 @@ async function unregisterLaneRoot(runtime, projectRoot, force) {
|
|
|
41060
41186
|
return void 0;
|
|
41061
41187
|
}
|
|
41062
41188
|
const paths = kxmRuntimePaths({ env: runtime.env });
|
|
41063
|
-
if (!
|
|
41189
|
+
if (!existsSync34(paths.registryDb)) return void 0;
|
|
41064
41190
|
const registry = new KxmRuntimeRegistry(paths.registryDb);
|
|
41065
41191
|
let outcome;
|
|
41066
41192
|
try {
|
|
@@ -41090,7 +41216,7 @@ async function cmdLaneDrop(runtime, unit, options = {}) {
|
|
|
41090
41216
|
if (!record) return refuse(runtime, command, "lane_missing", `lane ${unit} is not recorded`, { unit });
|
|
41091
41217
|
const force = options.force === true;
|
|
41092
41218
|
if (!force) {
|
|
41093
|
-
if (
|
|
41219
|
+
if (existsSync34(record.path)) {
|
|
41094
41220
|
const dirty = porcelainCount(record.path);
|
|
41095
41221
|
if (dirty === null) {
|
|
41096
41222
|
return refuse(runtime, command, "lane_git_failed", `lane ${unit} worktree status could not be read`, { unit });
|
|
@@ -41116,7 +41242,7 @@ async function cmdLaneDrop(runtime, unit, options = {}) {
|
|
|
41116
41242
|
if (unregistered !== void 0) {
|
|
41117
41243
|
return refuse(runtime, command, "lane_unregister_failed", `lane ${unit} could not be unregistered: ${unregistered.slice(0, 300)}`, { unit });
|
|
41118
41244
|
}
|
|
41119
|
-
if (
|
|
41245
|
+
if (existsSync34(record.path)) {
|
|
41120
41246
|
const removed = git2(root, ["worktree", "remove", ...force ? ["--force"] : [], record.path]);
|
|
41121
41247
|
if (removed.status !== 0) {
|
|
41122
41248
|
return refuse(runtime, command, "lane_git_failed", `git worktree remove failed: ${removed.stderr.trim().slice(0, 300)}`, { unit });
|
|
@@ -41187,7 +41313,7 @@ init_project_config();
|
|
|
41187
41313
|
init_repo_root();
|
|
41188
41314
|
init_types();
|
|
41189
41315
|
import { spawn as spawn5 } from "node:child_process";
|
|
41190
|
-
import { join as
|
|
41316
|
+
import { join as join39 } from "node:path";
|
|
41191
41317
|
function childEnv() {
|
|
41192
41318
|
const env = {};
|
|
41193
41319
|
for (const [name, value] of Object.entries(process.env)) {
|
|
@@ -41206,7 +41332,7 @@ function cmdLand(runtime, options = {}) {
|
|
|
41206
41332
|
if (!root) {
|
|
41207
41333
|
return Promise.resolve(refuse2(runtime, "project_required", "land requires a KXM project (run kxm init first)"));
|
|
41208
41334
|
}
|
|
41209
|
-
const script =
|
|
41335
|
+
const script = join39(findKxmRepoRoot(import.meta.url), "scripts", "pr-land.mjs");
|
|
41210
41336
|
const args = [script];
|
|
41211
41337
|
if (options.pr) args.push("--pr", options.pr);
|
|
41212
41338
|
if (options.stage) args.push("--stage", options.stage);
|
|
@@ -41226,19 +41352,19 @@ function cmdLand(runtime, options = {}) {
|
|
|
41226
41352
|
// plugins/kxm/src/cli/supervise.ts
|
|
41227
41353
|
init_project_config();
|
|
41228
41354
|
import { appendFileSync as appendFileSync2, chmodSync as chmodSync8, mkdirSync as mkdirSync26 } from "node:fs";
|
|
41229
|
-
import { join as
|
|
41355
|
+
import { join as join41 } from "node:path";
|
|
41230
41356
|
|
|
41231
41357
|
// plugins/kxm/src/supervise-state.ts
|
|
41232
41358
|
init_project_config();
|
|
41233
|
-
import { chmodSync as chmodSync7, existsSync as
|
|
41234
|
-
import { join as
|
|
41359
|
+
import { chmodSync as chmodSync7, existsSync as existsSync35, mkdirSync as mkdirSync25, readFileSync as readFileSync32, renameSync as renameSync8, writeFileSync as writeFileSync23 } from "node:fs";
|
|
41360
|
+
import { join as join40 } from "node:path";
|
|
41235
41361
|
var SUPERVISOR_SCHEMA = "kxm.supervisor.v1";
|
|
41236
41362
|
var TIMESTAMP_RE2 = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{1,9})?Z$/;
|
|
41237
41363
|
var MERGE_STATE_RE = /^[A-Z][A-Z0-9_]{0,31}$/;
|
|
41238
41364
|
var CI_TOKEN_RE = /^[a-z][a-z0-9_-]{0,31}$/;
|
|
41239
41365
|
var MAX_BACKOFF_MS = 24 * 60 * 60 * 1e3;
|
|
41240
41366
|
function supervisorStatePath(stateDir) {
|
|
41241
|
-
return
|
|
41367
|
+
return join40(stateDir, "supervisor.json");
|
|
41242
41368
|
}
|
|
41243
41369
|
function emptySupervisorState(now) {
|
|
41244
41370
|
return { schema: SUPERVISOR_SCHEMA, updatedAt: now, lanes: {} };
|
|
@@ -41287,10 +41413,10 @@ function isState(value) {
|
|
|
41287
41413
|
}
|
|
41288
41414
|
function loadSupervisorState(stateDir, now) {
|
|
41289
41415
|
const path4 = supervisorStatePath(stateDir);
|
|
41290
|
-
if (!
|
|
41416
|
+
if (!existsSync35(path4)) return emptySupervisorState(now);
|
|
41291
41417
|
let parsed;
|
|
41292
41418
|
try {
|
|
41293
|
-
parsed = JSON.parse(
|
|
41419
|
+
parsed = JSON.parse(readFileSync32(path4, "utf8"));
|
|
41294
41420
|
} catch {
|
|
41295
41421
|
return "supervisor_unreadable";
|
|
41296
41422
|
}
|
|
@@ -41453,7 +41579,7 @@ function cmdSuperviseStatus(runtime) {
|
|
|
41453
41579
|
}
|
|
41454
41580
|
function appendTick(runtime, line) {
|
|
41455
41581
|
mkdirSync26(runtime.dirs.logs, { recursive: true });
|
|
41456
|
-
const path4 =
|
|
41582
|
+
const path4 = join41(runtime.dirs.logs, "supervisor.log");
|
|
41457
41583
|
appendFileSync2(path4, `${line}
|
|
41458
41584
|
`, { encoding: "utf8", mode: 384 });
|
|
41459
41585
|
try {
|
|
@@ -41472,8 +41598,8 @@ function cmdSuperviseTick(runtime) {
|
|
|
41472
41598
|
const line = formatTickLine(now, tick.resumed, tick.waiting);
|
|
41473
41599
|
if (runtime.dryRun) {
|
|
41474
41600
|
printPlan(runtime, { command: "supervise", resumed: tick.resumed, waiting: tick.waiting, log: line, state: tick.state }, [
|
|
41475
|
-
{ action: "write", target:
|
|
41476
|
-
{ action: "write", target:
|
|
41601
|
+
{ action: "write", target: join41(runtime.dirs.state, "supervisor.json") },
|
|
41602
|
+
{ action: "write", target: join41(runtime.dirs.logs, "supervisor.log") }
|
|
41477
41603
|
], "resume lanes whose backoff has elapsed");
|
|
41478
41604
|
return 0;
|
|
41479
41605
|
}
|
|
@@ -41520,7 +41646,7 @@ function cmdSuperviseRecord(runtime, options) {
|
|
|
41520
41646
|
}
|
|
41521
41647
|
if (runtime.dryRun) {
|
|
41522
41648
|
printPlan(runtime, { command: "supervise", state: next }, [
|
|
41523
|
-
{ action: "write", target:
|
|
41649
|
+
{ action: "write", target: join41(runtime.dirs.state, "supervisor.json") }
|
|
41524
41650
|
], `record lane ${options.lane}`);
|
|
41525
41651
|
return 0;
|
|
41526
41652
|
}
|
|
@@ -41532,8 +41658,8 @@ function cmdSuperviseRecord(runtime, options) {
|
|
|
41532
41658
|
init_project_config();
|
|
41533
41659
|
init_types();
|
|
41534
41660
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
41535
|
-
import { existsSync as
|
|
41536
|
-
import { join as
|
|
41661
|
+
import { existsSync as existsSync36 } from "node:fs";
|
|
41662
|
+
import { join as join42 } from "node:path";
|
|
41537
41663
|
var kxmAssignCliSeams = {};
|
|
41538
41664
|
var RUNNER_REL = "scripts/assignment-run.mjs";
|
|
41539
41665
|
function refuse4(runtime, command, error, text) {
|
|
@@ -41605,7 +41731,7 @@ async function cmdAssign(runtime, subcommand, args) {
|
|
|
41605
41731
|
const command = `assign ${subcommand}`;
|
|
41606
41732
|
const root = discoverKxmProjectRoot(runtime.cwd);
|
|
41607
41733
|
if (!root) return refuse4(runtime, command, "project_required", `${command} requires a KXM project (run kxm init first)`);
|
|
41608
|
-
if (!
|
|
41734
|
+
if (!existsSync36(join42(root, "scripts", "assignment-run.mjs"))) {
|
|
41609
41735
|
return refuse4(runtime, command, "assign_runner_missing", `${command} requires scripts/assignment-run.mjs in the project root`);
|
|
41610
41736
|
}
|
|
41611
41737
|
const argv = ["node", RUNNER_REL, ...runnerArgv(subcommand, args)];
|
|
@@ -41641,8 +41767,8 @@ init_config();
|
|
|
41641
41767
|
init_hub_identity();
|
|
41642
41768
|
init_envelope();
|
|
41643
41769
|
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
41644
|
-
import { existsSync as
|
|
41645
|
-
import { join as
|
|
41770
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, readdirSync as readdirSync13, rmSync as rmSync11, writeFileSync as writeFileSync26 } from "node:fs";
|
|
41771
|
+
import { join as join52, resolve as resolve28 } from "node:path";
|
|
41646
41772
|
|
|
41647
41773
|
// node_modules/marked/lib/marked.esm.js
|
|
41648
41774
|
function A() {
|
|
@@ -44327,6 +44453,66 @@ var LEGACY_CTRL_SEQUENCES = {
|
|
|
44327
44453
|
home: ["\x1B[7^"],
|
|
44328
44454
|
end: ["\x1B[8^"]
|
|
44329
44455
|
};
|
|
44456
|
+
var LEGACY_SEQUENCE_KEY_IDS = {
|
|
44457
|
+
"\x1BOA": "up",
|
|
44458
|
+
"\x1BOB": "down",
|
|
44459
|
+
"\x1BOC": "right",
|
|
44460
|
+
"\x1BOD": "left",
|
|
44461
|
+
"\x1BOH": "home",
|
|
44462
|
+
"\x1BOF": "end",
|
|
44463
|
+
"\x1B[E": "clear",
|
|
44464
|
+
"\x1BOE": "clear",
|
|
44465
|
+
"\x1BOe": "ctrl+clear",
|
|
44466
|
+
"\x1B[e": "shift+clear",
|
|
44467
|
+
"\x1B[2~": "insert",
|
|
44468
|
+
"\x1B[2$": "shift+insert",
|
|
44469
|
+
"\x1B[2^": "ctrl+insert",
|
|
44470
|
+
"\x1B[3$": "shift+delete",
|
|
44471
|
+
"\x1B[3^": "ctrl+delete",
|
|
44472
|
+
"\x1B[[5~": "pageUp",
|
|
44473
|
+
"\x1B[[6~": "pageDown",
|
|
44474
|
+
"\x1B[a": "shift+up",
|
|
44475
|
+
"\x1B[b": "shift+down",
|
|
44476
|
+
"\x1B[c": "shift+right",
|
|
44477
|
+
"\x1B[d": "shift+left",
|
|
44478
|
+
"\x1BOa": "ctrl+up",
|
|
44479
|
+
"\x1BOb": "ctrl+down",
|
|
44480
|
+
"\x1BOc": "ctrl+right",
|
|
44481
|
+
"\x1BOd": "ctrl+left",
|
|
44482
|
+
"\x1B[5$": "shift+pageUp",
|
|
44483
|
+
"\x1B[6$": "shift+pageDown",
|
|
44484
|
+
"\x1B[7$": "shift+home",
|
|
44485
|
+
"\x1B[8$": "shift+end",
|
|
44486
|
+
"\x1B[5^": "ctrl+pageUp",
|
|
44487
|
+
"\x1B[6^": "ctrl+pageDown",
|
|
44488
|
+
"\x1B[7^": "ctrl+home",
|
|
44489
|
+
"\x1B[8^": "ctrl+end",
|
|
44490
|
+
"\x1BOP": "f1",
|
|
44491
|
+
"\x1BOQ": "f2",
|
|
44492
|
+
"\x1BOR": "f3",
|
|
44493
|
+
"\x1BOS": "f4",
|
|
44494
|
+
"\x1B[11~": "f1",
|
|
44495
|
+
"\x1B[12~": "f2",
|
|
44496
|
+
"\x1B[13~": "f3",
|
|
44497
|
+
"\x1B[14~": "f4",
|
|
44498
|
+
"\x1B[[A": "f1",
|
|
44499
|
+
"\x1B[[B": "f2",
|
|
44500
|
+
"\x1B[[C": "f3",
|
|
44501
|
+
"\x1B[[D": "f4",
|
|
44502
|
+
"\x1B[[E": "f5",
|
|
44503
|
+
"\x1B[15~": "f5",
|
|
44504
|
+
"\x1B[17~": "f6",
|
|
44505
|
+
"\x1B[18~": "f7",
|
|
44506
|
+
"\x1B[19~": "f8",
|
|
44507
|
+
"\x1B[20~": "f9",
|
|
44508
|
+
"\x1B[21~": "f10",
|
|
44509
|
+
"\x1B[23~": "f11",
|
|
44510
|
+
"\x1B[24~": "f12",
|
|
44511
|
+
"\x1Bb": "alt+left",
|
|
44512
|
+
"\x1Bf": "alt+right",
|
|
44513
|
+
"\x1Bp": "alt+up",
|
|
44514
|
+
"\x1Bn": "alt+down"
|
|
44515
|
+
};
|
|
44330
44516
|
var matchesLegacySequence = (data, sequences) => sequences.includes(data);
|
|
44331
44517
|
var matchesLegacyModifierSequence = (data, key, modifier) => {
|
|
44332
44518
|
if (modifier === MODIFIERS.shift) {
|
|
@@ -44472,6 +44658,22 @@ function matchesPrintableModifyOtherKeys(data, expectedKeycode, expectedModifier
|
|
|
44472
44658
|
return false;
|
|
44473
44659
|
return normalizeShiftedLetterIdentityCodepoint(parsed.codepoint, parsed.modifier) === normalizeShiftedLetterIdentityCodepoint(expectedKeycode, expectedModifier);
|
|
44474
44660
|
}
|
|
44661
|
+
function formatKeyNameWithModifiers(keyName, modifier) {
|
|
44662
|
+
const mods = [];
|
|
44663
|
+
const effectiveMod = modifier & ~LOCK_MASK;
|
|
44664
|
+
const supportedModifierMask = MODIFIERS.shift | MODIFIERS.ctrl | MODIFIERS.alt | MODIFIERS.super;
|
|
44665
|
+
if ((effectiveMod & ~supportedModifierMask) !== 0)
|
|
44666
|
+
return void 0;
|
|
44667
|
+
if (effectiveMod & MODIFIERS.shift)
|
|
44668
|
+
mods.push("shift");
|
|
44669
|
+
if (effectiveMod & MODIFIERS.ctrl)
|
|
44670
|
+
mods.push("ctrl");
|
|
44671
|
+
if (effectiveMod & MODIFIERS.alt)
|
|
44672
|
+
mods.push("alt");
|
|
44673
|
+
if (effectiveMod & MODIFIERS.super)
|
|
44674
|
+
mods.push("super");
|
|
44675
|
+
return mods.length > 0 ? `${mods.join("+")}+${keyName}` : keyName;
|
|
44676
|
+
}
|
|
44475
44677
|
function parseKeyId(keyId) {
|
|
44476
44678
|
const parts = keyId.toLowerCase().split("+");
|
|
44477
44679
|
const key = parts[parts.length - 1];
|
|
@@ -44728,6 +44930,149 @@ function matchesKey(data, keyId) {
|
|
|
44728
44930
|
}
|
|
44729
44931
|
return false;
|
|
44730
44932
|
}
|
|
44933
|
+
function formatParsedKey(codepoint, modifier, baseLayoutKey) {
|
|
44934
|
+
const normalizedCodepoint = normalizeKittyFunctionalCodepoint(codepoint);
|
|
44935
|
+
const identityCodepoint = normalizeShiftedLetterIdentityCodepoint(normalizedCodepoint, modifier);
|
|
44936
|
+
const isLatinLetter = identityCodepoint >= 97 && identityCodepoint <= 122;
|
|
44937
|
+
const isDigit = identityCodepoint >= 48 && identityCodepoint <= 57;
|
|
44938
|
+
const isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(identityCodepoint));
|
|
44939
|
+
const effectiveCodepoint = isLatinLetter || isDigit || isKnownSymbol ? identityCodepoint : baseLayoutKey ?? identityCodepoint;
|
|
44940
|
+
let keyName;
|
|
44941
|
+
if (effectiveCodepoint === CODEPOINTS.escape)
|
|
44942
|
+
keyName = "escape";
|
|
44943
|
+
else if (effectiveCodepoint === CODEPOINTS.tab)
|
|
44944
|
+
keyName = "tab";
|
|
44945
|
+
else if (effectiveCodepoint === CODEPOINTS.enter || effectiveCodepoint === CODEPOINTS.kpEnter)
|
|
44946
|
+
keyName = "enter";
|
|
44947
|
+
else if (effectiveCodepoint === CODEPOINTS.space)
|
|
44948
|
+
keyName = "space";
|
|
44949
|
+
else if (effectiveCodepoint === CODEPOINTS.backspace)
|
|
44950
|
+
keyName = "backspace";
|
|
44951
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.delete)
|
|
44952
|
+
keyName = "delete";
|
|
44953
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.insert)
|
|
44954
|
+
keyName = "insert";
|
|
44955
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.home)
|
|
44956
|
+
keyName = "home";
|
|
44957
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.end)
|
|
44958
|
+
keyName = "end";
|
|
44959
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageUp)
|
|
44960
|
+
keyName = "pageUp";
|
|
44961
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageDown)
|
|
44962
|
+
keyName = "pageDown";
|
|
44963
|
+
else if (effectiveCodepoint === ARROW_CODEPOINTS.up)
|
|
44964
|
+
keyName = "up";
|
|
44965
|
+
else if (effectiveCodepoint === ARROW_CODEPOINTS.down)
|
|
44966
|
+
keyName = "down";
|
|
44967
|
+
else if (effectiveCodepoint === ARROW_CODEPOINTS.left)
|
|
44968
|
+
keyName = "left";
|
|
44969
|
+
else if (effectiveCodepoint === ARROW_CODEPOINTS.right)
|
|
44970
|
+
keyName = "right";
|
|
44971
|
+
else if (effectiveCodepoint >= 48 && effectiveCodepoint <= 57)
|
|
44972
|
+
keyName = String.fromCharCode(effectiveCodepoint);
|
|
44973
|
+
else if (effectiveCodepoint >= 97 && effectiveCodepoint <= 122)
|
|
44974
|
+
keyName = String.fromCharCode(effectiveCodepoint);
|
|
44975
|
+
else if (SYMBOL_KEYS.has(String.fromCharCode(effectiveCodepoint)))
|
|
44976
|
+
keyName = String.fromCharCode(effectiveCodepoint);
|
|
44977
|
+
if (!keyName)
|
|
44978
|
+
return void 0;
|
|
44979
|
+
return formatKeyNameWithModifiers(keyName, modifier);
|
|
44980
|
+
}
|
|
44981
|
+
function parseKey(data) {
|
|
44982
|
+
const kitty = parseKittySequence(data);
|
|
44983
|
+
if (kitty) {
|
|
44984
|
+
return formatParsedKey(kitty.codepoint, kitty.modifier, kitty.baseLayoutKey);
|
|
44985
|
+
}
|
|
44986
|
+
const modifyOtherKeys = parseModifyOtherKeysSequence(data);
|
|
44987
|
+
if (modifyOtherKeys) {
|
|
44988
|
+
return formatParsedKey(modifyOtherKeys.codepoint, modifyOtherKeys.modifier);
|
|
44989
|
+
}
|
|
44990
|
+
if (_kittyProtocolActive) {
|
|
44991
|
+
if (data === "\x1B\r" || data === "\n")
|
|
44992
|
+
return "shift+enter";
|
|
44993
|
+
}
|
|
44994
|
+
const legacySequenceKeyId = LEGACY_SEQUENCE_KEY_IDS[data];
|
|
44995
|
+
if (legacySequenceKeyId)
|
|
44996
|
+
return legacySequenceKeyId;
|
|
44997
|
+
if (data === "\x1B")
|
|
44998
|
+
return "escape";
|
|
44999
|
+
if (data === "")
|
|
45000
|
+
return "ctrl+\\";
|
|
45001
|
+
if (data === "")
|
|
45002
|
+
return "ctrl+]";
|
|
45003
|
+
if (data === "")
|
|
45004
|
+
return "ctrl+-";
|
|
45005
|
+
if (data === "\x1B\x1B")
|
|
45006
|
+
return "ctrl+alt+[";
|
|
45007
|
+
if (data === "\x1B")
|
|
45008
|
+
return "ctrl+alt+\\";
|
|
45009
|
+
if (data === "\x1B")
|
|
45010
|
+
return "ctrl+alt+]";
|
|
45011
|
+
if (data === "\x1B")
|
|
45012
|
+
return "ctrl+alt+-";
|
|
45013
|
+
if (data === " ")
|
|
45014
|
+
return "tab";
|
|
45015
|
+
if (data === "\r" || !_kittyProtocolActive && data === "\n" || data === "\x1BOM")
|
|
45016
|
+
return "enter";
|
|
45017
|
+
if (data === "\0")
|
|
45018
|
+
return "ctrl+space";
|
|
45019
|
+
if (data === " ")
|
|
45020
|
+
return "space";
|
|
45021
|
+
if (data === "\x7F")
|
|
45022
|
+
return "backspace";
|
|
45023
|
+
if (data === "\b")
|
|
45024
|
+
return isWindowsTerminalSession() ? "ctrl+backspace" : "backspace";
|
|
45025
|
+
if (data === "\x1B[Z")
|
|
45026
|
+
return "shift+tab";
|
|
45027
|
+
if (!_kittyProtocolActive && data === "\x1B\r")
|
|
45028
|
+
return "alt+enter";
|
|
45029
|
+
if (!_kittyProtocolActive && data === "\x1B ")
|
|
45030
|
+
return "alt+space";
|
|
45031
|
+
if (data === "\x1B\x7F" || data === "\x1B\b")
|
|
45032
|
+
return "alt+backspace";
|
|
45033
|
+
if (!_kittyProtocolActive && data === "\x1BB")
|
|
45034
|
+
return "alt+left";
|
|
45035
|
+
if (!_kittyProtocolActive && data === "\x1BF")
|
|
45036
|
+
return "alt+right";
|
|
45037
|
+
if (!_kittyProtocolActive && data.length === 2 && data[0] === "\x1B") {
|
|
45038
|
+
const code = data.charCodeAt(1);
|
|
45039
|
+
if (code >= 1 && code <= 26) {
|
|
45040
|
+
return `ctrl+alt+${String.fromCharCode(code + 96)}`;
|
|
45041
|
+
}
|
|
45042
|
+
const key = String.fromCharCode(code);
|
|
45043
|
+
if (code >= 97 && code <= 122 || code >= 48 && code <= 57 || SYMBOL_KEYS.has(key)) {
|
|
45044
|
+
return `alt+${key}`;
|
|
45045
|
+
}
|
|
45046
|
+
}
|
|
45047
|
+
if (data === "\x1B[A")
|
|
45048
|
+
return "up";
|
|
45049
|
+
if (data === "\x1B[B")
|
|
45050
|
+
return "down";
|
|
45051
|
+
if (data === "\x1B[C")
|
|
45052
|
+
return "right";
|
|
45053
|
+
if (data === "\x1B[D")
|
|
45054
|
+
return "left";
|
|
45055
|
+
if (data === "\x1B[H" || data === "\x1BOH")
|
|
45056
|
+
return "home";
|
|
45057
|
+
if (data === "\x1B[F" || data === "\x1BOF")
|
|
45058
|
+
return "end";
|
|
45059
|
+
if (data === "\x1B[3~")
|
|
45060
|
+
return "delete";
|
|
45061
|
+
if (data === "\x1B[5~")
|
|
45062
|
+
return "pageUp";
|
|
45063
|
+
if (data === "\x1B[6~")
|
|
45064
|
+
return "pageDown";
|
|
45065
|
+
if (data.length === 1) {
|
|
45066
|
+
const code = data.charCodeAt(0);
|
|
45067
|
+
if (code >= 1 && code <= 26) {
|
|
45068
|
+
return `ctrl+${String.fromCharCode(code + 96)}`;
|
|
45069
|
+
}
|
|
45070
|
+
if (code >= 32 && code <= 126) {
|
|
45071
|
+
return data;
|
|
45072
|
+
}
|
|
45073
|
+
}
|
|
45074
|
+
return void 0;
|
|
45075
|
+
}
|
|
44731
45076
|
var KITTY_CSI_U_REGEX = /^\x1b\[(\d+)(?::(\d*))?(?::(\d+))?(?:;(\d+))?(?::(\d+))?u$/;
|
|
44732
45077
|
var KITTY_PRINTABLE_ALLOWED_MODIFIERS = MODIFIERS.shift | LOCK_MASK;
|
|
44733
45078
|
function decodeKittyPrintable(data) {
|
|
@@ -47415,7 +47760,7 @@ import * as path2 from "node:path";
|
|
|
47415
47760
|
|
|
47416
47761
|
// node_modules/@earendil-works/pi-tui/dist/native-module-path.js
|
|
47417
47762
|
import { createRequire as createRequire2 } from "node:module";
|
|
47418
|
-
import { dirname as dirname19, join as
|
|
47763
|
+
import { dirname as dirname19, join as join43 } from "node:path";
|
|
47419
47764
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
47420
47765
|
var moduleRequire = createRequire2(import.meta.url);
|
|
47421
47766
|
var TUI_PACKAGE_NAME = "@earendil-works/pi-tui";
|
|
@@ -47424,10 +47769,10 @@ function getNativeModuleCandidates(nativePath, options = {}) {
|
|
|
47424
47769
|
const candidates = [];
|
|
47425
47770
|
try {
|
|
47426
47771
|
const packageEntry = (options.resolvePackage ?? moduleRequire.resolve)(TUI_PACKAGE_NAME);
|
|
47427
|
-
candidates.push(
|
|
47772
|
+
candidates.push(join43(dirname19(packageEntry), "..", nativePath));
|
|
47428
47773
|
} catch {
|
|
47429
47774
|
}
|
|
47430
|
-
candidates.push(
|
|
47775
|
+
candidates.push(join43(moduleDir, "..", nativePath), join43(moduleDir, nativePath), join43(dirname19(options.execPath ?? process.execPath), nativePath));
|
|
47431
47776
|
return Array.from(new Set(candidates));
|
|
47432
47777
|
}
|
|
47433
47778
|
|
|
@@ -50255,10 +50600,17 @@ var MAX_RENDER_WRITE_CHARS = 1024 * 1024;
|
|
|
50255
50600
|
|
|
50256
50601
|
// plugins/kxm/src/tui.ts
|
|
50257
50602
|
import { mkdirSync as mkdirSync27 } from "node:fs";
|
|
50258
|
-
import { join as
|
|
50603
|
+
import { join as join47, resolve as resolve27, dirname as dirname20 } from "node:path";
|
|
50259
50604
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
50260
50605
|
|
|
50261
50606
|
// packages/core/tui/src/types/surface.ts
|
|
50607
|
+
var KXM_TUI_FIELD_KINDS = ["text", "enum", "choice", "info"];
|
|
50608
|
+
var KXM_TUI_STATUSES = ["ready", "available", "blocked"];
|
|
50609
|
+
var KXM_TUI_STEP_STATES = ["pending", "satisfied", "running", "done", "failed"];
|
|
50610
|
+
var KXM_TUI_NOTICE_LEVELS = ["info", "error"];
|
|
50611
|
+
var KXM_TUI_SET_ACTION = "set";
|
|
50612
|
+
var KXM_TUI_CLEAR_ACTION = "clear";
|
|
50613
|
+
var KXM_TUI_ABORT_ACTION = "abort";
|
|
50262
50614
|
var KXM_TUI_LIMITS = Object.freeze({
|
|
50263
50615
|
sourceMax: 128,
|
|
50264
50616
|
idMax: 128,
|
|
@@ -50280,8 +50632,231 @@ var KXM_TUI_LIMITS = Object.freeze({
|
|
|
50280
50632
|
sectionsMax: 32,
|
|
50281
50633
|
orderMax: 1e3
|
|
50282
50634
|
});
|
|
50635
|
+
var ID_PATTERN = /^[a-z0-9][a-z0-9._:-]*$/;
|
|
50636
|
+
var SOURCE_PATTERN = /^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/;
|
|
50637
|
+
var KEY_PATH_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9_.[\]-]*$/;
|
|
50638
|
+
var ACTION_KEY_PATTERN = /^[A-Za-z0-9]$/;
|
|
50639
|
+
function issue4(path4, code, message) {
|
|
50640
|
+
return { path: path4, code, message };
|
|
50641
|
+
}
|
|
50642
|
+
function isPlainObject(value) {
|
|
50643
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
50644
|
+
}
|
|
50645
|
+
function boundedText(issues, path4, value, max, required2) {
|
|
50646
|
+
if (value === void 0) {
|
|
50647
|
+
if (required2) issues.push(issue4(path4, "required", `${path4} is required`));
|
|
50648
|
+
return;
|
|
50649
|
+
}
|
|
50650
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
50651
|
+
issues.push(issue4(path4, "invalid_string", `${path4} must be a non-empty string`));
|
|
50652
|
+
return;
|
|
50653
|
+
}
|
|
50654
|
+
if (value.length > max) {
|
|
50655
|
+
issues.push(issue4(path4, "too_long", `${path4} exceeds ${max} characters`));
|
|
50656
|
+
return;
|
|
50657
|
+
}
|
|
50658
|
+
if (/[\u0000-\u0008\u000a-\u001f\u007f]/u.test(value)) {
|
|
50659
|
+
issues.push(issue4(path4, "control_character", `${path4} must not contain control characters`));
|
|
50660
|
+
}
|
|
50661
|
+
}
|
|
50662
|
+
function optionalEnum(issues, path4, value, allowed) {
|
|
50663
|
+
if (value === void 0) return;
|
|
50664
|
+
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
50665
|
+
issues.push(issue4(path4, "invalid_choice", `${path4} must be one of ${allowed.join(", ")}`));
|
|
50666
|
+
}
|
|
50667
|
+
}
|
|
50668
|
+
function ratioField(issues, path4, value) {
|
|
50669
|
+
if (value === void 0) return;
|
|
50670
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
|
|
50671
|
+
issues.push(issue4(path4, "invalid_ratio", `${path4} must be a finite number between 0 and 1`));
|
|
50672
|
+
}
|
|
50673
|
+
}
|
|
50674
|
+
function validateChoice(issues, path4, raw) {
|
|
50675
|
+
if (!isPlainObject(raw)) {
|
|
50676
|
+
issues.push(issue4(path4, "invalid_object", `${path4} must be an object`));
|
|
50677
|
+
return;
|
|
50678
|
+
}
|
|
50679
|
+
boundedText(issues, `${path4}.id`, raw.id, KXM_TUI_LIMITS.choiceIdMax, true);
|
|
50680
|
+
if (typeof raw.id === "string" && /[\s]/u.test(raw.id)) {
|
|
50681
|
+
issues.push(issue4(`${path4}.id`, "invalid_choice_id", `${path4}.id must not contain whitespace`));
|
|
50682
|
+
}
|
|
50683
|
+
boundedText(issues, `${path4}.label`, raw.label, KXM_TUI_LIMITS.choiceLabelMax, true);
|
|
50684
|
+
boundedText(issues, `${path4}.detail`, raw.detail, KXM_TUI_LIMITS.shortDetailMax, false);
|
|
50685
|
+
boundedText(issues, `${path4}.group`, raw.group, KXM_TUI_LIMITS.shortDetailMax, false);
|
|
50686
|
+
boundedText(issues, `${path4}.statusText`, raw.statusText, KXM_TUI_LIMITS.shortDetailMax, false);
|
|
50687
|
+
boundedText(issues, `${path4}.action`, raw.action, KXM_TUI_LIMITS.idMax, false);
|
|
50688
|
+
optionalEnum(issues, `${path4}.status`, raw.status, KXM_TUI_STATUSES);
|
|
50689
|
+
}
|
|
50690
|
+
function validateAction(issues, path4, raw) {
|
|
50691
|
+
if (!isPlainObject(raw)) {
|
|
50692
|
+
issues.push(issue4(path4, "invalid_object", `${path4} must be an object`));
|
|
50693
|
+
return;
|
|
50694
|
+
}
|
|
50695
|
+
if (typeof raw.key !== "string" || !ACTION_KEY_PATTERN.test(raw.key)) {
|
|
50696
|
+
issues.push(issue4(`${path4}.key`, "invalid_action_key", `${path4}.key must be a single alphanumeric key`));
|
|
50697
|
+
}
|
|
50698
|
+
boundedText(issues, `${path4}.label`, raw.label, KXM_TUI_LIMITS.shortDetailMax, true);
|
|
50699
|
+
boundedText(issues, `${path4}.action`, raw.action, KXM_TUI_LIMITS.idMax, true);
|
|
50700
|
+
}
|
|
50701
|
+
function validateStep(issues, path4, raw) {
|
|
50702
|
+
if (!isPlainObject(raw)) {
|
|
50703
|
+
issues.push(issue4(path4, "invalid_object", `${path4} must be an object`));
|
|
50704
|
+
return;
|
|
50705
|
+
}
|
|
50706
|
+
boundedText(issues, `${path4}.label`, raw.label, KXM_TUI_LIMITS.shortDetailMax, true);
|
|
50707
|
+
boundedText(issues, `${path4}.detail`, raw.detail, KXM_TUI_LIMITS.stepDetailMax, false);
|
|
50708
|
+
optionalEnum(issues, `${path4}.state`, raw.state, KXM_TUI_STEP_STATES);
|
|
50709
|
+
if (raw.state === void 0) issues.push(issue4(`${path4}.state`, "required", `${path4}.state is required`));
|
|
50710
|
+
}
|
|
50711
|
+
function validateField(issues, path4, raw) {
|
|
50712
|
+
if (!isPlainObject(raw)) {
|
|
50713
|
+
issues.push(issue4(path4, "invalid_object", `${path4} must be an object`));
|
|
50714
|
+
return;
|
|
50715
|
+
}
|
|
50716
|
+
boundedText(issues, `${path4}.id`, raw.id, KXM_TUI_LIMITS.idMax, true);
|
|
50717
|
+
if (typeof raw.id === "string" && !ID_PATTERN.test(raw.id)) {
|
|
50718
|
+
issues.push(issue4(`${path4}.id`, "invalid_id", `${path4}.id must match ${ID_PATTERN}`));
|
|
50719
|
+
}
|
|
50720
|
+
boundedText(issues, `${path4}.label`, raw.label, KXM_TUI_LIMITS.labelMax, true);
|
|
50721
|
+
if (raw.kind === void 0) {
|
|
50722
|
+
issues.push(issue4(`${path4}.kind`, "required", `${path4}.kind is required`));
|
|
50723
|
+
} else {
|
|
50724
|
+
optionalEnum(issues, `${path4}.kind`, raw.kind, KXM_TUI_FIELD_KINDS);
|
|
50725
|
+
}
|
|
50726
|
+
boundedText(issues, `${path4}.value`, raw.value, KXM_TUI_LIMITS.valueMax, false);
|
|
50727
|
+
boundedText(issues, `${path4}.placeholder`, raw.placeholder, KXM_TUI_LIMITS.shortDetailMax, false);
|
|
50728
|
+
boundedText(issues, `${path4}.detail`, raw.detail, KXM_TUI_LIMITS.detailMax, false);
|
|
50729
|
+
boundedText(issues, `${path4}.statusText`, raw.statusText, KXM_TUI_LIMITS.statusTextMax, false);
|
|
50730
|
+
if (raw.updatedAt !== void 0) {
|
|
50731
|
+
boundedText(issues, `${path4}.updatedAt`, raw.updatedAt, 64, false);
|
|
50732
|
+
if (typeof raw.updatedAt === "string" && !Number.isFinite(Date.parse(raw.updatedAt))) {
|
|
50733
|
+
issues.push(issue4(`${path4}.updatedAt`, "invalid_timestamp", `${path4}.updatedAt must be an ISO timestamp`));
|
|
50734
|
+
}
|
|
50735
|
+
}
|
|
50736
|
+
optionalEnum(issues, `${path4}.status`, raw.status, KXM_TUI_STATUSES);
|
|
50737
|
+
if (raw.keyPath !== void 0) {
|
|
50738
|
+
boundedText(issues, `${path4}.keyPath`, raw.keyPath, KXM_TUI_LIMITS.keyPathMax, false);
|
|
50739
|
+
if (typeof raw.keyPath === "string" && !KEY_PATH_PATTERN.test(raw.keyPath)) {
|
|
50740
|
+
issues.push(issue4(`${path4}.keyPath`, "invalid_key_path", `${path4}.keyPath must be a dotted config key`));
|
|
50741
|
+
}
|
|
50742
|
+
}
|
|
50743
|
+
for (const listKey of ["busy", "awaitingInput"]) {
|
|
50744
|
+
if (raw[listKey] !== void 0 && typeof raw[listKey] !== "boolean") {
|
|
50745
|
+
issues.push(issue4(`${path4}.${listKey}`, "invalid_boolean", `${path4}.${listKey} must be a boolean`));
|
|
50746
|
+
}
|
|
50747
|
+
}
|
|
50748
|
+
if (raw.progress !== void 0) {
|
|
50749
|
+
if (!isPlainObject(raw.progress)) {
|
|
50750
|
+
issues.push(issue4(`${path4}.progress`, "invalid_object", `${path4}.progress must be an object`));
|
|
50751
|
+
} else {
|
|
50752
|
+
boundedText(issues, `${path4}.progress.label`, raw.progress.label, KXM_TUI_LIMITS.shortDetailMax, true);
|
|
50753
|
+
ratioField(issues, `${path4}.progress.ratio`, raw.progress.ratio);
|
|
50754
|
+
}
|
|
50755
|
+
}
|
|
50756
|
+
for (const [listKey, max, check] of [
|
|
50757
|
+
["choices", KXM_TUI_LIMITS.choicesMax, validateChoice],
|
|
50758
|
+
["actions", KXM_TUI_LIMITS.actionsMax, validateAction],
|
|
50759
|
+
["steps", KXM_TUI_LIMITS.stepsMax, validateStep]
|
|
50760
|
+
]) {
|
|
50761
|
+
const list = raw[listKey];
|
|
50762
|
+
if (list === void 0) continue;
|
|
50763
|
+
if (!Array.isArray(list) || list.length > max) {
|
|
50764
|
+
issues.push(issue4(`${path4}.${listKey}`, "invalid_list", `${path4}.${listKey} must be an array of at most ${max}`));
|
|
50765
|
+
continue;
|
|
50766
|
+
}
|
|
50767
|
+
list.forEach((entry, index) => check(issues, `${path4}.${listKey}[${index}]`, entry));
|
|
50768
|
+
}
|
|
50769
|
+
const output = raw.output;
|
|
50770
|
+
if (output !== void 0) {
|
|
50771
|
+
if (!Array.isArray(output) || output.length > KXM_TUI_LIMITS.outputMaxLines) {
|
|
50772
|
+
issues.push(issue4(`${path4}.output`, "invalid_list", `${path4}.output must be an array of at most ${KXM_TUI_LIMITS.outputMaxLines} lines`));
|
|
50773
|
+
} else {
|
|
50774
|
+
output.forEach((line, index) => {
|
|
50775
|
+
if (typeof line !== "string") issues.push(issue4(`${path4}.output[${index}]`, "invalid_string", "output lines must be strings"));
|
|
50776
|
+
else if (line.length > KXM_TUI_LIMITS.outputLineMax) issues.push(issue4(`${path4}.output[${index}]`, "too_long", "output line is too long"));
|
|
50777
|
+
});
|
|
50778
|
+
}
|
|
50779
|
+
}
|
|
50780
|
+
}
|
|
50781
|
+
function validateSection(issues, path4, raw) {
|
|
50782
|
+
if (!isPlainObject(raw)) {
|
|
50783
|
+
issues.push(issue4(path4, "invalid_object", `${path4} must be an object`));
|
|
50784
|
+
return;
|
|
50785
|
+
}
|
|
50786
|
+
boundedText(issues, `${path4}.id`, raw.id, KXM_TUI_LIMITS.idMax, true);
|
|
50787
|
+
if (typeof raw.id === "string" && !ID_PATTERN.test(raw.id)) {
|
|
50788
|
+
issues.push(issue4(`${path4}.id`, "invalid_id", `${path4}.id must match ${ID_PATTERN}`));
|
|
50789
|
+
}
|
|
50790
|
+
boundedText(issues, `${path4}.title`, raw.title, KXM_TUI_LIMITS.labelMax, true);
|
|
50791
|
+
boundedText(issues, `${path4}.detail`, raw.detail, KXM_TUI_LIMITS.shortDetailMax, false);
|
|
50792
|
+
boundedText(issues, `${path4}.notice`, raw.notice, KXM_TUI_LIMITS.detailMax, false);
|
|
50793
|
+
optionalEnum(issues, `${path4}.noticeLevel`, raw.noticeLevel, KXM_TUI_NOTICE_LEVELS);
|
|
50794
|
+
if (typeof raw.order !== "number" || !Number.isInteger(raw.order) || raw.order < 0 || raw.order > KXM_TUI_LIMITS.orderMax) {
|
|
50795
|
+
issues.push(issue4(`${path4}.order`, "invalid_order", `${path4}.order must be an integer between 0 and ${KXM_TUI_LIMITS.orderMax}`));
|
|
50796
|
+
}
|
|
50797
|
+
if (!Array.isArray(raw.fields) || raw.fields.length > KXM_TUI_LIMITS.fieldsMax) {
|
|
50798
|
+
issues.push(issue4(`${path4}.fields`, "invalid_list", `${path4}.fields must be an array of at most ${KXM_TUI_LIMITS.fieldsMax}`));
|
|
50799
|
+
return;
|
|
50800
|
+
}
|
|
50801
|
+
const seen = /* @__PURE__ */ new Set();
|
|
50802
|
+
raw.fields.forEach((field, index) => {
|
|
50803
|
+
const fieldPath = `${path4}.fields[${index}]`;
|
|
50804
|
+
validateField(issues, fieldPath, field);
|
|
50805
|
+
if (isPlainObject(field) && typeof field.id === "string") {
|
|
50806
|
+
if (seen.has(field.id)) issues.push(issue4(`${fieldPath}.id`, "duplicate_id", `${field.id} is published twice in ${path4}`));
|
|
50807
|
+
seen.add(field.id);
|
|
50808
|
+
}
|
|
50809
|
+
});
|
|
50810
|
+
}
|
|
50811
|
+
function validateKxmTuiSurface(sections) {
|
|
50812
|
+
if (!Array.isArray(sections)) return [issue4("sections", "invalid_list", "sections must be an array")];
|
|
50813
|
+
if (sections.length > KXM_TUI_LIMITS.sectionsMax) {
|
|
50814
|
+
return [issue4("sections", "too_many", `sections must number at most ${KXM_TUI_LIMITS.sectionsMax}`)];
|
|
50815
|
+
}
|
|
50816
|
+
const issues = [];
|
|
50817
|
+
const seenSources = /* @__PURE__ */ new Map();
|
|
50818
|
+
sections.forEach((section, index) => {
|
|
50819
|
+
const path4 = `sections[${index}]`;
|
|
50820
|
+
validateSection(issues, path4, section);
|
|
50821
|
+
if (!isPlainObject(section)) return;
|
|
50822
|
+
boundedText(issues, `${path4}.source`, section.source, KXM_TUI_LIMITS.sourceMax, true);
|
|
50823
|
+
if (typeof section.source === "string" && !SOURCE_PATTERN.test(section.source)) {
|
|
50824
|
+
issues.push(issue4(`${path4}.source`, "invalid_source", `${path4}.source must be an addressable owner id`));
|
|
50825
|
+
}
|
|
50826
|
+
if (typeof section.source === "string" && typeof section.id === "string") {
|
|
50827
|
+
const ids = seenSources.get(section.source) ?? /* @__PURE__ */ new Set();
|
|
50828
|
+
seenSources.set(section.source, ids);
|
|
50829
|
+
if (ids.has(section.id)) issues.push(issue4(`${path4}.id`, "duplicate_section", `${section.source}/${section.id} is published twice`));
|
|
50830
|
+
ids.add(section.id);
|
|
50831
|
+
}
|
|
50832
|
+
});
|
|
50833
|
+
return issues;
|
|
50834
|
+
}
|
|
50835
|
+
function isSelectableField(field) {
|
|
50836
|
+
return field !== void 0 && field.kind !== "info";
|
|
50837
|
+
}
|
|
50838
|
+
function groupedKxmTuiChoices(field) {
|
|
50839
|
+
const choices = field.choices ?? [];
|
|
50840
|
+
if (choices.length === 0) return [];
|
|
50841
|
+
if (choices.every((choice) => choice.group === void 0)) return [{ group: "", choices }];
|
|
50842
|
+
const groups = [];
|
|
50843
|
+
const index = /* @__PURE__ */ new Map();
|
|
50844
|
+
for (const choice of choices) {
|
|
50845
|
+
const group = choice.group ?? "";
|
|
50846
|
+
let bucket = index.get(group);
|
|
50847
|
+
if (!bucket) {
|
|
50848
|
+
bucket = [];
|
|
50849
|
+
index.set(group, bucket);
|
|
50850
|
+
groups.push({ group, choices: bucket });
|
|
50851
|
+
}
|
|
50852
|
+
bucket.push(choice);
|
|
50853
|
+
}
|
|
50854
|
+
return groups;
|
|
50855
|
+
}
|
|
50283
50856
|
|
|
50284
50857
|
// packages/core/tui/src/tui/keys.ts
|
|
50858
|
+
var KXM_TUI_INPUT_MAX_LENGTH = 4096;
|
|
50859
|
+
var CONTROL_BYTES = /[\u0000-\u0008\u000a-\u001f\u007f\u0080-\u009f]/u;
|
|
50285
50860
|
var NAMED_KEYS = Object.freeze({
|
|
50286
50861
|
up: "up",
|
|
50287
50862
|
down: "down",
|
|
@@ -50299,8 +50874,109 @@ var NAMED_KEYS = Object.freeze({
|
|
|
50299
50874
|
backspace: "backspace",
|
|
50300
50875
|
delete: "delete"
|
|
50301
50876
|
});
|
|
50877
|
+
var CONTROL_BYTES_GLOBAL = /[\u0000-\u0008\u000a-\u001f\u007f\u0080-\u009f]/gu;
|
|
50878
|
+
function normalizeInputText(value) {
|
|
50879
|
+
return value.replace(CONTROL_BYTES_GLOBAL, " ").replace(/ {2,}/gu, " ").trim().slice(0, KXM_TUI_INPUT_MAX_LENGTH);
|
|
50880
|
+
}
|
|
50881
|
+
function decodeKxmTuiInput(data) {
|
|
50882
|
+
if (typeof data !== "string" || data.length === 0) return { kind: "ignored" };
|
|
50883
|
+
const named = parseKey(data);
|
|
50884
|
+
if (named === "ctrl+c") return { kind: "ctrlC" };
|
|
50885
|
+
if (named) {
|
|
50886
|
+
const kind = NAMED_KEYS[named.toLowerCase()];
|
|
50887
|
+
if (kind) return { kind };
|
|
50888
|
+
if (named === "space") return { kind: "text", text: " " };
|
|
50889
|
+
if (Array.from(named).length === 1 && !CONTROL_BYTES.test(named)) {
|
|
50890
|
+
return { kind: "text", text: named };
|
|
50891
|
+
}
|
|
50892
|
+
return { kind: "ignored" };
|
|
50893
|
+
}
|
|
50894
|
+
const kitty = decodeKittyPrintable(data);
|
|
50895
|
+
if (kitty) {
|
|
50896
|
+
const text = normalizeInputText(kitty);
|
|
50897
|
+
return text ? { kind: "text", text } : { kind: "ignored" };
|
|
50898
|
+
}
|
|
50899
|
+
if (data.startsWith("\x1B")) return { kind: "ignored" };
|
|
50900
|
+
const pasted = normalizeInputText(data);
|
|
50901
|
+
return pasted ? { kind: "text", text: pasted } : { kind: "ignored" };
|
|
50902
|
+
}
|
|
50903
|
+
function deleteBackward(text) {
|
|
50904
|
+
const units = Array.from(text);
|
|
50905
|
+
units.pop();
|
|
50906
|
+
return units.join("");
|
|
50907
|
+
}
|
|
50908
|
+
function insertAt(text, caret, insert, maxLength = KXM_TUI_INPUT_MAX_LENGTH) {
|
|
50909
|
+
const units = Array.from(text);
|
|
50910
|
+
const at2 = Math.max(0, Math.min(caret, units.length));
|
|
50911
|
+
const added = Array.from(insert);
|
|
50912
|
+
const merged = [...units.slice(0, at2), ...added, ...units.slice(at2)].join("");
|
|
50913
|
+
const next = merged.length > maxLength ? merged.slice(0, maxLength) : merged;
|
|
50914
|
+
return { text: next, caret: Math.min(next.length, at2 + added.length) };
|
|
50915
|
+
}
|
|
50302
50916
|
|
|
50303
50917
|
// packages/core/tui/src/tui/layout.ts
|
|
50918
|
+
var KXM_TUI_ELLIPSIS = "\u2026";
|
|
50919
|
+
function fitText(text, width) {
|
|
50920
|
+
if (width <= 0) return "";
|
|
50921
|
+
if (visibleWidth(text) <= width) return text;
|
|
50922
|
+
if (!text.includes("\x1B")) {
|
|
50923
|
+
let out = "";
|
|
50924
|
+
for (const unit of Array.from(text)) {
|
|
50925
|
+
if (visibleWidth(out) + visibleWidth(unit) > width - KXM_TUI_ELLIPSIS.length) break;
|
|
50926
|
+
out += unit;
|
|
50927
|
+
}
|
|
50928
|
+
return out + KXM_TUI_ELLIPSIS;
|
|
50929
|
+
}
|
|
50930
|
+
return truncateToWidth(text, width, KXM_TUI_ELLIPSIS);
|
|
50931
|
+
}
|
|
50932
|
+
function alignRight(left, right, width) {
|
|
50933
|
+
if (width <= 0) return "";
|
|
50934
|
+
const detail = fitText(right, Math.max(0, width - 2));
|
|
50935
|
+
const detailWidth = visibleWidth(detail);
|
|
50936
|
+
const leftWidth = Math.max(1, width - detailWidth - 1);
|
|
50937
|
+
const fitted = fitText(left, leftWidth);
|
|
50938
|
+
const filler = Math.max(1, width - visibleWidth(fitted) - detailWidth);
|
|
50939
|
+
return `${fitted}${" ".repeat(filler)}${detail}`;
|
|
50940
|
+
}
|
|
50941
|
+
function padRight(text, width) {
|
|
50942
|
+
const missing = width - visibleWidth(text);
|
|
50943
|
+
return missing > 0 ? text + " ".repeat(missing) : text;
|
|
50944
|
+
}
|
|
50945
|
+
function wrapPlain(text, width) {
|
|
50946
|
+
if (width <= 0) return [];
|
|
50947
|
+
const lines = [];
|
|
50948
|
+
let current = "";
|
|
50949
|
+
for (const word of text.split(/\s+/u).filter(Boolean)) {
|
|
50950
|
+
if (!current.length) current = word;
|
|
50951
|
+
else if (visibleWidth(current) + 1 + visibleWidth(word) <= width) current = `${current} ${word}`;
|
|
50952
|
+
else {
|
|
50953
|
+
lines.push(current);
|
|
50954
|
+
current = word;
|
|
50955
|
+
}
|
|
50956
|
+
}
|
|
50957
|
+
if (current.length) lines.push(current);
|
|
50958
|
+
return lines.length ? lines : [];
|
|
50959
|
+
}
|
|
50960
|
+
function formatAge(iso, nowMs) {
|
|
50961
|
+
if (!iso) return "\u2014";
|
|
50962
|
+
const at2 = Date.parse(iso);
|
|
50963
|
+
if (!Number.isFinite(at2)) return "\u2014";
|
|
50964
|
+
const seconds = Math.floor((nowMs - at2) / 1e3);
|
|
50965
|
+
if (seconds < 0) return "\u2014";
|
|
50966
|
+
if (seconds < 60) return `${seconds}s`;
|
|
50967
|
+
const minutes = Math.floor(seconds / 60);
|
|
50968
|
+
if (minutes < 60) return `${minutes}m${String(seconds % 60).padStart(2, "0")}s`;
|
|
50969
|
+
const hours = Math.floor(minutes / 60);
|
|
50970
|
+
if (hours < 24) return `${hours}h${String(minutes % 60).padStart(2, "0")}m`;
|
|
50971
|
+
const days = Math.floor(hours / 24);
|
|
50972
|
+
return `${days}d${String(hours % 24).padStart(2, "0")}h`;
|
|
50973
|
+
}
|
|
50974
|
+
function progressBar(ratio, width = 10) {
|
|
50975
|
+
const cells = Math.max(1, width);
|
|
50976
|
+
if (ratio === void 0 || !Number.isFinite(ratio)) return "\u2591".repeat(cells);
|
|
50977
|
+
const filled = Math.round(Math.max(0, Math.min(1, ratio)) * cells);
|
|
50978
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(Math.max(0, cells - filled));
|
|
50979
|
+
}
|
|
50304
50980
|
var KXM_TUI_MARKS = Object.freeze({
|
|
50305
50981
|
pending: "\u25CB",
|
|
50306
50982
|
active: "\u25B6",
|
|
@@ -50365,9 +51041,368 @@ function createKxmTuiAnsiTheme(color) {
|
|
|
50365
51041
|
panelBg: (text) => `\x1B[48;5;236m${text}\x1B[0m`
|
|
50366
51042
|
};
|
|
50367
51043
|
}
|
|
51044
|
+
function createDefaultKxmTuiTheme(env = process.env) {
|
|
51045
|
+
return createKxmTuiAnsiTheme(!(env.NO_COLOR || env.KXM_TUI_NO_COLOR));
|
|
51046
|
+
}
|
|
51047
|
+
|
|
51048
|
+
// packages/core/tui/src/tui/panel.ts
|
|
51049
|
+
var PANE_KEYS = /* @__PURE__ */ new Set(["j", "k", "h", "l"]);
|
|
51050
|
+
function initialKxmTuiPanelState(sections) {
|
|
51051
|
+
return {
|
|
51052
|
+
pane: "sections",
|
|
51053
|
+
mode: "browse",
|
|
51054
|
+
sectionIndex: 0,
|
|
51055
|
+
fieldIndex: firstSelectableFieldIndex(sections[0]?.fields ?? []),
|
|
51056
|
+
choiceIndex: 0,
|
|
51057
|
+
draft: "",
|
|
51058
|
+
caret: 0,
|
|
51059
|
+
filter: "",
|
|
51060
|
+
filtering: false,
|
|
51061
|
+
help: false,
|
|
51062
|
+
pending: []
|
|
51063
|
+
};
|
|
51064
|
+
}
|
|
51065
|
+
function pendingKey(invocation) {
|
|
51066
|
+
return `${invocation.source}\0${invocation.sectionId}\0${invocation.fieldId}`;
|
|
51067
|
+
}
|
|
51068
|
+
function firstSelectableFieldIndex(fields) {
|
|
51069
|
+
const index = fields.findIndex(isSelectableField);
|
|
51070
|
+
return index < 0 ? 0 : index;
|
|
51071
|
+
}
|
|
51072
|
+
function visibleChoices(field, filter) {
|
|
51073
|
+
const choices = field?.choices ?? [];
|
|
51074
|
+
const needle = filter.trim().toLowerCase();
|
|
51075
|
+
if (!needle) return [...choices];
|
|
51076
|
+
return choices.filter(
|
|
51077
|
+
(choice) => `${choice.group ?? ""} ${choice.label} ${choice.id} ${choice.detail ?? ""}`.toLowerCase().includes(needle)
|
|
51078
|
+
);
|
|
51079
|
+
}
|
|
51080
|
+
function currentSection(sections, state) {
|
|
51081
|
+
return sections[state.sectionIndex];
|
|
51082
|
+
}
|
|
51083
|
+
function currentField(sections, state) {
|
|
51084
|
+
return currentSection(sections, state)?.fields[state.fieldIndex];
|
|
51085
|
+
}
|
|
51086
|
+
function reconcileKxmTuiPanelState(state, sections) {
|
|
51087
|
+
const sectionIndex = Math.min(state.sectionIndex, Math.max(0, sections.length - 1));
|
|
51088
|
+
const fields = sections[sectionIndex]?.fields ?? [];
|
|
51089
|
+
let fieldIndex = Math.min(state.fieldIndex, Math.max(0, fields.length - 1));
|
|
51090
|
+
if (!isSelectableField(fields[fieldIndex])) fieldIndex = firstSelectableFieldIndex(fields);
|
|
51091
|
+
const field = fields[fieldIndex];
|
|
51092
|
+
const keepsEdit = state.mode === "edit" && (field?.statusText !== void 0 || field?.awaitingInput === true);
|
|
51093
|
+
return {
|
|
51094
|
+
...state,
|
|
51095
|
+
sectionIndex,
|
|
51096
|
+
fieldIndex,
|
|
51097
|
+
mode: keepsEdit ? "edit" : "browse",
|
|
51098
|
+
choiceIndex: 0,
|
|
51099
|
+
pending: []
|
|
51100
|
+
};
|
|
51101
|
+
}
|
|
51102
|
+
function reduceKxmTuiInput(state, sections, input) {
|
|
51103
|
+
return reduceDecoded(state, sections, decodeKxmTuiInput(input));
|
|
51104
|
+
}
|
|
51105
|
+
function invokeEffect(state, section, field, action, value) {
|
|
51106
|
+
const invocation = {
|
|
51107
|
+
source: section.source,
|
|
51108
|
+
sectionId: section.id,
|
|
51109
|
+
fieldId: field.id,
|
|
51110
|
+
action,
|
|
51111
|
+
...value === void 0 ? {} : { value }
|
|
51112
|
+
};
|
|
51113
|
+
const key = pendingKey(invocation);
|
|
51114
|
+
return {
|
|
51115
|
+
state: { ...state, pending: state.pending.includes(key) ? state.pending : [...state.pending, key] },
|
|
51116
|
+
effects: [{ type: "invoke", invocation }]
|
|
51117
|
+
};
|
|
51118
|
+
}
|
|
51119
|
+
function moveSection(state, sections, delta) {
|
|
51120
|
+
if (sections.length === 0) return state;
|
|
51121
|
+
const next = Math.max(0, Math.min(state.sectionIndex + delta, sections.length - 1));
|
|
51122
|
+
if (next === state.sectionIndex) return state;
|
|
51123
|
+
return {
|
|
51124
|
+
...state,
|
|
51125
|
+
sectionIndex: next,
|
|
51126
|
+
fieldIndex: firstSelectableFieldIndex(sections[next]?.fields ?? []),
|
|
51127
|
+
mode: "browse",
|
|
51128
|
+
choiceIndex: 0,
|
|
51129
|
+
draft: "",
|
|
51130
|
+
caret: 0,
|
|
51131
|
+
filter: "",
|
|
51132
|
+
filtering: false
|
|
51133
|
+
};
|
|
51134
|
+
}
|
|
51135
|
+
function moveField(state, fields, delta) {
|
|
51136
|
+
const step = delta < 0 ? -1 : 1;
|
|
51137
|
+
let next = state.fieldIndex + step;
|
|
51138
|
+
while (next >= 0 && next < fields.length && !isSelectableField(fields[next])) next += step;
|
|
51139
|
+
if (next < 0 || next >= fields.length) return state;
|
|
51140
|
+
return {
|
|
51141
|
+
...state,
|
|
51142
|
+
fieldIndex: next,
|
|
51143
|
+
mode: "browse",
|
|
51144
|
+
choiceIndex: 0,
|
|
51145
|
+
draft: "",
|
|
51146
|
+
caret: 0,
|
|
51147
|
+
filter: "",
|
|
51148
|
+
filtering: false
|
|
51149
|
+
};
|
|
51150
|
+
}
|
|
51151
|
+
function moveChoice(state, count, delta) {
|
|
51152
|
+
if (count === 0) return state;
|
|
51153
|
+
return { ...state, choiceIndex: Math.max(0, Math.min(state.choiceIndex + delta, count - 1)) };
|
|
51154
|
+
}
|
|
51155
|
+
function choiceAt(field, state) {
|
|
51156
|
+
const choices = visibleChoices(field, state.filter);
|
|
51157
|
+
return choices[Math.max(0, Math.min(state.choiceIndex, choices.length - 1))];
|
|
51158
|
+
}
|
|
51159
|
+
function enterField(state, section, field) {
|
|
51160
|
+
if (!field) return { state, effects: [] };
|
|
51161
|
+
if (field.kind === "choice") {
|
|
51162
|
+
if ((field.choices?.length ?? 0) === 0) {
|
|
51163
|
+
return { state, effects: [{ type: "notice", message: `${field.label} has nothing to offer yet` }] };
|
|
51164
|
+
}
|
|
51165
|
+
return { state: { ...state, mode: "choices", choiceIndex: 0 }, effects: [] };
|
|
51166
|
+
}
|
|
51167
|
+
if (field.kind === "enum") {
|
|
51168
|
+
const choices = field.choices ?? [];
|
|
51169
|
+
if (choices.length === 0) {
|
|
51170
|
+
return { state, effects: [{ type: "notice", message: `${field.label} has no values published` }] };
|
|
51171
|
+
}
|
|
51172
|
+
const current = choices.findIndex((choice) => choice.id === field.value);
|
|
51173
|
+
const next = choices[(current + 1) % choices.length];
|
|
51174
|
+
if (next.status === "blocked") {
|
|
51175
|
+
return { state, effects: [{ type: "notice", message: next.statusText ?? `${next.label} is unavailable` }] };
|
|
51176
|
+
}
|
|
51177
|
+
return invokeEffect(state, section, field, next.action ?? KXM_TUI_SET_ACTION, next.id);
|
|
51178
|
+
}
|
|
51179
|
+
if (field.kind !== "text") return { state, effects: [] };
|
|
51180
|
+
return { state: { ...state, mode: "edit", draft: field.value ?? "", caret: (field.value ?? "").length }, effects: [] };
|
|
51181
|
+
}
|
|
51182
|
+
function reduceDecoded(state, sections, decoded) {
|
|
51183
|
+
const section = sections[state.sectionIndex];
|
|
51184
|
+
const field = currentField(sections, state);
|
|
51185
|
+
if (decoded.kind === "ctrlC") {
|
|
51186
|
+
if (field?.busy && section) {
|
|
51187
|
+
return invokeEffect(state, section, field, KXM_TUI_ABORT_ACTION);
|
|
51188
|
+
}
|
|
51189
|
+
return { state, effects: [{ type: "quit" }] };
|
|
51190
|
+
}
|
|
51191
|
+
if (!section) return { state, effects: [{ type: "quit" }] };
|
|
51192
|
+
if (state.mode === "edit") return reduceEdit(state, section, field, decoded);
|
|
51193
|
+
if (state.mode === "choices") return reduceChoices(state, section, field, decoded);
|
|
51194
|
+
return reduceBrowse(state, sections, section, field, decoded);
|
|
51195
|
+
}
|
|
51196
|
+
function caretSource(text, caret) {
|
|
51197
|
+
return Array.from(text).slice(0, caret).join("").length;
|
|
51198
|
+
}
|
|
51199
|
+
function reduceEdit(state, section, field, decoded) {
|
|
51200
|
+
if (!field) return { state: { ...state, mode: "browse" }, effects: [] };
|
|
51201
|
+
switch (decoded.kind) {
|
|
51202
|
+
case "escape":
|
|
51203
|
+
return { state: { ...state, mode: "browse", draft: "", caret: 0 }, effects: [] };
|
|
51204
|
+
case "enter": {
|
|
51205
|
+
const value = normalizeInputText(state.draft).trim();
|
|
51206
|
+
if (!value) return { state: { ...state, mode: "browse", draft: "", caret: 0 }, effects: [] };
|
|
51207
|
+
const step = invokeEffect(state, section, field, KXM_TUI_SET_ACTION, value);
|
|
51208
|
+
return { state: { ...step.state, mode: "browse", draft: "", caret: 0 }, effects: step.effects };
|
|
51209
|
+
}
|
|
51210
|
+
case "backspace": {
|
|
51211
|
+
if (state.caret === 0) return { state, effects: [] };
|
|
51212
|
+
const cut = caretSource(state.draft, state.caret);
|
|
51213
|
+
const before = deleteBackward(state.draft.slice(0, cut));
|
|
51214
|
+
const draft = `${before}${state.draft.slice(cut)}`;
|
|
51215
|
+
return { state: { ...state, draft, caret: Array.from(before).length }, effects: [] };
|
|
51216
|
+
}
|
|
51217
|
+
case "delete": {
|
|
51218
|
+
const units = Array.from(state.draft);
|
|
51219
|
+
const text = [...units.slice(0, state.caret), ...units.slice(state.caret + 1)].join("");
|
|
51220
|
+
return { state: { ...state, draft: text }, effects: [] };
|
|
51221
|
+
}
|
|
51222
|
+
case "left":
|
|
51223
|
+
return { state: { ...state, caret: Math.max(0, state.caret - 1) }, effects: [] };
|
|
51224
|
+
case "right":
|
|
51225
|
+
return { state: { ...state, caret: Math.min(Array.from(state.draft).length, state.caret + 1) }, effects: [] };
|
|
51226
|
+
case "home":
|
|
51227
|
+
return { state: { ...state, caret: 0 }, effects: [] };
|
|
51228
|
+
case "end":
|
|
51229
|
+
return { state: { ...state, caret: Array.from(state.draft).length }, effects: [] };
|
|
51230
|
+
case "text": {
|
|
51231
|
+
const text = decoded.text ?? "";
|
|
51232
|
+
if (!text) return { state, effects: [] };
|
|
51233
|
+
const inserted = insertAt(state.draft, state.caret, text);
|
|
51234
|
+
return { state: { ...state, draft: inserted.text, caret: inserted.caret }, effects: [] };
|
|
51235
|
+
}
|
|
51236
|
+
default:
|
|
51237
|
+
return { state, effects: [] };
|
|
51238
|
+
}
|
|
51239
|
+
}
|
|
51240
|
+
function reduceChoices(state, section, field, decoded) {
|
|
51241
|
+
if (!field) return { state: { ...state, mode: "browse" }, effects: [] };
|
|
51242
|
+
const choices = visibleChoices(field, state.filter);
|
|
51243
|
+
switch (decoded.kind) {
|
|
51244
|
+
case "escape":
|
|
51245
|
+
return { state: { ...state, mode: "browse", filter: "", filtering: false, choiceIndex: 0 }, effects: [] };
|
|
51246
|
+
case "up":
|
|
51247
|
+
return { state: moveChoice(state, choices.length, -1), effects: [] };
|
|
51248
|
+
case "down":
|
|
51249
|
+
return { state: moveChoice(state, choices.length, 1), effects: [] };
|
|
51250
|
+
case "pageUp":
|
|
51251
|
+
return { state: moveChoice(state, choices.length, -8), effects: [] };
|
|
51252
|
+
case "pageDown":
|
|
51253
|
+
return { state: moveChoice(state, choices.length, 8), effects: [] };
|
|
51254
|
+
case "enter": {
|
|
51255
|
+
const choice = choiceAt(field, state);
|
|
51256
|
+
if (!choice) return { state, effects: [] };
|
|
51257
|
+
if (choice.status === "blocked") {
|
|
51258
|
+
return { state, effects: [{ type: "notice", message: choice.statusText ?? `${choice.label} is unavailable` }] };
|
|
51259
|
+
}
|
|
51260
|
+
const clearing = (choice.action ?? KXM_TUI_SET_ACTION) === KXM_TUI_CLEAR_ACTION;
|
|
51261
|
+
const step = invokeEffect(
|
|
51262
|
+
state,
|
|
51263
|
+
section,
|
|
51264
|
+
field,
|
|
51265
|
+
choice.action ?? KXM_TUI_SET_ACTION,
|
|
51266
|
+
clearing ? void 0 : choice.id
|
|
51267
|
+
);
|
|
51268
|
+
return { state: { ...step.state, mode: "browse", filter: "", filtering: false }, effects: step.effects };
|
|
51269
|
+
}
|
|
51270
|
+
case "backspace": {
|
|
51271
|
+
if (state.filter.length === 0) return { state: { ...state, filtering: false }, effects: [] };
|
|
51272
|
+
return { state: { ...state, filter: deleteBackward(state.filter), choiceIndex: 0 }, effects: [] };
|
|
51273
|
+
}
|
|
51274
|
+
case "text": {
|
|
51275
|
+
const text = normalizeInputText(decoded.text ?? "");
|
|
51276
|
+
if (!text) return { state, effects: [] };
|
|
51277
|
+
return { state: { ...state, filtering: true, filter: `${state.filter}${text}`, choiceIndex: 0 }, effects: [] };
|
|
51278
|
+
}
|
|
51279
|
+
default:
|
|
51280
|
+
return { state, effects: [] };
|
|
51281
|
+
}
|
|
51282
|
+
}
|
|
51283
|
+
function reduceBrowse(state, sections, section, field, decoded) {
|
|
51284
|
+
if (state.help && (decoded.kind === "escape" || decoded.kind === "enter")) {
|
|
51285
|
+
return { state: { ...state, help: false }, effects: [] };
|
|
51286
|
+
}
|
|
51287
|
+
if (state.help) return { state, effects: [] };
|
|
51288
|
+
if (decoded.kind === "text") {
|
|
51289
|
+
const key = decoded.text ?? "";
|
|
51290
|
+
if (key === "q") return { state, effects: [{ type: "quit" }] };
|
|
51291
|
+
if (key === "h" || key === "?") return { state: { ...state, help: true }, effects: [] };
|
|
51292
|
+
const action = field?.actions?.find((candidate) => candidate.key === key);
|
|
51293
|
+
if (action && field) return invokeEffect(state, section, field, action.action);
|
|
51294
|
+
if ((key === "x" || key === "d") && field && field.value !== void 0 && field.kind !== "info") {
|
|
51295
|
+
return invokeEffect(state, section, field, KXM_TUI_CLEAR_ACTION);
|
|
51296
|
+
}
|
|
51297
|
+
if (PANE_KEYS.has(key)) {
|
|
51298
|
+
if (key === "h") return { state: { ...state, pane: "sections" }, effects: [] };
|
|
51299
|
+
if (key === "l") return { state: { ...state, pane: "fields" }, effects: [] };
|
|
51300
|
+
const delta = key === "k" ? -1 : 1;
|
|
51301
|
+
return {
|
|
51302
|
+
state: state.pane === "fields" ? moveField(state, section.fields, delta) : moveSection(state, sections, delta),
|
|
51303
|
+
effects: []
|
|
51304
|
+
};
|
|
51305
|
+
}
|
|
51306
|
+
return { state, effects: [] };
|
|
51307
|
+
}
|
|
51308
|
+
switch (decoded.kind) {
|
|
51309
|
+
case "escape":
|
|
51310
|
+
return state.pane === "fields" ? { state: { ...state, pane: "sections", mode: "browse" }, effects: [] } : { state, effects: [{ type: "quit" }] };
|
|
51311
|
+
case "tab":
|
|
51312
|
+
return { state: { ...state, pane: state.pane === "fields" ? "sections" : "fields" }, effects: [] };
|
|
51313
|
+
case "right":
|
|
51314
|
+
return { state: { ...state, pane: "fields" }, effects: [] };
|
|
51315
|
+
case "left":
|
|
51316
|
+
return { state: { ...state, pane: "sections" }, effects: [] };
|
|
51317
|
+
case "up":
|
|
51318
|
+
return { state: state.pane === "fields" ? moveField(state, section.fields, -1) : moveSection(state, sections, -1), effects: [] };
|
|
51319
|
+
case "down":
|
|
51320
|
+
return { state: state.pane === "fields" ? moveField(state, section.fields, 1) : moveSection(state, sections, 1), effects: [] };
|
|
51321
|
+
case "pageUp":
|
|
51322
|
+
return { state: moveSection(state, sections, -1), effects: [] };
|
|
51323
|
+
case "pageDown":
|
|
51324
|
+
return { state: moveSection(state, sections, 1), effects: [] };
|
|
51325
|
+
case "enter":
|
|
51326
|
+
return state.pane === "fields" ? enterField(state, section, field) : { state: { ...state, pane: "fields" }, effects: [] };
|
|
51327
|
+
default:
|
|
51328
|
+
return { state, effects: [] };
|
|
51329
|
+
}
|
|
51330
|
+
}
|
|
50368
51331
|
|
|
50369
51332
|
// packages/core/tui/src/tui/render.ts
|
|
50370
51333
|
var SECTION_PANE_RATIO = 1 / 3;
|
|
51334
|
+
var MIN_PANE_WIDTH = 16;
|
|
51335
|
+
var PROGRESS_WIDTH = 20;
|
|
51336
|
+
var OUTPUT_TAIL = 6;
|
|
51337
|
+
var SPLIT_MIN_WIDTH = 76;
|
|
51338
|
+
function statusMark(status, theme, fallback) {
|
|
51339
|
+
switch (status) {
|
|
51340
|
+
case "ready":
|
|
51341
|
+
return theme.fg("success", KXM_TUI_MARKS.ready);
|
|
51342
|
+
case "available":
|
|
51343
|
+
return theme.fg("accent", KXM_TUI_MARKS.available);
|
|
51344
|
+
case "blocked":
|
|
51345
|
+
return theme.fg("error", KXM_TUI_MARKS.blocked);
|
|
51346
|
+
default:
|
|
51347
|
+
return theme.dim(fallback);
|
|
51348
|
+
}
|
|
51349
|
+
}
|
|
51350
|
+
function cursorMark(active, theme) {
|
|
51351
|
+
return active ? theme.cursor(KXM_TUI_MARKS.cursor) : KXM_TUI_MARKS.blankCursor;
|
|
51352
|
+
}
|
|
51353
|
+
function paneSplit(width) {
|
|
51354
|
+
const third = Math.max(MIN_PANE_WIDTH, Math.floor(width * SECTION_PANE_RATIO));
|
|
51355
|
+
const sections = Math.min(third, Math.max(MIN_PANE_WIDTH, Math.floor(width / 2)));
|
|
51356
|
+
return { sections, fields: Math.max(MIN_PANE_WIDTH, width - sections - 1) };
|
|
51357
|
+
}
|
|
51358
|
+
function fieldIsPending(state, section, field) {
|
|
51359
|
+
return state.pending.includes(pendingKey({ source: section.source, sectionId: section.id, fieldId: field.id }));
|
|
51360
|
+
}
|
|
51361
|
+
function sectionRow(section, focused, pane, theme, width) {
|
|
51362
|
+
const cursor2 = cursorMark(focused && pane === "sections", theme);
|
|
51363
|
+
const flag2 = section.notice ? theme.fg(section.noticeLevel === "error" ? "error" : "warning", "!") : " ";
|
|
51364
|
+
const count = section.fields.length;
|
|
51365
|
+
const unset = section.fields.filter((field) => field.kind !== "info" && field.value === void 0).length;
|
|
51366
|
+
const right = unset > 0 ? `${unset} unset / ${count}` : `${count} field${count === 1 ? "" : "s"}`;
|
|
51367
|
+
return fitText(alignRight(`${cursor2} ${flag2} ${section.title}`, right, width), width);
|
|
51368
|
+
}
|
|
51369
|
+
function choiceSuffix(choice) {
|
|
51370
|
+
const parts = [];
|
|
51371
|
+
if (choice.detail) parts.push(choice.detail);
|
|
51372
|
+
if (choice.statusText) parts.push(choice.statusText);
|
|
51373
|
+
return parts.join(" \xB7 ");
|
|
51374
|
+
}
|
|
51375
|
+
function choiceRows(field, state, theme, width) {
|
|
51376
|
+
const shown = visibleChoices(field, state.filter);
|
|
51377
|
+
const lines = [];
|
|
51378
|
+
if (state.filtering) {
|
|
51379
|
+
lines.push(fitText(` ${theme.dim(`filter: ${state.filter || "(type to filter \xB7 esc clears)"}`)}`, width));
|
|
51380
|
+
}
|
|
51381
|
+
if (shown.length === 0) {
|
|
51382
|
+
lines.push(fitText(` ${theme.dim("no match")}`, width));
|
|
51383
|
+
return lines;
|
|
51384
|
+
}
|
|
51385
|
+
let position = -1;
|
|
51386
|
+
for (const group of groupedKxmTuiChoices(field)) {
|
|
51387
|
+
const filtered = shown.filter((choice) => (choice.group ?? "") === group.group);
|
|
51388
|
+
if (filtered.length === 0) continue;
|
|
51389
|
+
if (group.group) lines.push(fitText(` ${theme.key(group.group.toUpperCase())}`, width));
|
|
51390
|
+
for (const choice of filtered) {
|
|
51391
|
+
position += 1;
|
|
51392
|
+
lines.push(
|
|
51393
|
+
fitText(
|
|
51394
|
+
alignRight(
|
|
51395
|
+
`${cursorMark(position === state.choiceIndex, theme)} ${statusMark(choice.status, theme, KXM_TUI_MARKS.pending)} ${choice.label}`,
|
|
51396
|
+
choiceSuffix(choice),
|
|
51397
|
+
width
|
|
51398
|
+
),
|
|
51399
|
+
width
|
|
51400
|
+
)
|
|
51401
|
+
);
|
|
51402
|
+
}
|
|
51403
|
+
}
|
|
51404
|
+
return lines;
|
|
51405
|
+
}
|
|
50371
51406
|
var STEP_MARKS = {
|
|
50372
51407
|
pending: "\xB7",
|
|
50373
51408
|
satisfied: KXM_TUI_MARKS.passed,
|
|
@@ -50375,12 +51410,494 @@ var STEP_MARKS = {
|
|
|
50375
51410
|
done: KXM_TUI_MARKS.passed,
|
|
50376
51411
|
failed: KXM_TUI_MARKS.failed
|
|
50377
51412
|
};
|
|
51413
|
+
function stepColor(state, theme) {
|
|
51414
|
+
switch (state) {
|
|
51415
|
+
case "running":
|
|
51416
|
+
return (text) => theme.fg("accent", text);
|
|
51417
|
+
case "done":
|
|
51418
|
+
return (text) => theme.fg("success", text);
|
|
51419
|
+
case "failed":
|
|
51420
|
+
return (text) => theme.fg("error", text);
|
|
51421
|
+
default:
|
|
51422
|
+
return theme.dim;
|
|
51423
|
+
}
|
|
51424
|
+
}
|
|
51425
|
+
function stepRows(steps, theme, width) {
|
|
51426
|
+
return steps.map((step) => {
|
|
51427
|
+
const paint = stepColor(step.state, theme);
|
|
51428
|
+
return fitText(alignRight(` ${paint(STEP_MARKS[step.state])} ${step.label}`, step.detail ?? "", width), width);
|
|
51429
|
+
});
|
|
51430
|
+
}
|
|
51431
|
+
function progressRow(field, theme, width) {
|
|
51432
|
+
const progress = field.progress;
|
|
51433
|
+
if (!progress) return "";
|
|
51434
|
+
const ratio = progress.ratio;
|
|
51435
|
+
const right = ratio === void 0 ? "working" : `${Math.round(ratio * 100)}%`;
|
|
51436
|
+
return fitText(alignRight(` ${progress.label}`, `${progressBar(ratio, PROGRESS_WIDTH)} ${right}`, width), width);
|
|
51437
|
+
}
|
|
51438
|
+
function focusedFieldDetail(field, state, theme, width) {
|
|
51439
|
+
const lines = [];
|
|
51440
|
+
if (field.keyPath) lines.push(fitText(` ${theme.dim(`key: ${field.keyPath}`)}`, width));
|
|
51441
|
+
for (const line of wrapPlain(field.detail ?? "", width - 3)) {
|
|
51442
|
+
if (line) lines.push(fitText(` ${theme.dim(line)}`, width));
|
|
51443
|
+
}
|
|
51444
|
+
for (const line of wrapPlain(field.statusText ?? "", width - 3)) {
|
|
51445
|
+
if (line) lines.push(fitText(` ${theme.fg("error", line)}`, width));
|
|
51446
|
+
}
|
|
51447
|
+
const progress = progressRow(field, theme, width);
|
|
51448
|
+
if (progress) lines.push(progress);
|
|
51449
|
+
if (field.steps?.length) lines.push(...stepRows(field.steps, theme, width));
|
|
51450
|
+
if (field.output?.length) {
|
|
51451
|
+
lines.push(...field.output.slice(-OUTPUT_TAIL).map((line) => fitText(` ${theme.dim(line)}`, width)));
|
|
51452
|
+
}
|
|
51453
|
+
if (field.actions?.length) {
|
|
51454
|
+
lines.push(fitText(` ${theme.key(field.actions.map((action) => `${action.key} ${action.label}`).join(" \xB7 "))}`, width));
|
|
51455
|
+
}
|
|
51456
|
+
if (state.mode === "choices") lines.push(...choiceRows(field, state, theme, width));
|
|
51457
|
+
if (state.mode === "edit" || field.awaitingInput) {
|
|
51458
|
+
const draft = state.mode === "edit" ? `${state.draft.slice(0, state.caret)}|${state.draft.slice(state.caret)}` : "|";
|
|
51459
|
+
lines.push(fitText(` ${theme.dim("\u203A")} ${draft}`, width));
|
|
51460
|
+
}
|
|
51461
|
+
return lines;
|
|
51462
|
+
}
|
|
51463
|
+
function fieldRows(section, state, theme, width, nowMs) {
|
|
51464
|
+
const lines = [];
|
|
51465
|
+
section.fields.forEach((field, index) => {
|
|
51466
|
+
const focused = state.pane === "fields" && state.fieldIndex === index;
|
|
51467
|
+
if (field.kind === "info") {
|
|
51468
|
+
lines.push(fitText(` ${theme.dim(field.label)} ${theme.dim(field.value ?? field.placeholder ?? "")}`, width));
|
|
51469
|
+
return;
|
|
51470
|
+
}
|
|
51471
|
+
const pending = fieldIsPending(state, section, field);
|
|
51472
|
+
const mark = statusMark(field.status, theme, pending ? "\u25CC" : KXM_TUI_MARKS.pending);
|
|
51473
|
+
const value = pending && field.value === void 0 ? theme.dim("saving\u2026") : field.value;
|
|
51474
|
+
const shown = value ?? theme.dim(field.placeholder ?? "not set");
|
|
51475
|
+
const age2 = field.updatedAt ? theme.dim(` (${formatAge(field.updatedAt, nowMs)})`) : "";
|
|
51476
|
+
lines.push(fitText(`${cursorMark(focused, theme)} ${mark} ${alignRight(field.label, `${shown}${age2}`, width - 4)}`, width));
|
|
51477
|
+
if (focused) lines.push(...focusedFieldDetail(field, state, theme, width));
|
|
51478
|
+
});
|
|
51479
|
+
return lines;
|
|
51480
|
+
}
|
|
51481
|
+
function noticeRows(section, theme, width) {
|
|
51482
|
+
if (!section?.notice) return [];
|
|
51483
|
+
const token = section.noticeLevel === "error" ? "error" : "warning";
|
|
51484
|
+
return wrapPlain(section.notice, width - 2).map((line) => fitText(theme.fg(token, line), width));
|
|
51485
|
+
}
|
|
51486
|
+
function singlePaneLines(sections, state, theme, width, nowMs) {
|
|
51487
|
+
const section = sections[state.sectionIndex];
|
|
51488
|
+
if (state.pane === "fields" && section) {
|
|
51489
|
+
return [
|
|
51490
|
+
fitText(theme.headerBg(alignRight(` ${section.title}`, section.source, width)), width),
|
|
51491
|
+
...noticeRows(section, theme, width),
|
|
51492
|
+
...fieldRows(section, state, theme, width, nowMs)
|
|
51493
|
+
];
|
|
51494
|
+
}
|
|
51495
|
+
return [
|
|
51496
|
+
fitText(theme.headerBg(alignRight(" SECTIONS", `${sections.length}`, width)), width),
|
|
51497
|
+
...sections.map((candidate, index) => sectionRow(candidate, index === state.sectionIndex, state.pane, theme, width))
|
|
51498
|
+
];
|
|
51499
|
+
}
|
|
51500
|
+
function twoPaneLines(sections, state, theme, width, nowMs) {
|
|
51501
|
+
const split = paneSplit(width);
|
|
51502
|
+
const left = [fitText(theme.key(" SECTIONS"), split.sections)];
|
|
51503
|
+
sections.forEach(
|
|
51504
|
+
(candidate, index) => left.push(sectionRow(candidate, index === state.sectionIndex, state.pane, theme, split.sections))
|
|
51505
|
+
);
|
|
51506
|
+
const right = [];
|
|
51507
|
+
const section = sections[state.sectionIndex];
|
|
51508
|
+
if (section) {
|
|
51509
|
+
right.push(fitText(alignRight(` ${section.title}`, section.source, split.fields), split.fields));
|
|
51510
|
+
if (section.detail) right.push(fitText(` ${theme.dim(section.detail)}`, split.fields));
|
|
51511
|
+
right.push(...noticeRows(section, theme, split.fields));
|
|
51512
|
+
right.push(...fieldRows(section, state, theme, split.fields, nowMs));
|
|
51513
|
+
} else {
|
|
51514
|
+
right.push(fitText(theme.dim("no surface is published"), split.fields));
|
|
51515
|
+
}
|
|
51516
|
+
const rows = [];
|
|
51517
|
+
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
51518
|
+
rows.push(fitText(`${padRight(left[index] ?? "", split.sections)} ${right[index] ?? ""}`, width));
|
|
51519
|
+
}
|
|
51520
|
+
return rows;
|
|
51521
|
+
}
|
|
51522
|
+
function renderKxmTuiPanel(sections, state, theme, options, width = 120) {
|
|
51523
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
51524
|
+
const title = `${theme.cursor(options.title)}${options.breadcrumb ? ` ${theme.dim(options.breadcrumb)}` : ""}`;
|
|
51525
|
+
const header = [fitText(title, width)];
|
|
51526
|
+
if (state.help) {
|
|
51527
|
+
const help = (options.helpLines ?? ["no help published"]).flatMap((line) => wrapPlain(line, Math.max(1, width - 2)));
|
|
51528
|
+
return [...header, ...help.map((line) => fitText(line, width)), fitText(theme.dim("esc close help"), width)];
|
|
51529
|
+
}
|
|
51530
|
+
const body = width < SPLIT_MIN_WIDTH ? singlePaneLines(sections, state, theme, width, nowMs) : twoPaneLines(sections, state, theme, width, nowMs);
|
|
51531
|
+
const footer = options.footer ? [fitText(theme.dim(options.footer), width)] : [];
|
|
51532
|
+
return [...header, ...body, ...footer];
|
|
51533
|
+
}
|
|
50378
51534
|
|
|
50379
51535
|
// packages/core/tui/src/tui/panelComponent.ts
|
|
50380
51536
|
var KXM_TUI_PANEL_HELP = Object.freeze([
|
|
50381
51537
|
"\u2191\u2193 move \xB7 \u2190\u2192 or Tab panes \xB7 PgUp/PgDn section \xB7 enter edit or open list",
|
|
50382
51538
|
"type to filter a list \xB7 x clears a value \xB7 esc backs out \xB7 q quit \xB7 h help"
|
|
50383
51539
|
]);
|
|
51540
|
+
function rowsComponent() {
|
|
51541
|
+
const stack = new VStack([], { gap: 0 });
|
|
51542
|
+
const scroll = new ScrollView(stack, { primary: true, overscroll: "contain", scrollbar: "auto" });
|
|
51543
|
+
const fill = (rows) => {
|
|
51544
|
+
stack.clear();
|
|
51545
|
+
for (const row of rows) stack.addChild(new TruncatedText(row, 0, 0));
|
|
51546
|
+
};
|
|
51547
|
+
return { stack, scroll, fill };
|
|
51548
|
+
}
|
|
51549
|
+
var KxmTuiPanelComponent = class {
|
|
51550
|
+
root = new VStack([], { gap: 0 });
|
|
51551
|
+
options;
|
|
51552
|
+
body = rowsComponent();
|
|
51553
|
+
sections;
|
|
51554
|
+
state;
|
|
51555
|
+
notice;
|
|
51556
|
+
unsubscribe;
|
|
51557
|
+
constructor(options) {
|
|
51558
|
+
this.options = options;
|
|
51559
|
+
this.sections = options.registry.getSections();
|
|
51560
|
+
this.state = initialKxmTuiPanelState(this.sections);
|
|
51561
|
+
this.unsubscribe = options.registry.subscribe(() => this.refresh());
|
|
51562
|
+
this.rebuild();
|
|
51563
|
+
}
|
|
51564
|
+
getState() {
|
|
51565
|
+
return this.state;
|
|
51566
|
+
}
|
|
51567
|
+
getSections() {
|
|
51568
|
+
return this.sections;
|
|
51569
|
+
}
|
|
51570
|
+
/** Point the cursor at a field, e.g. after opening a named section. */
|
|
51571
|
+
focus(sectionId, fieldId) {
|
|
51572
|
+
const sectionIndex = this.sections.findIndex((section) => section.id === sectionId);
|
|
51573
|
+
if (sectionIndex < 0) return;
|
|
51574
|
+
const fields = this.sections[sectionIndex]?.fields ?? [];
|
|
51575
|
+
const fieldIndex = fieldId ? Math.max(0, fields.findIndex((field) => field.id === fieldId)) : this.state.fieldIndex;
|
|
51576
|
+
this.state = { ...this.state, pane: "fields", sectionIndex, fieldIndex };
|
|
51577
|
+
this.rebuild();
|
|
51578
|
+
this.options.requestRender();
|
|
51579
|
+
}
|
|
51580
|
+
setStatus(message) {
|
|
51581
|
+
this.notice = message;
|
|
51582
|
+
this.rebuild();
|
|
51583
|
+
this.options.requestRender();
|
|
51584
|
+
}
|
|
51585
|
+
refresh() {
|
|
51586
|
+
const next = this.options.registry.getSections();
|
|
51587
|
+
this.sections = next;
|
|
51588
|
+
this.state = reconcileKxmTuiPanelState(this.state, next);
|
|
51589
|
+
this.rebuild();
|
|
51590
|
+
this.options.requestRender();
|
|
51591
|
+
}
|
|
51592
|
+
handleInput(data) {
|
|
51593
|
+
if (matchesKey(data, Key.pageUp) || matchesKey(data, Key.ctrl("u"))) {
|
|
51594
|
+
this.body.scroll.scrollBy(-(this.viewport() - 2));
|
|
51595
|
+
this.options.requestRender();
|
|
51596
|
+
return;
|
|
51597
|
+
}
|
|
51598
|
+
if (matchesKey(data, Key.pageDown) || matchesKey(data, Key.ctrl("d"))) {
|
|
51599
|
+
this.body.scroll.scrollBy(this.viewport() - 2);
|
|
51600
|
+
this.options.requestRender();
|
|
51601
|
+
return;
|
|
51602
|
+
}
|
|
51603
|
+
const step = reduceKxmTuiInput(this.state, this.sections, data);
|
|
51604
|
+
this.state = step.state;
|
|
51605
|
+
for (const effect of step.effects) this.run(effect);
|
|
51606
|
+
this.rebuild();
|
|
51607
|
+
this.options.requestRender();
|
|
51608
|
+
}
|
|
51609
|
+
viewport() {
|
|
51610
|
+
const height = this.body.scroll.viewportHeight;
|
|
51611
|
+
return height > 3 ? height : 10;
|
|
51612
|
+
}
|
|
51613
|
+
run(effect) {
|
|
51614
|
+
if (effect.type === "quit") {
|
|
51615
|
+
this.options.onQuit();
|
|
51616
|
+
return;
|
|
51617
|
+
}
|
|
51618
|
+
if (effect.type === "notice") {
|
|
51619
|
+
this.notice = effect.message;
|
|
51620
|
+
return;
|
|
51621
|
+
}
|
|
51622
|
+
this.notice = void 0;
|
|
51623
|
+
void this.options.registry.invoke(effect.invocation).then((result) => {
|
|
51624
|
+
if (!result.ok) this.setStatus(result.message ?? "the owner refused that change");
|
|
51625
|
+
else if (this.notice === void 0) this.setStatus(void 0);
|
|
51626
|
+
});
|
|
51627
|
+
}
|
|
51628
|
+
footer() {
|
|
51629
|
+
const status = this.notice ?? this.options.statusText?.();
|
|
51630
|
+
const base = "\u2191\u2193 \xB7 \u2190\u2192 panes \xB7 enter edit \xB7 x clear \xB7 esc back \xB7 h help \xB7 q quit";
|
|
51631
|
+
return status ? `${base} ${status}` : base;
|
|
51632
|
+
}
|
|
51633
|
+
rebuild() {
|
|
51634
|
+
const theme = this.options.theme;
|
|
51635
|
+
const width = this.options.getWidth?.() ?? 120;
|
|
51636
|
+
const title = new Box(1, 0, theme.headerBg);
|
|
51637
|
+
title.addChild(new HStack([
|
|
51638
|
+
{ component: new Text(theme.cursor(this.options.title), 0, 0), basis: 12, shrink: 1, minSize: 6 },
|
|
51639
|
+
{
|
|
51640
|
+
component: new Text(
|
|
51641
|
+
this.options.breadcrumb ? theme.dim(fitText(this.options.breadcrumb, Math.max(4, width - 16))) : "",
|
|
51642
|
+
0,
|
|
51643
|
+
0
|
|
51644
|
+
),
|
|
51645
|
+
grow: 1,
|
|
51646
|
+
shrink: 1,
|
|
51647
|
+
minSize: 8
|
|
51648
|
+
}
|
|
51649
|
+
], { gap: 2 }));
|
|
51650
|
+
this.body.fill(
|
|
51651
|
+
renderKxmTuiPanel(this.sections, this.state, theme, {
|
|
51652
|
+
title: this.options.title,
|
|
51653
|
+
...this.options.breadcrumb ? { breadcrumb: this.options.breadcrumb } : {},
|
|
51654
|
+
helpLines: this.options.helpLines ?? KXM_TUI_PANEL_HELP,
|
|
51655
|
+
footer: this.footer()
|
|
51656
|
+
}, width)
|
|
51657
|
+
);
|
|
51658
|
+
const panel = new Box(0, 0, theme.panelBg);
|
|
51659
|
+
panel.addChild(this.body.scroll);
|
|
51660
|
+
this.root.clear();
|
|
51661
|
+
this.root.addChild(title, { basis: "auto", shrink: 0 });
|
|
51662
|
+
this.root.addChild(panel, { basis: "auto", grow: 1, shrink: 1, minSize: 1 });
|
|
51663
|
+
}
|
|
51664
|
+
handleMouse(event) {
|
|
51665
|
+
return this.root.handleMouse?.(event);
|
|
51666
|
+
}
|
|
51667
|
+
invalidate() {
|
|
51668
|
+
this.root.invalidate();
|
|
51669
|
+
}
|
|
51670
|
+
render(width) {
|
|
51671
|
+
return this.root.render(width);
|
|
51672
|
+
}
|
|
51673
|
+
dispose() {
|
|
51674
|
+
this.unsubscribe();
|
|
51675
|
+
this.unsubscribe = () => void 0;
|
|
51676
|
+
}
|
|
51677
|
+
};
|
|
51678
|
+
|
|
51679
|
+
// packages/core/tui/src/services/registry.ts
|
|
51680
|
+
function describe(error) {
|
|
51681
|
+
if (error instanceof Error && error.message) return error.message.split(/\r?\n/u)[0].slice(0, KXM_TUI_LIMITS.detailMax);
|
|
51682
|
+
return String(error).split(/\r?\n/u)[0].slice(0, KXM_TUI_LIMITS.detailMax);
|
|
51683
|
+
}
|
|
51684
|
+
function normalizeSource(source) {
|
|
51685
|
+
if (!/^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/u.test(source) || source.length > KXM_TUI_LIMITS.sourceMax) {
|
|
51686
|
+
throw new Error(`invalid kxm tui surface source: ${source.slice(0, 64)}`);
|
|
51687
|
+
}
|
|
51688
|
+
return source;
|
|
51689
|
+
}
|
|
51690
|
+
function createKxmTuiRegistry(options = {}) {
|
|
51691
|
+
const contributions = /* @__PURE__ */ new Map();
|
|
51692
|
+
const notices = /* @__PURE__ */ new Map();
|
|
51693
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
51694
|
+
const published = /* @__PURE__ */ new Map();
|
|
51695
|
+
const issues = /* @__PURE__ */ new Map();
|
|
51696
|
+
let generation = 0;
|
|
51697
|
+
let disposed = false;
|
|
51698
|
+
let epoch = 0;
|
|
51699
|
+
const report2 = (error, context) => {
|
|
51700
|
+
if (options.onInternalError) options.onInternalError(error, context);
|
|
51701
|
+
};
|
|
51702
|
+
const emit = (event) => {
|
|
51703
|
+
for (const listener of Array.from(listeners)) {
|
|
51704
|
+
try {
|
|
51705
|
+
listener(event);
|
|
51706
|
+
} catch (error) {
|
|
51707
|
+
report2(error, "listener");
|
|
51708
|
+
}
|
|
51709
|
+
}
|
|
51710
|
+
};
|
|
51711
|
+
function readOwner(source, force) {
|
|
51712
|
+
if (!force) {
|
|
51713
|
+
const cached = published.get(source);
|
|
51714
|
+
if (cached) return cached;
|
|
51715
|
+
}
|
|
51716
|
+
const contribution = contributions.get(source);
|
|
51717
|
+
if (!contribution) {
|
|
51718
|
+
published.set(source, []);
|
|
51719
|
+
return [];
|
|
51720
|
+
}
|
|
51721
|
+
let sections;
|
|
51722
|
+
try {
|
|
51723
|
+
const raw = contribution.listSections();
|
|
51724
|
+
const ownerIssues = validateKxmTuiSurface(raw);
|
|
51725
|
+
if (ownerIssues.length > 0) {
|
|
51726
|
+
issues.set(source, ownerIssues);
|
|
51727
|
+
sections = raw.filter((section) => Boolean(section) && typeof section === "object").map((section) => ({
|
|
51728
|
+
...section,
|
|
51729
|
+
notice: section.notice ?? `published surface was refused: ${ownerIssues[0].code} at ${ownerIssues[0].path}`,
|
|
51730
|
+
noticeLevel: "error"
|
|
51731
|
+
}));
|
|
51732
|
+
} else {
|
|
51733
|
+
issues.delete(source);
|
|
51734
|
+
sections = raw;
|
|
51735
|
+
}
|
|
51736
|
+
} catch (error) {
|
|
51737
|
+
issues.set(source, [{ path: source, code: "list_sections_failed", message: describe(error) }]);
|
|
51738
|
+
report2(error, `listSections:${source}`);
|
|
51739
|
+
sections = [{ source, id: "error", title: source, order: 900, notice: describe(error), noticeLevel: "error", fields: [] }];
|
|
51740
|
+
}
|
|
51741
|
+
const pending = notices.get(source);
|
|
51742
|
+
const withNotice = pending !== void 0 && sections.length > 0 ? sections.map((section, index) => index === 0 ? { ...section, notice: pending, noticeLevel: "error" } : section) : sections;
|
|
51743
|
+
published.set(source, withNotice);
|
|
51744
|
+
return withNotice;
|
|
51745
|
+
}
|
|
51746
|
+
function merged() {
|
|
51747
|
+
const all = [];
|
|
51748
|
+
for (const source of contributions.keys()) all.push(...readOwner(source, false));
|
|
51749
|
+
return all.slice().sort((a, b2) => a.order - b2.order || a.source.localeCompare(b2.source) || a.title.localeCompare(b2.title));
|
|
51750
|
+
}
|
|
51751
|
+
return {
|
|
51752
|
+
register(contribution) {
|
|
51753
|
+
if (disposed) throw new Error("kxm tui registry is disposed");
|
|
51754
|
+
const source = normalizeSource(contribution.source);
|
|
51755
|
+
notices.delete(source);
|
|
51756
|
+
published.delete(source);
|
|
51757
|
+
issues.delete(source);
|
|
51758
|
+
contributions.set(source, { ...contribution, source });
|
|
51759
|
+
emit({ reason: "publish", source });
|
|
51760
|
+
},
|
|
51761
|
+
unregister(source) {
|
|
51762
|
+
contributions.delete(source);
|
|
51763
|
+
published.delete(source);
|
|
51764
|
+
issues.delete(source);
|
|
51765
|
+
notices.delete(source);
|
|
51766
|
+
emit({ reason: "publish", source });
|
|
51767
|
+
},
|
|
51768
|
+
getSections() {
|
|
51769
|
+
return merged();
|
|
51770
|
+
},
|
|
51771
|
+
getIssues() {
|
|
51772
|
+
return [...issues.values()].flat();
|
|
51773
|
+
},
|
|
51774
|
+
async invoke(invocation) {
|
|
51775
|
+
if (disposed) return { ok: false, message: "kxm tui registry is disposed" };
|
|
51776
|
+
const contribution = contributions.get(invocation.source);
|
|
51777
|
+
if (!contribution) {
|
|
51778
|
+
emit({ reason: "refused", source: invocation.source, message: "unknown owner" });
|
|
51779
|
+
return { ok: false, message: `${invocation.source} does not own a surface here` };
|
|
51780
|
+
}
|
|
51781
|
+
const handler = contribution.handlers[invocation.action];
|
|
51782
|
+
if (!handler) {
|
|
51783
|
+
const message = `${invocation.source} does not accept "${invocation.action}"`;
|
|
51784
|
+
emit({ reason: "refused", source: invocation.source, message });
|
|
51785
|
+
return { ok: false, message };
|
|
51786
|
+
}
|
|
51787
|
+
const currentEpoch = epoch;
|
|
51788
|
+
try {
|
|
51789
|
+
await handler({
|
|
51790
|
+
sectionId: invocation.sectionId,
|
|
51791
|
+
fieldId: invocation.fieldId,
|
|
51792
|
+
action: invocation.action,
|
|
51793
|
+
...invocation.value === void 0 ? {} : { value: invocation.value }
|
|
51794
|
+
});
|
|
51795
|
+
} catch (error) {
|
|
51796
|
+
const failure = contribution.describeError?.(error, invocation.action) ?? describe(error);
|
|
51797
|
+
notices.set(invocation.source, failure);
|
|
51798
|
+
emit({ reason: "failed", source: invocation.source, message: failure });
|
|
51799
|
+
readOwner(invocation.source, true);
|
|
51800
|
+
generation += 1;
|
|
51801
|
+
return { ok: false, message: failure };
|
|
51802
|
+
}
|
|
51803
|
+
if (currentEpoch !== epoch) {
|
|
51804
|
+
readOwner(invocation.source, true);
|
|
51805
|
+
return { ok: false, message: "stale write result was discarded" };
|
|
51806
|
+
}
|
|
51807
|
+
notices.delete(invocation.source);
|
|
51808
|
+
readOwner(invocation.source, true);
|
|
51809
|
+
generation += 1;
|
|
51810
|
+
emit({ reason: "publish", source: invocation.source });
|
|
51811
|
+
return { ok: true };
|
|
51812
|
+
},
|
|
51813
|
+
subscribe(listener) {
|
|
51814
|
+
listeners.add(listener);
|
|
51815
|
+
return () => listeners.delete(listener);
|
|
51816
|
+
},
|
|
51817
|
+
getGeneration() {
|
|
51818
|
+
return generation;
|
|
51819
|
+
},
|
|
51820
|
+
hasSource(source) {
|
|
51821
|
+
return contributions.has(source);
|
|
51822
|
+
},
|
|
51823
|
+
listSources() {
|
|
51824
|
+
return [...contributions.keys()];
|
|
51825
|
+
},
|
|
51826
|
+
dispose() {
|
|
51827
|
+
if (disposed) return;
|
|
51828
|
+
disposed = true;
|
|
51829
|
+
epoch += 1;
|
|
51830
|
+
listeners.clear();
|
|
51831
|
+
contributions.clear();
|
|
51832
|
+
published.clear();
|
|
51833
|
+
issues.clear();
|
|
51834
|
+
notices.clear();
|
|
51835
|
+
}
|
|
51836
|
+
};
|
|
51837
|
+
}
|
|
51838
|
+
|
|
51839
|
+
// packages/core/tui/src/adapters/terminal.ts
|
|
51840
|
+
function renderKxmTuiPanelFrame(input) {
|
|
51841
|
+
const theme = input.theme ?? createPlainKxmTuiTheme();
|
|
51842
|
+
const lines = renderKxmTuiPanel(
|
|
51843
|
+
input.registry.getSections(),
|
|
51844
|
+
initialKxmTuiPanelState(input.registry.getSections()),
|
|
51845
|
+
theme,
|
|
51846
|
+
{ title: input.title, footer: input.footer ?? "non-interactive frame", nowMs: input.nowMs ?? Date.now() },
|
|
51847
|
+
input.width ?? 100
|
|
51848
|
+
);
|
|
51849
|
+
return `${lines.map((line) => stripTerminalSequences(line)).join("\n")}
|
|
51850
|
+
`;
|
|
51851
|
+
}
|
|
51852
|
+
async function runKxmTuiPanel(input) {
|
|
51853
|
+
const theme = input.theme ?? createDefaultKxmTuiTheme();
|
|
51854
|
+
const tty = input.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
51855
|
+
if (!tty) {
|
|
51856
|
+
input.stdout(renderKxmTuiPanelFrame({
|
|
51857
|
+
registry: input.registry,
|
|
51858
|
+
title: input.title,
|
|
51859
|
+
theme,
|
|
51860
|
+
...input.now === void 0 ? {} : { nowMs: input.now() },
|
|
51861
|
+
...input.breadcrumb ? { footer: input.breadcrumb } : {}
|
|
51862
|
+
}));
|
|
51863
|
+
return 0;
|
|
51864
|
+
}
|
|
51865
|
+
const terminal = input.terminal ?? new ProcessTerminal();
|
|
51866
|
+
const tui = new TuiAltScreen(terminal, false, void 0, { mouse: true });
|
|
51867
|
+
let timer;
|
|
51868
|
+
let stopped = false;
|
|
51869
|
+
let finished = () => void 0;
|
|
51870
|
+
const done = new Promise((resolve35) => {
|
|
51871
|
+
finished = resolve35;
|
|
51872
|
+
});
|
|
51873
|
+
const stop = () => {
|
|
51874
|
+
if (stopped) return;
|
|
51875
|
+
stopped = true;
|
|
51876
|
+
if (timer) clearInterval(timer);
|
|
51877
|
+
input.abort?.removeEventListener("abort", stop);
|
|
51878
|
+
panel.dispose();
|
|
51879
|
+
tui.stop();
|
|
51880
|
+
finished();
|
|
51881
|
+
};
|
|
51882
|
+
const panel = new KxmTuiPanelComponent({
|
|
51883
|
+
registry: input.registry,
|
|
51884
|
+
theme,
|
|
51885
|
+
title: input.title,
|
|
51886
|
+
...input.breadcrumb ? { breadcrumb: input.breadcrumb } : {},
|
|
51887
|
+
...input.helpLines ? { helpLines: input.helpLines } : {},
|
|
51888
|
+
requestRender: () => tui.requestRender(),
|
|
51889
|
+
onQuit: stop,
|
|
51890
|
+
getWidth: () => terminal.columns
|
|
51891
|
+
});
|
|
51892
|
+
input.abort?.addEventListener("abort", stop, { once: true });
|
|
51893
|
+
tui.setLayoutRoot(panel.root);
|
|
51894
|
+
tui.setFocus(panel);
|
|
51895
|
+
tui.start();
|
|
51896
|
+
timer = setInterval(() => panel.refresh(), input.pollMs ?? 2e3);
|
|
51897
|
+
timer.unref?.();
|
|
51898
|
+
await done;
|
|
51899
|
+
return stopped && input.abort?.aborted ? 130 : 0;
|
|
51900
|
+
}
|
|
50384
51901
|
|
|
50385
51902
|
// plugins/kxm/src/external-effects.ts
|
|
50386
51903
|
init_database();
|
|
@@ -50399,9 +51916,9 @@ var SHARED_EFFECT_KINDS = Object.freeze([
|
|
|
50399
51916
|
// plugins/kxm/src/local-snapshot.ts
|
|
50400
51917
|
init_sqlite();
|
|
50401
51918
|
init_telemetry();
|
|
50402
|
-
import { existsSync as
|
|
51919
|
+
import { existsSync as existsSync37, readdirSync as readdirSync12, readFileSync as readFileSync33 } from "node:fs";
|
|
50403
51920
|
import { homedir as homedir6 } from "node:os";
|
|
50404
|
-
import { isAbsolute as
|
|
51921
|
+
import { isAbsolute as isAbsolute11, join as join46, resolve as resolve26 } from "node:path";
|
|
50405
51922
|
var DEFAULT_BUSY_TIMEOUT_MS = 5e3;
|
|
50406
51923
|
var RUNTIME_PROJECT_KEY = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
|
|
50407
51924
|
function processExists2(pid) {
|
|
@@ -50526,36 +52043,36 @@ function readOpenMessageMetadata(database, where = OPEN_MESSAGE_WHERE, params =
|
|
|
50526
52043
|
return messages;
|
|
50527
52044
|
}
|
|
50528
52045
|
function resolveKxmSnapshotPaths(cwd, env = process.env) {
|
|
50529
|
-
const stateDir = env.KXM_STATE_DIR?.trim() ||
|
|
52046
|
+
const stateDir = env.KXM_STATE_DIR?.trim() || join46(cwd, ".kxm", "state");
|
|
50530
52047
|
const configured = env.KXM_DATA_PATH?.trim();
|
|
50531
|
-
const dataPath = configured ? resolve26(cwd, configured) :
|
|
52048
|
+
const dataPath = configured ? resolve26(cwd, configured) : join46(stateDir, "kxm.db");
|
|
50532
52049
|
return { dataPath, stateDir };
|
|
50533
52050
|
}
|
|
50534
52051
|
function resolveKxmStateRoot(stateDir, options) {
|
|
50535
|
-
if (options?.kxmStateRoot &&
|
|
52052
|
+
if (options?.kxmStateRoot && existsSync37(options.kxmStateRoot)) {
|
|
50536
52053
|
return resolve26(options.kxmStateRoot);
|
|
50537
52054
|
}
|
|
50538
|
-
if (
|
|
52055
|
+
if (existsSync37(join46(stateDir, "runtime", "registry.db")) || existsSync37(join46(stateDir, "runtime", "projects"))) {
|
|
50539
52056
|
return stateDir;
|
|
50540
52057
|
}
|
|
50541
52058
|
const env = options?.env ?? process.env;
|
|
50542
52059
|
const explicit = env.KXM_STATE_HOME?.trim() || env.KXM_USER_STATE_DIR?.trim() || env.KXM_STATE_ROOT?.trim();
|
|
50543
|
-
if (explicit &&
|
|
52060
|
+
if (explicit && isAbsolute11(explicit) && existsSync37(resolve26(explicit))) {
|
|
50544
52061
|
return resolve26(explicit);
|
|
50545
52062
|
}
|
|
50546
52063
|
let base;
|
|
50547
52064
|
if (process.platform === "win32") {
|
|
50548
52065
|
const localAppData = env.LOCALAPPDATA?.trim();
|
|
50549
|
-
base = localAppData &&
|
|
52066
|
+
base = localAppData && isAbsolute11(localAppData) ? localAppData : join46(homedir6(), "AppData", "Local");
|
|
50550
52067
|
base = resolve26(base, "KXM");
|
|
50551
52068
|
} else if (process.platform === "darwin") {
|
|
50552
52069
|
base = resolve26(homedir6(), "Library", "Application Support", "KXM");
|
|
50553
52070
|
} else {
|
|
50554
52071
|
const xdgState = env.XDG_STATE_HOME?.trim();
|
|
50555
|
-
base = xdgState &&
|
|
52072
|
+
base = xdgState && isAbsolute11(xdgState) ? xdgState : join46(homedir6(), ".local", "state");
|
|
50556
52073
|
base = resolve26(base, "kxm");
|
|
50557
52074
|
}
|
|
50558
|
-
if (
|
|
52075
|
+
if (existsSync37(base)) return base;
|
|
50559
52076
|
return void 0;
|
|
50560
52077
|
}
|
|
50561
52078
|
function readRuntimeRuns(eventDb, projectId) {
|
|
@@ -50588,7 +52105,7 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
50588
52105
|
let legacyRuns = [];
|
|
50589
52106
|
let legacyRunTotal = 0;
|
|
50590
52107
|
let plans = [];
|
|
50591
|
-
if (
|
|
52108
|
+
if (existsSync37(dataPath)) {
|
|
50592
52109
|
hasLegacy = true;
|
|
50593
52110
|
const database = openReadOnlyDatabase(dataPath);
|
|
50594
52111
|
try {
|
|
@@ -50619,11 +52136,11 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
50619
52136
|
let kxmRunTotal = 0;
|
|
50620
52137
|
const kxmStateRoot = scope && !scope.runtimeProjectId ? void 0 : resolveKxmStateRoot(stateDir, options);
|
|
50621
52138
|
if (kxmStateRoot) {
|
|
50622
|
-
const runtimeDir =
|
|
50623
|
-
const registryDbPath =
|
|
50624
|
-
const projectsDir =
|
|
52139
|
+
const runtimeDir = join46(kxmStateRoot, "runtime");
|
|
52140
|
+
const registryDbPath = join46(runtimeDir, "registry.db");
|
|
52141
|
+
const projectsDir = join46(runtimeDir, "projects");
|
|
50625
52142
|
const projectKeys = /* @__PURE__ */ new Set();
|
|
50626
|
-
if (
|
|
52143
|
+
if (existsSync37(registryDbPath)) {
|
|
50627
52144
|
hasKxm = true;
|
|
50628
52145
|
try {
|
|
50629
52146
|
const regDb = openReadOnlyDatabase(registryDbPath);
|
|
@@ -50639,7 +52156,7 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
50639
52156
|
} catch {
|
|
50640
52157
|
}
|
|
50641
52158
|
}
|
|
50642
|
-
if (!scope &&
|
|
52159
|
+
if (!scope && existsSync37(projectsDir)) {
|
|
50643
52160
|
try {
|
|
50644
52161
|
for (const entry of readdirSync12(projectsDir, { withFileTypes: true })) {
|
|
50645
52162
|
if (entry.isDirectory()) {
|
|
@@ -50651,8 +52168,8 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
50651
52168
|
}
|
|
50652
52169
|
for (const key of projectKeys) {
|
|
50653
52170
|
if (scope && !RUNTIME_PROJECT_KEY.test(key)) continue;
|
|
50654
|
-
const eventDbPath =
|
|
50655
|
-
if (
|
|
52171
|
+
const eventDbPath = join46(projectsDir, key, "run-events.db");
|
|
52172
|
+
if (existsSync37(eventDbPath)) {
|
|
50656
52173
|
hasKxm = true;
|
|
50657
52174
|
try {
|
|
50658
52175
|
const eventDb = openReadOnlyDatabase(eventDbPath);
|
|
@@ -50670,10 +52187,10 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
50670
52187
|
}
|
|
50671
52188
|
}
|
|
50672
52189
|
const pids = [];
|
|
50673
|
-
if (
|
|
52190
|
+
if (existsSync37(stateDir)) {
|
|
50674
52191
|
for (const file of readdirSync12(stateDir).filter((name) => name.endsWith(".pid"))) {
|
|
50675
52192
|
try {
|
|
50676
|
-
const record = JSON.parse(
|
|
52193
|
+
const record = JSON.parse(readFileSync33(join46(stateDir, file), "utf8"));
|
|
50677
52194
|
pids.push({
|
|
50678
52195
|
file,
|
|
50679
52196
|
...record.role ? { role: record.role } : {},
|
|
@@ -50712,8 +52229,8 @@ function loadLocalMeshSnapshot(dataPath, stateDir, options) {
|
|
|
50712
52229
|
} else {
|
|
50713
52230
|
source = "legacy";
|
|
50714
52231
|
}
|
|
50715
|
-
const telemetryFile =
|
|
50716
|
-
const spend =
|
|
52232
|
+
const telemetryFile = join46(stateDir, "telemetry.jsonl");
|
|
52233
|
+
const spend = existsSync37(telemetryFile) ? readRoutingRecords(telemetryFile) : [];
|
|
50717
52234
|
return {
|
|
50718
52235
|
source,
|
|
50719
52236
|
agents: agents.sort((a, b2) => Number(b2.online) - Number(a.online) || a.name.localeCompare(b2.name)),
|
|
@@ -50847,7 +52364,7 @@ function spawnDegradeWorktree(repoRoot, runId, options) {
|
|
|
50847
52364
|
const cleanRunId = runId.replace(/^run_/, "");
|
|
50848
52365
|
const desc = options?.description ? slugifyBranchPart(options.description, 30) : "degrade";
|
|
50849
52366
|
const branchName = `kxm/run-${cleanRunId}-${desc}`;
|
|
50850
|
-
const relativeWtPath =
|
|
52367
|
+
const relativeWtPath = join47(".kxm", "worktrees", `run-${cleanRunId}-${desc}`);
|
|
50851
52368
|
const absoluteWtPath = resolve27(repoRoot, relativeWtPath);
|
|
50852
52369
|
const jumpCommand = `cd "${absoluteWtPath}"`;
|
|
50853
52370
|
try {
|
|
@@ -51616,20 +53133,20 @@ async function runMeshTui(input) {
|
|
|
51616
53133
|
// plugins/kxm/src/session-work.ts
|
|
51617
53134
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
51618
53135
|
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
51619
|
-
import { existsSync as
|
|
51620
|
-
import { join as
|
|
53136
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync28, readFileSync as readFileSync34, renameSync as renameSync9, writeFileSync as writeFileSync24 } from "node:fs";
|
|
53137
|
+
import { join as join48 } from "node:path";
|
|
51621
53138
|
init_hub_binding();
|
|
51622
53139
|
init_telemetry();
|
|
51623
53140
|
var MAX_SESSION_BRIEF_TASKS = 5;
|
|
51624
53141
|
var MAX_SESSION_BRIEF_PLANS = 5;
|
|
51625
53142
|
var SESSION_BRIEF_SCHEMA = "kxm.session-brief.v1";
|
|
51626
53143
|
var DEFAULT_SESSION_BRIEF_STALE_SECONDS = 5;
|
|
51627
|
-
function
|
|
53144
|
+
function hubStatusPart(hub) {
|
|
51628
53145
|
const suffix = hub?.scope === "remote" ? "/remote" : "";
|
|
51629
|
-
if (hub?.state === "
|
|
51630
|
-
if (hub?.state === "
|
|
51631
|
-
if (hub?.
|
|
51632
|
-
return
|
|
53146
|
+
if (hub?.state === "off" || hub?.state === void 0 && hub?.online === false) return `hub:off${suffix}`;
|
|
53147
|
+
if (hub?.state === "unknown") return `hub:unknown${suffix}`;
|
|
53148
|
+
if (hub?.scope === "remote") return "hub:remote";
|
|
53149
|
+
return void 0;
|
|
51633
53150
|
}
|
|
51634
53151
|
function truncate(value, width) {
|
|
51635
53152
|
const text = value.replace(/\s+/g, " ").trim();
|
|
@@ -51705,40 +53222,34 @@ function readGitShip(cwd) {
|
|
|
51705
53222
|
return void 0;
|
|
51706
53223
|
}
|
|
51707
53224
|
}
|
|
51708
|
-
function formatSessionStatusLine(stats, current, hub, ship, updateLatest
|
|
51709
|
-
const
|
|
51710
|
-
|
|
51711
|
-
|
|
51712
|
-
|
|
51713
|
-
|
|
51714
|
-
|
|
51715
|
-
return finalLine.length <= 80 ? finalLine : `${finalLine.slice(0, 79)}\u2026`;
|
|
51716
|
-
}
|
|
51717
|
-
const parts = [];
|
|
51718
|
-
if (current?.kind === "task") parts.push(`${head} ${current.detail}`);
|
|
51719
|
-
else if (current?.kind === "plan") parts.push(`${head} plan ${truncate(current.detail, 36)}`);
|
|
51720
|
-
else parts.push(head);
|
|
51721
|
-
parts.push(`${stats.activeTasks} task${stats.activeTasks === 1 ? "" : "s"}`);
|
|
53225
|
+
function formatSessionStatusLine(stats, current, hub, ship, updateLatest) {
|
|
53226
|
+
const parts = ["kxm"];
|
|
53227
|
+
const hubPart = hubStatusPart(hub);
|
|
53228
|
+
if (hubPart) parts.push(hubPart);
|
|
53229
|
+
if (current?.kind === "task") parts.push(current.detail);
|
|
53230
|
+
else if (current?.kind === "plan") parts.push(`plan ${truncate(current.detail, 36)}`);
|
|
53231
|
+
if (stats.activeTasks > 0) parts.push(`${stats.activeTasks} task${stats.activeTasks === 1 ? "" : "s"}`);
|
|
51722
53232
|
if (stats.waitingTasks > 0) parts.push(`${stats.waitingTasks} waiting`);
|
|
51723
|
-
parts.push(`${stats.planCount} plan${stats.planCount === 1 ? "" : "s"}`);
|
|
53233
|
+
if (stats.planCount > 0) parts.push(`${stats.planCount} plan${stats.planCount === 1 ? "" : "s"}`);
|
|
51724
53234
|
if (stats.inbox > 0) parts.push(`inbox ${stats.inbox}`);
|
|
51725
53235
|
if (ship?.dirty) parts.push("dirty");
|
|
51726
53236
|
else if (ship && ship.ahead > 0) parts.push(`${ship.ahead} local`);
|
|
51727
|
-
if (cost) parts.push(cost);
|
|
51728
53237
|
if (updateLatest) parts.push(`upd ${updateLatest}`);
|
|
51729
|
-
|
|
51730
|
-
return line.length <= 80 ? line : `${line.slice(0, 79)}\u2026`;
|
|
53238
|
+
return truncate(parts.join(" \xB7 "), 80);
|
|
51731
53239
|
}
|
|
51732
53240
|
function formatSessionWidget(stats, current, hub, ship, updateLatest, cost) {
|
|
51733
|
-
const
|
|
51734
|
-
const
|
|
51735
|
-
|
|
51736
|
-
|
|
53241
|
+
const suffix = hub?.scope === "remote" ? "/remote" : "";
|
|
53242
|
+
const hubMark = hub?.state === "on" || hub?.state === void 0 && hub?.online === true ? `hub:on${suffix}` : hub?.state === "off" || hub?.state === void 0 && hub?.online === false ? `hub:off${suffix}` : hub?.state === "unknown" ? `hub:unknown${suffix}` : void 0;
|
|
53243
|
+
const summary = ["KXM", hubMark];
|
|
53244
|
+
if (stats.activeTasks > 0) summary.push(`${stats.activeTasks} task${stats.activeTasks === 1 ? "" : "s"}`);
|
|
53245
|
+
if (stats.waitingTasks > 0) summary.push(`${stats.waitingTasks} waiting`);
|
|
53246
|
+
if (stats.planCount > 0) summary.push(`${stats.planCount} plan${stats.planCount === 1 ? "" : "s"}`);
|
|
53247
|
+
if (stats.inbox > 0) summary.push(`inbox ${stats.inbox}`);
|
|
53248
|
+
const lines = [summary.filter(Boolean).join(" \xB7 ")];
|
|
51737
53249
|
if (current) lines.push(`now ${current.kind} ${truncate(current.detail, 56)}`);
|
|
51738
|
-
else if (stats.latestPlan) lines.push(`plan
|
|
51739
|
-
|
|
51740
|
-
lines.push(
|
|
51741
|
-
if (cost) lines.push(`cost ${cost}`);
|
|
53250
|
+
else if (stats.latestPlan) lines.push(`plan ${truncate(stats.latestPlan, 60)}`);
|
|
53251
|
+
if (ship?.dirty || ship && ship.ahead > 0) lines.push(formatShipLine(ship));
|
|
53252
|
+
if (cost && cost !== "unknown") lines.push(`cost ${cost}`);
|
|
51742
53253
|
if (updateLatest) lines.push(`update ${updateLatest} available \xB7 kxm update --kxm`);
|
|
51743
53254
|
return lines;
|
|
51744
53255
|
}
|
|
@@ -51758,7 +53269,7 @@ function buildSessionBrief(snapshot, current, hub, ship, updateLatest, cost, ses
|
|
|
51758
53269
|
const tasks = uniqueLabels(recentTasks(snapshot.runs).map(taskItem));
|
|
51759
53270
|
const plans = uniqueLabels(snapshot.plans.slice(0, MAX_SESSION_BRIEF_PLANS).map(planItem));
|
|
51760
53271
|
const resolvedHub = hub ?? { state: "off", evidence: "unconfigured", online: false };
|
|
51761
|
-
const statusLine = formatSessionStatusLine(stats, current, resolvedHub, ship, updateLatest
|
|
53272
|
+
const statusLine = formatSessionStatusLine(stats, current, resolvedHub, ship, updateLatest);
|
|
51762
53273
|
const widgetLines = formatSessionWidget(stats, current, resolvedHub, ship, updateLatest, cost);
|
|
51763
53274
|
return {
|
|
51764
53275
|
schema: SESSION_BRIEF_SCHEMA,
|
|
@@ -51776,10 +53287,10 @@ function buildSessionBrief(snapshot, current, hub, ship, updateLatest, cost, ses
|
|
|
51776
53287
|
};
|
|
51777
53288
|
}
|
|
51778
53289
|
function readCachedSessionBrief(stateDir) {
|
|
51779
|
-
const file =
|
|
53290
|
+
const file = join48(stateDir, "session-brief.json");
|
|
51780
53291
|
try {
|
|
51781
|
-
if (!
|
|
51782
|
-
const raw =
|
|
53292
|
+
if (!existsSync38(file)) return void 0;
|
|
53293
|
+
const raw = readFileSync34(file, "utf8");
|
|
51783
53294
|
const parsed = JSON.parse(raw);
|
|
51784
53295
|
if (parsed && parsed.schema === "kxm.session-brief.v1" && typeof parsed.generatedAt === "string") {
|
|
51785
53296
|
const ageMs = Date.now() - Date.parse(parsed.generatedAt);
|
|
@@ -51795,7 +53306,7 @@ function readCachedSessionBrief(stateDir) {
|
|
|
51795
53306
|
function writeCachedSessionBrief(stateDir, brief) {
|
|
51796
53307
|
try {
|
|
51797
53308
|
mkdirSync28(stateDir, { recursive: true, mode: 448 });
|
|
51798
|
-
const file =
|
|
53309
|
+
const file = join48(stateDir, "session-brief.json");
|
|
51799
53310
|
const tmp = `${file}.tmp.${randomUUID13().slice(0, 8)}`;
|
|
51800
53311
|
writeFileSync24(tmp, JSON.stringify(brief, null, 2), { encoding: "utf8", mode: 384 });
|
|
51801
53312
|
renameSync9(tmp, file);
|
|
@@ -51804,8 +53315,8 @@ function writeCachedSessionBrief(stateDir, brief) {
|
|
|
51804
53315
|
}
|
|
51805
53316
|
function estimateSessionCost(stateDir) {
|
|
51806
53317
|
try {
|
|
51807
|
-
const telemetryFile =
|
|
51808
|
-
if (!
|
|
53318
|
+
const telemetryFile = join48(stateDir, "telemetry.jsonl");
|
|
53319
|
+
if (!existsSync38(telemetryFile)) return void 0;
|
|
51809
53320
|
const records = readRoutingRecords(telemetryFile);
|
|
51810
53321
|
if (records.length === 0) return void 0;
|
|
51811
53322
|
let totalCost = 0;
|
|
@@ -51822,7 +53333,7 @@ function estimateSessionCost(stateDir) {
|
|
|
51822
53333
|
latestModel = r.effectiveModel ?? r.requestedModel ?? latestModel;
|
|
51823
53334
|
latestHarness = r.harness ?? latestHarness;
|
|
51824
53335
|
}
|
|
51825
|
-
if (!hasMetered) return
|
|
53336
|
+
if (!hasMetered) return void 0;
|
|
51826
53337
|
const route = latestHarness && latestModel ? `${latestHarness}/${latestModel}` : latestModel;
|
|
51827
53338
|
return `$${totalCost.toFixed(2)} sess${route ? ` \xB7 ${route}` : ""}`;
|
|
51828
53339
|
} catch {
|
|
@@ -51859,8 +53370,7 @@ function loadSessionBrief(cwd, env = process.env, current, hub, options = {}) {
|
|
|
51859
53370
|
current,
|
|
51860
53371
|
effectiveHub,
|
|
51861
53372
|
options.ship,
|
|
51862
|
-
options.updateLatest
|
|
51863
|
-
effectiveCost
|
|
53373
|
+
options.updateLatest
|
|
51864
53374
|
);
|
|
51865
53375
|
const widgetLines = formatSessionWidget(
|
|
51866
53376
|
cached.stats,
|
|
@@ -51925,8 +53435,7 @@ async function loadSessionBriefAsync(cwd, env = process.env, current, hub, optio
|
|
|
51925
53435
|
current,
|
|
51926
53436
|
effectiveHub,
|
|
51927
53437
|
options.ship,
|
|
51928
|
-
void 0
|
|
51929
|
-
effectiveCost
|
|
53438
|
+
void 0
|
|
51930
53439
|
);
|
|
51931
53440
|
const widgetLines = formatSessionWidget(
|
|
51932
53441
|
cached.stats,
|
|
@@ -51977,16 +53486,16 @@ init_hub_binding();
|
|
|
51977
53486
|
|
|
51978
53487
|
// plugins/kxm/src/kxm-install-kind.ts
|
|
51979
53488
|
init_bindings();
|
|
51980
|
-
import { existsSync as
|
|
51981
|
-
import { dirname as dirname21, join as
|
|
53489
|
+
import { existsSync as existsSync39, readFileSync as readFileSync35 } from "node:fs";
|
|
53490
|
+
import { dirname as dirname21, join as join49 } from "node:path";
|
|
51982
53491
|
function kxmSourceCheckoutRoot(dir) {
|
|
51983
|
-
if (!
|
|
53492
|
+
if (!existsSync39(join49(dir, ".git"))) return void 0;
|
|
51984
53493
|
if (!packageIsKxm(dir)) return void 0;
|
|
51985
53494
|
return dir;
|
|
51986
53495
|
}
|
|
51987
53496
|
function packageIsKxm(root) {
|
|
51988
53497
|
try {
|
|
51989
|
-
const pkg = JSON.parse(
|
|
53498
|
+
const pkg = JSON.parse(readFileSync35(join49(root, "package.json"), "utf8"));
|
|
51990
53499
|
return pkg.name === "@kontextmind/kxm";
|
|
51991
53500
|
} catch {
|
|
51992
53501
|
return false;
|
|
@@ -52035,12 +53544,12 @@ function report(kind, root) {
|
|
|
52035
53544
|
return { kind, root, instruction: installKindInstruction(kind, root) };
|
|
52036
53545
|
}
|
|
52037
53546
|
function classifyInstallRoot(probe) {
|
|
52038
|
-
const piGitRoot =
|
|
53547
|
+
const piGitRoot = join49(probe.homeDir, ".pi", "agent", "git");
|
|
52039
53548
|
if (isUnder(probe.repoRoot, piGitRoot, probe.platform)) return report("pi-git", probe.repoRoot);
|
|
52040
|
-
const claudeHome = probe.env.CLAUDE_CONFIG_DIR?.trim() ||
|
|
52041
|
-
const pluginsRoot =
|
|
53549
|
+
const claudeHome = probe.env.CLAUDE_CONFIG_DIR?.trim() || join49(probe.homeDir, ".claude");
|
|
53550
|
+
const pluginsRoot = join49(claudeHome, "plugins");
|
|
52042
53551
|
if (isUnder(probe.moduleDir, pluginsRoot, probe.platform)) return report("claude-marketplace", probe.moduleDir);
|
|
52043
|
-
const hasGit =
|
|
53552
|
+
const hasGit = existsSync39(join49(probe.repoRoot, ".git"));
|
|
52044
53553
|
if (hasGit && packageIsKxm(probe.repoRoot)) return report("source", probe.repoRoot);
|
|
52045
53554
|
if (isNpmPackageLayout(probe.repoRoot, probe.platform) && packageIsKxm(probe.repoRoot) && !hasGit) {
|
|
52046
53555
|
return report("npm-package", probe.repoRoot);
|
|
@@ -52063,16 +53572,16 @@ function resolveInstallKind(probe, npmGlobalRoot) {
|
|
|
52063
53572
|
init_bindings();
|
|
52064
53573
|
|
|
52065
53574
|
// plugins/kxm/src/kxm-update-config.ts
|
|
52066
|
-
var
|
|
52067
|
-
import { existsSync as
|
|
52068
|
-
import { join as
|
|
53575
|
+
var import_yaml20 = __toESM(require_dist(), 1);
|
|
53576
|
+
import { existsSync as existsSync40, readFileSync as readFileSync36 } from "node:fs";
|
|
53577
|
+
import { join as join50 } from "node:path";
|
|
52069
53578
|
init_bindings();
|
|
52070
53579
|
function loadKxmUpdateConfig(env = process.env) {
|
|
52071
|
-
const path4 =
|
|
52072
|
-
if (!
|
|
53580
|
+
const path4 = join50(kxmUserStateRoot({ env }), "update.yaml");
|
|
53581
|
+
if (!existsSync40(path4)) return { schema: KXM_UPDATE_SCHEMA, auto: false, source: "npm" };
|
|
52073
53582
|
let parsed;
|
|
52074
53583
|
try {
|
|
52075
|
-
parsed = (0,
|
|
53584
|
+
parsed = (0, import_yaml20.parse)(readFileSync36(path4, "utf8"));
|
|
52076
53585
|
} catch {
|
|
52077
53586
|
throw new KxmUpdateConfigError("update.yaml is not valid YAML");
|
|
52078
53587
|
}
|
|
@@ -52100,8 +53609,8 @@ function loadKxmUpdateConfig(env = process.env) {
|
|
|
52100
53609
|
// plugins/kxm/src/session.ts
|
|
52101
53610
|
init_protocol();
|
|
52102
53611
|
init_envelope();
|
|
52103
|
-
import { existsSync as
|
|
52104
|
-
import { join as
|
|
53612
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync29, readFileSync as readFileSync37, writeFileSync as writeFileSync25 } from "node:fs";
|
|
53613
|
+
import { join as join51 } from "node:path";
|
|
52105
53614
|
var SESSION_SCHEMA = "kxm.session.v1";
|
|
52106
53615
|
var SessionConfigError = class extends Error {
|
|
52107
53616
|
code = "session_config_invalid";
|
|
@@ -52111,12 +53620,12 @@ var SessionConfigError = class extends Error {
|
|
|
52111
53620
|
}
|
|
52112
53621
|
};
|
|
52113
53622
|
function workflowAssetDirs(assetsDir, workflowId) {
|
|
52114
|
-
const root =
|
|
52115
|
-
return [root,
|
|
53623
|
+
const root = join51(assetsDir, "workflows", workflowId);
|
|
53624
|
+
return [root, join51(root, "inputs"), join51(root, "outputs"), join51(root, "generated")];
|
|
52116
53625
|
}
|
|
52117
53626
|
function sessionAssetDirs(assetsDir, sessionId) {
|
|
52118
|
-
const root =
|
|
52119
|
-
return [root,
|
|
53627
|
+
const root = join51(assetsDir, "sessions", sessionId);
|
|
53628
|
+
return [root, join51(root, "inputs"), join51(root, "outputs")];
|
|
52120
53629
|
}
|
|
52121
53630
|
function rosterNames(configDir) {
|
|
52122
53631
|
return [...loadRosterMap(configDir).values()].map((row) => String(row.name));
|
|
@@ -52166,7 +53675,7 @@ function workerFromRosterRow(name, row, project) {
|
|
|
52166
53675
|
function loadRosterMap(configDir) {
|
|
52167
53676
|
const byName = /* @__PURE__ */ new Map();
|
|
52168
53677
|
for (const [file, key] of [["agents.json", "agents"], ["gates.json", "gates"]]) {
|
|
52169
|
-
const path4 =
|
|
53678
|
+
const path4 = join51(configDir, file);
|
|
52170
53679
|
for (const row of loadRoster(path4, key)) {
|
|
52171
53680
|
const name = String(row.name).trim();
|
|
52172
53681
|
const lowered = name.toLowerCase();
|
|
@@ -52180,10 +53689,10 @@ function loadRosterMap(configDir) {
|
|
|
52180
53689
|
return byName;
|
|
52181
53690
|
}
|
|
52182
53691
|
function loadRoster(path4, key) {
|
|
52183
|
-
if (!
|
|
53692
|
+
if (!existsSync41(path4)) return [];
|
|
52184
53693
|
let parsed;
|
|
52185
53694
|
try {
|
|
52186
|
-
parsed = JSON.parse(
|
|
53695
|
+
parsed = JSON.parse(readFileSync37(path4, "utf8"));
|
|
52187
53696
|
} catch (error) {
|
|
52188
53697
|
throw new SessionConfigError(
|
|
52189
53698
|
`${key} roster at ${path4} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -52208,7 +53717,7 @@ function loadRoster(path4, key) {
|
|
|
52208
53717
|
});
|
|
52209
53718
|
}
|
|
52210
53719
|
function createSession(input) {
|
|
52211
|
-
const assetDir = input.mode === "workflow" && input.workflowId ?
|
|
53720
|
+
const assetDir = input.mode === "workflow" && input.workflowId ? join51("assets", "workflows", input.workflowId) : join51("assets", "sessions", input.id);
|
|
52212
53721
|
return {
|
|
52213
53722
|
schema: SESSION_SCHEMA,
|
|
52214
53723
|
id: input.id,
|
|
@@ -52221,8 +53730,8 @@ function createSession(input) {
|
|
|
52221
53730
|
};
|
|
52222
53731
|
}
|
|
52223
53732
|
function writeSession(assetsDir, session, dryRun = false) {
|
|
52224
|
-
const dir =
|
|
52225
|
-
const path4 =
|
|
53733
|
+
const dir = join51(assetsDir, "sessions", session.id);
|
|
53734
|
+
const path4 = join51(dir, "session.json");
|
|
52226
53735
|
if (!dryRun) {
|
|
52227
53736
|
mkdirSync29(dir, { recursive: true });
|
|
52228
53737
|
writeFileSync25(path4, `${JSON.stringify(session, null, 2)}
|
|
@@ -52264,9 +53773,9 @@ function moduleInstallRoot() {
|
|
|
52264
53773
|
}
|
|
52265
53774
|
}
|
|
52266
53775
|
function warnIgnoredProjectUpdateYaml(runtime) {
|
|
52267
|
-
const projectFile =
|
|
52268
|
-
if (!
|
|
52269
|
-
const userFile =
|
|
53776
|
+
const projectFile = join52(runtime.dirs.workspace, "update.yaml");
|
|
53777
|
+
if (!existsSync42(projectFile)) return;
|
|
53778
|
+
const userFile = join52(kxmUserStateRoot({ env: runtime.env }), "update.yaml");
|
|
52270
53779
|
runtime.io.stderr(`kxm: ignoring .kxm/update.yaml in ${runtime.dirs.workdir}; update settings are read only from ${userFile}
|
|
52271
53780
|
`);
|
|
52272
53781
|
}
|
|
@@ -52350,7 +53859,7 @@ async function cmdDash(runtime, options = {}) {
|
|
|
52350
53859
|
return 2;
|
|
52351
53860
|
}
|
|
52352
53861
|
const screen = requested;
|
|
52353
|
-
const dataPath = resolve28(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() ||
|
|
53862
|
+
const dataPath = resolve28(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join52(runtime.dirs.state, "kxm.db"));
|
|
52354
53863
|
if (runtime.dryRun) {
|
|
52355
53864
|
print(runtime.io, runtime.json, {
|
|
52356
53865
|
ok: true,
|
|
@@ -52405,7 +53914,7 @@ async function cmdHub(runtime) {
|
|
|
52405
53914
|
config = loadKxmUpdateConfig(runtime.env);
|
|
52406
53915
|
} catch (error) {
|
|
52407
53916
|
if (error instanceof KxmUpdateConfigError) {
|
|
52408
|
-
const yamlPath =
|
|
53917
|
+
const yamlPath = join52(kxmUserStateRoot({ env: runtime.env }), "update.yaml");
|
|
52409
53918
|
runtime.io.stderr(`kxm: ${error.message}; update check skipped; fix or remove ${yamlPath}
|
|
52410
53919
|
`);
|
|
52411
53920
|
} else {
|
|
@@ -52560,7 +54069,7 @@ async function cmdHubBind(runtime, rawUrl, options = {}) {
|
|
|
52560
54069
|
...cloudFields
|
|
52561
54070
|
};
|
|
52562
54071
|
const identity = resolveProjectIdentity(runtime.dirs.workdir, runtime.env);
|
|
52563
|
-
const configFile =
|
|
54072
|
+
const configFile = join52(findConfigRoot(runtime.dirs.workdir), ".kxm", "config.yaml");
|
|
52564
54073
|
const key = {
|
|
52565
54074
|
...keyOp ? { op: keyOp } : {},
|
|
52566
54075
|
...tokenEnv ? { env: tokenEnv } : {}
|
|
@@ -52720,7 +54229,7 @@ async function cmdWorker(runtime, options) {
|
|
|
52720
54229
|
return await (runtime.io.spawnWorker ?? ((launchEnv) => spawnScript("kxm-worker.mjs", launchEnv)))(extraEnv);
|
|
52721
54230
|
}
|
|
52722
54231
|
async function cmdStop(runtime, waitMsFlag) {
|
|
52723
|
-
const pids =
|
|
54232
|
+
const pids = existsSync42(runtime.dirs.state) ? readdirSync13(runtime.dirs.state).filter((name) => name.endsWith(".pid")) : [];
|
|
52724
54233
|
if (runtime.dryRun) {
|
|
52725
54234
|
print(runtime.io, runtime.json, { ok: true, command: "stop", dryRun: true, pidFiles: pids }, "would signal pid files");
|
|
52726
54235
|
return 0;
|
|
@@ -52735,7 +54244,7 @@ async function cmdStop(runtime, waitMsFlag) {
|
|
|
52735
54244
|
const orphans = [];
|
|
52736
54245
|
for (const file of pids) {
|
|
52737
54246
|
try {
|
|
52738
|
-
const record = JSON.parse(
|
|
54247
|
+
const record = JSON.parse(readFileSync38(join52(runtime.dirs.state, file), "utf8"));
|
|
52739
54248
|
const expectedControl = file === "hub.pid" ? "hub.stop" : file.startsWith("worker-") ? `${file.slice(0, -4)}.stop` : void 0;
|
|
52740
54249
|
const expectedRole = file === "hub.pid" ? "hub" : file.startsWith("worker-") ? "worker" : void 0;
|
|
52741
54250
|
if (record.version !== 1 || !Number.isInteger(record.pid) || record.pid <= 0 || !record.startedAt || !expectedControl || record.controlFile !== expectedControl || record.role !== expectedRole) {
|
|
@@ -52755,7 +54264,7 @@ async function cmdStop(runtime, waitMsFlag) {
|
|
|
52755
54264
|
ignored.push(file);
|
|
52756
54265
|
continue;
|
|
52757
54266
|
}
|
|
52758
|
-
writeFileSync26(
|
|
54267
|
+
writeFileSync26(join52(runtime.dirs.state, record.controlFile), `${JSON.stringify({ startedAt: record.startedAt, ...record.generation ? { generation: record.generation } : {}, requestedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
52759
54268
|
`, { encoding: "utf8", mode: 384 });
|
|
52760
54269
|
requested.push(file);
|
|
52761
54270
|
records.set(file, { pid: record.pid, startedAt: record.startedAt, ...record.generation ? { generation: record.generation } : {} });
|
|
@@ -52773,7 +54282,7 @@ async function cmdStop(runtime, waitMsFlag) {
|
|
|
52773
54282
|
while (Date.now() <= deadline && stopped.size < requested.length) {
|
|
52774
54283
|
for (const [file, record] of records) {
|
|
52775
54284
|
try {
|
|
52776
|
-
const current = JSON.parse(
|
|
54285
|
+
const current = JSON.parse(readFileSync38(join52(runtime.dirs.state, file), "utf8"));
|
|
52777
54286
|
if (current.pid !== record.pid || current.startedAt !== record.startedAt || current.generation !== record.generation || !processExists(record.pid)) stopped.add(file);
|
|
52778
54287
|
} catch {
|
|
52779
54288
|
stopped.add(file);
|
|
@@ -52786,7 +54295,7 @@ async function cmdStop(runtime, waitMsFlag) {
|
|
|
52786
54295
|
for (const file of orphans) {
|
|
52787
54296
|
while (Date.now() <= deadlineOrphans) {
|
|
52788
54297
|
try {
|
|
52789
|
-
const current = JSON.parse(
|
|
54298
|
+
const current = JSON.parse(readFileSync38(join52(runtime.dirs.state, file), "utf8"));
|
|
52790
54299
|
if (!Number.isInteger(current.serverPid) || !processExists(current.serverPid)) break;
|
|
52791
54300
|
} catch {
|
|
52792
54301
|
break;
|
|
@@ -52794,14 +54303,14 @@ async function cmdStop(runtime, waitMsFlag) {
|
|
|
52794
54303
|
await (runtime.io.sleep ?? ((ms) => new Promise((resolveSleep) => setTimeout(resolveSleep, ms))))(100);
|
|
52795
54304
|
}
|
|
52796
54305
|
try {
|
|
52797
|
-
const current = JSON.parse(
|
|
54306
|
+
const current = JSON.parse(readFileSync38(join52(runtime.dirs.state, file), "utf8"));
|
|
52798
54307
|
if (Number.isInteger(current.serverPid) && processExists(current.serverPid)) {
|
|
52799
54308
|
try {
|
|
52800
54309
|
process.kill(current.serverPid, "SIGKILL");
|
|
52801
54310
|
} catch {
|
|
52802
54311
|
}
|
|
52803
54312
|
}
|
|
52804
|
-
rmSync11(
|
|
54313
|
+
rmSync11(join52(runtime.dirs.state, file), { force: true });
|
|
52805
54314
|
} catch {
|
|
52806
54315
|
}
|
|
52807
54316
|
stopped.add(file);
|
|
@@ -52812,11 +54321,11 @@ async function cmdStop(runtime, waitMsFlag) {
|
|
|
52812
54321
|
}
|
|
52813
54322
|
async function cmdSessionStatus(runtime) {
|
|
52814
54323
|
const stateDir = runtime.dirs.state;
|
|
52815
|
-
const names3 =
|
|
54324
|
+
const names3 = existsSync42(stateDir) ? readdirSync13(stateDir) : [];
|
|
52816
54325
|
const claims = [];
|
|
52817
54326
|
for (const file of names3.filter((name) => name.endsWith(".pid"))) {
|
|
52818
54327
|
try {
|
|
52819
|
-
const record = JSON.parse(
|
|
54328
|
+
const record = JSON.parse(readFileSync38(join52(stateDir, file), "utf8"));
|
|
52820
54329
|
claims.push({
|
|
52821
54330
|
file,
|
|
52822
54331
|
role: record.role,
|
|
@@ -52831,7 +54340,7 @@ async function cmdSessionStatus(runtime) {
|
|
|
52831
54340
|
const recoveries = [];
|
|
52832
54341
|
for (const file of names3.filter((name) => name.startsWith("worker-recovery-") && name.endsWith(".json"))) {
|
|
52833
54342
|
try {
|
|
52834
|
-
const envelope2 = JSON.parse(
|
|
54343
|
+
const envelope2 = JSON.parse(readFileSync38(join52(stateDir, file), "utf8"));
|
|
52835
54344
|
recoveries.push({
|
|
52836
54345
|
file,
|
|
52837
54346
|
reason: envelope2.reason,
|
|
@@ -52857,7 +54366,7 @@ async function cmdSessionStatus(runtime) {
|
|
|
52857
54366
|
async function cmdAuthToken(runtime, options = {}) {
|
|
52858
54367
|
const tokenFile = sessionTokenPath(runtime.env.KXM_USER_CONFIG_DIR);
|
|
52859
54368
|
if (options.clear && runtime.dryRun) {
|
|
52860
|
-
const present =
|
|
54369
|
+
const present = existsSync42(tokenFile);
|
|
52861
54370
|
printPlan(runtime, { command: "auth token", cleared: present }, present ? [{ action: "delete", target: tokenFile }] : [], present ? "clear the session token from disk" : "no session token file to clear");
|
|
52862
54371
|
return 0;
|
|
52863
54372
|
}
|
|
@@ -52946,7 +54455,7 @@ async function cmdSessionBrief(runtime, options = {}) {
|
|
|
52946
54455
|
print(runtime.io, runtime.json, { ok: true, command: "session brief", sessionToken }, sessionToken);
|
|
52947
54456
|
return 0;
|
|
52948
54457
|
}
|
|
52949
|
-
const dataPath = resolve28(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() ||
|
|
54458
|
+
const dataPath = resolve28(runtime.dirs.workdir, runtime.env.KXM_DATA_PATH?.trim() || join52(runtime.dirs.state, "kxm.db"));
|
|
52950
54459
|
const env = {
|
|
52951
54460
|
...runtime.env,
|
|
52952
54461
|
KXM_STATE_DIR: runtime.dirs.state,
|
|
@@ -52972,7 +54481,7 @@ async function cmdSessionBrief(runtime, options = {}) {
|
|
|
52972
54481
|
writeCache: !runtime.dryRun
|
|
52973
54482
|
});
|
|
52974
54483
|
if (runtime.dryRun) {
|
|
52975
|
-
if (!readCachedSessionBrief(runtime.dirs.state)) planned.push({ action: "write", target:
|
|
54484
|
+
if (!readCachedSessionBrief(runtime.dirs.state)) planned.push({ action: "write", target: join52(runtime.dirs.state, "session-brief.json") });
|
|
52976
54485
|
print(runtime.io, runtime.json, { ...brief, dryRun: true, planned }, [
|
|
52977
54486
|
options.status ? brief.statusLine : formatSessionBriefText(brief),
|
|
52978
54487
|
...planned.map((change) => `dry run: would ${change.action} ${change.target}`)
|
|
@@ -53056,9 +54565,9 @@ init_routing();
|
|
|
53056
54565
|
init_prices();
|
|
53057
54566
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
53058
54567
|
import { createHash as createHash19 } from "node:crypto";
|
|
53059
|
-
import { existsSync as
|
|
54568
|
+
import { existsSync as existsSync50, mkdtempSync as mkdtempSync3, readFileSync as readFileSync44, rmSync as rmSync13 } from "node:fs";
|
|
53060
54569
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
53061
|
-
import { basename as basename11, extname as extname3, join as
|
|
54570
|
+
import { basename as basename11, extname as extname3, join as join58, resolve as resolve33 } from "node:path";
|
|
53062
54571
|
import { createInterface as createInterface3 } from "node:readline";
|
|
53063
54572
|
|
|
53064
54573
|
// plugins/kxm/src/improve.ts
|
|
@@ -53066,8 +54575,8 @@ init_routing();
|
|
|
53066
54575
|
init_protocol();
|
|
53067
54576
|
init_relevance();
|
|
53068
54577
|
import { createHash as createHash18 } from "node:crypto";
|
|
53069
|
-
import { existsSync as
|
|
53070
|
-
import { join as
|
|
54578
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync31, writeFileSync as writeFileSync27 } from "node:fs";
|
|
54579
|
+
import { join as join53, relative as relative7, resolve as resolve29 } from "node:path";
|
|
53071
54580
|
var CANDIDATE_SCHEMA = "kxm.candidate.v1";
|
|
53072
54581
|
var IMPROVEMENT_REPORT_SCHEMA = "kxm.improvement-report.v2";
|
|
53073
54582
|
function classifyCandidateKind(stepId, agentRole) {
|
|
@@ -53323,7 +54832,7 @@ function groupRoutingRecords(records, options = {}) {
|
|
|
53323
54832
|
}
|
|
53324
54833
|
function buildImprovementReport(records, options = {}) {
|
|
53325
54834
|
const projectRoot = options.projectRoot ? resolve29(options.projectRoot) : process.cwd();
|
|
53326
|
-
const candidatesDir = options.candidatesDir ? resolve29(options.candidatesDir) :
|
|
54835
|
+
const candidatesDir = options.candidatesDir ? resolve29(options.candidatesDir) : join53(projectRoot, ".kxm", "candidates");
|
|
53327
54836
|
const promotionPolicy = options.promotionPolicy ?? "manual_pr";
|
|
53328
54837
|
const groups = groupRoutingRecords(records, options);
|
|
53329
54838
|
const candidates = [];
|
|
@@ -53337,7 +54846,7 @@ function buildImprovementReport(records, options = {}) {
|
|
|
53337
54846
|
group.workflowId
|
|
53338
54847
|
);
|
|
53339
54848
|
const diffFileName = `${group.candidateId}.diff`;
|
|
53340
|
-
const diffFilePath =
|
|
54849
|
+
const diffFilePath = join53(candidatesDir, diffFileName);
|
|
53341
54850
|
const relDiffPath = relative7(projectRoot, diffFilePath).replace(/\\/g, "/");
|
|
53342
54851
|
const candidate = {
|
|
53343
54852
|
schema: CANDIDATE_SCHEMA,
|
|
@@ -53359,11 +54868,11 @@ function buildImprovementReport(records, options = {}) {
|
|
|
53359
54868
|
createdAt: nowIso()
|
|
53360
54869
|
};
|
|
53361
54870
|
if (!options.dryRun) {
|
|
53362
|
-
if (!
|
|
54871
|
+
if (!existsSync43(candidatesDir)) {
|
|
53363
54872
|
mkdirSync31(candidatesDir, { recursive: true });
|
|
53364
54873
|
}
|
|
53365
54874
|
writeFileSync27(diffFilePath, diff, "utf8");
|
|
53366
|
-
const jsonFilePath =
|
|
54875
|
+
const jsonFilePath = join53(candidatesDir, `${group.candidateId}.json`);
|
|
53367
54876
|
writeFileSync27(jsonFilePath, JSON.stringify(candidate, null, 2) + "\n", "utf8");
|
|
53368
54877
|
}
|
|
53369
54878
|
candidates.push(candidate);
|
|
@@ -53394,7 +54903,7 @@ function buildImprovementReport(records, options = {}) {
|
|
|
53394
54903
|
}
|
|
53395
54904
|
function writeImprovementReport(improvementsDir, report2, dryRun = false) {
|
|
53396
54905
|
const stamp = report2.createdAt.replace(/[:.]/g, "-");
|
|
53397
|
-
const path4 =
|
|
54906
|
+
const path4 = join53(improvementsDir, `${stamp}.json`);
|
|
53398
54907
|
if (!dryRun) {
|
|
53399
54908
|
mkdirSync31(improvementsDir, { recursive: true });
|
|
53400
54909
|
writeFileSync27(path4, `${JSON.stringify(report2, null, 2)}
|
|
@@ -53487,7 +54996,7 @@ init_runtime_store();
|
|
|
53487
54996
|
init_route_switch();
|
|
53488
54997
|
init_routing();
|
|
53489
54998
|
init_telemetry();
|
|
53490
|
-
import { existsSync as
|
|
54999
|
+
import { existsSync as existsSync44 } from "node:fs";
|
|
53491
55000
|
import { resolve as resolve30 } from "node:path";
|
|
53492
55001
|
var ENGINE_EVENTS_SQL = "SELECT run_id, sequence, event_type, payload FROM events WHERE event_type IN ('routing.attempt.recorded','routing.route_switched','step.entered','run.status_changed') ORDER BY run_id, sequence";
|
|
53493
55002
|
var SIMULATED_HARNESS = "driver-simulated";
|
|
@@ -53510,7 +55019,7 @@ function readEngineRoutingRecords(path4) {
|
|
|
53510
55019
|
undecided: 0,
|
|
53511
55020
|
duplicatesDropped: 0
|
|
53512
55021
|
};
|
|
53513
|
-
if (!
|
|
55022
|
+
if (!existsSync44(path4)) return { records: [], source, routeSwitches: [] };
|
|
53514
55023
|
source.exists = true;
|
|
53515
55024
|
let rows;
|
|
53516
55025
|
let database;
|
|
@@ -53596,7 +55105,7 @@ function readEngineRoutingRecords(path4) {
|
|
|
53596
55105
|
};
|
|
53597
55106
|
}
|
|
53598
55107
|
function readJsonlSource(kind, path4) {
|
|
53599
|
-
const exists =
|
|
55108
|
+
const exists = existsSync44(path4);
|
|
53600
55109
|
const records = exists ? readRoutingRecords(path4).map((entry) => entry.routing) : [];
|
|
53601
55110
|
return { records, source: { kind, path: path4, exists, records: records.length, duplicatesDropped: 0 } };
|
|
53602
55111
|
}
|
|
@@ -53640,10 +55149,10 @@ function loadRoutingSources(options) {
|
|
|
53640
55149
|
}
|
|
53641
55150
|
|
|
53642
55151
|
// plugins/kxm/src/modes.ts
|
|
53643
|
-
var
|
|
55152
|
+
var import_yaml21 = __toESM(require_dist(), 1);
|
|
53644
55153
|
init_prices();
|
|
53645
|
-
import { existsSync as
|
|
53646
|
-
import { join as
|
|
55154
|
+
import { existsSync as existsSync45, readFileSync as readFileSync40 } from "node:fs";
|
|
55155
|
+
import { join as join54 } from "node:path";
|
|
53647
55156
|
var DEFAULT_MODES_CONFIG = Object.freeze({
|
|
53648
55157
|
schema: "kxm.modes.v1",
|
|
53649
55158
|
majorModes: {
|
|
@@ -53706,13 +55215,13 @@ function loadModesConfig(projectRoot) {
|
|
|
53706
55215
|
if (!projectRoot) {
|
|
53707
55216
|
return DEFAULT_MODES_CONFIG;
|
|
53708
55217
|
}
|
|
53709
|
-
const modesPath =
|
|
53710
|
-
if (!
|
|
55218
|
+
const modesPath = join54(projectRoot, ".kxm", "modes.yaml");
|
|
55219
|
+
if (!existsSync45(modesPath)) {
|
|
53711
55220
|
return DEFAULT_MODES_CONFIG;
|
|
53712
55221
|
}
|
|
53713
55222
|
try {
|
|
53714
|
-
const raw =
|
|
53715
|
-
const parsed = (0,
|
|
55223
|
+
const raw = readFileSync40(modesPath, "utf8");
|
|
55224
|
+
const parsed = (0, import_yaml21.parse)(raw);
|
|
53716
55225
|
if (parsed && parsed.schema === "kxm.modes.v1" && parsed.majorModes) {
|
|
53717
55226
|
return {
|
|
53718
55227
|
schema: "kxm.modes.v1",
|
|
@@ -53797,11 +55306,11 @@ function calculatePromptFootprint(resolved, projectRoot = process.cwd(), catalog
|
|
|
53797
55306
|
estimatedTokens: estimateTokens(baseSystemPromptChars)
|
|
53798
55307
|
});
|
|
53799
55308
|
for (const relPath of resolved.contextFiles) {
|
|
53800
|
-
const fullPath =
|
|
55309
|
+
const fullPath = join54(projectRoot, relPath);
|
|
53801
55310
|
let chars = 0;
|
|
53802
|
-
if (
|
|
55311
|
+
if (existsSync45(fullPath)) {
|
|
53803
55312
|
try {
|
|
53804
|
-
chars =
|
|
55313
|
+
chars = readFileSync40(fullPath, "utf8").length;
|
|
53805
55314
|
} catch {
|
|
53806
55315
|
chars = 0;
|
|
53807
55316
|
}
|
|
@@ -53934,9 +55443,9 @@ ${divider}
|
|
|
53934
55443
|
// plugins/kxm/src/ssh-remote.ts
|
|
53935
55444
|
init_safety_integrity();
|
|
53936
55445
|
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
53937
|
-
import { existsSync as
|
|
55446
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync32, readFileSync as readFileSync41, readdirSync as readdirSync14, rmSync as rmSync12, statSync as statSync7 } from "node:fs";
|
|
53938
55447
|
import { homedir as homedir8 } from "node:os";
|
|
53939
|
-
import { join as
|
|
55448
|
+
import { join as join55, resolve as resolve31 } from "node:path";
|
|
53940
55449
|
var MAX_SSH_OUTPUT_BYTES = 50 * 1024;
|
|
53941
55450
|
var MAX_SSH_OUTPUT_LINES = 2e3;
|
|
53942
55451
|
var DEFAULT_SOCKET_DIR = ".kxm/run/ssh-sockets";
|
|
@@ -53962,12 +55471,12 @@ function truncateSshOutput(raw) {
|
|
|
53962
55471
|
return { text, truncated };
|
|
53963
55472
|
}
|
|
53964
55473
|
function parseSshConfig(configPath) {
|
|
53965
|
-
const targetPath = configPath ??
|
|
53966
|
-
if (!
|
|
55474
|
+
const targetPath = configPath ?? join55(homedir8(), ".ssh", "config");
|
|
55475
|
+
if (!existsSync46(targetPath)) {
|
|
53967
55476
|
return [];
|
|
53968
55477
|
}
|
|
53969
55478
|
try {
|
|
53970
|
-
const content =
|
|
55479
|
+
const content = readFileSync41(targetPath, "utf-8");
|
|
53971
55480
|
const lines = content.split("\n");
|
|
53972
55481
|
const hosts = [];
|
|
53973
55482
|
let currentHosts = [];
|
|
@@ -54045,14 +55554,14 @@ function resolveSshHostG(host, execFn = spawnSync14) {
|
|
|
54045
55554
|
}
|
|
54046
55555
|
function ensureSocketDir(socketDir = DEFAULT_SOCKET_DIR) {
|
|
54047
55556
|
const resolved = resolve31(socketDir);
|
|
54048
|
-
if (!
|
|
55557
|
+
if (!existsSync46(resolved)) {
|
|
54049
55558
|
mkdirSync32(resolved, { recursive: true, mode: 448 });
|
|
54050
55559
|
}
|
|
54051
55560
|
return resolved;
|
|
54052
55561
|
}
|
|
54053
55562
|
function buildSshArgs(options) {
|
|
54054
55563
|
const socketDir = ensureSocketDir(options.socketDir ?? DEFAULT_SOCKET_DIR);
|
|
54055
|
-
const controlPath =
|
|
55564
|
+
const controlPath = join55(socketDir, "%C");
|
|
54056
55565
|
const persist = options.controlPersist ?? DEFAULT_CONTROL_PERSIST;
|
|
54057
55566
|
const args = [
|
|
54058
55567
|
"-o",
|
|
@@ -54077,7 +55586,7 @@ function buildSshArgs(options) {
|
|
|
54077
55586
|
}
|
|
54078
55587
|
function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync14) {
|
|
54079
55588
|
const resolvedDir = ensureSocketDir(socketDir);
|
|
54080
|
-
const controlPath =
|
|
55589
|
+
const controlPath = join55(resolvedDir, "%C");
|
|
54081
55590
|
try {
|
|
54082
55591
|
const result = execFn("ssh", ["-O", "check", "-o", `ControlPath=${controlPath}`, host], {
|
|
54083
55592
|
encoding: "utf-8"
|
|
@@ -54089,7 +55598,7 @@ function checkControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawn
|
|
|
54089
55598
|
}
|
|
54090
55599
|
function closeControlSocket(host, socketDir = DEFAULT_SOCKET_DIR, execFn = spawnSync14) {
|
|
54091
55600
|
const resolvedDir = ensureSocketDir(socketDir);
|
|
54092
|
-
const controlPath =
|
|
55601
|
+
const controlPath = join55(resolvedDir, "%C");
|
|
54093
55602
|
try {
|
|
54094
55603
|
const result = execFn("ssh", ["-O", "stop", "-o", `ControlPath=${controlPath}`, host], {
|
|
54095
55604
|
encoding: "utf-8"
|
|
@@ -54301,6 +55810,304 @@ function executeSshRun(params) {
|
|
|
54301
55810
|
// plugins/kxm/src/cli/system.ts
|
|
54302
55811
|
init_config();
|
|
54303
55812
|
|
|
55813
|
+
// plugins/kxm/src/config-editor.ts
|
|
55814
|
+
var import_yaml22 = __toESM(require_dist(), 1);
|
|
55815
|
+
import { existsSync as existsSync47, readFileSync as readFileSync42 } from "node:fs";
|
|
55816
|
+
init_config();
|
|
55817
|
+
var KXM_CONFIG_EDITOR_SOURCE = "kxm/config";
|
|
55818
|
+
var INTEGER_TEXT = /^(0|[1-9][0-9]*)$/;
|
|
55819
|
+
var DECIMAL_TEXT = /^(0|[1-9][0-9]*)(\.[0-9]{1,6})?$/;
|
|
55820
|
+
var TEXT_VALUE = /^[A-Za-z0-9@][A-Za-z0-9@._:/-]{0,127}$/;
|
|
55821
|
+
var SCREEN_CHOICES = ["agents", "tasks", "workflows", "plans", "inbox", "procs", "spend"];
|
|
55822
|
+
var SECTIONS = [
|
|
55823
|
+
{
|
|
55824
|
+
id: "user",
|
|
55825
|
+
title: "User",
|
|
55826
|
+
order: 10,
|
|
55827
|
+
fields: [
|
|
55828
|
+
{ id: "user-theme", label: "Theme", keyPath: "user.theme", spec: { kind: "enum", choices: ["dark", "light", "minimal"] } },
|
|
55829
|
+
{ id: "user-token-budget", label: "Token budget", keyPath: "user.tokenBudget", spec: { kind: "number", integer: true, min: 1, max: 2e6 } }
|
|
55830
|
+
]
|
|
55831
|
+
},
|
|
55832
|
+
{
|
|
55833
|
+
id: "defaults",
|
|
55834
|
+
title: "Defaults",
|
|
55835
|
+
order: 20,
|
|
55836
|
+
fields: [
|
|
55837
|
+
{ id: "defaults-workflow", label: "Workflow", keyPath: "defaults.workflow", spec: { kind: "text" } },
|
|
55838
|
+
{ id: "defaults-harness", label: "Harness", keyPath: "defaults.harness", spec: { kind: "text" } }
|
|
55839
|
+
]
|
|
55840
|
+
},
|
|
55841
|
+
{
|
|
55842
|
+
id: "dash",
|
|
55843
|
+
title: "Dashboard",
|
|
55844
|
+
order: 30,
|
|
55845
|
+
fields: [
|
|
55846
|
+
{ id: "dash-screen", label: "Default screen", keyPath: "dash.defaultScreen", spec: { kind: "enum", choices: SCREEN_CHOICES } },
|
|
55847
|
+
{ id: "dash-refresh", label: "Refresh interval", keyPath: "dash.refreshIntervalMs", spec: { kind: "number", integer: true, min: 250, max: 36e5 } },
|
|
55848
|
+
{ id: "dash-auto-open", label: "Auto-open", keyPath: "dash.autoOpen", spec: { kind: "enum", choices: ["true", "false"] } }
|
|
55849
|
+
]
|
|
55850
|
+
},
|
|
55851
|
+
{
|
|
55852
|
+
id: "sync",
|
|
55853
|
+
title: "Sync",
|
|
55854
|
+
order: 40,
|
|
55855
|
+
fields: [
|
|
55856
|
+
{ id: "sync-tracker", label: "Default tracker", keyPath: "sync.defaultTracker", spec: { kind: "enum", choices: ["github", "jira", "none"] } }
|
|
55857
|
+
]
|
|
55858
|
+
},
|
|
55859
|
+
{
|
|
55860
|
+
id: "hub",
|
|
55861
|
+
title: "Hub",
|
|
55862
|
+
order: 50,
|
|
55863
|
+
fields: [
|
|
55864
|
+
{
|
|
55865
|
+
id: "hub-auto-start",
|
|
55866
|
+
label: "Auto-start",
|
|
55867
|
+
keyPath: "hub.autoStart",
|
|
55868
|
+
spec: { kind: "enum", choices: ["background", "off"] },
|
|
55869
|
+
note: "Hub URLs and key references stay out of this panel."
|
|
55870
|
+
}
|
|
55871
|
+
]
|
|
55872
|
+
},
|
|
55873
|
+
{
|
|
55874
|
+
id: "improvement",
|
|
55875
|
+
title: "Improvement",
|
|
55876
|
+
order: 60,
|
|
55877
|
+
fields: [
|
|
55878
|
+
{ id: "improvement-policy", label: "Promotion policy", keyPath: "improvement.promotionPolicy", spec: { kind: "enum", choices: ["manual_pr", "critic_quorum", "auto_threshold"] } },
|
|
55879
|
+
{ id: "improvement-half-life", label: "Telemetry half-life", keyPath: "improvement.telemetryHalfLifeDays", spec: { kind: "number", integer: false, min: 1e-6, max: 3650 } },
|
|
55880
|
+
{ id: "improvement-min-runs", label: "Minimum runs", keyPath: "improvement.autoThreshold.minRuns", spec: { kind: "number", integer: true, min: 1, max: 1e6 } },
|
|
55881
|
+
{ id: "improvement-min-pass-rate", label: "Minimum pass rate", keyPath: "improvement.autoThreshold.minPassRate", spec: { kind: "number", integer: false, min: 0, max: 1 } },
|
|
55882
|
+
{ id: "improvement-min-cost-savings", label: "Minimum cost savings", keyPath: "improvement.autoThreshold.minCostSavings", spec: { kind: "number", integer: false, min: 0, max: 1e6 } }
|
|
55883
|
+
]
|
|
55884
|
+
},
|
|
55885
|
+
{
|
|
55886
|
+
id: "routing",
|
|
55887
|
+
title: "Routing",
|
|
55888
|
+
order: 70,
|
|
55889
|
+
fields: [
|
|
55890
|
+
{
|
|
55891
|
+
id: "routing-shadow",
|
|
55892
|
+
label: "Shadow execution",
|
|
55893
|
+
keyPath: "routing.shadowExecution.enabled",
|
|
55894
|
+
spec: { kind: "enum", choices: ["true", "false"] },
|
|
55895
|
+
note: "Strategy, fallback, and context limits stay in role and route files."
|
|
55896
|
+
},
|
|
55897
|
+
{ id: "routing-sample-rate", label: "Sample rate", keyPath: "routing.shadowExecution.sampleRate", spec: { kind: "number", integer: false, min: 0, max: 1 } },
|
|
55898
|
+
{ id: "routing-breaker-mode", label: "Breaker mode", keyPath: "routing.circuitBreaker.mode", spec: { kind: "enum", choices: ["soft_demotion", "quarantine"] } },
|
|
55899
|
+
{ id: "routing-failure-threshold", label: "Failure threshold", keyPath: "routing.circuitBreaker.failureThreshold", spec: { kind: "number", integer: true, min: 1, max: 1e4 } },
|
|
55900
|
+
{ id: "routing-window-seconds", label: "Window seconds", keyPath: "routing.circuitBreaker.windowSeconds", spec: { kind: "number", integer: true, min: 1, max: 31536e3 } },
|
|
55901
|
+
{ id: "routing-cooldown-seconds", label: "Cooldown seconds", keyPath: "routing.circuitBreaker.cooldownSeconds", spec: { kind: "number", integer: true, min: 0, max: 31536e3 } },
|
|
55902
|
+
{ id: "routing-penalty", label: "Penalty multiplier", keyPath: "routing.circuitBreaker.penaltyMultiplier", spec: { kind: "number", integer: false, min: 1e-6, max: 1e3 } }
|
|
55903
|
+
]
|
|
55904
|
+
},
|
|
55905
|
+
{
|
|
55906
|
+
id: "telemetry",
|
|
55907
|
+
title: "Telemetry",
|
|
55908
|
+
order: 80,
|
|
55909
|
+
fields: [
|
|
55910
|
+
{ id: "telemetry-federated", label: "Federated", keyPath: "telemetry.federated", spec: { kind: "enum", choices: ["true", "false"] } },
|
|
55911
|
+
{ id: "telemetry-anonymize", label: "Anonymize", keyPath: "telemetry.anonymize", spec: { kind: "enum", choices: ["true", "false"] } }
|
|
55912
|
+
]
|
|
55913
|
+
}
|
|
55914
|
+
];
|
|
55915
|
+
var KxmConfigEditorError = class extends Error {
|
|
55916
|
+
code = "config_edit_refused";
|
|
55917
|
+
constructor(message) {
|
|
55918
|
+
super(message);
|
|
55919
|
+
this.name = "KxmConfigEditorError";
|
|
55920
|
+
}
|
|
55921
|
+
};
|
|
55922
|
+
function createKxmConfigEditorContribution(target) {
|
|
55923
|
+
return {
|
|
55924
|
+
source: KXM_CONFIG_EDITOR_SOURCE,
|
|
55925
|
+
republishAfterWrite: true,
|
|
55926
|
+
listSections: () => listConfigEditorSections(target),
|
|
55927
|
+
handlers: {
|
|
55928
|
+
[KXM_TUI_SET_ACTION]: (input) => {
|
|
55929
|
+
writeConfigEditorValue(target, input.sectionId, input.fieldId, input.value);
|
|
55930
|
+
},
|
|
55931
|
+
[KXM_TUI_CLEAR_ACTION]: (input) => {
|
|
55932
|
+
clearConfigEditorValue(target, input.sectionId, input.fieldId);
|
|
55933
|
+
}
|
|
55934
|
+
},
|
|
55935
|
+
describeError: (error) => error instanceof Error && error.message ? error.message : "config edit failed"
|
|
55936
|
+
};
|
|
55937
|
+
}
|
|
55938
|
+
function scopeFile(target) {
|
|
55939
|
+
return kxmConfigFileForScope(target.cwd, target.scope, target.userConfigDir);
|
|
55940
|
+
}
|
|
55941
|
+
function scopePhrase(scope) {
|
|
55942
|
+
return scope === "project" ? "the project file .kxm/config.yaml" : "the user file config.yaml";
|
|
55943
|
+
}
|
|
55944
|
+
function listConfigEditorSections(target) {
|
|
55945
|
+
const config = loadKxmConfig(target.cwd, {
|
|
55946
|
+
...target.userConfigDir === void 0 ? {} : { userConfigDir: target.userConfigDir },
|
|
55947
|
+
layers: target.scope === "user" ? "user" : "all"
|
|
55948
|
+
});
|
|
55949
|
+
const owned = readScopeDocument(scopeFile(target));
|
|
55950
|
+
const where = scopePhrase(target.scope);
|
|
55951
|
+
return SECTIONS.map((section) => {
|
|
55952
|
+
const fields = section.fields.map((field) => publishField(field, config, owned, where));
|
|
55953
|
+
const diagnosis = fields.find((field) => field.statusText)?.statusText;
|
|
55954
|
+
return {
|
|
55955
|
+
source: KXM_CONFIG_EDITOR_SOURCE,
|
|
55956
|
+
id: section.id,
|
|
55957
|
+
title: section.title,
|
|
55958
|
+
order: section.order,
|
|
55959
|
+
detail: `kxm.config.v1 \xB7 ${target.scope} scope. Re-read from disk after each write.`,
|
|
55960
|
+
...diagnosis === void 0 ? {} : { notice: diagnosis, noticeLevel: "error" },
|
|
55961
|
+
fields
|
|
55962
|
+
};
|
|
55963
|
+
});
|
|
55964
|
+
}
|
|
55965
|
+
function publishField(field, config, owned, where) {
|
|
55966
|
+
const effective = getKxmConfigValue(config, field.keyPath);
|
|
55967
|
+
const stored = ownValue(owned, field.keyPath);
|
|
55968
|
+
const shown = displayScalar(effective);
|
|
55969
|
+
const inherited = !stored.present;
|
|
55970
|
+
const diagnosis = storedDiagnosis(field, stored, effective);
|
|
55971
|
+
const detail = [
|
|
55972
|
+
`Writes ${where}.`,
|
|
55973
|
+
inherited ? "Inherited." : "Set in this scope.",
|
|
55974
|
+
"Clear removes only this override.",
|
|
55975
|
+
field.note ?? "",
|
|
55976
|
+
diagnosis ?? ""
|
|
55977
|
+
].filter((part) => part.length > 0).join(" ");
|
|
55978
|
+
const choices = field.spec.kind === "enum" ? field.spec.choices.map((choice) => ({ id: choice, label: choice })) : void 0;
|
|
55979
|
+
return {
|
|
55980
|
+
id: field.id,
|
|
55981
|
+
label: field.label,
|
|
55982
|
+
kind: field.spec.kind === "enum" ? "enum" : "text",
|
|
55983
|
+
keyPath: field.keyPath,
|
|
55984
|
+
detail,
|
|
55985
|
+
status: "ready",
|
|
55986
|
+
...shown === void 0 ? {} : { value: shown },
|
|
55987
|
+
...choices === void 0 ? {} : { choices },
|
|
55988
|
+
...diagnosis === void 0 ? {} : { statusText: diagnosis }
|
|
55989
|
+
};
|
|
55990
|
+
}
|
|
55991
|
+
function displayScalar(value) {
|
|
55992
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
55993
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
55994
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
55995
|
+
return void 0;
|
|
55996
|
+
}
|
|
55997
|
+
function isBooleanEnum(spec) {
|
|
55998
|
+
return spec.kind === "enum" && spec.choices.length === 2 && spec.choices.every((choice) => choice === "true" || choice === "false");
|
|
55999
|
+
}
|
|
56000
|
+
function storedTypeMatches(field, value) {
|
|
56001
|
+
if (field.spec.kind === "number") {
|
|
56002
|
+
return typeof value === "number" && Number.isFinite(value) && (!field.spec.integer || Number.isInteger(value));
|
|
56003
|
+
}
|
|
56004
|
+
if (isBooleanEnum(field.spec)) return typeof value === "boolean";
|
|
56005
|
+
return typeof value === "string";
|
|
56006
|
+
}
|
|
56007
|
+
function storedDiagnosis(field, stored, effective) {
|
|
56008
|
+
if (!stored.present) return void 0;
|
|
56009
|
+
if (!storedTypeMatches(field, stored.value)) return "stored type does not match this field";
|
|
56010
|
+
if (field.spec.kind === "enum" && !isBooleanEnum(field.spec) && !field.spec.choices.includes(stored.value)) {
|
|
56011
|
+
return "stored value is not a supported scalar";
|
|
56012
|
+
}
|
|
56013
|
+
if (field.spec.kind === "number") {
|
|
56014
|
+
const value = stored.value;
|
|
56015
|
+
if (value < field.spec.min || value > field.spec.max) return "stored value is not a supported scalar";
|
|
56016
|
+
}
|
|
56017
|
+
if (stored.value !== effective) return "stored value is not a supported scalar";
|
|
56018
|
+
return void 0;
|
|
56019
|
+
}
|
|
56020
|
+
function writeConfigEditorValue(target, sectionId, fieldId, raw) {
|
|
56021
|
+
const field = requireField(sectionId, fieldId);
|
|
56022
|
+
const value = parseEditorValue(field, raw);
|
|
56023
|
+
setKxmConfigValue(target.cwd, field.keyPath, value, {
|
|
56024
|
+
scope: target.scope,
|
|
56025
|
+
...target.userConfigDir === void 0 ? {} : { userConfigDir: target.userConfigDir }
|
|
56026
|
+
});
|
|
56027
|
+
}
|
|
56028
|
+
function clearConfigEditorValue(target, sectionId, fieldId) {
|
|
56029
|
+
const field = requireField(sectionId, fieldId);
|
|
56030
|
+
deleteKxmConfigValue(target.cwd, field.keyPath, {
|
|
56031
|
+
scope: target.scope,
|
|
56032
|
+
...target.userConfigDir === void 0 ? {} : { userConfigDir: target.userConfigDir }
|
|
56033
|
+
});
|
|
56034
|
+
}
|
|
56035
|
+
function requireField(sectionId, fieldId) {
|
|
56036
|
+
const section = SECTIONS.find((item) => item.id === sectionId);
|
|
56037
|
+
const field = section?.fields.find((item) => item.id === fieldId);
|
|
56038
|
+
if (!field) throw new KxmConfigEditorError("unknown configuration field");
|
|
56039
|
+
return field;
|
|
56040
|
+
}
|
|
56041
|
+
function parseEditorValue(field, raw) {
|
|
56042
|
+
if (raw === void 0) throw new KxmConfigEditorError("a value is required");
|
|
56043
|
+
if (raw.length === 0 || raw.length > 128 || /[\u0000-\u001f\u007f]/u.test(raw)) {
|
|
56044
|
+
throw new KxmConfigEditorError("value must be a single bounded line");
|
|
56045
|
+
}
|
|
56046
|
+
if (field.spec.kind === "enum") {
|
|
56047
|
+
if (!field.spec.choices.includes(raw)) throw new KxmConfigEditorError("value is not a supported choice");
|
|
56048
|
+
if (isBooleanEnum(field.spec)) return raw === "true";
|
|
56049
|
+
return raw;
|
|
56050
|
+
}
|
|
56051
|
+
if (field.spec.kind === "text") {
|
|
56052
|
+
if (!TEXT_VALUE.test(raw)) throw new KxmConfigEditorError("value must be a single bounded line");
|
|
56053
|
+
return raw;
|
|
56054
|
+
}
|
|
56055
|
+
if (!(field.spec.integer ? INTEGER_TEXT : DECIMAL_TEXT).test(raw)) {
|
|
56056
|
+
throw new KxmConfigEditorError("value must be a finite number in range");
|
|
56057
|
+
}
|
|
56058
|
+
const parsed = Number(raw);
|
|
56059
|
+
if (!Number.isFinite(parsed) || field.spec.integer && !Number.isInteger(parsed) || parsed < field.spec.min || parsed > field.spec.max) {
|
|
56060
|
+
throw new KxmConfigEditorError("value must be a finite number in range");
|
|
56061
|
+
}
|
|
56062
|
+
return parsed;
|
|
56063
|
+
}
|
|
56064
|
+
function readScopeDocument(file) {
|
|
56065
|
+
if (!existsSync47(file)) return void 0;
|
|
56066
|
+
const parsed = (0, import_yaml22.parse)(readFileSync42(file, "utf8"));
|
|
56067
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
56068
|
+
throw new KxmConfigEditorError(`config file ${file} must be a mapping`);
|
|
56069
|
+
}
|
|
56070
|
+
return parsed;
|
|
56071
|
+
}
|
|
56072
|
+
function ownValue(root, keyPath) {
|
|
56073
|
+
if (!root) return { present: false, value: void 0 };
|
|
56074
|
+
let cursor2 = root;
|
|
56075
|
+
for (const part of keyPath.split(".")) {
|
|
56076
|
+
if (!cursor2 || typeof cursor2 !== "object" || Array.isArray(cursor2)) return { present: false, value: void 0 };
|
|
56077
|
+
const record = cursor2;
|
|
56078
|
+
if (!Object.prototype.hasOwnProperty.call(record, part)) return { present: false, value: void 0 };
|
|
56079
|
+
cursor2 = record[part];
|
|
56080
|
+
}
|
|
56081
|
+
return { present: true, value: cursor2 };
|
|
56082
|
+
}
|
|
56083
|
+
var EDITOR_HELP = [
|
|
56084
|
+
"\u2191\u2193 move \xB7 enter edits \xB7 x clears this scope's override \xB7 q quits",
|
|
56085
|
+
"Writes are checked, saved, then re-read. A refused edit leaves the file unchanged."
|
|
56086
|
+
];
|
|
56087
|
+
var FRAME_NOW_MS = Date.UTC(2026, 9, 11);
|
|
56088
|
+
async function runKxmConfigEditor(input) {
|
|
56089
|
+
const registry = createKxmTuiRegistry();
|
|
56090
|
+
try {
|
|
56091
|
+
registry.register(createKxmConfigEditorContribution(input.target));
|
|
56092
|
+
registry.getSections();
|
|
56093
|
+
const failed2 = registry.getIssues()[0];
|
|
56094
|
+
if (failed2) throw new KxmConfigEditorError(failed2.message);
|
|
56095
|
+
const interactive = input.isTty && !input.dryRun;
|
|
56096
|
+
return await runKxmTuiPanel({
|
|
56097
|
+
registry,
|
|
56098
|
+
title: "KXM configuration",
|
|
56099
|
+
breadcrumb: `kxm.config.v1 \xB7 ${input.target.scope} scope`,
|
|
56100
|
+
helpLines: EDITOR_HELP,
|
|
56101
|
+
stdout: input.stdout,
|
|
56102
|
+
isTty: interactive,
|
|
56103
|
+
...interactive ? {} : { theme: createPlainKxmTuiTheme() },
|
|
56104
|
+
now: () => FRAME_NOW_MS
|
|
56105
|
+
});
|
|
56106
|
+
} finally {
|
|
56107
|
+
registry.dispose();
|
|
56108
|
+
}
|
|
56109
|
+
}
|
|
56110
|
+
|
|
54304
56111
|
// plugins/kxm/src/autocomplete.ts
|
|
54305
56112
|
var TOP_LEVEL_COMMANDS = [
|
|
54306
56113
|
"init",
|
|
@@ -54354,7 +56161,7 @@ var SUBCOMMANDS = {
|
|
|
54354
56161
|
memory: ["brief", "note", "sync"],
|
|
54355
56162
|
routing: ["report", "benchmark"],
|
|
54356
56163
|
hub: ["view", "start", "stop", "bind", "unbind"],
|
|
54357
|
-
config: ["get", "set", "list"],
|
|
56164
|
+
config: ["get", "set", "edit", "list"],
|
|
54358
56165
|
goal: ["create", "list", "get"],
|
|
54359
56166
|
task: ["create", "list", "get", "run", "sync"],
|
|
54360
56167
|
studio: ["layout", "serve"],
|
|
@@ -54476,7 +56283,7 @@ _kxm() {
|
|
|
54476
56283
|
_arguments '1:shell:(bash zsh fish)'
|
|
54477
56284
|
;;
|
|
54478
56285
|
config)
|
|
54479
|
-
_arguments '1:action:(get set list)'
|
|
56286
|
+
_arguments '1:action:(get set edit list)'
|
|
54480
56287
|
;;
|
|
54481
56288
|
runs)
|
|
54482
56289
|
_arguments '1:subcommand:(status cancel list)'
|
|
@@ -54520,7 +56327,7 @@ complete -c kxm -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
|
|
|
54520
56327
|
complete -c kxm -n "__fish_seen_subcommand_from dash" -l screen -a "agents tasks workflows plans inbox procs spend"
|
|
54521
56328
|
|
|
54522
56329
|
# Config subcommand
|
|
54523
|
-
complete -c kxm -n "__fish_seen_subcommand_from config" -a "get set list"
|
|
56330
|
+
complete -c kxm -n "__fish_seen_subcommand_from config" -a "get set edit list"
|
|
54524
56331
|
|
|
54525
56332
|
# Runs subcommand
|
|
54526
56333
|
complete -c kxm -n "__fish_seen_subcommand_from runs" -a "status cancel list"
|
|
@@ -54540,9 +56347,9 @@ complete -c kxm -n "__fish_seen_subcommand_from goal" -a "create list get"
|
|
|
54540
56347
|
}
|
|
54541
56348
|
|
|
54542
56349
|
// plugins/kxm/src/completion-install.ts
|
|
54543
|
-
import { existsSync as
|
|
56350
|
+
import { existsSync as existsSync48, mkdirSync as mkdirSync33, readFileSync as readFileSync43, writeFileSync as writeFileSync28 } from "node:fs";
|
|
54544
56351
|
import { homedir as homedir9 } from "node:os";
|
|
54545
|
-
import { basename as basename10, delimiter, dirname as dirname24, isAbsolute as
|
|
56352
|
+
import { basename as basename10, delimiter, dirname as dirname24, isAbsolute as isAbsolute12, join as join56, resolve as resolve32 } from "node:path";
|
|
54546
56353
|
var COMPLETION_MARKER = "# kxm completion";
|
|
54547
56354
|
var PATH_MARKER = "# kxm path";
|
|
54548
56355
|
function detectShell(env = process.env, platform = process.platform) {
|
|
@@ -54564,25 +56371,25 @@ function completionScriptPath(shell, options = {}) {
|
|
|
54564
56371
|
const env = options.env ?? process.env;
|
|
54565
56372
|
const explicit = options.configDir ?? env.KXM_USER_CONFIG_DIR?.trim();
|
|
54566
56373
|
const base = explicit && explicit.length > 0 ? resolve32(explicit) : resolve32(effectiveHome(options), ".config", "kxm");
|
|
54567
|
-
return
|
|
56374
|
+
return join56(base, "completions", `kxm.${shell}`);
|
|
54568
56375
|
}
|
|
54569
56376
|
function bashRcCandidate(options) {
|
|
54570
56377
|
const home = effectiveHome(options);
|
|
54571
|
-
const candidates = [
|
|
54572
|
-
const existing = candidates.find((candidate) =>
|
|
56378
|
+
const candidates = [join56(home, ".bashrc"), join56(home, ".bash_profile")];
|
|
56379
|
+
const existing = candidates.find((candidate) => existsSync48(candidate));
|
|
54573
56380
|
return existing ?? candidates[0];
|
|
54574
56381
|
}
|
|
54575
56382
|
function zshRcCandidate(options) {
|
|
54576
56383
|
const env = options.env ?? process.env;
|
|
54577
56384
|
const home = effectiveHome(options);
|
|
54578
|
-
if (env.ZDOTDIR?.trim()) return
|
|
54579
|
-
return
|
|
56385
|
+
if (env.ZDOTDIR?.trim()) return join56(resolve32(env.ZDOTDIR.trim()), ".zshrc");
|
|
56386
|
+
return join56(home, ".zshrc");
|
|
54580
56387
|
}
|
|
54581
56388
|
function fishCompletionTarget(shell, options) {
|
|
54582
56389
|
const env = options.env ?? process.env;
|
|
54583
56390
|
const home = effectiveHome(options);
|
|
54584
|
-
if (env.XDG_CONFIG_HOME?.trim()) return
|
|
54585
|
-
return
|
|
56391
|
+
if (env.XDG_CONFIG_HOME?.trim()) return join56(resolve32(env.XDG_CONFIG_HOME.trim()), "fish", "completions", "kxm.fish");
|
|
56392
|
+
return join56(home, ".config", "fish", "completions", "kxm.fish");
|
|
54586
56393
|
}
|
|
54587
56394
|
function completionRcTarget(shell, options = {}) {
|
|
54588
56395
|
if (shell === "fish") {
|
|
@@ -54614,13 +56421,13 @@ function installShellCompletion(shellInput, options = {}) {
|
|
|
54614
56421
|
const scriptPath = completionScriptPath(shell, options);
|
|
54615
56422
|
const { rcFile } = completionRcTarget(shell, options);
|
|
54616
56423
|
const script = generateShellCompletion(shell);
|
|
54617
|
-
const existingScript =
|
|
54618
|
-
const existingScriptMatches = existingScript &&
|
|
56424
|
+
const existingScript = existsSync48(scriptPath);
|
|
56425
|
+
const existingScriptMatches = existingScript && readFileSync43(scriptPath, "utf8") === script;
|
|
54619
56426
|
let rcModified = false;
|
|
54620
56427
|
let alreadyInstalled = false;
|
|
54621
56428
|
if (shell === "fish") {
|
|
54622
56429
|
const fishTarget = fishCompletionTarget(shell, options);
|
|
54623
|
-
const fishInstalled =
|
|
56430
|
+
const fishInstalled = existsSync48(fishTarget) && readFileSync43(fishTarget, "utf8") === script;
|
|
54624
56431
|
alreadyInstalled = fishInstalled;
|
|
54625
56432
|
if (!options.dryRun && (!fishInstalled || options.overwrite)) {
|
|
54626
56433
|
mkdirSync33(resolve32(fishTarget, ".."), { recursive: true });
|
|
@@ -54637,7 +56444,7 @@ function installShellCompletion(shellInput, options = {}) {
|
|
|
54637
56444
|
}
|
|
54638
56445
|
if (rcFile) {
|
|
54639
56446
|
const line = sourceLine(shell, scriptPath);
|
|
54640
|
-
const rcContent =
|
|
56447
|
+
const rcContent = existsSync48(rcFile) ? readFileSync43(rcFile, "utf8") : "";
|
|
54641
56448
|
alreadyInstalled = rcContent.includes(line) || rcContent.includes(`${COMPLETION_MARKER}`);
|
|
54642
56449
|
}
|
|
54643
56450
|
if (!options.dryRun) {
|
|
@@ -54653,7 +56460,7 @@ ${marker}
|
|
|
54653
56460
|
${line}
|
|
54654
56461
|
`;
|
|
54655
56462
|
mkdirSync33(resolve32(rcFile, ".."), { recursive: true });
|
|
54656
|
-
writeFileSync28(rcFile, (
|
|
56463
|
+
writeFileSync28(rcFile, (existsSync48(rcFile) ? readFileSync43(rcFile, "utf8") : "") + stanza, { encoding: "utf8" });
|
|
54657
56464
|
rcModified = true;
|
|
54658
56465
|
}
|
|
54659
56466
|
}
|
|
@@ -54668,15 +56475,15 @@ ${line}
|
|
|
54668
56475
|
}
|
|
54669
56476
|
function kxmBinDir(env = process.env) {
|
|
54670
56477
|
const argv1 = env.KXM_ENTRY ?? process.argv[1];
|
|
54671
|
-
if (argv1 &&
|
|
56478
|
+
if (argv1 && isAbsolute12(argv1)) {
|
|
54672
56479
|
const dir = dirname24(argv1);
|
|
54673
|
-
if (
|
|
56480
|
+
if (existsSync48(join56(dir, process.platform === "win32" ? "kxm.cmd" : "kxm"))) return dir;
|
|
54674
56481
|
}
|
|
54675
56482
|
const pathEnv = env.PATH ?? "";
|
|
54676
56483
|
for (const part of pathEnv.split(delimiter)) {
|
|
54677
56484
|
if (!part) continue;
|
|
54678
56485
|
const candidate = resolve32(part, process.platform === "win32" ? "kxm.cmd" : "kxm");
|
|
54679
|
-
if (
|
|
56486
|
+
if (existsSync48(candidate)) return resolve32(part);
|
|
54680
56487
|
}
|
|
54681
56488
|
return void 0;
|
|
54682
56489
|
}
|
|
@@ -54694,7 +56501,7 @@ function installPathEntry(shellInput, options = {}) {
|
|
|
54694
56501
|
}
|
|
54695
56502
|
const { rcFile } = completionRcTarget(shell, options);
|
|
54696
56503
|
const line = pathLine(binDir);
|
|
54697
|
-
const rcContent =
|
|
56504
|
+
const rcContent = existsSync48(rcFile ?? "") ? readFileSync43(rcFile, "utf8") : "";
|
|
54698
56505
|
const alreadyInstalled = rcContent.includes(binDir) || (options.env?.PATH ?? process.env.PATH ?? "").split(delimiter).includes(binDir);
|
|
54699
56506
|
if (!options.dryRun && rcFile && !alreadyInstalled) {
|
|
54700
56507
|
mkdirSync33(dirname24(rcFile), { recursive: true });
|
|
@@ -54707,10 +56514,10 @@ ${line}
|
|
|
54707
56514
|
}
|
|
54708
56515
|
|
|
54709
56516
|
// plugins/kxm/src/init-guide-setup.ts
|
|
54710
|
-
var
|
|
56517
|
+
var import_yaml23 = __toESM(require_dist(), 1);
|
|
54711
56518
|
init_routes();
|
|
54712
|
-
import { existsSync as
|
|
54713
|
-
import { dirname as dirname25, join as
|
|
56519
|
+
import { existsSync as existsSync49, mkdirSync as mkdirSync34, writeFileSync as writeFileSync29 } from "node:fs";
|
|
56520
|
+
import { dirname as dirname25, join as join57 } from "node:path";
|
|
54714
56521
|
var ADMITTED_GUIDE_BINDINGS = Object.freeze({
|
|
54715
56522
|
"anthropic/claude-fable-5.1": { harness: "claude", provider: "anthropic", model: "fable" },
|
|
54716
56523
|
"anthropic/fable": { harness: "claude", provider: "anthropic", model: "fable" },
|
|
@@ -55141,8 +56948,8 @@ function renderGuideSetupFiles(projectRoot, plan) {
|
|
|
55141
56948
|
const piMatch = binding.harness === "pi" && guidePiNoteMatches(binding);
|
|
55142
56949
|
if (binding.harness === "pi" && !piMatch) {
|
|
55143
56950
|
files.push({
|
|
55144
|
-
path:
|
|
55145
|
-
content: (0,
|
|
56951
|
+
path: join57(projectRoot, ".kxm", "agents", `${role}.yaml`),
|
|
56952
|
+
content: (0, import_yaml23.stringify)(agentDocument(role, stage))
|
|
55146
56953
|
});
|
|
55147
56954
|
continue;
|
|
55148
56955
|
}
|
|
@@ -55159,23 +56966,23 @@ function renderGuideSetupFiles(projectRoot, plan) {
|
|
|
55159
56966
|
modelDocument.origin = { source: GUIDE_PI_EVIDENCE_SOURCE, sha256: GUIDE_PI_EVIDENCE_SHA256 };
|
|
55160
56967
|
if (!evidenceQueued) {
|
|
55161
56968
|
files.push({
|
|
55162
|
-
path:
|
|
56969
|
+
path: join57(projectRoot, GUIDE_PI_EVIDENCE_SOURCE),
|
|
55163
56970
|
content: GUIDE_PI_EVIDENCE_NOTE
|
|
55164
56971
|
});
|
|
55165
56972
|
evidenceQueued = true;
|
|
55166
56973
|
}
|
|
55167
56974
|
}
|
|
55168
56975
|
files.push({
|
|
55169
|
-
path:
|
|
55170
|
-
content: (0,
|
|
56976
|
+
path: join57(projectRoot, ".kxm", "agents", `${role}.yaml`),
|
|
56977
|
+
content: (0, import_yaml23.stringify)(agentDocument(role, stage))
|
|
55171
56978
|
});
|
|
55172
56979
|
files.push({
|
|
55173
|
-
path:
|
|
55174
|
-
content: (0,
|
|
56980
|
+
path: join57(projectRoot, ".kxm", "models", `${routeId2}.yaml`),
|
|
56981
|
+
content: (0, import_yaml23.stringify)(modelDocument)
|
|
55175
56982
|
});
|
|
55176
56983
|
files.push({
|
|
55177
|
-
path:
|
|
55178
|
-
content: (0,
|
|
56984
|
+
path: join57(projectRoot, ".kxm", "roles", `${role}.yaml`),
|
|
56985
|
+
content: (0, import_yaml23.stringify)({
|
|
55179
56986
|
schema: "kxm.role.v2",
|
|
55180
56987
|
id: role,
|
|
55181
56988
|
purpose: writer ? "writer" : "experiment",
|
|
@@ -55187,8 +56994,8 @@ function renderGuideSetupFiles(projectRoot, plan) {
|
|
|
55187
56994
|
}
|
|
55188
56995
|
for (const workflow of plan.workflows) {
|
|
55189
56996
|
files.push({
|
|
55190
|
-
path:
|
|
55191
|
-
content: (0,
|
|
56997
|
+
path: join57(projectRoot, ".kxm", "workflows", `${workflow.slug}.yaml`),
|
|
56998
|
+
content: (0, import_yaml23.stringify)(workflowDocument(workflow))
|
|
55192
56999
|
});
|
|
55193
57000
|
}
|
|
55194
57001
|
return files;
|
|
@@ -55204,16 +57011,16 @@ function mergeGuideRouteAdmission(projectRoot, plan) {
|
|
|
55204
57011
|
}
|
|
55205
57012
|
if (added.length === 0) return added;
|
|
55206
57013
|
policy.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
55207
|
-
const path4 =
|
|
57014
|
+
const path4 = join57(projectRoot, ".kxm", "routes.yaml");
|
|
55208
57015
|
mkdirSync34(dirname25(path4), { recursive: true });
|
|
55209
|
-
writeFileSync29(path4, (0,
|
|
57016
|
+
writeFileSync29(path4, (0, import_yaml23.stringify)(policy), "utf8");
|
|
55210
57017
|
return added;
|
|
55211
57018
|
}
|
|
55212
57019
|
function writeGuideSetupFiles(files) {
|
|
55213
57020
|
const written = [];
|
|
55214
57021
|
const existed = [];
|
|
55215
57022
|
for (const file of files) {
|
|
55216
|
-
if (
|
|
57023
|
+
if (existsSync49(file.path)) {
|
|
55217
57024
|
existed.push(file.path);
|
|
55218
57025
|
continue;
|
|
55219
57026
|
}
|
|
@@ -55284,7 +57091,7 @@ function applyKxmPackageUpdate(runtime, notice) {
|
|
|
55284
57091
|
detail: `release v${notice.latest} has no sha256 digest for ${name}; refusing to install`
|
|
55285
57092
|
};
|
|
55286
57093
|
}
|
|
55287
|
-
const releaseDir = mkdtempSync3(
|
|
57094
|
+
const releaseDir = mkdtempSync3(join58(tmpdir3(), "kxm-pkg-update-"));
|
|
55288
57095
|
try {
|
|
55289
57096
|
const downloader = notice.source === "github" && !ghReleaseDownloadAvailable(runtime.env) ? "curl" : "gh";
|
|
55290
57097
|
const planned = planKxmPackageUpdate(notice.source, notice.latest, releaseDir, notice.asset, downloader, notice.localRoot);
|
|
@@ -55294,7 +57101,7 @@ function applyKxmPackageUpdate(runtime, notice) {
|
|
|
55294
57101
|
for (const step of planned) {
|
|
55295
57102
|
if (step.kind === "verify") {
|
|
55296
57103
|
if (!verifyReleaseAssetDigest(step.path, step.sha256)) {
|
|
55297
|
-
const actual =
|
|
57104
|
+
const actual = existsSync50(step.path) ? createHash19("sha256").update(readFileSync44(step.path)).digest("hex") : "missing";
|
|
55298
57105
|
return {
|
|
55299
57106
|
ok: false,
|
|
55300
57107
|
error: "release_digest_mismatch",
|
|
@@ -55622,12 +57429,12 @@ async function cmdValidate(runtime, fileFlag) {
|
|
|
55622
57429
|
}
|
|
55623
57430
|
const selectedFile = explicitFile || configuredFile;
|
|
55624
57431
|
const file = selectedFile ? resolve33(runtime.cwd, selectedFile) : void 0;
|
|
55625
|
-
if (file && !
|
|
57432
|
+
if (file && !existsSync50(file)) {
|
|
55626
57433
|
printWorker(runtime, worker, { ok: false, command: "validate", error: "file_not_found", file }, `workflow file not found: ${file}`);
|
|
55627
57434
|
return 1;
|
|
55628
57435
|
}
|
|
55629
57436
|
try {
|
|
55630
|
-
const raw = file ?
|
|
57437
|
+
const raw = file ? readFileSync44(file, "utf8") : inline;
|
|
55631
57438
|
if (explicitFile && file && !raw.trimStart().startsWith("[")) {
|
|
55632
57439
|
const value = parseRestrictedYaml2(raw, file);
|
|
55633
57440
|
const issues = new KxmSchemaRegistry().validate("workflow", value, file);
|
|
@@ -55722,7 +57529,7 @@ async function cmdImprove(runtime, options = {}) {
|
|
|
55722
57529
|
print(runtime.io, runtime.json, { ok: false, command: "improve", error: "config_invalid", detail: message }, `config_invalid: ${message}`);
|
|
55723
57530
|
return 1;
|
|
55724
57531
|
}
|
|
55725
|
-
const candidatesDir = options.outDir ? resolve33(runtime.cwd, options.outDir) :
|
|
57532
|
+
const candidatesDir = options.outDir ? resolve33(runtime.cwd, options.outDir) : join58(root, ".kxm", "candidates");
|
|
55726
57533
|
const report2 = buildImprovementReport(loaded2.records, {
|
|
55727
57534
|
candidatesDir,
|
|
55728
57535
|
projectRoot: root,
|
|
@@ -55732,7 +57539,7 @@ async function cmdImprove(runtime, options = {}) {
|
|
|
55732
57539
|
halfLifeDays: config.improvement.telemetryHalfLifeDays,
|
|
55733
57540
|
routeSwitches: loaded2.routeSwitches
|
|
55734
57541
|
});
|
|
55735
|
-
const reportDir =
|
|
57542
|
+
const reportDir = join58(runtime.dirs.assets, "improvements");
|
|
55736
57543
|
const reportPath = writeImprovementReport(reportDir, report2, runtime.dryRun);
|
|
55737
57544
|
const text = [
|
|
55738
57545
|
"Sources:",
|
|
@@ -55802,6 +57609,37 @@ async function cmdConfigSet(runtime, key, value, options) {
|
|
|
55802
57609
|
return 1;
|
|
55803
57610
|
}
|
|
55804
57611
|
}
|
|
57612
|
+
async function cmdConfigEdit(runtime, options, isTty = Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
57613
|
+
const scope = options.scope;
|
|
57614
|
+
if (scope !== "project" && scope !== "user") {
|
|
57615
|
+
const detail = "config edit requires --scope project or --scope user";
|
|
57616
|
+
print(runtime.io, runtime.json, { ok: false, command: "config edit", error: "config_edit_unsupported", detail }, detail);
|
|
57617
|
+
return 2;
|
|
57618
|
+
}
|
|
57619
|
+
if (runtime.json) {
|
|
57620
|
+
const detail = "config edit does not print machine-readable output";
|
|
57621
|
+
print(runtime.io, runtime.json, { ok: false, command: "config edit", error: "config_edit_unsupported", detail }, detail);
|
|
57622
|
+
return 2;
|
|
57623
|
+
}
|
|
57624
|
+
try {
|
|
57625
|
+
const userConfigDir = userConfigDirForEnv(runtime.env);
|
|
57626
|
+
return await runKxmConfigEditor({
|
|
57627
|
+
target: {
|
|
57628
|
+
cwd: runtime.cwd,
|
|
57629
|
+
scope,
|
|
57630
|
+
...userConfigDir === void 0 ? {} : { userConfigDir }
|
|
57631
|
+
},
|
|
57632
|
+
stdout: (text) => runtime.io.stdout(text),
|
|
57633
|
+
isTty,
|
|
57634
|
+
dryRun: runtime.dryRun
|
|
57635
|
+
});
|
|
57636
|
+
} catch (error) {
|
|
57637
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
57638
|
+
runtime.io.stderr(`config edit failed: ${message}
|
|
57639
|
+
`);
|
|
57640
|
+
return 1;
|
|
57641
|
+
}
|
|
57642
|
+
}
|
|
55805
57643
|
async function cmdConfigList(runtime) {
|
|
55806
57644
|
try {
|
|
55807
57645
|
const config = loadKxmConfig(runtime.cwd);
|
|
@@ -55876,9 +57714,9 @@ async function maybeOfferCompletionInstall(runtime) {
|
|
|
55876
57714
|
if (!pathNeeded) {
|
|
55877
57715
|
const scriptPath = completionScriptPath(shell, { env: runtime.env, configDir: runtime.env.KXM_USER_CONFIG_DIR });
|
|
55878
57716
|
const { rcFile } = completionRcTarget(shell, { env: runtime.env });
|
|
55879
|
-
if (rcFile &&
|
|
57717
|
+
if (rcFile && existsSync50(rcFile)) {
|
|
55880
57718
|
try {
|
|
55881
|
-
if (
|
|
57719
|
+
if (readFileSync44(rcFile, "utf8").includes(scriptPath)) return;
|
|
55882
57720
|
} catch {
|
|
55883
57721
|
}
|
|
55884
57722
|
}
|
|
@@ -56043,7 +57881,7 @@ async function cmdRoutingReport(runtime, options) {
|
|
|
56043
57881
|
let catalog;
|
|
56044
57882
|
if (includeEquivalentListCost) {
|
|
56045
57883
|
try {
|
|
56046
|
-
const pricesPath = options.prices ? resolve33(runtime.cwd, options.prices) :
|
|
57884
|
+
const pricesPath = options.prices ? resolve33(runtime.cwd, options.prices) : join58(runtime.dirs.workspace, "prices.yaml");
|
|
56047
57885
|
catalog = loadPriceCatalog(pricesPath);
|
|
56048
57886
|
} catch {
|
|
56049
57887
|
}
|
|
@@ -56253,6 +58091,7 @@ var DRY_RUN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
56253
58091
|
"hub unbind",
|
|
56254
58092
|
"config get",
|
|
56255
58093
|
"config set",
|
|
58094
|
+
"config edit",
|
|
56256
58095
|
"config list",
|
|
56257
58096
|
"goal create",
|
|
56258
58097
|
"goal list",
|
|
@@ -56659,7 +58498,7 @@ function createProgram(ctx, result, argv) {
|
|
|
56659
58498
|
addGlobalOptions(harnessCmd.command("list").description("Show installed harnesses, auth, and native updaters")).action(bind(cmdHarnessList));
|
|
56660
58499
|
const pluginCmd = addGlobalOptions(program2.command("plugin").description("Install or manage KXM harness plugins"));
|
|
56661
58500
|
pluginCmd.helpCommand("help", "Show plugin help");
|
|
56662
|
-
addGlobalOptions(pluginCmd.command("install", { isDefault: true }).description("Install the KXM plugin into discovered or specified harnesses")).option("--all", "Install plugin for all discovered harnesses (default)").option("--claude", "Install plugin for Claude Code").option("--omp", "Install plugin for Oh My Pi (OMP)").option("--pi", "Install plugin for Pi").action(async function pluginInstallAction(options) {
|
|
58501
|
+
addGlobalOptions(pluginCmd.command("install", { isDefault: true }).description("Install the KXM plugin into discovered or specified harnesses")).option("--all", "Install plugin for all discovered harnesses (default)").option("--claude", "Install plugin for Claude Code").option("--omp", "Install plugin for Oh My Pi (OMP)").option("--pi", "Install plugin for Pi").option("--status-band", "Configure OMP's focused status band with live subscription usage").action(async function pluginInstallAction(options) {
|
|
56663
58502
|
result.code = await cmdPluginInstall(runtimeFrom(ctx, this), options);
|
|
56664
58503
|
});
|
|
56665
58504
|
const authCmd = addGlobalOptions(program2.command("auth").description("Manage credentials, tokens, and authorization"));
|
|
@@ -56825,7 +58664,7 @@ function createProgram(ctx, result, argv) {
|
|
|
56825
58664
|
});
|
|
56826
58665
|
const gate = addGlobalOptions(program2.command("gate").description("Validate definitions and operate evidence gates"));
|
|
56827
58666
|
gate.helpCommand("help", "Show gate help");
|
|
56828
|
-
addGlobalOptions(gate.command("validate").description("Parse workflow definitions without printing secrets")).option("--file <path>", "Workflow definition file").action(async function
|
|
58667
|
+
addGlobalOptions(gate.command("validate").description("Parse workflow definitions without printing secrets")).option("--file <path>", "Workflow definition file").action(async function validateAction2(options) {
|
|
56829
58668
|
result.code = await cmdValidate(runtimeFrom(ctx, this), options.file);
|
|
56830
58669
|
});
|
|
56831
58670
|
addGlobalOptions(gate.command("artifacts-exist").description("Verify a non-empty file under workspace assets")).requiredOption("--path <file>", "Artifact file under workspace assets").action(async function artifactsExistAction(options) {
|
|
@@ -56953,6 +58792,9 @@ function createProgram(ctx, result, argv) {
|
|
|
56953
58792
|
addGlobalOptions(configCmd.command("set <key> <value>").description("Set a configuration value")).option("--scope <scope>", "Configuration scope: user or project (default: project)", "project").action(async function configSetAction(key, value, options) {
|
|
56954
58793
|
result.code = await cmdConfigSet(runtimeFrom(ctx, this), key, value, options);
|
|
56955
58794
|
});
|
|
58795
|
+
addGlobalOptions(configCmd.command("edit").description("Edit supported non-secret settings in the terminal panel")).option("--scope <scope>", "Write scope: project or user").action(async function configEditAction(options) {
|
|
58796
|
+
result.code = await cmdConfigEdit(runtimeFrom(ctx, this), options);
|
|
58797
|
+
});
|
|
56956
58798
|
addGlobalOptions(configCmd.command("list", { isDefault: true }).description("List resolved configuration values")).action(async function configListAction() {
|
|
56957
58799
|
result.code = await cmdConfigList(runtimeFrom(ctx, this));
|
|
56958
58800
|
});
|