@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.
@@ -8880,7 +8880,7 @@ var require_public_api = __commonJS({
8880
8880
  }
8881
8881
  return doc;
8882
8882
  }
8883
- function parse12(src, reviver, options) {
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 = parse12;
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
- parse12(serialize(uri, options), options);
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 = parse12(serialize(base, options), options);
14087
- relative8 = parse12(serialize(relative8, options), options);
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 parse12(uri, opts) {
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: parse12
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((issue4) => issue4 === "auth_context_required" || issue4 === "auth_unknown" || issue4 === "auth_unparsed" || issue4.startsWith("auth_"));
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 join59 = objectValue(step.join);
20763
- const minimumPassed = join59 && typeof join59.minimumPassed === "number" ? join59.minimumPassed : void 0;
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((issue4) => `${issue4.file}: ${issue4.code}: ${issue4.message}`).join("\n"));
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 issue4 = { phase: "semantic", code, file, message };
22360
- return new KxmConfigError([issue4]);
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 join59 = compileJoin(step, id, sink);
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: join59
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 join59 = objectValue2(step.join);
25182
- if (!join59) return { strategy: "all" };
25183
- const declared = stringValue2(join59.strategy);
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(join59.minimumPassed, `${stepId}.join.minimumPassed`, stepId, sink);
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 join59.cancelRemaining === "boolean" ? { cancelRemaining: join59.cancelRemaining } : {}
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, issue4) {
25455
- sink.issues.push({ workflowId: sink.workflowId, ...issue4 });
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((issue4) => `${issue4.workflowId}: ${issue4.code}: ${issue4.message}`).join("\n"));
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((issue4) => `${issue4.file}: ${issue4.code}: ${issue4.message}`)].join("\n"));
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((issue4) => issue4.severity !== "error") ? 0 : 1;
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.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync9(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
40448
- if (run.error || run.status !== 0 && run.status !== null) {
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.stderr || run.stdout || "pi install exited with error").trim()
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.io.spawnSync ? runtime.io.spawnSync(command, args) : spawnSync9(command, args, { encoding: "utf8", windowsHide: true, env: runtime.env });
40479
- if (run.error || run.status !== 0 && run.status !== null) {
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.stderr || run.stdout || "omp plugin install exited with error").trim()
40600
+ detail: spawnFailureDetail(run, "omp plugin install exited with error")
40487
40601
  });
40488
40602
  } else {
40489
- results.push({
40490
- harness: "omp",
40491
- status: "passed",
40492
- command,
40493
- args,
40494
- detail: "installed kxm plugin into omp"
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.io.spawnSync ? runtime.io.spawnSync(command, installArgs) : spawnSync9(command, installArgs, { encoding: "utf8", windowsHide: true, env: runtime.env });
40516
- if (run.error || run.status !== 0 && run.status !== null) {
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.stderr || run.stdout || "claude plugin install exited with error").trim()
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 existsSync32 } from "node:fs";
40568
- import { join as join36 } from "node:path";
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 = join36(root, relative8);
40586
- if (existsSync32(file)) return file;
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 existsSync33, mkdirSync as mkdirSync24, readFileSync as readFileSync30, writeFileSync as writeFileSync22 } from "node:fs";
40641
- import { basename as basename8, dirname as dirname18, join as join37, resolve as resolve25 } from "node:path";
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 join37(runtime.dirs.state, "lanes.json");
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 (!existsSync33(file)) return { schema: LANE_SCHEMA, lanes: {} };
40822
+ if (!existsSync34(file)) return { schema: LANE_SCHEMA, lanes: {} };
40697
40823
  try {
40698
- const parsed = JSON.parse(readFileSync30(file, "utf8"));
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 || !existsSync33(lane.path)) {
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: readFileSync30(resolve25(runtime.cwd, file), "utf8").trim() };
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 = existsSync33(record.path);
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 (!existsSync33(eventsPath)) return void 0;
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] || existsSync33(path4) || branchExists(root, 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 (!existsSync33(join37(recordedPath, ".kxm", "project.yaml"))) {
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 (!existsSync33(paths.registryDb)) return void 0;
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 (existsSync33(record.path)) {
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 (existsSync33(record.path)) {
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 join38 } from "node:path";
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 = join38(findKxmRepoRoot(import.meta.url), "scripts", "pr-land.mjs");
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 join40 } from "node:path";
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 existsSync34, mkdirSync as mkdirSync25, readFileSync as readFileSync31, renameSync as renameSync8, writeFileSync as writeFileSync23 } from "node:fs";
41234
- import { join as join39 } from "node:path";
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 join39(stateDir, "supervisor.json");
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 (!existsSync34(path4)) return emptySupervisorState(now);
41416
+ if (!existsSync35(path4)) return emptySupervisorState(now);
41291
41417
  let parsed;
41292
41418
  try {
41293
- parsed = JSON.parse(readFileSync31(path4, "utf8"));
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 = join40(runtime.dirs.logs, "supervisor.log");
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: join40(runtime.dirs.state, "supervisor.json") },
41476
- { action: "write", target: join40(runtime.dirs.logs, "supervisor.log") }
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: join40(runtime.dirs.state, "supervisor.json") }
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 existsSync35 } from "node:fs";
41536
- import { join as join41 } from "node:path";
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 (!existsSync35(join41(root, "scripts", "assignment-run.mjs"))) {
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 existsSync41, mkdirSync as mkdirSync30, readFileSync as readFileSync37, readdirSync as readdirSync13, rmSync as rmSync11, writeFileSync as writeFileSync26 } from "node:fs";
41645
- import { join as join51, resolve as resolve28 } from "node:path";
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 join42 } from "node:path";
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(join42(dirname19(packageEntry), "..", nativePath));
47772
+ candidates.push(join43(dirname19(packageEntry), "..", nativePath));
47428
47773
  } catch {
47429
47774
  }
47430
- candidates.push(join42(moduleDir, "..", nativePath), join42(moduleDir, nativePath), join42(dirname19(options.execPath ?? process.execPath), nativePath));
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 join46, resolve as resolve27, dirname as dirname20 } from "node:path";
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 existsSync36, readdirSync as readdirSync12, readFileSync as readFileSync32 } from "node:fs";
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 isAbsolute10, join as join45, resolve as resolve26 } from "node:path";
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() || join45(cwd, ".kxm", "state");
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) : join45(stateDir, "kxm.db");
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 && existsSync36(options.kxmStateRoot)) {
52052
+ if (options?.kxmStateRoot && existsSync37(options.kxmStateRoot)) {
50536
52053
  return resolve26(options.kxmStateRoot);
50537
52054
  }
50538
- if (existsSync36(join45(stateDir, "runtime", "registry.db")) || existsSync36(join45(stateDir, "runtime", "projects"))) {
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 && isAbsolute10(explicit) && existsSync36(resolve26(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 && isAbsolute10(localAppData) ? localAppData : join45(homedir6(), "AppData", "Local");
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 && isAbsolute10(xdgState) ? xdgState : join45(homedir6(), ".local", "state");
52072
+ base = xdgState && isAbsolute11(xdgState) ? xdgState : join46(homedir6(), ".local", "state");
50556
52073
  base = resolve26(base, "kxm");
50557
52074
  }
50558
- if (existsSync36(base)) return base;
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 (existsSync36(dataPath)) {
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 = join45(kxmStateRoot, "runtime");
50623
- const registryDbPath = join45(runtimeDir, "registry.db");
50624
- const projectsDir = join45(runtimeDir, "projects");
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 (existsSync36(registryDbPath)) {
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 && existsSync36(projectsDir)) {
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 = join45(projectsDir, key, "run-events.db");
50655
- if (existsSync36(eventDbPath)) {
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 (existsSync36(stateDir)) {
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(readFileSync32(join45(stateDir, file), "utf8"));
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 = join45(stateDir, "telemetry.jsonl");
50716
- const spend = existsSync36(telemetryFile) ? readRoutingRecords(telemetryFile) : [];
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 = join46(".kxm", "worktrees", `run-${cleanRunId}-${desc}`);
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 existsSync37, mkdirSync as mkdirSync28, readFileSync as readFileSync33, renameSync as renameSync9, writeFileSync as writeFileSync24 } from "node:fs";
51620
- import { join as join47 } from "node:path";
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 hubPrefix(hub) {
53144
+ function hubStatusPart(hub) {
51628
53145
  const suffix = hub?.scope === "remote" ? "/remote" : "";
51629
- if (hub?.state === "on" || hub?.state === void 0 && hub?.online === true) return `kxm hub:on${suffix}`;
51630
- if (hub?.state === "off" || hub?.state === void 0 && hub?.online === false) return `kxm hub:off${suffix}`;
51631
- if (hub?.state === "unknown") return `kxm hub:unknown${suffix}`;
51632
- return "kxm";
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, cost) {
51709
- const head = hubPrefix(hub);
51710
- if (!current && stats.activeTasks === 0 && stats.planCount === 0 && stats.inbox === 0) {
51711
- const idle = hub?.online === void 0 && hub?.state === void 0 ? "kxm idle" : `${head} \xB7 idle`;
51712
- const withShip = ship?.dirty ? `${idle} \xB7 dirty` : ship && ship.ahead > 0 ? `${idle} \xB7 ${ship.ahead} local` : idle;
51713
- const withCost = cost ? `${withShip} \xB7 ${cost}` : withShip;
51714
- const finalLine = updateLatest ? `${withCost} \xB7 upd ${updateLatest}` : withCost;
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
- const line = parts.join(" \xB7 ");
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 hubMark = hub?.state === "on" || hub?.online === true ? "hub:on " : hub?.state === "off" || hub?.online === false ? "hub:off " : hub?.state === "unknown" ? "hub:unknown " : "";
51734
- const lines = [
51735
- `KXM ${hubMark}${stats.activeTasks} tasks ${stats.waitingTasks} waiting ${stats.planCount} plans inbox ${stats.inbox}`
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 ${truncate(stats.latestPlan, 60)}`);
51739
- else lines.push("now no selected work");
51740
- lines.push(formatShipLine(ship));
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, cost);
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 = join47(stateDir, "session-brief.json");
53290
+ const file = join48(stateDir, "session-brief.json");
51780
53291
  try {
51781
- if (!existsSync37(file)) return void 0;
51782
- const raw = readFileSync33(file, "utf8");
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 = join47(stateDir, "session-brief.json");
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 = join47(stateDir, "telemetry.jsonl");
51808
- if (!existsSync37(telemetryFile)) return void 0;
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 "unknown";
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 existsSync38, readFileSync as readFileSync34 } from "node:fs";
51981
- import { dirname as dirname21, join as join48 } from "node:path";
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 (!existsSync38(join48(dir, ".git"))) return void 0;
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(readFileSync34(join48(root, "package.json"), "utf8"));
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 = join48(probe.homeDir, ".pi", "agent", "git");
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() || join48(probe.homeDir, ".claude");
52041
- const pluginsRoot = join48(claudeHome, "plugins");
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 = existsSync38(join48(probe.repoRoot, ".git"));
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 import_yaml19 = __toESM(require_dist(), 1);
52067
- import { existsSync as existsSync39, readFileSync as readFileSync35 } from "node:fs";
52068
- import { join as join49 } from "node:path";
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 = join49(kxmUserStateRoot({ env }), "update.yaml");
52072
- if (!existsSync39(path4)) return { schema: KXM_UPDATE_SCHEMA, auto: false, source: "npm" };
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, import_yaml19.parse)(readFileSync35(path4, "utf8"));
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 existsSync40, mkdirSync as mkdirSync29, readFileSync as readFileSync36, writeFileSync as writeFileSync25 } from "node:fs";
52104
- import { join as join50 } from "node:path";
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 = join50(assetsDir, "workflows", workflowId);
52115
- return [root, join50(root, "inputs"), join50(root, "outputs"), join50(root, "generated")];
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 = join50(assetsDir, "sessions", sessionId);
52119
- return [root, join50(root, "inputs"), join50(root, "outputs")];
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 = join50(configDir, file);
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 (!existsSync40(path4)) return [];
53692
+ if (!existsSync41(path4)) return [];
52184
53693
  let parsed;
52185
53694
  try {
52186
- parsed = JSON.parse(readFileSync36(path4, "utf8"));
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 ? join50("assets", "workflows", input.workflowId) : join50("assets", "sessions", input.id);
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 = join50(assetsDir, "sessions", session.id);
52225
- const path4 = join50(dir, "session.json");
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 = join51(runtime.dirs.workspace, "update.yaml");
52268
- if (!existsSync41(projectFile)) return;
52269
- const userFile = join51(kxmUserStateRoot({ env: runtime.env }), "update.yaml");
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() || join51(runtime.dirs.state, "kxm.db"));
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 = join51(kxmUserStateRoot({ env: runtime.env }), "update.yaml");
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 = join51(findConfigRoot(runtime.dirs.workdir), ".kxm", "config.yaml");
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 = existsSync41(runtime.dirs.state) ? readdirSync13(runtime.dirs.state).filter((name) => name.endsWith(".pid")) : [];
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(readFileSync37(join51(runtime.dirs.state, file), "utf8"));
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(join51(runtime.dirs.state, record.controlFile), `${JSON.stringify({ startedAt: record.startedAt, ...record.generation ? { generation: record.generation } : {}, requestedAt: (/* @__PURE__ */ new Date()).toISOString() })}
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(readFileSync37(join51(runtime.dirs.state, file), "utf8"));
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(readFileSync37(join51(runtime.dirs.state, file), "utf8"));
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(readFileSync37(join51(runtime.dirs.state, file), "utf8"));
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(join51(runtime.dirs.state, file), { force: true });
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 = existsSync41(stateDir) ? readdirSync13(stateDir) : [];
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(readFileSync37(join51(stateDir, file), "utf8"));
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(readFileSync37(join51(stateDir, file), "utf8"));
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 = existsSync41(tokenFile);
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() || join51(runtime.dirs.state, "kxm.db"));
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: join51(runtime.dirs.state, "session-brief.json") });
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 existsSync48, mkdtempSync as mkdtempSync3, readFileSync as readFileSync42, rmSync as rmSync13 } from "node:fs";
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 join57, resolve as resolve33 } from "node:path";
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 existsSync42, mkdirSync as mkdirSync31, writeFileSync as writeFileSync27 } from "node:fs";
53070
- import { join as join52, relative as relative7, resolve as resolve29 } from "node:path";
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) : join52(projectRoot, ".kxm", "candidates");
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 = join52(candidatesDir, diffFileName);
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 (!existsSync42(candidatesDir)) {
54871
+ if (!existsSync43(candidatesDir)) {
53363
54872
  mkdirSync31(candidatesDir, { recursive: true });
53364
54873
  }
53365
54874
  writeFileSync27(diffFilePath, diff, "utf8");
53366
- const jsonFilePath = join52(candidatesDir, `${group.candidateId}.json`);
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 = join52(improvementsDir, `${stamp}.json`);
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 existsSync43 } from "node:fs";
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 (!existsSync43(path4)) return { records: [], source, routeSwitches: [] };
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 = existsSync43(path4);
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 import_yaml20 = __toESM(require_dist(), 1);
55152
+ var import_yaml21 = __toESM(require_dist(), 1);
53644
55153
  init_prices();
53645
- import { existsSync as existsSync44, readFileSync as readFileSync39 } from "node:fs";
53646
- import { join as join53 } from "node:path";
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 = join53(projectRoot, ".kxm", "modes.yaml");
53710
- if (!existsSync44(modesPath)) {
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 = readFileSync39(modesPath, "utf8");
53715
- const parsed = (0, import_yaml20.parse)(raw);
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 = join53(projectRoot, relPath);
55309
+ const fullPath = join54(projectRoot, relPath);
53801
55310
  let chars = 0;
53802
- if (existsSync44(fullPath)) {
55311
+ if (existsSync45(fullPath)) {
53803
55312
  try {
53804
- chars = readFileSync39(fullPath, "utf8").length;
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 existsSync45, mkdirSync as mkdirSync32, readFileSync as readFileSync40, readdirSync as readdirSync14, rmSync as rmSync12, statSync as statSync7 } from "node:fs";
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 join54, resolve as resolve31 } from "node:path";
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 ?? join54(homedir8(), ".ssh", "config");
53966
- if (!existsSync45(targetPath)) {
55474
+ const targetPath = configPath ?? join55(homedir8(), ".ssh", "config");
55475
+ if (!existsSync46(targetPath)) {
53967
55476
  return [];
53968
55477
  }
53969
55478
  try {
53970
- const content = readFileSync40(targetPath, "utf-8");
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 (!existsSync45(resolved)) {
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 = join54(socketDir, "%C");
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 = join54(resolvedDir, "%C");
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 = join54(resolvedDir, "%C");
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 existsSync46, mkdirSync as mkdirSync33, readFileSync as readFileSync41, writeFileSync as writeFileSync28 } from "node:fs";
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 isAbsolute11, join as join55, resolve as resolve32 } from "node:path";
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 join55(base, "completions", `kxm.${shell}`);
56374
+ return join56(base, "completions", `kxm.${shell}`);
54568
56375
  }
54569
56376
  function bashRcCandidate(options) {
54570
56377
  const home = effectiveHome(options);
54571
- const candidates = [join55(home, ".bashrc"), join55(home, ".bash_profile")];
54572
- const existing = candidates.find((candidate) => existsSync46(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 join55(resolve32(env.ZDOTDIR.trim()), ".zshrc");
54579
- return join55(home, ".zshrc");
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 join55(resolve32(env.XDG_CONFIG_HOME.trim()), "fish", "completions", "kxm.fish");
54585
- return join55(home, ".config", "fish", "completions", "kxm.fish");
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 = existsSync46(scriptPath);
54618
- const existingScriptMatches = existingScript && readFileSync41(scriptPath, "utf8") === script;
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 = existsSync46(fishTarget) && readFileSync41(fishTarget, "utf8") === script;
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 = existsSync46(rcFile) ? readFileSync41(rcFile, "utf8") : "";
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, (existsSync46(rcFile) ? readFileSync41(rcFile, "utf8") : "") + stanza, { encoding: "utf8" });
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 && isAbsolute11(argv1)) {
56478
+ if (argv1 && isAbsolute12(argv1)) {
54672
56479
  const dir = dirname24(argv1);
54673
- if (existsSync46(join55(dir, process.platform === "win32" ? "kxm.cmd" : "kxm"))) return dir;
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 (existsSync46(candidate)) return resolve32(part);
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 = existsSync46(rcFile ?? "") ? readFileSync41(rcFile, "utf8") : "";
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 import_yaml21 = __toESM(require_dist(), 1);
56517
+ var import_yaml23 = __toESM(require_dist(), 1);
54711
56518
  init_routes();
54712
- import { existsSync as existsSync47, mkdirSync as mkdirSync34, writeFileSync as writeFileSync29 } from "node:fs";
54713
- import { dirname as dirname25, join as join56 } from "node:path";
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: join56(projectRoot, ".kxm", "agents", `${role}.yaml`),
55145
- content: (0, import_yaml21.stringify)(agentDocument(role, stage))
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: join56(projectRoot, GUIDE_PI_EVIDENCE_SOURCE),
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: join56(projectRoot, ".kxm", "agents", `${role}.yaml`),
55170
- content: (0, import_yaml21.stringify)(agentDocument(role, stage))
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: join56(projectRoot, ".kxm", "models", `${routeId2}.yaml`),
55174
- content: (0, import_yaml21.stringify)(modelDocument)
56980
+ path: join57(projectRoot, ".kxm", "models", `${routeId2}.yaml`),
56981
+ content: (0, import_yaml23.stringify)(modelDocument)
55175
56982
  });
55176
56983
  files.push({
55177
- path: join56(projectRoot, ".kxm", "roles", `${role}.yaml`),
55178
- content: (0, import_yaml21.stringify)({
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: join56(projectRoot, ".kxm", "workflows", `${workflow.slug}.yaml`),
55191
- content: (0, import_yaml21.stringify)(workflowDocument(workflow))
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 = join56(projectRoot, ".kxm", "routes.yaml");
57014
+ const path4 = join57(projectRoot, ".kxm", "routes.yaml");
55208
57015
  mkdirSync34(dirname25(path4), { recursive: true });
55209
- writeFileSync29(path4, (0, import_yaml21.stringify)(policy), "utf8");
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 (existsSync47(file.path)) {
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(join57(tmpdir3(), "kxm-pkg-update-"));
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 = existsSync48(step.path) ? createHash19("sha256").update(readFileSync42(step.path)).digest("hex") : "missing";
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 && !existsSync48(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 ? readFileSync42(file, "utf8") : inline;
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) : join57(root, ".kxm", "candidates");
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 = join57(runtime.dirs.assets, "improvements");
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 && existsSync48(rcFile)) {
57717
+ if (rcFile && existsSync50(rcFile)) {
55880
57718
  try {
55881
- if (readFileSync42(rcFile, "utf8").includes(scriptPath)) return;
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) : join57(runtime.dirs.workspace, "prices.yaml");
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 validateAction(options) {
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
  });