@kontextmind/kxm 0.7.176 → 0.7.178

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 };
@@ -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);
@@ -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;
@@ -43129,8 +43130,12 @@ function finalizeTruncatedResult(prefix, prefixWidth, ellipsis, ellipsisWidth, m
43129
43130
  return pad2 ? result + " ".repeat(Math.max(0, maxWidth - visibleWidth2)) : result;
43130
43131
  }
43131
43132
  function graphemeWidth(segment) {
43132
- if (segment === " ") {
43133
- return 3;
43133
+ if (segment.length === 1) {
43134
+ const code = segment.charCodeAt(0);
43135
+ if (code >= 32 && code <= 126)
43136
+ return 1;
43137
+ if (code === 9)
43138
+ return 3;
43134
43139
  }
43135
43140
  if (terminalSpacingMarkRegex.test(segment)) {
43136
43141
  return [...segment].length;
@@ -43174,8 +43179,9 @@ function visibleWidth(str) {
43174
43179
  if (str.length === 0) {
43175
43180
  return 0;
43176
43181
  }
43177
- if (isPrintableAscii(str)) {
43178
- return str.length;
43182
+ const asciiWidth = asciiVisibleWidth(str);
43183
+ if (asciiWidth !== -1) {
43184
+ return asciiWidth;
43179
43185
  }
43180
43186
  const cached = widthCache.get(str);
43181
43187
  if (cached !== void 0) {
@@ -43185,19 +43191,22 @@ function visibleWidth(str) {
43185
43191
  if (str.includes(" ")) {
43186
43192
  clean = clean.replace(/\t/g, " ");
43187
43193
  }
43188
- if (clean.includes("\x1B")) {
43194
+ let escapeIndex = clean.indexOf("\x1B");
43195
+ if (escapeIndex !== -1) {
43189
43196
  let stripped = "";
43190
- let i = 0;
43191
- while (i < clean.length) {
43192
- const ansi = extractAnsiCode(clean, i);
43193
- if (ansi) {
43194
- i += ansi.length;
43195
- continue;
43197
+ let copyFrom = 0;
43198
+ while (escapeIndex !== -1) {
43199
+ const length = ansiCodeLength(clean, escapeIndex);
43200
+ if (length > 0) {
43201
+ stripped += clean.slice(copyFrom, escapeIndex);
43202
+ escapeIndex += length;
43203
+ copyFrom = escapeIndex;
43204
+ } else {
43205
+ escapeIndex++;
43196
43206
  }
43197
- stripped += clean[i];
43198
- i++;
43207
+ escapeIndex = clean.indexOf("\x1B", escapeIndex);
43199
43208
  }
43200
- clean = stripped;
43209
+ clean = stripped + clean.slice(copyFrom);
43201
43210
  }
43202
43211
  let width = 0;
43203
43212
  for (const { segment } of graphemeSegmenter.segment(clean)) {
@@ -43301,40 +43310,54 @@ function normalizeTerminalOutput(str) {
43301
43310
  return result;
43302
43311
  }
43303
43312
  function extractAnsiCode(str, pos) {
43304
- if (pos >= str.length || str[pos] !== "\x1B")
43305
- return null;
43313
+ const length = ansiCodeLength(str, pos);
43314
+ return length > 0 ? { code: str.substring(pos, pos + length), length } : null;
43315
+ }
43316
+ function asciiVisibleWidth(str) {
43317
+ let width = 0;
43318
+ let i = 0;
43319
+ while (i < str.length) {
43320
+ const code = str.charCodeAt(i);
43321
+ if (code >= 32 && code <= 126) {
43322
+ width++;
43323
+ i++;
43324
+ } else if (code === 9) {
43325
+ width += 3;
43326
+ i++;
43327
+ } else if (code === 27) {
43328
+ const length = ansiCodeLength(str, i);
43329
+ if (length === 0)
43330
+ return -1;
43331
+ i += length;
43332
+ } else {
43333
+ return -1;
43334
+ }
43335
+ }
43336
+ return width;
43337
+ }
43338
+ function ansiCodeLength(str, pos) {
43339
+ if (pos >= str.length || str.charCodeAt(pos) !== 27)
43340
+ return 0;
43306
43341
  const next = str[pos + 1];
43307
43342
  if (next === "[") {
43308
- let j2 = pos + 2;
43309
- while (j2 < str.length && !/[mGKHJ]/.test(str[j2]))
43310
- j2++;
43311
- if (j2 < str.length)
43312
- return { code: str.substring(pos, j2 + 1), length: j2 + 1 - pos };
43313
- return null;
43314
- }
43315
- if (next === "]") {
43316
- let j2 = pos + 2;
43317
- while (j2 < str.length) {
43318
- if (str[j2] === "\x07")
43319
- return { code: str.substring(pos, j2 + 1), length: j2 + 1 - pos };
43320
- if (str[j2] === "\x1B" && str[j2 + 1] === "\\")
43321
- return { code: str.substring(pos, j2 + 2), length: j2 + 2 - pos };
43322
- j2++;
43343
+ for (let j2 = pos + 2; j2 < str.length; j2++) {
43344
+ const c = str.charCodeAt(j2);
43345
+ if (c === 109 || c === 71 || c === 75 || c === 72 || c === 74)
43346
+ return j2 + 1 - pos;
43323
43347
  }
43324
- return null;
43348
+ return 0;
43325
43349
  }
43326
- if (next === "_") {
43327
- let j2 = pos + 2;
43328
- while (j2 < str.length) {
43329
- if (str[j2] === "\x07")
43330
- return { code: str.substring(pos, j2 + 1), length: j2 + 1 - pos };
43331
- if (str[j2] === "\x1B" && str[j2 + 1] === "\\")
43332
- return { code: str.substring(pos, j2 + 2), length: j2 + 2 - pos };
43333
- j2++;
43350
+ if (next === "]" || next === "_") {
43351
+ for (let j2 = pos + 2; j2 < str.length; j2++) {
43352
+ const c = str.charCodeAt(j2);
43353
+ if (c === 7)
43354
+ return j2 + 1 - pos;
43355
+ if (c === 27 && str[j2 + 1] === "\\")
43356
+ return j2 + 2 - pos;
43334
43357
  }
43335
- return null;
43358
+ return 0;
43336
43359
  }
43337
- return null;
43360
+ return 0;
43338
43361
  }
43339
43362
  function parseOsc8Hyperlink(ansiCode) {
43340
43363
  if (!ansiCode.startsWith("\x1B]8;")) {
@@ -43578,15 +43601,16 @@ var AnsiCodeTracker = class {
43578
43601
  }
43579
43602
  };
43580
43603
  function updateTrackerFromText(text, tracker) {
43581
- let i = 0;
43582
- while (i < text.length) {
43583
- const ansiResult = extractAnsiCode(text, i);
43584
- if (ansiResult) {
43585
- tracker.process(ansiResult.code);
43586
- i += ansiResult.length;
43604
+ let i = text.indexOf("\x1B");
43605
+ while (i !== -1) {
43606
+ const length = ansiCodeLength(text, i);
43607
+ if (length > 0) {
43608
+ tracker.process(text.substring(i, i + length));
43609
+ i += length;
43587
43610
  } else {
43588
43611
  i++;
43589
43612
  }
43613
+ i = text.indexOf("\x1B", i);
43590
43614
  }
43591
43615
  }
43592
43616
  function getActiveBackgroundAnsi(text) {
@@ -43594,6 +43618,10 @@ function getActiveBackgroundAnsi(text) {
43594
43618
  updateTrackerFromText(text, tracker);
43595
43619
  return tracker.getActiveBackgroundCode();
43596
43620
  }
43621
+ function* graphemeSegments(text) {
43622
+ for (const { segment } of graphemeSegmenter.segment(text))
43623
+ yield segment;
43624
+ }
43597
43625
  function splitIntoTokensWithAnsi(text) {
43598
43626
  const tokens = [];
43599
43627
  let current = "";
@@ -43615,13 +43643,17 @@ function splitIntoTokensWithAnsi(text) {
43615
43643
  i += ansiResult.length;
43616
43644
  continue;
43617
43645
  }
43618
- let end = i;
43619
- while (end < text.length && !extractAnsiCode(text, end)) {
43620
- end++;
43646
+ let end = text.indexOf("\x1B", i + 1);
43647
+ while (end !== -1 && ansiCodeLength(text, end) === 0) {
43648
+ end = text.indexOf("\x1B", end + 1);
43621
43649
  }
43622
- for (const { segment } of graphemeSegmenter.segment(text.slice(i, end))) {
43650
+ if (end === -1)
43651
+ end = text.length;
43652
+ const chunk = text.slice(i, end);
43653
+ const ascii = isPrintableAscii(chunk);
43654
+ for (const segment of ascii ? chunk : graphemeSegments(chunk)) {
43623
43655
  const segmentIsSpace = segment === " ";
43624
- if (!segmentIsSpace && cjkBreakRegex.test(segment)) {
43656
+ if (!ascii && !segmentIsSpace && cjkBreakRegex.test(segment)) {
43625
43657
  flushCurrent();
43626
43658
  const token = pendingAnsi + segment;
43627
43659
  pendingAnsi = "";
@@ -43655,6 +43687,10 @@ function splitIntoTokensWithAnsi(text) {
43655
43687
  }
43656
43688
  return tokens;
43657
43689
  }
43690
+ function flattenLines(lines) {
43691
+ for (const line of lines)
43692
+ Number(line);
43693
+ }
43658
43694
  function wrapTextWithAnsi(text, width) {
43659
43695
  if (!text) {
43660
43696
  return [""];
@@ -43927,9 +43963,10 @@ function sliceWithWidth(line, startCol, length, strict = false) {
43927
43963
  while (i < line.length) {
43928
43964
  const ansi = extractAnsiCode(line, i);
43929
43965
  if (ansi) {
43930
- if (currentCol >= startCol && currentCol < endCol)
43931
- result += ansi.code;
43932
- else if (currentCol < startCol)
43966
+ if (currentCol >= startCol && currentCol < endCol) {
43967
+ result += pendingAnsi + ansi.code;
43968
+ pendingAnsi = "";
43969
+ } else if (currentCol < startCol)
43933
43970
  pendingAnsi += ansi.code;
43934
43971
  i += ansi.length;
43935
43972
  continue;
@@ -44016,8 +44053,19 @@ function extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter = fa
44016
44053
  // node_modules/@earendil-works/pi-tui/dist/autocomplete.js
44017
44054
  var tokenStartRegex = new RegExp(`${autocompleteBoundaryRegex.source}$`, "u");
44018
44055
 
44056
+ // node_modules/@earendil-works/pi-tui/dist/oklab.js
44057
+ var K1 = 0.206;
44058
+ var K2 = 0.03;
44059
+ var K3 = (1 + K1) / (1 + K2);
44060
+
44061
+ // node_modules/@earendil-works/pi-tui/dist/colors.js
44062
+ var NUMBER_PATTERN = String.raw`[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?`;
44063
+ var OKLCH_PATTERN = new RegExp(`^oklch\\(\\s*(${NUMBER_PATTERN})(%)?\\s+(${NUMBER_PATTERN})\\s+(${NUMBER_PATTERN})(?:deg)?\\s*\\)$`, "i");
44064
+ var OKHSL_PATTERN = new RegExp(`^okhsl\\(\\s*(${NUMBER_PATTERN})(?:deg)?\\s+(${NUMBER_PATTERN})(%)?\\s+(${NUMBER_PATTERN})(%)?\\s*\\)$`, "i");
44065
+ var GRAY_VALUES = Array.from({ length: 24 }, (_2, index) => 8 + index * 10);
44066
+
44019
44067
  // node_modules/@earendil-works/pi-tui/dist/tui.js
44020
- import { performance } from "node:perf_hooks";
44068
+ import { performance as performance2 } from "node:perf_hooks";
44021
44069
 
44022
44070
  // node_modules/@earendil-works/pi-tui/dist/keys.js
44023
44071
  var _kittyProtocolActive = false;
@@ -44280,6 +44328,66 @@ var LEGACY_CTRL_SEQUENCES = {
44280
44328
  home: ["\x1B[7^"],
44281
44329
  end: ["\x1B[8^"]
44282
44330
  };
44331
+ var LEGACY_SEQUENCE_KEY_IDS = {
44332
+ "\x1BOA": "up",
44333
+ "\x1BOB": "down",
44334
+ "\x1BOC": "right",
44335
+ "\x1BOD": "left",
44336
+ "\x1BOH": "home",
44337
+ "\x1BOF": "end",
44338
+ "\x1B[E": "clear",
44339
+ "\x1BOE": "clear",
44340
+ "\x1BOe": "ctrl+clear",
44341
+ "\x1B[e": "shift+clear",
44342
+ "\x1B[2~": "insert",
44343
+ "\x1B[2$": "shift+insert",
44344
+ "\x1B[2^": "ctrl+insert",
44345
+ "\x1B[3$": "shift+delete",
44346
+ "\x1B[3^": "ctrl+delete",
44347
+ "\x1B[[5~": "pageUp",
44348
+ "\x1B[[6~": "pageDown",
44349
+ "\x1B[a": "shift+up",
44350
+ "\x1B[b": "shift+down",
44351
+ "\x1B[c": "shift+right",
44352
+ "\x1B[d": "shift+left",
44353
+ "\x1BOa": "ctrl+up",
44354
+ "\x1BOb": "ctrl+down",
44355
+ "\x1BOc": "ctrl+right",
44356
+ "\x1BOd": "ctrl+left",
44357
+ "\x1B[5$": "shift+pageUp",
44358
+ "\x1B[6$": "shift+pageDown",
44359
+ "\x1B[7$": "shift+home",
44360
+ "\x1B[8$": "shift+end",
44361
+ "\x1B[5^": "ctrl+pageUp",
44362
+ "\x1B[6^": "ctrl+pageDown",
44363
+ "\x1B[7^": "ctrl+home",
44364
+ "\x1B[8^": "ctrl+end",
44365
+ "\x1BOP": "f1",
44366
+ "\x1BOQ": "f2",
44367
+ "\x1BOR": "f3",
44368
+ "\x1BOS": "f4",
44369
+ "\x1B[11~": "f1",
44370
+ "\x1B[12~": "f2",
44371
+ "\x1B[13~": "f3",
44372
+ "\x1B[14~": "f4",
44373
+ "\x1B[[A": "f1",
44374
+ "\x1B[[B": "f2",
44375
+ "\x1B[[C": "f3",
44376
+ "\x1B[[D": "f4",
44377
+ "\x1B[[E": "f5",
44378
+ "\x1B[15~": "f5",
44379
+ "\x1B[17~": "f6",
44380
+ "\x1B[18~": "f7",
44381
+ "\x1B[19~": "f8",
44382
+ "\x1B[20~": "f9",
44383
+ "\x1B[21~": "f10",
44384
+ "\x1B[23~": "f11",
44385
+ "\x1B[24~": "f12",
44386
+ "\x1Bb": "alt+left",
44387
+ "\x1Bf": "alt+right",
44388
+ "\x1Bp": "alt+up",
44389
+ "\x1Bn": "alt+down"
44390
+ };
44283
44391
  var matchesLegacySequence = (data, sequences) => sequences.includes(data);
44284
44392
  var matchesLegacyModifierSequence = (data, key, modifier) => {
44285
44393
  if (modifier === MODIFIERS.shift) {
@@ -44425,6 +44533,22 @@ function matchesPrintableModifyOtherKeys(data, expectedKeycode, expectedModifier
44425
44533
  return false;
44426
44534
  return normalizeShiftedLetterIdentityCodepoint(parsed.codepoint, parsed.modifier) === normalizeShiftedLetterIdentityCodepoint(expectedKeycode, expectedModifier);
44427
44535
  }
44536
+ function formatKeyNameWithModifiers(keyName, modifier) {
44537
+ const mods = [];
44538
+ const effectiveMod = modifier & ~LOCK_MASK;
44539
+ const supportedModifierMask = MODIFIERS.shift | MODIFIERS.ctrl | MODIFIERS.alt | MODIFIERS.super;
44540
+ if ((effectiveMod & ~supportedModifierMask) !== 0)
44541
+ return void 0;
44542
+ if (effectiveMod & MODIFIERS.shift)
44543
+ mods.push("shift");
44544
+ if (effectiveMod & MODIFIERS.ctrl)
44545
+ mods.push("ctrl");
44546
+ if (effectiveMod & MODIFIERS.alt)
44547
+ mods.push("alt");
44548
+ if (effectiveMod & MODIFIERS.super)
44549
+ mods.push("super");
44550
+ return mods.length > 0 ? `${mods.join("+")}+${keyName}` : keyName;
44551
+ }
44428
44552
  function parseKeyId(keyId) {
44429
44553
  const parts = keyId.toLowerCase().split("+");
44430
44554
  const key = parts[parts.length - 1];
@@ -44681,6 +44805,149 @@ function matchesKey(data, keyId) {
44681
44805
  }
44682
44806
  return false;
44683
44807
  }
44808
+ function formatParsedKey(codepoint, modifier, baseLayoutKey) {
44809
+ const normalizedCodepoint = normalizeKittyFunctionalCodepoint(codepoint);
44810
+ const identityCodepoint = normalizeShiftedLetterIdentityCodepoint(normalizedCodepoint, modifier);
44811
+ const isLatinLetter = identityCodepoint >= 97 && identityCodepoint <= 122;
44812
+ const isDigit = identityCodepoint >= 48 && identityCodepoint <= 57;
44813
+ const isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(identityCodepoint));
44814
+ const effectiveCodepoint = isLatinLetter || isDigit || isKnownSymbol ? identityCodepoint : baseLayoutKey ?? identityCodepoint;
44815
+ let keyName;
44816
+ if (effectiveCodepoint === CODEPOINTS.escape)
44817
+ keyName = "escape";
44818
+ else if (effectiveCodepoint === CODEPOINTS.tab)
44819
+ keyName = "tab";
44820
+ else if (effectiveCodepoint === CODEPOINTS.enter || effectiveCodepoint === CODEPOINTS.kpEnter)
44821
+ keyName = "enter";
44822
+ else if (effectiveCodepoint === CODEPOINTS.space)
44823
+ keyName = "space";
44824
+ else if (effectiveCodepoint === CODEPOINTS.backspace)
44825
+ keyName = "backspace";
44826
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.delete)
44827
+ keyName = "delete";
44828
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.insert)
44829
+ keyName = "insert";
44830
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.home)
44831
+ keyName = "home";
44832
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.end)
44833
+ keyName = "end";
44834
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageUp)
44835
+ keyName = "pageUp";
44836
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageDown)
44837
+ keyName = "pageDown";
44838
+ else if (effectiveCodepoint === ARROW_CODEPOINTS.up)
44839
+ keyName = "up";
44840
+ else if (effectiveCodepoint === ARROW_CODEPOINTS.down)
44841
+ keyName = "down";
44842
+ else if (effectiveCodepoint === ARROW_CODEPOINTS.left)
44843
+ keyName = "left";
44844
+ else if (effectiveCodepoint === ARROW_CODEPOINTS.right)
44845
+ keyName = "right";
44846
+ else if (effectiveCodepoint >= 48 && effectiveCodepoint <= 57)
44847
+ keyName = String.fromCharCode(effectiveCodepoint);
44848
+ else if (effectiveCodepoint >= 97 && effectiveCodepoint <= 122)
44849
+ keyName = String.fromCharCode(effectiveCodepoint);
44850
+ else if (SYMBOL_KEYS.has(String.fromCharCode(effectiveCodepoint)))
44851
+ keyName = String.fromCharCode(effectiveCodepoint);
44852
+ if (!keyName)
44853
+ return void 0;
44854
+ return formatKeyNameWithModifiers(keyName, modifier);
44855
+ }
44856
+ function parseKey(data) {
44857
+ const kitty = parseKittySequence(data);
44858
+ if (kitty) {
44859
+ return formatParsedKey(kitty.codepoint, kitty.modifier, kitty.baseLayoutKey);
44860
+ }
44861
+ const modifyOtherKeys = parseModifyOtherKeysSequence(data);
44862
+ if (modifyOtherKeys) {
44863
+ return formatParsedKey(modifyOtherKeys.codepoint, modifyOtherKeys.modifier);
44864
+ }
44865
+ if (_kittyProtocolActive) {
44866
+ if (data === "\x1B\r" || data === "\n")
44867
+ return "shift+enter";
44868
+ }
44869
+ const legacySequenceKeyId = LEGACY_SEQUENCE_KEY_IDS[data];
44870
+ if (legacySequenceKeyId)
44871
+ return legacySequenceKeyId;
44872
+ if (data === "\x1B")
44873
+ return "escape";
44874
+ if (data === "")
44875
+ return "ctrl+\\";
44876
+ if (data === "")
44877
+ return "ctrl+]";
44878
+ if (data === "")
44879
+ return "ctrl+-";
44880
+ if (data === "\x1B\x1B")
44881
+ return "ctrl+alt+[";
44882
+ if (data === "\x1B")
44883
+ return "ctrl+alt+\\";
44884
+ if (data === "\x1B")
44885
+ return "ctrl+alt+]";
44886
+ if (data === "\x1B")
44887
+ return "ctrl+alt+-";
44888
+ if (data === " ")
44889
+ return "tab";
44890
+ if (data === "\r" || !_kittyProtocolActive && data === "\n" || data === "\x1BOM")
44891
+ return "enter";
44892
+ if (data === "\0")
44893
+ return "ctrl+space";
44894
+ if (data === " ")
44895
+ return "space";
44896
+ if (data === "\x7F")
44897
+ return "backspace";
44898
+ if (data === "\b")
44899
+ return isWindowsTerminalSession() ? "ctrl+backspace" : "backspace";
44900
+ if (data === "\x1B[Z")
44901
+ return "shift+tab";
44902
+ if (!_kittyProtocolActive && data === "\x1B\r")
44903
+ return "alt+enter";
44904
+ if (!_kittyProtocolActive && data === "\x1B ")
44905
+ return "alt+space";
44906
+ if (data === "\x1B\x7F" || data === "\x1B\b")
44907
+ return "alt+backspace";
44908
+ if (!_kittyProtocolActive && data === "\x1BB")
44909
+ return "alt+left";
44910
+ if (!_kittyProtocolActive && data === "\x1BF")
44911
+ return "alt+right";
44912
+ if (!_kittyProtocolActive && data.length === 2 && data[0] === "\x1B") {
44913
+ const code = data.charCodeAt(1);
44914
+ if (code >= 1 && code <= 26) {
44915
+ return `ctrl+alt+${String.fromCharCode(code + 96)}`;
44916
+ }
44917
+ const key = String.fromCharCode(code);
44918
+ if (code >= 97 && code <= 122 || code >= 48 && code <= 57 || SYMBOL_KEYS.has(key)) {
44919
+ return `alt+${key}`;
44920
+ }
44921
+ }
44922
+ if (data === "\x1B[A")
44923
+ return "up";
44924
+ if (data === "\x1B[B")
44925
+ return "down";
44926
+ if (data === "\x1B[C")
44927
+ return "right";
44928
+ if (data === "\x1B[D")
44929
+ return "left";
44930
+ if (data === "\x1B[H" || data === "\x1BOH")
44931
+ return "home";
44932
+ if (data === "\x1B[F" || data === "\x1BOF")
44933
+ return "end";
44934
+ if (data === "\x1B[3~")
44935
+ return "delete";
44936
+ if (data === "\x1B[5~")
44937
+ return "pageUp";
44938
+ if (data === "\x1B[6~")
44939
+ return "pageDown";
44940
+ if (data.length === 1) {
44941
+ const code = data.charCodeAt(0);
44942
+ if (code >= 1 && code <= 26) {
44943
+ return `ctrl+${String.fromCharCode(code + 96)}`;
44944
+ }
44945
+ if (code >= 32 && code <= 126) {
44946
+ return data;
44947
+ }
44948
+ }
44949
+ return void 0;
44950
+ }
44684
44951
  var KITTY_CSI_U_REGEX = /^\x1b\[(\d+)(?::(\d*))?(?::(\d+))?(?:;(\d+))?(?::(\d+))?u$/;
44685
44952
  var KITTY_PRINTABLE_ALLOWED_MODIFIERS = MODIFIERS.shift | LOCK_MASK;
44686
44953
  function decodeKittyPrintable(data) {
@@ -44729,17 +44996,18 @@ function parseOscHexChannel(channel) {
44729
44996
  }
44730
44997
  return Math.round(parseInt(channel, 16) / max * 255);
44731
44998
  }
44732
- var OSC11_BACKGROUND_COLOR_RESPONSE_PATTERN = /^\x1b\]11;([^\x07\x1b]*)(?:\x07|\x1b\\)$/i;
44999
+ var OSC_COLOR_RESPONSE_PATTERN = /^\x1b\](?:(1[01])|4;(\d{1,3}));([^\x07\x1b]*)(?:\x07|\x1b\\)$/i;
44733
45000
  var COLOR_SCHEME_REPORT_PATTERN = /^(?:\x1b\[\?997;(1|2)n)+$/;
44734
- function isOsc11BackgroundColorResponse(data) {
44735
- return OSC11_BACKGROUND_COLOR_RESPONSE_PATTERN.test(data);
44736
- }
44737
- function parseOsc11BackgroundColor(data) {
44738
- const match = data.match(OSC11_BACKGROUND_COLOR_RESPONSE_PATTERN);
45001
+ function parseOscColorResponse(data) {
45002
+ const match = data.match(OSC_COLOR_RESPONSE_PATTERN);
44739
45003
  if (!match) {
44740
45004
  return void 0;
44741
45005
  }
44742
- const value = match[1].trim();
45006
+ const target = match[1] === "10" ? "foreground" : match[1] === "11" ? "background" : Number.parseInt(match[2], 10);
45007
+ return { target, rgb: parseOscColorValue(match[3]) };
45008
+ }
45009
+ function parseOscColorValue(rawValue) {
45010
+ const value = rawValue.trim();
44743
45011
  if (value.startsWith("#")) {
44744
45012
  const hex = value.slice(1);
44745
45013
  if (/^[0-9a-f]{6}$/i.test(hex)) {
@@ -44796,7 +45064,7 @@ function detectCapabilitiesFromEnvironment(tmuxForwardsHyperlink) {
44796
45064
  const terminalEmulator = process.env.TERMINAL_EMULATOR?.toLowerCase() || "";
44797
45065
  const term = process.env.TERM?.toLowerCase() || "";
44798
45066
  const colorTerm = process.env.COLORTERM?.toLowerCase() || "";
44799
- const hasTrueColorHint = colorTerm === "truecolor" || colorTerm === "24bit";
45067
+ const hasTrueColorHint = colorTerm === "truecolor" || colorTerm === "24bit" || term.endsWith("-direct");
44800
45068
  const isWindowsConsole = process.platform === "win32";
44801
45069
  if (process.env.TMUX || term.startsWith("tmux")) {
44802
45070
  return { images: null, trueColor: hasTrueColorHint, hyperlinks: tmuxForwardsHyperlink() };
@@ -44880,12 +45148,23 @@ function deleteAllKittyPlacements() {
44880
45148
  return "\x1B_Ga=d,d=a,q=2\x1B\\";
44881
45149
  }
44882
45150
  var kittyImageMetadata = /* @__PURE__ */ new Map();
45151
+ function getRegisteredKittyImageMetadataFromControls(controls) {
45152
+ const imageId = /(?:^|,)i=(\d+)(?:,|$)/.exec(controls)?.[1];
45153
+ return imageId === void 0 ? void 0 : kittyImageMetadata.get(Number.parseInt(imageId, 10));
45154
+ }
44883
45155
  function getRegisteredKittyImageMetadata(line) {
44884
45156
  const controls = /\x1b_G([^;]*);/.exec(line)?.[1];
44885
- if (!controls)
45157
+ return controls === void 0 ? void 0 : getRegisteredKittyImageMetadataFromControls(controls);
45158
+ }
45159
+ function getExplicitKittyImageRows(controls) {
45160
+ const value = /(?:^|,)r=(\d+)(?:,|$)/.exec(controls)?.[1];
45161
+ if (value === void 0)
44886
45162
  return void 0;
44887
- const imageId = /(?:^|,)i=(\d+)(?:,|$)/.exec(controls)?.[1];
44888
- return imageId === void 0 ? void 0 : kittyImageMetadata.get(Number.parseInt(imageId, 10));
45163
+ const rows = Number.parseInt(value, 10);
45164
+ return rows > 0 ? rows : void 0;
45165
+ }
45166
+ function getKittyImageRowsFromControls(controls, fallbackRows) {
45167
+ return getExplicitKittyImageRows(controls) ?? fallbackRows;
44889
45168
  }
44890
45169
  function getKittyImageMetadata(line) {
44891
45170
  const metadata = getRegisteredKittyImageMetadata(line);
@@ -44918,10 +45197,21 @@ var KITTY_PLACEMENT_CONTROL_KEYS = /* @__PURE__ */ new Set([
44918
45197
  "H",
44919
45198
  "V"
44920
45199
  ]);
45200
+ function getKittyImagePlacementRows(line) {
45201
+ const controls = /\x1b_G([^;]*);/.exec(line)?.[1];
45202
+ if (controls === void 0)
45203
+ return void 0;
45204
+ const explicitRows = getExplicitKittyImageRows(controls);
45205
+ if (explicitRows !== void 0)
45206
+ return explicitRows;
45207
+ return getRegisteredKittyImageMetadataFromControls(controls)?.rows;
45208
+ }
44921
45209
  function getKittyImagePlacement(line) {
44922
45210
  const match = /\x1b_G([^;]*);/.exec(line);
44923
- const metadata = getRegisteredKittyImageMetadata(line);
44924
- if (!match || !metadata)
45211
+ if (!match)
45212
+ return void 0;
45213
+ const metadata = getRegisteredKittyImageMetadataFromControls(match[1]);
45214
+ if (!metadata)
44925
45215
  return void 0;
44926
45216
  let commandStart = match.index;
44927
45217
  let commandControls = match[1];
@@ -44948,6 +45238,7 @@ function getKittyImagePlacement(line) {
44948
45238
  transmissionGeneration: metadata.transmissionGeneration,
44949
45239
  transmissionBytes: transmissionEnd - match.index,
44950
45240
  estimatedDecodedBytes: metadata.widthPx * metadata.heightPx * 4,
45241
+ rows: getKittyImageRowsFromControls(match[1], metadata.rows),
44951
45242
  sequence,
44952
45243
  replacementLine: `${line.slice(0, match.index)}${sequence}${line.slice(transmissionEnd)}`
44953
45244
  };
@@ -44973,8 +45264,10 @@ function dispatchMouseEvent(component, event) {
44973
45264
  const result = component.handleMouse?.(event);
44974
45265
  if (!result)
44975
45266
  return void 0;
44976
- if ("target" in result)
44977
- return result;
45267
+ if ("target" in result) {
45268
+ const forwarded = result;
45269
+ return forwarded.focus && component.handleInput ? { ...forwarded, focusTarget: component } : forwarded;
45270
+ }
44978
45271
  if (!result.handled && !result.capture && !result.focus)
44979
45272
  return void 0;
44980
45273
  return {
@@ -44999,6 +45292,10 @@ function retargetMouseEvent(event, target) {
44999
45292
  height: target.height
45000
45293
  };
45001
45294
  }
45295
+ var TERMINAL_PALETTE_SIZE = 16;
45296
+ var TERMINAL_COLOR_REPLY_COUNT = 2 + TERMINAL_PALETTE_SIZE;
45297
+ var TERMINAL_COLOR_QUERY = `\x1B]10;?\x07\x1B]11;?\x07${Array.from({ length: TERMINAL_PALETTE_SIZE }, (_2, index) => `\x1B]4;${index};?\x07`).join("")}\x1B[c`;
45298
+ var DEVICE_ATTRIBUTES_RESPONSE_PATTERN = /^\x1b\[\?[\d;]*c$/;
45002
45299
  function isFocusable(component) {
45003
45300
  return component !== null && "focused" in component;
45004
45301
  }
@@ -45100,8 +45397,11 @@ var TuiBase = class _TuiBase extends Container {
45100
45397
  clearOnShrink = false;
45101
45398
  fullRedrawCount = 0;
45102
45399
  stopped = false;
45103
- pendingOsc11BackgroundReplies = 0;
45104
- pendingOsc11BackgroundQueries = [];
45400
+ /**
45401
+ * Color queries waiting for their DA1 reply, oldest first. Terminals answer in order, so color
45402
+ * replies belong to the oldest one. Queries stay here after a timeout to collect late replies.
45403
+ */
45404
+ pendingTerminalColorQueries = [];
45105
45405
  terminalColorSchemeListeners = /* @__PURE__ */ new Set();
45106
45406
  terminalColorSchemeNotificationsEnabled = false;
45107
45407
  /** Directory for debug/crash logs. When undefined, debug logging is disabled and crash dumps fall back to the OS temp directory. */
@@ -45143,7 +45443,7 @@ var TuiBase = class _TuiBase extends Container {
45143
45443
  return;
45144
45444
  this.showHardwareCursor = enabled;
45145
45445
  if (!enabled) {
45146
- this.terminal.hideCursor();
45446
+ this.hideTerminalCursor();
45147
45447
  }
45148
45448
  this.requestRender();
45149
45449
  }
@@ -45279,7 +45579,7 @@ var TuiBase = class _TuiBase extends Container {
45279
45579
  if (!options?.nonCapturing && this.isOverlayVisible(entry)) {
45280
45580
  this.setFocus(component);
45281
45581
  }
45282
- this.terminal.hideCursor();
45582
+ this.hideTerminalCursor();
45283
45583
  this.requestRender();
45284
45584
  return {
45285
45585
  hide: () => {
@@ -45293,7 +45593,7 @@ var TuiBase = class _TuiBase extends Container {
45293
45593
  this.setFocus(topVisible?.component ?? entry.preFocus);
45294
45594
  }
45295
45595
  if (this.overlayStack.length === 0)
45296
- this.terminal.hideCursor();
45596
+ this.hideTerminalCursor();
45297
45597
  this.requestRender();
45298
45598
  }
45299
45599
  },
@@ -45372,9 +45672,14 @@ var TuiBase = class _TuiBase extends Container {
45372
45672
  this.setFocus(topVisible?.component ?? overlay.preFocus);
45373
45673
  }
45374
45674
  if (this.overlayStack.length === 0)
45375
- this.terminal.hideCursor();
45675
+ this.hideTerminalCursor();
45376
45676
  this.requestRender();
45377
45677
  }
45678
+ /** Hide the cursor while running. After stop(), the shell owns the cursor and it must stay visible. */
45679
+ hideTerminalCursor() {
45680
+ if (!this.stopped)
45681
+ this.terminal.hideCursor();
45682
+ }
45378
45683
  /** Check if there are any visible overlays */
45379
45684
  hasOverlay() {
45380
45685
  return this.overlayStack.some((o) => this.isOverlayVisible(o));
@@ -45499,7 +45804,7 @@ var TuiBase = class _TuiBase extends Container {
45499
45804
  this.resetRenderState();
45500
45805
  this.renderRequested = false;
45501
45806
  this.cancelRenderTimer();
45502
- this.lastRenderAt = performance.now();
45807
+ this.lastRenderAt = performance2.now();
45503
45808
  this.doRender();
45504
45809
  }
45505
45810
  requestRender(force = false) {
@@ -45525,7 +45830,7 @@ var TuiBase = class _TuiBase extends Container {
45525
45830
  return;
45526
45831
  this.cancelRenderTimer();
45527
45832
  this.renderRequested = false;
45528
- this.lastRenderAt = performance.now();
45833
+ this.lastRenderAt = performance2.now();
45529
45834
  this.doRender();
45530
45835
  });
45531
45836
  }
@@ -45539,7 +45844,7 @@ var TuiBase = class _TuiBase extends Container {
45539
45844
  if (this.stopped || this.renderTimer || !this.renderRequested) {
45540
45845
  return;
45541
45846
  }
45542
- const elapsed = performance.now() - this.lastRenderAt;
45847
+ const elapsed = performance2.now() - this.lastRenderAt;
45543
45848
  const delay = Math.max(0, _TuiBase.MIN_RENDER_INTERVAL_MS - elapsed);
45544
45849
  this.renderTimer = setTimeout(() => {
45545
45850
  this.renderTimer = void 0;
@@ -45547,7 +45852,7 @@ var TuiBase = class _TuiBase extends Container {
45547
45852
  return;
45548
45853
  }
45549
45854
  this.renderRequested = false;
45550
- this.lastRenderAt = performance.now();
45855
+ this.lastRenderAt = performance2.now();
45551
45856
  this.doRender();
45552
45857
  if (this.renderRequested) {
45553
45858
  this.scheduleRender();
@@ -45555,7 +45860,7 @@ var TuiBase = class _TuiBase extends Container {
45555
45860
  }, delay);
45556
45861
  }
45557
45862
  handleTerminalInput(data) {
45558
- if (this.consumeOsc11BackgroundResponse(data)) {
45863
+ if (this.consumeTerminalColorResponse(data)) {
45559
45864
  return;
45560
45865
  }
45561
45866
  if (this.consumeTerminalColorSchemeReport(data)) {
@@ -45615,27 +45920,48 @@ var TuiBase = class _TuiBase extends Container {
45615
45920
  this.requestImmediateRender();
45616
45921
  }
45617
45922
  }
45618
- consumeOsc11BackgroundResponse(data) {
45619
- if (this.pendingOsc11BackgroundReplies <= 0) {
45923
+ consumeTerminalColorResponse(data) {
45924
+ const query = this.pendingTerminalColorQueries[0];
45925
+ if (!query) {
45620
45926
  return false;
45621
45927
  }
45622
- if (!isOsc11BackgroundColorResponse(data)) {
45928
+ if (DEVICE_ATTRIBUTES_RESPONSE_PATTERN.test(data)) {
45929
+ this.pendingTerminalColorQueries.shift();
45930
+ this.completeTerminalColorQuery(query);
45931
+ return true;
45932
+ }
45933
+ const response = parseOscColorResponse(data);
45934
+ if (!response) {
45623
45935
  return false;
45624
45936
  }
45625
- const rgb = parseOsc11BackgroundColor(data);
45626
- this.pendingOsc11BackgroundReplies -= 1;
45627
- const query = this.pendingOsc11BackgroundQueries.shift();
45628
- if (query && !query.settled) {
45629
- query.settled = true;
45630
- if (query.timer) {
45631
- clearTimeout(query.timer);
45632
- query.timer = void 0;
45633
- }
45634
- query.resolve?.(rgb);
45635
- query.resolve = void 0;
45937
+ const { target, rgb } = response;
45938
+ const key = String(target);
45939
+ if (!query.deliver || query.replied.has(key)) {
45940
+ return true;
45941
+ }
45942
+ query.replied.add(key);
45943
+ if (target === "foreground") {
45944
+ query.foreground = rgb;
45945
+ } else if (target === "background") {
45946
+ query.background = rgb;
45947
+ } else if (target < TERMINAL_PALETTE_SIZE) {
45948
+ query.palette[target] = rgb;
45949
+ }
45950
+ if (query.replied.size === TERMINAL_COLOR_REPLY_COUNT) {
45951
+ this.completeTerminalColorQuery(query);
45636
45952
  }
45637
45953
  return true;
45638
45954
  }
45955
+ terminalColorQueryResult(query) {
45956
+ const palette = query.palette.every((color) => color !== void 0) ? query.palette : void 0;
45957
+ return { foreground: query.foreground, background: query.background, palette };
45958
+ }
45959
+ completeTerminalColorQuery(query) {
45960
+ const deliver = query.deliver;
45961
+ query.deliver = void 0;
45962
+ clearTimeout(query.timer);
45963
+ deliver?.(this.terminalColorQueryResult(query));
45964
+ }
45639
45965
  consumeTerminalColorSchemeReport(data) {
45640
45966
  const scheme = parseTerminalColorSchemeReport(data);
45641
45967
  if (!scheme) {
@@ -45844,56 +46170,27 @@ var TuiBase = class _TuiBase extends Container {
45844
46170
  return null;
45845
46171
  }
45846
46172
  /**
45847
- * Query the terminal's default background color with OSC 11 (`ESC ] 11 ; ? BEL`).
45848
- * @param timeoutMs Query timeout in milliseconds.
45849
- * @returns Promise containing the parsed RGB color, or undefined if it times out or fails to parse.
46173
+ * Query the terminal's theme colors: the default foreground (OSC 10), the default background
46174
+ * (OSC 11), and ANSI colors 0-15 (OSC 4), followed by a DA1 request that marks the end of the
46175
+ * replies. Resolves when the DA1 reply or all color replies arrive, or when the timeout expires.
46176
+ * Colors the terminal did not report are undefined; the palette is only set when all 16 arrived.
46177
+ * @param timeoutMs Query timeout in milliseconds, for terminals that do not answer DA1 either.
46178
+ * @param onLateReply Receives the replies if the query completes after the timeout, e.g. over slow links.
45850
46179
  */
45851
- queryTerminalBackgroundColor({ timeoutMs }) {
46180
+ queryTerminalColors({ timeoutMs, onLateReply }) {
45852
46181
  return new Promise((resolve35) => {
45853
46182
  const query = {
45854
- settled: false,
45855
- resolve: resolve35,
46183
+ palette: Array.from({ length: TERMINAL_PALETTE_SIZE }, () => void 0),
46184
+ replied: /* @__PURE__ */ new Set(),
46185
+ deliver: resolve35,
45856
46186
  timer: void 0
45857
46187
  };
45858
46188
  query.timer = setTimeout(() => {
45859
- if (query.settled) {
45860
- return;
45861
- }
45862
- query.settled = true;
45863
- query.timer = void 0;
45864
- query.resolve?.(void 0);
45865
- query.resolve = void 0;
46189
+ query.deliver = onLateReply;
46190
+ resolve35(this.terminalColorQueryResult(query));
45866
46191
  }, timeoutMs);
45867
- this.pendingOsc11BackgroundQueries.push(query);
45868
- this.pendingOsc11BackgroundReplies += 1;
45869
- this.terminal.write("\x1B]11;?\x07");
45870
- });
45871
- }
45872
- /**
45873
- * Query the terminal's color-scheme preference with DSR (`CSI ? 996 n`).
45874
- * Terminals that support the color palette notification protocol reply with
45875
- * `CSI ? 997 ; 1 n` for dark or `CSI ? 997 ; 2 n` for light.
45876
- */
45877
- queryTerminalColorScheme({ timeoutMs }) {
45878
- return new Promise((resolve35) => {
45879
- let settled = false;
45880
- let timer;
45881
- let unsubscribe = () => {
45882
- };
45883
- const settle = (scheme) => {
45884
- if (settled)
45885
- return;
45886
- settled = true;
45887
- if (timer) {
45888
- clearTimeout(timer);
45889
- timer = void 0;
45890
- }
45891
- unsubscribe();
45892
- resolve35(scheme);
45893
- };
45894
- unsubscribe = this.onTerminalColorSchemeChange(settle);
45895
- timer = setTimeout(() => settle(void 0), timeoutMs);
45896
- this.terminal.write("\x1B[?996n");
46192
+ this.pendingTerminalColorQueries.push(query);
46193
+ this.terminal.write(TERMINAL_COLOR_QUERY);
45897
46194
  });
45898
46195
  }
45899
46196
  };
@@ -45977,7 +46274,7 @@ var Box = class {
45977
46274
  const lines = child.render(contentWidth);
45978
46275
  mouseChildren.push({ component: child, height: lines.length });
45979
46276
  for (const line of lines) {
45980
- childLines.push(leftPad + line);
46277
+ childLines.push(line);
45981
46278
  }
45982
46279
  }
45983
46280
  this.mouseLayout = { width: contentWidth, children: mouseChildren };
@@ -45993,11 +46290,12 @@ var Box = class {
45993
46290
  result.push(this.applyBg("", width));
45994
46291
  }
45995
46292
  for (const line of childLines) {
45996
- result.push(this.applyBg(line, width));
46293
+ result.push(this.applyBg(leftPad + line, width));
45997
46294
  }
45998
46295
  for (let i = 0; i < this.paddingY; i++) {
45999
46296
  result.push(this.applyBg("", width));
46000
46297
  }
46298
+ flattenLines(result);
46001
46299
  this.cache = { childLines, width, bgSample, lines: result };
46002
46300
  return result;
46003
46301
  }
@@ -46005,10 +46303,7 @@ var Box = class {
46005
46303
  const visLen = visibleWidth(line);
46006
46304
  const padNeeded = Math.max(0, width - visLen);
46007
46305
  const padded = line + " ".repeat(padNeeded);
46008
- if (this.bgFn) {
46009
- return applyBackgroundToLine(padded, width, this.bgFn);
46010
- }
46011
- return padded;
46306
+ return this.bgFn ? this.bgFn(padded) : padded;
46012
46307
  }
46013
46308
  };
46014
46309
 
@@ -46312,6 +46607,7 @@ var Text = class {
46312
46607
  emptyLines.push(line);
46313
46608
  }
46314
46609
  const result = [...emptyLines, ...contentLines, ...emptyLines];
46610
+ flattenLines(result);
46315
46611
  this.cachedText = this.text;
46316
46612
  this.cachedWidth = width;
46317
46613
  this.cachedLines = result;
@@ -46443,6 +46739,7 @@ function findWordForward(text, cursor2, options) {
46443
46739
  var graphemeSegmenter2 = getGraphemeSegmenter();
46444
46740
  var wordSegmenter3 = getWordSegmenter();
46445
46741
  var unquotedAutocompleteSuffixRegex = new RegExp(`(?:(?!${autocompleteSeparatorRegex.source}).)*`, "u");
46742
+ var autocompleteTokenStartSource = `${autocompleteBoundaryRegex.source}[([{<\`]*`;
46446
46743
 
46447
46744
  // node_modules/@earendil-works/pi-tui/dist/layout-node.js
46448
46745
  var LAYOUT_NODE = /* @__PURE__ */ Symbol.for("@earendil-works/pi-tui/layout-node");
@@ -47733,6 +48030,8 @@ var ProcessTerminal = class {
47733
48030
  _kittyProtocolActive = false;
47734
48031
  _modifyOtherKeysActive = false;
47735
48032
  keyboardProtocolPushed = false;
48033
+ /** DA1 replies owed to keyboard protocol queries. Later DA1 replies answer other queries and are forwarded. */
48034
+ pendingKeyboardProtocolDeviceAttributes = 0;
47736
48035
  keyboardProtocolNegotiationBuffer = "";
47737
48036
  keyboardProtocolBufferFlushTimer;
47738
48037
  stdinBuffer;
@@ -47784,15 +48083,15 @@ var ProcessTerminal = class {
47784
48083
  setupStdinBuffer() {
47785
48084
  this.stdinBuffer = new StdinBuffer({ escapeTimeout: resolveEscapeTimeoutMs() });
47786
48085
  this.stdinBuffer.on("data", (sequence) => {
47787
- const negotiationSequence = this.readKeyboardProtocolNegotiationSequence(sequence);
47788
- if (negotiationSequence === "pending") {
48086
+ const negotiation = this.readKeyboardProtocolNegotiationSequence(sequence);
48087
+ if (negotiation === "pending") {
47789
48088
  this.scheduleKeyboardProtocolNegotiationBufferFlush();
47790
48089
  return;
47791
48090
  }
47792
- if (this.handleKeyboardProtocolNegotiationSequence(negotiationSequence)) {
48091
+ if (negotiation && this.handleKeyboardProtocolNegotiationSequence(negotiation.parsed)) {
47793
48092
  return;
47794
48093
  }
47795
- this.forwardInputSequence(sequence);
48094
+ this.forwardInputSequence(negotiation?.sequence ?? sequence);
47796
48095
  });
47797
48096
  this.stdinBuffer.on("paste", (content) => {
47798
48097
  if (this.inputHandler) {
@@ -47820,13 +48119,17 @@ var ProcessTerminal = class {
47820
48119
  this.setupStdinBuffer();
47821
48120
  process.stdin.on("data", this.stdinDataHandler);
47822
48121
  this.keyboardProtocolPushed = true;
48122
+ this.pendingKeyboardProtocolDeviceAttributes += 1;
47823
48123
  this.clearKeyboardProtocolNegotiationBuffer();
47824
48124
  process.stdout.write(KITTY_KEYBOARD_PROTOCOL_QUERY);
47825
48125
  }
47826
48126
  handleKeyboardProtocolNegotiationSequence(negotiationSequence) {
47827
- if (!negotiationSequence)
47828
- return false;
47829
48127
  this.clearKeyboardProtocolNegotiationBuffer();
48128
+ if (negotiationSequence.type === "device-attributes") {
48129
+ if (this.pendingKeyboardProtocolDeviceAttributes === 0)
48130
+ return false;
48131
+ this.pendingKeyboardProtocolDeviceAttributes -= 1;
48132
+ }
47830
48133
  if (negotiationSequence.type === "kitty-flags") {
47831
48134
  if (negotiationSequence.flags !== 0) {
47832
48135
  this.disableModifyOtherKeys();
@@ -47844,13 +48147,14 @@ var ProcessTerminal = class {
47844
48147
  }
47845
48148
  return true;
47846
48149
  }
48150
+ /** Returns the parsed negotiation reply with its full (possibly reassembled) sequence. */
47847
48151
  readKeyboardProtocolNegotiationSequence(sequence) {
47848
48152
  if (this.keyboardProtocolNegotiationBuffer) {
47849
48153
  const bufferedSequence = this.keyboardProtocolNegotiationBuffer + sequence;
47850
48154
  const negotiationSequence2 = parseKeyboardProtocolNegotiationSequence(bufferedSequence);
47851
48155
  if (negotiationSequence2) {
47852
48156
  this.clearKeyboardProtocolNegotiationBuffer();
47853
- return negotiationSequence2;
48157
+ return { parsed: negotiationSequence2, sequence: bufferedSequence };
47854
48158
  }
47855
48159
  if (isKeyboardProtocolNegotiationSequencePrefix(bufferedSequence)) {
47856
48160
  this.setKeyboardProtocolNegotiationBuffer(bufferedSequence);
@@ -47860,7 +48164,7 @@ var ProcessTerminal = class {
47860
48164
  }
47861
48165
  const negotiationSequence = parseKeyboardProtocolNegotiationSequence(sequence);
47862
48166
  if (negotiationSequence)
47863
- return negotiationSequence;
48167
+ return { parsed: negotiationSequence, sequence };
47864
48168
  if (isKeyboardProtocolNegotiationSequencePrefix(sequence)) {
47865
48169
  this.setKeyboardProtocolNegotiationBuffer(sequence);
47866
48170
  return "pending";
@@ -48677,6 +48981,61 @@ function getScrollViewsAt(frame, x2, y2) {
48677
48981
  return result.map((entry) => entry.scrollView);
48678
48982
  }
48679
48983
 
48984
+ // node_modules/@earendil-works/pi-tui/dist/wheel-scroll.js
48985
+ var BURST_GAP_MS = 5;
48986
+ var GESTURE_GAP_MS = 200;
48987
+ var REFERENCE_GAP_MS = 100;
48988
+ var MAX_AUTO_LINES = 6;
48989
+ function terminalAcceleratesWheel() {
48990
+ const env = process.env;
48991
+ return process.platform === "darwin" && env.SSH_CONNECTION === void 0 && env.SSH_CLIENT === void 0 && env.SSH_TTY === void 0;
48992
+ }
48993
+ var WheelScrollAccelerator = class {
48994
+ lines;
48995
+ accelerate;
48996
+ lastTime = Number.NEGATIVE_INFINITY;
48997
+ lastDirection = 0;
48998
+ averageGap;
48999
+ carry = 0;
49000
+ constructor(lines = "auto", accelerate = !terminalAcceleratesWheel()) {
49001
+ this.lines = lines;
49002
+ this.accelerate = accelerate;
49003
+ }
49004
+ setLines(lines) {
49005
+ this.lines = lines;
49006
+ this.reset();
49007
+ }
49008
+ /** Return the positive line count for a wheel event in `direction` at time `now` (milliseconds). */
49009
+ next(direction, now) {
49010
+ if (this.lines !== "auto")
49011
+ return Number.isFinite(this.lines) ? Math.max(1, Math.floor(this.lines)) : 1;
49012
+ if (!this.accelerate)
49013
+ return 1;
49014
+ const gap = now - this.lastTime;
49015
+ const sameGesture = direction === this.lastDirection && gap <= GESTURE_GAP_MS;
49016
+ this.lastTime = now;
49017
+ this.lastDirection = direction;
49018
+ if (!sameGesture) {
49019
+ this.averageGap = void 0;
49020
+ this.carry = 0;
49021
+ return 1;
49022
+ }
49023
+ if (gap < BURST_GAP_MS)
49024
+ return 1;
49025
+ this.averageGap = this.averageGap === void 0 ? gap : (this.averageGap + gap) / 2;
49026
+ const lines = Math.min(MAX_AUTO_LINES, Math.max(1, REFERENCE_GAP_MS / this.averageGap)) + this.carry;
49027
+ const whole = Math.floor(lines);
49028
+ this.carry = lines - whole;
49029
+ return whole;
49030
+ }
49031
+ reset() {
49032
+ this.lastTime = Number.NEGATIVE_INFINITY;
49033
+ this.lastDirection = 0;
49034
+ this.averageGap = void 0;
49035
+ this.carry = 0;
49036
+ }
49037
+ };
49038
+
48680
49039
  // node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.js
48681
49040
  var ENTER_ALT_SCREEN = "\x1B[?1049h";
48682
49041
  var EXIT_ALT_SCREEN = "\x1B[?1049l";
@@ -48736,7 +49095,7 @@ var TuiAltScreen = class extends TuiBase {
48736
49095
  mousePressPoint;
48737
49096
  mousePressMoved = false;
48738
49097
  lastComponentClick;
48739
- wheelScrollLines;
49098
+ wheelScroll;
48740
49099
  mouseEnabled;
48741
49100
  searchMatchStyle;
48742
49101
  searchCurrentMatchStyle;
@@ -48758,7 +49117,7 @@ var TuiAltScreen = class extends TuiBase {
48758
49117
  };
48759
49118
  this.implicitScrollView = new ScrollView(this.implicitDocument, { follow: "end", primary: true });
48760
49119
  this.flashes = new AltScreenFlashContainer(() => this.requestRender());
48761
- this.wheelScrollLines = Math.max(1, Math.floor(options.wheelScrollLines ?? 1));
49120
+ this.wheelScroll = new WheelScrollAccelerator(options.wheelScrollLines ?? 1);
48762
49121
  this.mouseEnabled = options.mouse ?? true;
48763
49122
  this.searchMatchStyle = options.searchMatchStyle ?? ((text) => `\x1B[4m${text}\x1B[24m`);
48764
49123
  this.searchCurrentMatchStyle = options.searchCurrentMatchStyle ?? ((text) => `\x1B[1;7m${text}\x1B[22;27m`);
@@ -48776,6 +49135,9 @@ var TuiAltScreen = class extends TuiBase {
48776
49135
  get isFollowingOutput() {
48777
49136
  return this.getPrimaryScrollView().isFollowingEnd;
48778
49137
  }
49138
+ setWheelScrollLines(lines) {
49139
+ this.wheelScroll.setLines(lines);
49140
+ }
48779
49141
  getCopyOnSelect() {
48780
49142
  return this.copyOnSelect;
48781
49143
  }
@@ -48793,6 +49155,10 @@ var TuiAltScreen = class extends TuiBase {
48793
49155
  return false;
48794
49156
  return this.copyTextToClipboard(text);
48795
49157
  }
49158
+ /** The lines of the last rendered frame, one per terminal row, as written to the terminal. */
49159
+ getScreenLines() {
49160
+ return [...this.previousScreen];
49161
+ }
48796
49162
  setLayoutRoot(component) {
48797
49163
  if (this.layoutRoot === component)
48798
49164
  return;
@@ -49135,9 +49501,9 @@ var TuiAltScreen = class extends TuiBase {
49135
49501
  return { consume: true };
49136
49502
  const wheelEvent = this.parseWheelEvent(data);
49137
49503
  if (wheelEvent) {
49138
- const event = this.createMouseEvent("wheel", wheelEvent.button, wheelEvent.x, wheelEvent.y, {
49139
- wheelDelta: wheelEvent.direction * this.getWheelScrollLines(wheelEvent.button)
49140
- });
49504
+ const lines = this.wheelScroll.next(wheelEvent.direction, performance.now());
49505
+ const wheelDelta = wheelEvent.direction * ((wheelEvent.button & 8) !== 0 ? lines * ALT_WHEEL_SCROLL_MULTIPLIER : lines);
49506
+ const event = this.createMouseEvent("wheel", wheelEvent.button, wheelEvent.x, wheelEvent.y, { wheelDelta });
49141
49507
  const overlay = this.dispatchMouseToOverlay(event);
49142
49508
  const result = overlay.result ?? (overlay.hit ? void 0 : this.dispatchMouseToLayout(event));
49143
49509
  if (result) {
@@ -49147,7 +49513,7 @@ var TuiAltScreen = class extends TuiBase {
49147
49513
  }
49148
49514
  if (this.shouldDeferViewportInputToOverlay())
49149
49515
  return void 0;
49150
- this.routeWheel(wheelEvent);
49516
+ this.routeWheel(wheelEvent, wheelDelta);
49151
49517
  return { consume: true };
49152
49518
  }
49153
49519
  const mouseEvent = this.parseSgrMouseEvent(data);
@@ -49410,11 +49776,8 @@ var TuiAltScreen = class extends TuiBase {
49410
49776
  }
49411
49777
  return void 0;
49412
49778
  }
49413
- getWheelScrollLines(button) {
49414
- return (button & 8) !== 0 ? this.wheelScrollLines * ALT_WHEEL_SCROLL_MULTIPLIER : this.wheelScrollLines;
49415
- }
49416
- routeWheel(event) {
49417
- let remaining = event.direction * this.getWheelScrollLines(event.button);
49779
+ routeWheel(event, delta) {
49780
+ let remaining = delta;
49418
49781
  const seen = /* @__PURE__ */ new Set();
49419
49782
  for (const scrollView of this.currentLayout ? getScrollViewsAt(this.currentLayout, event.x, event.y) : []) {
49420
49783
  seen.add(scrollView);
@@ -50035,7 +50398,20 @@ var TuiAltScreen = class extends TuiBase {
50035
50398
  return sliceByColumn(line, 0, width, true);
50036
50399
  });
50037
50400
  const fullRedraw = this.previousScreen.length === 0 || this.previousScreenWidth !== width || this.previousScreenHeight !== height;
50038
- const imagesNeedRedraw = screen.some((line, row) => line !== this.previousScreen[row] && (isImageLine(line) || isImageLine(this.previousScreen[row] ?? "")));
50401
+ const changedRows = screen.map((line, row) => line !== this.previousScreen[row]);
50402
+ const imageAnchorsNeedRedraw = screen.some((line, row) => changedRows[row] && (isImageLine(line) || isImageLine(this.previousScreen[row] ?? "")));
50403
+ const isWezTerm = Boolean(process.env.WEZTERM_PANE) || process.env.TERM_PROGRAM?.toLowerCase() === "wezterm";
50404
+ const imageCellsNeedRedraw = !imageAnchorsNeedRedraw && isWezTerm && this.imageProtocol === "kitty" && changedRows.some(Boolean) && screen.some((line, row) => {
50405
+ const placementRows = getKittyImagePlacementRows(line);
50406
+ if (placementRows === void 0)
50407
+ return false;
50408
+ for (let coveredRow = row; coveredRow < row + placementRows; coveredRow++) {
50409
+ if (changedRows[coveredRow])
50410
+ return true;
50411
+ }
50412
+ return false;
50413
+ });
50414
+ const imagesNeedRedraw = imageAnchorsNeedRedraw || imageCellsNeedRedraw;
50039
50415
  const redrawImages = fullRedraw || imagesNeedRedraw;
50040
50416
  const hadUploadedKittyImages = this.uploadedKittyImages.size > 0;
50041
50417
  const preparedKittyScreen = redrawImages && this.imageProtocol === "kitty" ? this.prepareKittyScreen(screen) : { lines: screen, evictedImageDeletion: "" };
@@ -50051,18 +50427,33 @@ var TuiAltScreen = class extends TuiBase {
50051
50427
  buffer += deleteAllKittyPlacements();
50052
50428
  }
50053
50429
  buffer += preparedKittyScreen.evictedImageDeletion;
50054
- const clearRowsBeforeKittyImages = redrawImages && this.imageProtocol === "kitty" && screen.some(isImageLine) && (Boolean(process.env.WEZTERM_PANE) || process.env.TERM_PROGRAM?.toLowerCase() === "wezterm");
50055
- if (clearRowsBeforeKittyImages) {
50430
+ const drawKittyImagesLast = redrawImages && this.imageProtocol === "kitty" && screen.some(isImageLine) && isWezTerm;
50431
+ if (drawKittyImagesLast) {
50056
50432
  for (let row = 0; row < height; row++) {
50057
50433
  if (!fullRedraw && !imagesNeedRedraw && screen[row] === this.previousScreen[row])
50058
50434
  continue;
50059
50435
  buffer += `\x1B[${row + 1};1H\x1B[2K`;
50060
50436
  }
50061
- }
50062
- for (let row = 0; row < height; row++) {
50063
- if (!fullRedraw && !imagesNeedRedraw && screen[row] === this.previousScreen[row])
50064
- continue;
50065
- buffer += `\x1B[${row + 1};1H${clearRowsBeforeKittyImages ? "" : "\x1B[2K"}${preparedKittyScreen.lines[row] ?? ""}`;
50437
+ for (let row = 0; row < height; row++) {
50438
+ if (!fullRedraw && !imagesNeedRedraw && screen[row] === this.previousScreen[row])
50439
+ continue;
50440
+ if (isImageLine(preparedKittyScreen.lines[row] ?? ""))
50441
+ continue;
50442
+ buffer += `\x1B[${row + 1};1H${preparedKittyScreen.lines[row] ?? ""}`;
50443
+ }
50444
+ for (let row = 0; row < height; row++) {
50445
+ if (!fullRedraw && !imagesNeedRedraw && screen[row] === this.previousScreen[row])
50446
+ continue;
50447
+ if (!isImageLine(preparedKittyScreen.lines[row] ?? ""))
50448
+ continue;
50449
+ buffer += `\x1B[${row + 1};1H${preparedKittyScreen.lines[row] ?? ""}`;
50450
+ }
50451
+ } else {
50452
+ for (let row = 0; row < height; row++) {
50453
+ if (!fullRedraw && !imagesNeedRedraw && screen[row] === this.previousScreen[row])
50454
+ continue;
50455
+ buffer += `\x1B[${row + 1};1H\x1B[2K${preparedKittyScreen.lines[row] ?? ""}`;
50456
+ }
50066
50457
  }
50067
50458
  if (cursorPos) {
50068
50459
  buffer += `\x1B[${cursorPos.row + 1};${Math.min(width, cursorPos.col) + 1}H`;
@@ -50088,6 +50479,13 @@ import { join as join46, resolve as resolve27, dirname as dirname20 } from "node
50088
50479
  import { spawnSync as spawnSync12 } from "node:child_process";
50089
50480
 
50090
50481
  // packages/core/tui/src/types/surface.ts
50482
+ var KXM_TUI_FIELD_KINDS = ["text", "enum", "choice", "info"];
50483
+ var KXM_TUI_STATUSES = ["ready", "available", "blocked"];
50484
+ var KXM_TUI_STEP_STATES = ["pending", "satisfied", "running", "done", "failed"];
50485
+ var KXM_TUI_NOTICE_LEVELS = ["info", "error"];
50486
+ var KXM_TUI_SET_ACTION = "set";
50487
+ var KXM_TUI_CLEAR_ACTION = "clear";
50488
+ var KXM_TUI_ABORT_ACTION = "abort";
50091
50489
  var KXM_TUI_LIMITS = Object.freeze({
50092
50490
  sourceMax: 128,
50093
50491
  idMax: 128,
@@ -50109,8 +50507,231 @@ var KXM_TUI_LIMITS = Object.freeze({
50109
50507
  sectionsMax: 32,
50110
50508
  orderMax: 1e3
50111
50509
  });
50510
+ var ID_PATTERN = /^[a-z0-9][a-z0-9._:-]*$/;
50511
+ var SOURCE_PATTERN = /^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/;
50512
+ var KEY_PATH_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9_.[\]-]*$/;
50513
+ var ACTION_KEY_PATTERN = /^[A-Za-z0-9]$/;
50514
+ function issue4(path4, code, message) {
50515
+ return { path: path4, code, message };
50516
+ }
50517
+ function isPlainObject(value) {
50518
+ return typeof value === "object" && value !== null && !Array.isArray(value);
50519
+ }
50520
+ function boundedText(issues, path4, value, max, required2) {
50521
+ if (value === void 0) {
50522
+ if (required2) issues.push(issue4(path4, "required", `${path4} is required`));
50523
+ return;
50524
+ }
50525
+ if (typeof value !== "string" || value.length === 0) {
50526
+ issues.push(issue4(path4, "invalid_string", `${path4} must be a non-empty string`));
50527
+ return;
50528
+ }
50529
+ if (value.length > max) {
50530
+ issues.push(issue4(path4, "too_long", `${path4} exceeds ${max} characters`));
50531
+ return;
50532
+ }
50533
+ if (/[\u0000-\u0008\u000a-\u001f\u007f]/u.test(value)) {
50534
+ issues.push(issue4(path4, "control_character", `${path4} must not contain control characters`));
50535
+ }
50536
+ }
50537
+ function optionalEnum(issues, path4, value, allowed) {
50538
+ if (value === void 0) return;
50539
+ if (typeof value !== "string" || !allowed.includes(value)) {
50540
+ issues.push(issue4(path4, "invalid_choice", `${path4} must be one of ${allowed.join(", ")}`));
50541
+ }
50542
+ }
50543
+ function ratioField(issues, path4, value) {
50544
+ if (value === void 0) return;
50545
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
50546
+ issues.push(issue4(path4, "invalid_ratio", `${path4} must be a finite number between 0 and 1`));
50547
+ }
50548
+ }
50549
+ function validateChoice(issues, path4, raw) {
50550
+ if (!isPlainObject(raw)) {
50551
+ issues.push(issue4(path4, "invalid_object", `${path4} must be an object`));
50552
+ return;
50553
+ }
50554
+ boundedText(issues, `${path4}.id`, raw.id, KXM_TUI_LIMITS.choiceIdMax, true);
50555
+ if (typeof raw.id === "string" && /[\s]/u.test(raw.id)) {
50556
+ issues.push(issue4(`${path4}.id`, "invalid_choice_id", `${path4}.id must not contain whitespace`));
50557
+ }
50558
+ boundedText(issues, `${path4}.label`, raw.label, KXM_TUI_LIMITS.choiceLabelMax, true);
50559
+ boundedText(issues, `${path4}.detail`, raw.detail, KXM_TUI_LIMITS.shortDetailMax, false);
50560
+ boundedText(issues, `${path4}.group`, raw.group, KXM_TUI_LIMITS.shortDetailMax, false);
50561
+ boundedText(issues, `${path4}.statusText`, raw.statusText, KXM_TUI_LIMITS.shortDetailMax, false);
50562
+ boundedText(issues, `${path4}.action`, raw.action, KXM_TUI_LIMITS.idMax, false);
50563
+ optionalEnum(issues, `${path4}.status`, raw.status, KXM_TUI_STATUSES);
50564
+ }
50565
+ function validateAction(issues, path4, raw) {
50566
+ if (!isPlainObject(raw)) {
50567
+ issues.push(issue4(path4, "invalid_object", `${path4} must be an object`));
50568
+ return;
50569
+ }
50570
+ if (typeof raw.key !== "string" || !ACTION_KEY_PATTERN.test(raw.key)) {
50571
+ issues.push(issue4(`${path4}.key`, "invalid_action_key", `${path4}.key must be a single alphanumeric key`));
50572
+ }
50573
+ boundedText(issues, `${path4}.label`, raw.label, KXM_TUI_LIMITS.shortDetailMax, true);
50574
+ boundedText(issues, `${path4}.action`, raw.action, KXM_TUI_LIMITS.idMax, true);
50575
+ }
50576
+ function validateStep(issues, path4, raw) {
50577
+ if (!isPlainObject(raw)) {
50578
+ issues.push(issue4(path4, "invalid_object", `${path4} must be an object`));
50579
+ return;
50580
+ }
50581
+ boundedText(issues, `${path4}.label`, raw.label, KXM_TUI_LIMITS.shortDetailMax, true);
50582
+ boundedText(issues, `${path4}.detail`, raw.detail, KXM_TUI_LIMITS.stepDetailMax, false);
50583
+ optionalEnum(issues, `${path4}.state`, raw.state, KXM_TUI_STEP_STATES);
50584
+ if (raw.state === void 0) issues.push(issue4(`${path4}.state`, "required", `${path4}.state is required`));
50585
+ }
50586
+ function validateField(issues, path4, raw) {
50587
+ if (!isPlainObject(raw)) {
50588
+ issues.push(issue4(path4, "invalid_object", `${path4} must be an object`));
50589
+ return;
50590
+ }
50591
+ boundedText(issues, `${path4}.id`, raw.id, KXM_TUI_LIMITS.idMax, true);
50592
+ if (typeof raw.id === "string" && !ID_PATTERN.test(raw.id)) {
50593
+ issues.push(issue4(`${path4}.id`, "invalid_id", `${path4}.id must match ${ID_PATTERN}`));
50594
+ }
50595
+ boundedText(issues, `${path4}.label`, raw.label, KXM_TUI_LIMITS.labelMax, true);
50596
+ if (raw.kind === void 0) {
50597
+ issues.push(issue4(`${path4}.kind`, "required", `${path4}.kind is required`));
50598
+ } else {
50599
+ optionalEnum(issues, `${path4}.kind`, raw.kind, KXM_TUI_FIELD_KINDS);
50600
+ }
50601
+ boundedText(issues, `${path4}.value`, raw.value, KXM_TUI_LIMITS.valueMax, false);
50602
+ boundedText(issues, `${path4}.placeholder`, raw.placeholder, KXM_TUI_LIMITS.shortDetailMax, false);
50603
+ boundedText(issues, `${path4}.detail`, raw.detail, KXM_TUI_LIMITS.detailMax, false);
50604
+ boundedText(issues, `${path4}.statusText`, raw.statusText, KXM_TUI_LIMITS.statusTextMax, false);
50605
+ if (raw.updatedAt !== void 0) {
50606
+ boundedText(issues, `${path4}.updatedAt`, raw.updatedAt, 64, false);
50607
+ if (typeof raw.updatedAt === "string" && !Number.isFinite(Date.parse(raw.updatedAt))) {
50608
+ issues.push(issue4(`${path4}.updatedAt`, "invalid_timestamp", `${path4}.updatedAt must be an ISO timestamp`));
50609
+ }
50610
+ }
50611
+ optionalEnum(issues, `${path4}.status`, raw.status, KXM_TUI_STATUSES);
50612
+ if (raw.keyPath !== void 0) {
50613
+ boundedText(issues, `${path4}.keyPath`, raw.keyPath, KXM_TUI_LIMITS.keyPathMax, false);
50614
+ if (typeof raw.keyPath === "string" && !KEY_PATH_PATTERN.test(raw.keyPath)) {
50615
+ issues.push(issue4(`${path4}.keyPath`, "invalid_key_path", `${path4}.keyPath must be a dotted config key`));
50616
+ }
50617
+ }
50618
+ for (const listKey of ["busy", "awaitingInput"]) {
50619
+ if (raw[listKey] !== void 0 && typeof raw[listKey] !== "boolean") {
50620
+ issues.push(issue4(`${path4}.${listKey}`, "invalid_boolean", `${path4}.${listKey} must be a boolean`));
50621
+ }
50622
+ }
50623
+ if (raw.progress !== void 0) {
50624
+ if (!isPlainObject(raw.progress)) {
50625
+ issues.push(issue4(`${path4}.progress`, "invalid_object", `${path4}.progress must be an object`));
50626
+ } else {
50627
+ boundedText(issues, `${path4}.progress.label`, raw.progress.label, KXM_TUI_LIMITS.shortDetailMax, true);
50628
+ ratioField(issues, `${path4}.progress.ratio`, raw.progress.ratio);
50629
+ }
50630
+ }
50631
+ for (const [listKey, max, check] of [
50632
+ ["choices", KXM_TUI_LIMITS.choicesMax, validateChoice],
50633
+ ["actions", KXM_TUI_LIMITS.actionsMax, validateAction],
50634
+ ["steps", KXM_TUI_LIMITS.stepsMax, validateStep]
50635
+ ]) {
50636
+ const list = raw[listKey];
50637
+ if (list === void 0) continue;
50638
+ if (!Array.isArray(list) || list.length > max) {
50639
+ issues.push(issue4(`${path4}.${listKey}`, "invalid_list", `${path4}.${listKey} must be an array of at most ${max}`));
50640
+ continue;
50641
+ }
50642
+ list.forEach((entry, index) => check(issues, `${path4}.${listKey}[${index}]`, entry));
50643
+ }
50644
+ const output = raw.output;
50645
+ if (output !== void 0) {
50646
+ if (!Array.isArray(output) || output.length > KXM_TUI_LIMITS.outputMaxLines) {
50647
+ issues.push(issue4(`${path4}.output`, "invalid_list", `${path4}.output must be an array of at most ${KXM_TUI_LIMITS.outputMaxLines} lines`));
50648
+ } else {
50649
+ output.forEach((line, index) => {
50650
+ if (typeof line !== "string") issues.push(issue4(`${path4}.output[${index}]`, "invalid_string", "output lines must be strings"));
50651
+ else if (line.length > KXM_TUI_LIMITS.outputLineMax) issues.push(issue4(`${path4}.output[${index}]`, "too_long", "output line is too long"));
50652
+ });
50653
+ }
50654
+ }
50655
+ }
50656
+ function validateSection(issues, path4, raw) {
50657
+ if (!isPlainObject(raw)) {
50658
+ issues.push(issue4(path4, "invalid_object", `${path4} must be an object`));
50659
+ return;
50660
+ }
50661
+ boundedText(issues, `${path4}.id`, raw.id, KXM_TUI_LIMITS.idMax, true);
50662
+ if (typeof raw.id === "string" && !ID_PATTERN.test(raw.id)) {
50663
+ issues.push(issue4(`${path4}.id`, "invalid_id", `${path4}.id must match ${ID_PATTERN}`));
50664
+ }
50665
+ boundedText(issues, `${path4}.title`, raw.title, KXM_TUI_LIMITS.labelMax, true);
50666
+ boundedText(issues, `${path4}.detail`, raw.detail, KXM_TUI_LIMITS.shortDetailMax, false);
50667
+ boundedText(issues, `${path4}.notice`, raw.notice, KXM_TUI_LIMITS.detailMax, false);
50668
+ optionalEnum(issues, `${path4}.noticeLevel`, raw.noticeLevel, KXM_TUI_NOTICE_LEVELS);
50669
+ if (typeof raw.order !== "number" || !Number.isInteger(raw.order) || raw.order < 0 || raw.order > KXM_TUI_LIMITS.orderMax) {
50670
+ issues.push(issue4(`${path4}.order`, "invalid_order", `${path4}.order must be an integer between 0 and ${KXM_TUI_LIMITS.orderMax}`));
50671
+ }
50672
+ if (!Array.isArray(raw.fields) || raw.fields.length > KXM_TUI_LIMITS.fieldsMax) {
50673
+ issues.push(issue4(`${path4}.fields`, "invalid_list", `${path4}.fields must be an array of at most ${KXM_TUI_LIMITS.fieldsMax}`));
50674
+ return;
50675
+ }
50676
+ const seen = /* @__PURE__ */ new Set();
50677
+ raw.fields.forEach((field, index) => {
50678
+ const fieldPath = `${path4}.fields[${index}]`;
50679
+ validateField(issues, fieldPath, field);
50680
+ if (isPlainObject(field) && typeof field.id === "string") {
50681
+ if (seen.has(field.id)) issues.push(issue4(`${fieldPath}.id`, "duplicate_id", `${field.id} is published twice in ${path4}`));
50682
+ seen.add(field.id);
50683
+ }
50684
+ });
50685
+ }
50686
+ function validateKxmTuiSurface(sections) {
50687
+ if (!Array.isArray(sections)) return [issue4("sections", "invalid_list", "sections must be an array")];
50688
+ if (sections.length > KXM_TUI_LIMITS.sectionsMax) {
50689
+ return [issue4("sections", "too_many", `sections must number at most ${KXM_TUI_LIMITS.sectionsMax}`)];
50690
+ }
50691
+ const issues = [];
50692
+ const seenSources = /* @__PURE__ */ new Map();
50693
+ sections.forEach((section, index) => {
50694
+ const path4 = `sections[${index}]`;
50695
+ validateSection(issues, path4, section);
50696
+ if (!isPlainObject(section)) return;
50697
+ boundedText(issues, `${path4}.source`, section.source, KXM_TUI_LIMITS.sourceMax, true);
50698
+ if (typeof section.source === "string" && !SOURCE_PATTERN.test(section.source)) {
50699
+ issues.push(issue4(`${path4}.source`, "invalid_source", `${path4}.source must be an addressable owner id`));
50700
+ }
50701
+ if (typeof section.source === "string" && typeof section.id === "string") {
50702
+ const ids = seenSources.get(section.source) ?? /* @__PURE__ */ new Set();
50703
+ seenSources.set(section.source, ids);
50704
+ if (ids.has(section.id)) issues.push(issue4(`${path4}.id`, "duplicate_section", `${section.source}/${section.id} is published twice`));
50705
+ ids.add(section.id);
50706
+ }
50707
+ });
50708
+ return issues;
50709
+ }
50710
+ function isSelectableField(field) {
50711
+ return field !== void 0 && field.kind !== "info";
50712
+ }
50713
+ function groupedKxmTuiChoices(field) {
50714
+ const choices = field.choices ?? [];
50715
+ if (choices.length === 0) return [];
50716
+ if (choices.every((choice) => choice.group === void 0)) return [{ group: "", choices }];
50717
+ const groups = [];
50718
+ const index = /* @__PURE__ */ new Map();
50719
+ for (const choice of choices) {
50720
+ const group = choice.group ?? "";
50721
+ let bucket = index.get(group);
50722
+ if (!bucket) {
50723
+ bucket = [];
50724
+ index.set(group, bucket);
50725
+ groups.push({ group, choices: bucket });
50726
+ }
50727
+ bucket.push(choice);
50728
+ }
50729
+ return groups;
50730
+ }
50112
50731
 
50113
50732
  // packages/core/tui/src/tui/keys.ts
50733
+ var KXM_TUI_INPUT_MAX_LENGTH = 4096;
50734
+ var CONTROL_BYTES = /[\u0000-\u0008\u000a-\u001f\u007f\u0080-\u009f]/u;
50114
50735
  var NAMED_KEYS = Object.freeze({
50115
50736
  up: "up",
50116
50737
  down: "down",
@@ -50128,8 +50749,109 @@ var NAMED_KEYS = Object.freeze({
50128
50749
  backspace: "backspace",
50129
50750
  delete: "delete"
50130
50751
  });
50752
+ var CONTROL_BYTES_GLOBAL = /[\u0000-\u0008\u000a-\u001f\u007f\u0080-\u009f]/gu;
50753
+ function normalizeInputText(value) {
50754
+ return value.replace(CONTROL_BYTES_GLOBAL, " ").replace(/ {2,}/gu, " ").trim().slice(0, KXM_TUI_INPUT_MAX_LENGTH);
50755
+ }
50756
+ function decodeKxmTuiInput(data) {
50757
+ if (typeof data !== "string" || data.length === 0) return { kind: "ignored" };
50758
+ const named = parseKey(data);
50759
+ if (named === "ctrl+c") return { kind: "ctrlC" };
50760
+ if (named) {
50761
+ const kind = NAMED_KEYS[named.toLowerCase()];
50762
+ if (kind) return { kind };
50763
+ if (named === "space") return { kind: "text", text: " " };
50764
+ if (Array.from(named).length === 1 && !CONTROL_BYTES.test(named)) {
50765
+ return { kind: "text", text: named };
50766
+ }
50767
+ return { kind: "ignored" };
50768
+ }
50769
+ const kitty = decodeKittyPrintable(data);
50770
+ if (kitty) {
50771
+ const text = normalizeInputText(kitty);
50772
+ return text ? { kind: "text", text } : { kind: "ignored" };
50773
+ }
50774
+ if (data.startsWith("\x1B")) return { kind: "ignored" };
50775
+ const pasted = normalizeInputText(data);
50776
+ return pasted ? { kind: "text", text: pasted } : { kind: "ignored" };
50777
+ }
50778
+ function deleteBackward(text) {
50779
+ const units = Array.from(text);
50780
+ units.pop();
50781
+ return units.join("");
50782
+ }
50783
+ function insertAt(text, caret, insert, maxLength = KXM_TUI_INPUT_MAX_LENGTH) {
50784
+ const units = Array.from(text);
50785
+ const at2 = Math.max(0, Math.min(caret, units.length));
50786
+ const added = Array.from(insert);
50787
+ const merged = [...units.slice(0, at2), ...added, ...units.slice(at2)].join("");
50788
+ const next = merged.length > maxLength ? merged.slice(0, maxLength) : merged;
50789
+ return { text: next, caret: Math.min(next.length, at2 + added.length) };
50790
+ }
50131
50791
 
50132
50792
  // packages/core/tui/src/tui/layout.ts
50793
+ var KXM_TUI_ELLIPSIS = "\u2026";
50794
+ function fitText(text, width) {
50795
+ if (width <= 0) return "";
50796
+ if (visibleWidth(text) <= width) return text;
50797
+ if (!text.includes("\x1B")) {
50798
+ let out = "";
50799
+ for (const unit of Array.from(text)) {
50800
+ if (visibleWidth(out) + visibleWidth(unit) > width - KXM_TUI_ELLIPSIS.length) break;
50801
+ out += unit;
50802
+ }
50803
+ return out + KXM_TUI_ELLIPSIS;
50804
+ }
50805
+ return truncateToWidth(text, width, KXM_TUI_ELLIPSIS);
50806
+ }
50807
+ function alignRight(left, right, width) {
50808
+ if (width <= 0) return "";
50809
+ const detail = fitText(right, Math.max(0, width - 2));
50810
+ const detailWidth = visibleWidth(detail);
50811
+ const leftWidth = Math.max(1, width - detailWidth - 1);
50812
+ const fitted = fitText(left, leftWidth);
50813
+ const filler = Math.max(1, width - visibleWidth(fitted) - detailWidth);
50814
+ return `${fitted}${" ".repeat(filler)}${detail}`;
50815
+ }
50816
+ function padRight(text, width) {
50817
+ const missing = width - visibleWidth(text);
50818
+ return missing > 0 ? text + " ".repeat(missing) : text;
50819
+ }
50820
+ function wrapPlain(text, width) {
50821
+ if (width <= 0) return [];
50822
+ const lines = [];
50823
+ let current = "";
50824
+ for (const word of text.split(/\s+/u).filter(Boolean)) {
50825
+ if (!current.length) current = word;
50826
+ else if (visibleWidth(current) + 1 + visibleWidth(word) <= width) current = `${current} ${word}`;
50827
+ else {
50828
+ lines.push(current);
50829
+ current = word;
50830
+ }
50831
+ }
50832
+ if (current.length) lines.push(current);
50833
+ return lines.length ? lines : [];
50834
+ }
50835
+ function formatAge(iso, nowMs) {
50836
+ if (!iso) return "\u2014";
50837
+ const at2 = Date.parse(iso);
50838
+ if (!Number.isFinite(at2)) return "\u2014";
50839
+ const seconds = Math.floor((nowMs - at2) / 1e3);
50840
+ if (seconds < 0) return "\u2014";
50841
+ if (seconds < 60) return `${seconds}s`;
50842
+ const minutes = Math.floor(seconds / 60);
50843
+ if (minutes < 60) return `${minutes}m${String(seconds % 60).padStart(2, "0")}s`;
50844
+ const hours = Math.floor(minutes / 60);
50845
+ if (hours < 24) return `${hours}h${String(minutes % 60).padStart(2, "0")}m`;
50846
+ const days = Math.floor(hours / 24);
50847
+ return `${days}d${String(hours % 24).padStart(2, "0")}h`;
50848
+ }
50849
+ function progressBar(ratio, width = 10) {
50850
+ const cells = Math.max(1, width);
50851
+ if (ratio === void 0 || !Number.isFinite(ratio)) return "\u2591".repeat(cells);
50852
+ const filled = Math.round(Math.max(0, Math.min(1, ratio)) * cells);
50853
+ return "\u2588".repeat(filled) + "\u2591".repeat(Math.max(0, cells - filled));
50854
+ }
50133
50855
  var KXM_TUI_MARKS = Object.freeze({
50134
50856
  pending: "\u25CB",
50135
50857
  active: "\u25B6",
@@ -50194,9 +50916,368 @@ function createKxmTuiAnsiTheme(color) {
50194
50916
  panelBg: (text) => `\x1B[48;5;236m${text}\x1B[0m`
50195
50917
  };
50196
50918
  }
50919
+ function createDefaultKxmTuiTheme(env = process.env) {
50920
+ return createKxmTuiAnsiTheme(!(env.NO_COLOR || env.KXM_TUI_NO_COLOR));
50921
+ }
50922
+
50923
+ // packages/core/tui/src/tui/panel.ts
50924
+ var PANE_KEYS = /* @__PURE__ */ new Set(["j", "k", "h", "l"]);
50925
+ function initialKxmTuiPanelState(sections) {
50926
+ return {
50927
+ pane: "sections",
50928
+ mode: "browse",
50929
+ sectionIndex: 0,
50930
+ fieldIndex: firstSelectableFieldIndex(sections[0]?.fields ?? []),
50931
+ choiceIndex: 0,
50932
+ draft: "",
50933
+ caret: 0,
50934
+ filter: "",
50935
+ filtering: false,
50936
+ help: false,
50937
+ pending: []
50938
+ };
50939
+ }
50940
+ function pendingKey(invocation) {
50941
+ return `${invocation.source}\0${invocation.sectionId}\0${invocation.fieldId}`;
50942
+ }
50943
+ function firstSelectableFieldIndex(fields) {
50944
+ const index = fields.findIndex(isSelectableField);
50945
+ return index < 0 ? 0 : index;
50946
+ }
50947
+ function visibleChoices(field, filter) {
50948
+ const choices = field?.choices ?? [];
50949
+ const needle = filter.trim().toLowerCase();
50950
+ if (!needle) return [...choices];
50951
+ return choices.filter(
50952
+ (choice) => `${choice.group ?? ""} ${choice.label} ${choice.id} ${choice.detail ?? ""}`.toLowerCase().includes(needle)
50953
+ );
50954
+ }
50955
+ function currentSection(sections, state) {
50956
+ return sections[state.sectionIndex];
50957
+ }
50958
+ function currentField(sections, state) {
50959
+ return currentSection(sections, state)?.fields[state.fieldIndex];
50960
+ }
50961
+ function reconcileKxmTuiPanelState(state, sections) {
50962
+ const sectionIndex = Math.min(state.sectionIndex, Math.max(0, sections.length - 1));
50963
+ const fields = sections[sectionIndex]?.fields ?? [];
50964
+ let fieldIndex = Math.min(state.fieldIndex, Math.max(0, fields.length - 1));
50965
+ if (!isSelectableField(fields[fieldIndex])) fieldIndex = firstSelectableFieldIndex(fields);
50966
+ const field = fields[fieldIndex];
50967
+ const keepsEdit = state.mode === "edit" && (field?.statusText !== void 0 || field?.awaitingInput === true);
50968
+ return {
50969
+ ...state,
50970
+ sectionIndex,
50971
+ fieldIndex,
50972
+ mode: keepsEdit ? "edit" : "browse",
50973
+ choiceIndex: 0,
50974
+ pending: []
50975
+ };
50976
+ }
50977
+ function reduceKxmTuiInput(state, sections, input) {
50978
+ return reduceDecoded(state, sections, decodeKxmTuiInput(input));
50979
+ }
50980
+ function invokeEffect(state, section, field, action, value) {
50981
+ const invocation = {
50982
+ source: section.source,
50983
+ sectionId: section.id,
50984
+ fieldId: field.id,
50985
+ action,
50986
+ ...value === void 0 ? {} : { value }
50987
+ };
50988
+ const key = pendingKey(invocation);
50989
+ return {
50990
+ state: { ...state, pending: state.pending.includes(key) ? state.pending : [...state.pending, key] },
50991
+ effects: [{ type: "invoke", invocation }]
50992
+ };
50993
+ }
50994
+ function moveSection(state, sections, delta) {
50995
+ if (sections.length === 0) return state;
50996
+ const next = Math.max(0, Math.min(state.sectionIndex + delta, sections.length - 1));
50997
+ if (next === state.sectionIndex) return state;
50998
+ return {
50999
+ ...state,
51000
+ sectionIndex: next,
51001
+ fieldIndex: firstSelectableFieldIndex(sections[next]?.fields ?? []),
51002
+ mode: "browse",
51003
+ choiceIndex: 0,
51004
+ draft: "",
51005
+ caret: 0,
51006
+ filter: "",
51007
+ filtering: false
51008
+ };
51009
+ }
51010
+ function moveField(state, fields, delta) {
51011
+ const step = delta < 0 ? -1 : 1;
51012
+ let next = state.fieldIndex + step;
51013
+ while (next >= 0 && next < fields.length && !isSelectableField(fields[next])) next += step;
51014
+ if (next < 0 || next >= fields.length) return state;
51015
+ return {
51016
+ ...state,
51017
+ fieldIndex: next,
51018
+ mode: "browse",
51019
+ choiceIndex: 0,
51020
+ draft: "",
51021
+ caret: 0,
51022
+ filter: "",
51023
+ filtering: false
51024
+ };
51025
+ }
51026
+ function moveChoice(state, count, delta) {
51027
+ if (count === 0) return state;
51028
+ return { ...state, choiceIndex: Math.max(0, Math.min(state.choiceIndex + delta, count - 1)) };
51029
+ }
51030
+ function choiceAt(field, state) {
51031
+ const choices = visibleChoices(field, state.filter);
51032
+ return choices[Math.max(0, Math.min(state.choiceIndex, choices.length - 1))];
51033
+ }
51034
+ function enterField(state, section, field) {
51035
+ if (!field) return { state, effects: [] };
51036
+ if (field.kind === "choice") {
51037
+ if ((field.choices?.length ?? 0) === 0) {
51038
+ return { state, effects: [{ type: "notice", message: `${field.label} has nothing to offer yet` }] };
51039
+ }
51040
+ return { state: { ...state, mode: "choices", choiceIndex: 0 }, effects: [] };
51041
+ }
51042
+ if (field.kind === "enum") {
51043
+ const choices = field.choices ?? [];
51044
+ if (choices.length === 0) {
51045
+ return { state, effects: [{ type: "notice", message: `${field.label} has no values published` }] };
51046
+ }
51047
+ const current = choices.findIndex((choice) => choice.id === field.value);
51048
+ const next = choices[(current + 1) % choices.length];
51049
+ if (next.status === "blocked") {
51050
+ return { state, effects: [{ type: "notice", message: next.statusText ?? `${next.label} is unavailable` }] };
51051
+ }
51052
+ return invokeEffect(state, section, field, next.action ?? KXM_TUI_SET_ACTION, next.id);
51053
+ }
51054
+ if (field.kind !== "text") return { state, effects: [] };
51055
+ return { state: { ...state, mode: "edit", draft: field.value ?? "", caret: (field.value ?? "").length }, effects: [] };
51056
+ }
51057
+ function reduceDecoded(state, sections, decoded) {
51058
+ const section = sections[state.sectionIndex];
51059
+ const field = currentField(sections, state);
51060
+ if (decoded.kind === "ctrlC") {
51061
+ if (field?.busy && section) {
51062
+ return invokeEffect(state, section, field, KXM_TUI_ABORT_ACTION);
51063
+ }
51064
+ return { state, effects: [{ type: "quit" }] };
51065
+ }
51066
+ if (!section) return { state, effects: [{ type: "quit" }] };
51067
+ if (state.mode === "edit") return reduceEdit(state, section, field, decoded);
51068
+ if (state.mode === "choices") return reduceChoices(state, section, field, decoded);
51069
+ return reduceBrowse(state, sections, section, field, decoded);
51070
+ }
51071
+ function caretSource(text, caret) {
51072
+ return Array.from(text).slice(0, caret).join("").length;
51073
+ }
51074
+ function reduceEdit(state, section, field, decoded) {
51075
+ if (!field) return { state: { ...state, mode: "browse" }, effects: [] };
51076
+ switch (decoded.kind) {
51077
+ case "escape":
51078
+ return { state: { ...state, mode: "browse", draft: "", caret: 0 }, effects: [] };
51079
+ case "enter": {
51080
+ const value = normalizeInputText(state.draft).trim();
51081
+ if (!value) return { state: { ...state, mode: "browse", draft: "", caret: 0 }, effects: [] };
51082
+ const step = invokeEffect(state, section, field, KXM_TUI_SET_ACTION, value);
51083
+ return { state: { ...step.state, mode: "browse", draft: "", caret: 0 }, effects: step.effects };
51084
+ }
51085
+ case "backspace": {
51086
+ if (state.caret === 0) return { state, effects: [] };
51087
+ const cut = caretSource(state.draft, state.caret);
51088
+ const before = deleteBackward(state.draft.slice(0, cut));
51089
+ const draft = `${before}${state.draft.slice(cut)}`;
51090
+ return { state: { ...state, draft, caret: Array.from(before).length }, effects: [] };
51091
+ }
51092
+ case "delete": {
51093
+ const units = Array.from(state.draft);
51094
+ const text = [...units.slice(0, state.caret), ...units.slice(state.caret + 1)].join("");
51095
+ return { state: { ...state, draft: text }, effects: [] };
51096
+ }
51097
+ case "left":
51098
+ return { state: { ...state, caret: Math.max(0, state.caret - 1) }, effects: [] };
51099
+ case "right":
51100
+ return { state: { ...state, caret: Math.min(Array.from(state.draft).length, state.caret + 1) }, effects: [] };
51101
+ case "home":
51102
+ return { state: { ...state, caret: 0 }, effects: [] };
51103
+ case "end":
51104
+ return { state: { ...state, caret: Array.from(state.draft).length }, effects: [] };
51105
+ case "text": {
51106
+ const text = decoded.text ?? "";
51107
+ if (!text) return { state, effects: [] };
51108
+ const inserted = insertAt(state.draft, state.caret, text);
51109
+ return { state: { ...state, draft: inserted.text, caret: inserted.caret }, effects: [] };
51110
+ }
51111
+ default:
51112
+ return { state, effects: [] };
51113
+ }
51114
+ }
51115
+ function reduceChoices(state, section, field, decoded) {
51116
+ if (!field) return { state: { ...state, mode: "browse" }, effects: [] };
51117
+ const choices = visibleChoices(field, state.filter);
51118
+ switch (decoded.kind) {
51119
+ case "escape":
51120
+ return { state: { ...state, mode: "browse", filter: "", filtering: false, choiceIndex: 0 }, effects: [] };
51121
+ case "up":
51122
+ return { state: moveChoice(state, choices.length, -1), effects: [] };
51123
+ case "down":
51124
+ return { state: moveChoice(state, choices.length, 1), effects: [] };
51125
+ case "pageUp":
51126
+ return { state: moveChoice(state, choices.length, -8), effects: [] };
51127
+ case "pageDown":
51128
+ return { state: moveChoice(state, choices.length, 8), effects: [] };
51129
+ case "enter": {
51130
+ const choice = choiceAt(field, state);
51131
+ if (!choice) return { state, effects: [] };
51132
+ if (choice.status === "blocked") {
51133
+ return { state, effects: [{ type: "notice", message: choice.statusText ?? `${choice.label} is unavailable` }] };
51134
+ }
51135
+ const clearing = (choice.action ?? KXM_TUI_SET_ACTION) === KXM_TUI_CLEAR_ACTION;
51136
+ const step = invokeEffect(
51137
+ state,
51138
+ section,
51139
+ field,
51140
+ choice.action ?? KXM_TUI_SET_ACTION,
51141
+ clearing ? void 0 : choice.id
51142
+ );
51143
+ return { state: { ...step.state, mode: "browse", filter: "", filtering: false }, effects: step.effects };
51144
+ }
51145
+ case "backspace": {
51146
+ if (state.filter.length === 0) return { state: { ...state, filtering: false }, effects: [] };
51147
+ return { state: { ...state, filter: deleteBackward(state.filter), choiceIndex: 0 }, effects: [] };
51148
+ }
51149
+ case "text": {
51150
+ const text = normalizeInputText(decoded.text ?? "");
51151
+ if (!text) return { state, effects: [] };
51152
+ return { state: { ...state, filtering: true, filter: `${state.filter}${text}`, choiceIndex: 0 }, effects: [] };
51153
+ }
51154
+ default:
51155
+ return { state, effects: [] };
51156
+ }
51157
+ }
51158
+ function reduceBrowse(state, sections, section, field, decoded) {
51159
+ if (state.help && (decoded.kind === "escape" || decoded.kind === "enter")) {
51160
+ return { state: { ...state, help: false }, effects: [] };
51161
+ }
51162
+ if (state.help) return { state, effects: [] };
51163
+ if (decoded.kind === "text") {
51164
+ const key = decoded.text ?? "";
51165
+ if (key === "q") return { state, effects: [{ type: "quit" }] };
51166
+ if (key === "h" || key === "?") return { state: { ...state, help: true }, effects: [] };
51167
+ const action = field?.actions?.find((candidate) => candidate.key === key);
51168
+ if (action && field) return invokeEffect(state, section, field, action.action);
51169
+ if ((key === "x" || key === "d") && field && field.value !== void 0 && field.kind !== "info") {
51170
+ return invokeEffect(state, section, field, KXM_TUI_CLEAR_ACTION);
51171
+ }
51172
+ if (PANE_KEYS.has(key)) {
51173
+ if (key === "h") return { state: { ...state, pane: "sections" }, effects: [] };
51174
+ if (key === "l") return { state: { ...state, pane: "fields" }, effects: [] };
51175
+ const delta = key === "k" ? -1 : 1;
51176
+ return {
51177
+ state: state.pane === "fields" ? moveField(state, section.fields, delta) : moveSection(state, sections, delta),
51178
+ effects: []
51179
+ };
51180
+ }
51181
+ return { state, effects: [] };
51182
+ }
51183
+ switch (decoded.kind) {
51184
+ case "escape":
51185
+ return state.pane === "fields" ? { state: { ...state, pane: "sections", mode: "browse" }, effects: [] } : { state, effects: [{ type: "quit" }] };
51186
+ case "tab":
51187
+ return { state: { ...state, pane: state.pane === "fields" ? "sections" : "fields" }, effects: [] };
51188
+ case "right":
51189
+ return { state: { ...state, pane: "fields" }, effects: [] };
51190
+ case "left":
51191
+ return { state: { ...state, pane: "sections" }, effects: [] };
51192
+ case "up":
51193
+ return { state: state.pane === "fields" ? moveField(state, section.fields, -1) : moveSection(state, sections, -1), effects: [] };
51194
+ case "down":
51195
+ return { state: state.pane === "fields" ? moveField(state, section.fields, 1) : moveSection(state, sections, 1), effects: [] };
51196
+ case "pageUp":
51197
+ return { state: moveSection(state, sections, -1), effects: [] };
51198
+ case "pageDown":
51199
+ return { state: moveSection(state, sections, 1), effects: [] };
51200
+ case "enter":
51201
+ return state.pane === "fields" ? enterField(state, section, field) : { state: { ...state, pane: "fields" }, effects: [] };
51202
+ default:
51203
+ return { state, effects: [] };
51204
+ }
51205
+ }
50197
51206
 
50198
51207
  // packages/core/tui/src/tui/render.ts
50199
51208
  var SECTION_PANE_RATIO = 1 / 3;
51209
+ var MIN_PANE_WIDTH = 16;
51210
+ var PROGRESS_WIDTH = 20;
51211
+ var OUTPUT_TAIL = 6;
51212
+ var SPLIT_MIN_WIDTH = 76;
51213
+ function statusMark(status, theme, fallback) {
51214
+ switch (status) {
51215
+ case "ready":
51216
+ return theme.fg("success", KXM_TUI_MARKS.ready);
51217
+ case "available":
51218
+ return theme.fg("accent", KXM_TUI_MARKS.available);
51219
+ case "blocked":
51220
+ return theme.fg("error", KXM_TUI_MARKS.blocked);
51221
+ default:
51222
+ return theme.dim(fallback);
51223
+ }
51224
+ }
51225
+ function cursorMark(active, theme) {
51226
+ return active ? theme.cursor(KXM_TUI_MARKS.cursor) : KXM_TUI_MARKS.blankCursor;
51227
+ }
51228
+ function paneSplit(width) {
51229
+ const third = Math.max(MIN_PANE_WIDTH, Math.floor(width * SECTION_PANE_RATIO));
51230
+ const sections = Math.min(third, Math.max(MIN_PANE_WIDTH, Math.floor(width / 2)));
51231
+ return { sections, fields: Math.max(MIN_PANE_WIDTH, width - sections - 1) };
51232
+ }
51233
+ function fieldIsPending(state, section, field) {
51234
+ return state.pending.includes(pendingKey({ source: section.source, sectionId: section.id, fieldId: field.id }));
51235
+ }
51236
+ function sectionRow(section, focused, pane, theme, width) {
51237
+ const cursor2 = cursorMark(focused && pane === "sections", theme);
51238
+ const flag2 = section.notice ? theme.fg(section.noticeLevel === "error" ? "error" : "warning", "!") : " ";
51239
+ const count = section.fields.length;
51240
+ const unset = section.fields.filter((field) => field.kind !== "info" && field.value === void 0).length;
51241
+ const right = unset > 0 ? `${unset} unset / ${count}` : `${count} field${count === 1 ? "" : "s"}`;
51242
+ return fitText(alignRight(`${cursor2} ${flag2} ${section.title}`, right, width), width);
51243
+ }
51244
+ function choiceSuffix(choice) {
51245
+ const parts = [];
51246
+ if (choice.detail) parts.push(choice.detail);
51247
+ if (choice.statusText) parts.push(choice.statusText);
51248
+ return parts.join(" \xB7 ");
51249
+ }
51250
+ function choiceRows(field, state, theme, width) {
51251
+ const shown = visibleChoices(field, state.filter);
51252
+ const lines = [];
51253
+ if (state.filtering) {
51254
+ lines.push(fitText(` ${theme.dim(`filter: ${state.filter || "(type to filter \xB7 esc clears)"}`)}`, width));
51255
+ }
51256
+ if (shown.length === 0) {
51257
+ lines.push(fitText(` ${theme.dim("no match")}`, width));
51258
+ return lines;
51259
+ }
51260
+ let position = -1;
51261
+ for (const group of groupedKxmTuiChoices(field)) {
51262
+ const filtered = shown.filter((choice) => (choice.group ?? "") === group.group);
51263
+ if (filtered.length === 0) continue;
51264
+ if (group.group) lines.push(fitText(` ${theme.key(group.group.toUpperCase())}`, width));
51265
+ for (const choice of filtered) {
51266
+ position += 1;
51267
+ lines.push(
51268
+ fitText(
51269
+ alignRight(
51270
+ `${cursorMark(position === state.choiceIndex, theme)} ${statusMark(choice.status, theme, KXM_TUI_MARKS.pending)} ${choice.label}`,
51271
+ choiceSuffix(choice),
51272
+ width
51273
+ ),
51274
+ width
51275
+ )
51276
+ );
51277
+ }
51278
+ }
51279
+ return lines;
51280
+ }
50200
51281
  var STEP_MARKS = {
50201
51282
  pending: "\xB7",
50202
51283
  satisfied: KXM_TUI_MARKS.passed,
@@ -50204,12 +51285,494 @@ var STEP_MARKS = {
50204
51285
  done: KXM_TUI_MARKS.passed,
50205
51286
  failed: KXM_TUI_MARKS.failed
50206
51287
  };
51288
+ function stepColor(state, theme) {
51289
+ switch (state) {
51290
+ case "running":
51291
+ return (text) => theme.fg("accent", text);
51292
+ case "done":
51293
+ return (text) => theme.fg("success", text);
51294
+ case "failed":
51295
+ return (text) => theme.fg("error", text);
51296
+ default:
51297
+ return theme.dim;
51298
+ }
51299
+ }
51300
+ function stepRows(steps, theme, width) {
51301
+ return steps.map((step) => {
51302
+ const paint = stepColor(step.state, theme);
51303
+ return fitText(alignRight(` ${paint(STEP_MARKS[step.state])} ${step.label}`, step.detail ?? "", width), width);
51304
+ });
51305
+ }
51306
+ function progressRow(field, theme, width) {
51307
+ const progress = field.progress;
51308
+ if (!progress) return "";
51309
+ const ratio = progress.ratio;
51310
+ const right = ratio === void 0 ? "working" : `${Math.round(ratio * 100)}%`;
51311
+ return fitText(alignRight(` ${progress.label}`, `${progressBar(ratio, PROGRESS_WIDTH)} ${right}`, width), width);
51312
+ }
51313
+ function focusedFieldDetail(field, state, theme, width) {
51314
+ const lines = [];
51315
+ if (field.keyPath) lines.push(fitText(` ${theme.dim(`key: ${field.keyPath}`)}`, width));
51316
+ for (const line of wrapPlain(field.detail ?? "", width - 3)) {
51317
+ if (line) lines.push(fitText(` ${theme.dim(line)}`, width));
51318
+ }
51319
+ for (const line of wrapPlain(field.statusText ?? "", width - 3)) {
51320
+ if (line) lines.push(fitText(` ${theme.fg("error", line)}`, width));
51321
+ }
51322
+ const progress = progressRow(field, theme, width);
51323
+ if (progress) lines.push(progress);
51324
+ if (field.steps?.length) lines.push(...stepRows(field.steps, theme, width));
51325
+ if (field.output?.length) {
51326
+ lines.push(...field.output.slice(-OUTPUT_TAIL).map((line) => fitText(` ${theme.dim(line)}`, width)));
51327
+ }
51328
+ if (field.actions?.length) {
51329
+ lines.push(fitText(` ${theme.key(field.actions.map((action) => `${action.key} ${action.label}`).join(" \xB7 "))}`, width));
51330
+ }
51331
+ if (state.mode === "choices") lines.push(...choiceRows(field, state, theme, width));
51332
+ if (state.mode === "edit" || field.awaitingInput) {
51333
+ const draft = state.mode === "edit" ? `${state.draft.slice(0, state.caret)}|${state.draft.slice(state.caret)}` : "|";
51334
+ lines.push(fitText(` ${theme.dim("\u203A")} ${draft}`, width));
51335
+ }
51336
+ return lines;
51337
+ }
51338
+ function fieldRows(section, state, theme, width, nowMs) {
51339
+ const lines = [];
51340
+ section.fields.forEach((field, index) => {
51341
+ const focused = state.pane === "fields" && state.fieldIndex === index;
51342
+ if (field.kind === "info") {
51343
+ lines.push(fitText(` ${theme.dim(field.label)} ${theme.dim(field.value ?? field.placeholder ?? "")}`, width));
51344
+ return;
51345
+ }
51346
+ const pending = fieldIsPending(state, section, field);
51347
+ const mark = statusMark(field.status, theme, pending ? "\u25CC" : KXM_TUI_MARKS.pending);
51348
+ const value = pending && field.value === void 0 ? theme.dim("saving\u2026") : field.value;
51349
+ const shown = value ?? theme.dim(field.placeholder ?? "not set");
51350
+ const age2 = field.updatedAt ? theme.dim(` (${formatAge(field.updatedAt, nowMs)})`) : "";
51351
+ lines.push(fitText(`${cursorMark(focused, theme)} ${mark} ${alignRight(field.label, `${shown}${age2}`, width - 4)}`, width));
51352
+ if (focused) lines.push(...focusedFieldDetail(field, state, theme, width));
51353
+ });
51354
+ return lines;
51355
+ }
51356
+ function noticeRows(section, theme, width) {
51357
+ if (!section?.notice) return [];
51358
+ const token = section.noticeLevel === "error" ? "error" : "warning";
51359
+ return wrapPlain(section.notice, width - 2).map((line) => fitText(theme.fg(token, line), width));
51360
+ }
51361
+ function singlePaneLines(sections, state, theme, width, nowMs) {
51362
+ const section = sections[state.sectionIndex];
51363
+ if (state.pane === "fields" && section) {
51364
+ return [
51365
+ fitText(theme.headerBg(alignRight(` ${section.title}`, section.source, width)), width),
51366
+ ...noticeRows(section, theme, width),
51367
+ ...fieldRows(section, state, theme, width, nowMs)
51368
+ ];
51369
+ }
51370
+ return [
51371
+ fitText(theme.headerBg(alignRight(" SECTIONS", `${sections.length}`, width)), width),
51372
+ ...sections.map((candidate, index) => sectionRow(candidate, index === state.sectionIndex, state.pane, theme, width))
51373
+ ];
51374
+ }
51375
+ function twoPaneLines(sections, state, theme, width, nowMs) {
51376
+ const split = paneSplit(width);
51377
+ const left = [fitText(theme.key(" SECTIONS"), split.sections)];
51378
+ sections.forEach(
51379
+ (candidate, index) => left.push(sectionRow(candidate, index === state.sectionIndex, state.pane, theme, split.sections))
51380
+ );
51381
+ const right = [];
51382
+ const section = sections[state.sectionIndex];
51383
+ if (section) {
51384
+ right.push(fitText(alignRight(` ${section.title}`, section.source, split.fields), split.fields));
51385
+ if (section.detail) right.push(fitText(` ${theme.dim(section.detail)}`, split.fields));
51386
+ right.push(...noticeRows(section, theme, split.fields));
51387
+ right.push(...fieldRows(section, state, theme, split.fields, nowMs));
51388
+ } else {
51389
+ right.push(fitText(theme.dim("no surface is published"), split.fields));
51390
+ }
51391
+ const rows = [];
51392
+ for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
51393
+ rows.push(fitText(`${padRight(left[index] ?? "", split.sections)} ${right[index] ?? ""}`, width));
51394
+ }
51395
+ return rows;
51396
+ }
51397
+ function renderKxmTuiPanel(sections, state, theme, options, width = 120) {
51398
+ const nowMs = options.nowMs ?? Date.now();
51399
+ const title = `${theme.cursor(options.title)}${options.breadcrumb ? ` ${theme.dim(options.breadcrumb)}` : ""}`;
51400
+ const header = [fitText(title, width)];
51401
+ if (state.help) {
51402
+ const help = (options.helpLines ?? ["no help published"]).flatMap((line) => wrapPlain(line, Math.max(1, width - 2)));
51403
+ return [...header, ...help.map((line) => fitText(line, width)), fitText(theme.dim("esc close help"), width)];
51404
+ }
51405
+ const body = width < SPLIT_MIN_WIDTH ? singlePaneLines(sections, state, theme, width, nowMs) : twoPaneLines(sections, state, theme, width, nowMs);
51406
+ const footer = options.footer ? [fitText(theme.dim(options.footer), width)] : [];
51407
+ return [...header, ...body, ...footer];
51408
+ }
50207
51409
 
50208
51410
  // packages/core/tui/src/tui/panelComponent.ts
50209
51411
  var KXM_TUI_PANEL_HELP = Object.freeze([
50210
51412
  "\u2191\u2193 move \xB7 \u2190\u2192 or Tab panes \xB7 PgUp/PgDn section \xB7 enter edit or open list",
50211
51413
  "type to filter a list \xB7 x clears a value \xB7 esc backs out \xB7 q quit \xB7 h help"
50212
51414
  ]);
51415
+ function rowsComponent() {
51416
+ const stack = new VStack([], { gap: 0 });
51417
+ const scroll = new ScrollView(stack, { primary: true, overscroll: "contain", scrollbar: "auto" });
51418
+ const fill = (rows) => {
51419
+ stack.clear();
51420
+ for (const row of rows) stack.addChild(new TruncatedText(row, 0, 0));
51421
+ };
51422
+ return { stack, scroll, fill };
51423
+ }
51424
+ var KxmTuiPanelComponent = class {
51425
+ root = new VStack([], { gap: 0 });
51426
+ options;
51427
+ body = rowsComponent();
51428
+ sections;
51429
+ state;
51430
+ notice;
51431
+ unsubscribe;
51432
+ constructor(options) {
51433
+ this.options = options;
51434
+ this.sections = options.registry.getSections();
51435
+ this.state = initialKxmTuiPanelState(this.sections);
51436
+ this.unsubscribe = options.registry.subscribe(() => this.refresh());
51437
+ this.rebuild();
51438
+ }
51439
+ getState() {
51440
+ return this.state;
51441
+ }
51442
+ getSections() {
51443
+ return this.sections;
51444
+ }
51445
+ /** Point the cursor at a field, e.g. after opening a named section. */
51446
+ focus(sectionId, fieldId) {
51447
+ const sectionIndex = this.sections.findIndex((section) => section.id === sectionId);
51448
+ if (sectionIndex < 0) return;
51449
+ const fields = this.sections[sectionIndex]?.fields ?? [];
51450
+ const fieldIndex = fieldId ? Math.max(0, fields.findIndex((field) => field.id === fieldId)) : this.state.fieldIndex;
51451
+ this.state = { ...this.state, pane: "fields", sectionIndex, fieldIndex };
51452
+ this.rebuild();
51453
+ this.options.requestRender();
51454
+ }
51455
+ setStatus(message) {
51456
+ this.notice = message;
51457
+ this.rebuild();
51458
+ this.options.requestRender();
51459
+ }
51460
+ refresh() {
51461
+ const next = this.options.registry.getSections();
51462
+ this.sections = next;
51463
+ this.state = reconcileKxmTuiPanelState(this.state, next);
51464
+ this.rebuild();
51465
+ this.options.requestRender();
51466
+ }
51467
+ handleInput(data) {
51468
+ if (matchesKey(data, Key.pageUp) || matchesKey(data, Key.ctrl("u"))) {
51469
+ this.body.scroll.scrollBy(-(this.viewport() - 2));
51470
+ this.options.requestRender();
51471
+ return;
51472
+ }
51473
+ if (matchesKey(data, Key.pageDown) || matchesKey(data, Key.ctrl("d"))) {
51474
+ this.body.scroll.scrollBy(this.viewport() - 2);
51475
+ this.options.requestRender();
51476
+ return;
51477
+ }
51478
+ const step = reduceKxmTuiInput(this.state, this.sections, data);
51479
+ this.state = step.state;
51480
+ for (const effect of step.effects) this.run(effect);
51481
+ this.rebuild();
51482
+ this.options.requestRender();
51483
+ }
51484
+ viewport() {
51485
+ const height = this.body.scroll.viewportHeight;
51486
+ return height > 3 ? height : 10;
51487
+ }
51488
+ run(effect) {
51489
+ if (effect.type === "quit") {
51490
+ this.options.onQuit();
51491
+ return;
51492
+ }
51493
+ if (effect.type === "notice") {
51494
+ this.notice = effect.message;
51495
+ return;
51496
+ }
51497
+ this.notice = void 0;
51498
+ void this.options.registry.invoke(effect.invocation).then((result) => {
51499
+ if (!result.ok) this.setStatus(result.message ?? "the owner refused that change");
51500
+ else if (this.notice === void 0) this.setStatus(void 0);
51501
+ });
51502
+ }
51503
+ footer() {
51504
+ const status = this.notice ?? this.options.statusText?.();
51505
+ const base = "\u2191\u2193 \xB7 \u2190\u2192 panes \xB7 enter edit \xB7 x clear \xB7 esc back \xB7 h help \xB7 q quit";
51506
+ return status ? `${base} ${status}` : base;
51507
+ }
51508
+ rebuild() {
51509
+ const theme = this.options.theme;
51510
+ const width = this.options.getWidth?.() ?? 120;
51511
+ const title = new Box(1, 0, theme.headerBg);
51512
+ title.addChild(new HStack([
51513
+ { component: new Text(theme.cursor(this.options.title), 0, 0), basis: 12, shrink: 1, minSize: 6 },
51514
+ {
51515
+ component: new Text(
51516
+ this.options.breadcrumb ? theme.dim(fitText(this.options.breadcrumb, Math.max(4, width - 16))) : "",
51517
+ 0,
51518
+ 0
51519
+ ),
51520
+ grow: 1,
51521
+ shrink: 1,
51522
+ minSize: 8
51523
+ }
51524
+ ], { gap: 2 }));
51525
+ this.body.fill(
51526
+ renderKxmTuiPanel(this.sections, this.state, theme, {
51527
+ title: this.options.title,
51528
+ ...this.options.breadcrumb ? { breadcrumb: this.options.breadcrumb } : {},
51529
+ helpLines: this.options.helpLines ?? KXM_TUI_PANEL_HELP,
51530
+ footer: this.footer()
51531
+ }, width)
51532
+ );
51533
+ const panel = new Box(0, 0, theme.panelBg);
51534
+ panel.addChild(this.body.scroll);
51535
+ this.root.clear();
51536
+ this.root.addChild(title, { basis: "auto", shrink: 0 });
51537
+ this.root.addChild(panel, { basis: "auto", grow: 1, shrink: 1, minSize: 1 });
51538
+ }
51539
+ handleMouse(event) {
51540
+ return this.root.handleMouse?.(event);
51541
+ }
51542
+ invalidate() {
51543
+ this.root.invalidate();
51544
+ }
51545
+ render(width) {
51546
+ return this.root.render(width);
51547
+ }
51548
+ dispose() {
51549
+ this.unsubscribe();
51550
+ this.unsubscribe = () => void 0;
51551
+ }
51552
+ };
51553
+
51554
+ // packages/core/tui/src/services/registry.ts
51555
+ function describe(error) {
51556
+ if (error instanceof Error && error.message) return error.message.split(/\r?\n/u)[0].slice(0, KXM_TUI_LIMITS.detailMax);
51557
+ return String(error).split(/\r?\n/u)[0].slice(0, KXM_TUI_LIMITS.detailMax);
51558
+ }
51559
+ function normalizeSource(source) {
51560
+ if (!/^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/u.test(source) || source.length > KXM_TUI_LIMITS.sourceMax) {
51561
+ throw new Error(`invalid kxm tui surface source: ${source.slice(0, 64)}`);
51562
+ }
51563
+ return source;
51564
+ }
51565
+ function createKxmTuiRegistry(options = {}) {
51566
+ const contributions = /* @__PURE__ */ new Map();
51567
+ const notices = /* @__PURE__ */ new Map();
51568
+ const listeners = /* @__PURE__ */ new Set();
51569
+ const published = /* @__PURE__ */ new Map();
51570
+ const issues = /* @__PURE__ */ new Map();
51571
+ let generation = 0;
51572
+ let disposed = false;
51573
+ let epoch = 0;
51574
+ const report2 = (error, context) => {
51575
+ if (options.onInternalError) options.onInternalError(error, context);
51576
+ };
51577
+ const emit = (event) => {
51578
+ for (const listener of Array.from(listeners)) {
51579
+ try {
51580
+ listener(event);
51581
+ } catch (error) {
51582
+ report2(error, "listener");
51583
+ }
51584
+ }
51585
+ };
51586
+ function readOwner(source, force) {
51587
+ if (!force) {
51588
+ const cached = published.get(source);
51589
+ if (cached) return cached;
51590
+ }
51591
+ const contribution = contributions.get(source);
51592
+ if (!contribution) {
51593
+ published.set(source, []);
51594
+ return [];
51595
+ }
51596
+ let sections;
51597
+ try {
51598
+ const raw = contribution.listSections();
51599
+ const ownerIssues = validateKxmTuiSurface(raw);
51600
+ if (ownerIssues.length > 0) {
51601
+ issues.set(source, ownerIssues);
51602
+ sections = raw.filter((section) => Boolean(section) && typeof section === "object").map((section) => ({
51603
+ ...section,
51604
+ notice: section.notice ?? `published surface was refused: ${ownerIssues[0].code} at ${ownerIssues[0].path}`,
51605
+ noticeLevel: "error"
51606
+ }));
51607
+ } else {
51608
+ issues.delete(source);
51609
+ sections = raw;
51610
+ }
51611
+ } catch (error) {
51612
+ issues.set(source, [{ path: source, code: "list_sections_failed", message: describe(error) }]);
51613
+ report2(error, `listSections:${source}`);
51614
+ sections = [{ source, id: "error", title: source, order: 900, notice: describe(error), noticeLevel: "error", fields: [] }];
51615
+ }
51616
+ const pending = notices.get(source);
51617
+ const withNotice = pending !== void 0 && sections.length > 0 ? sections.map((section, index) => index === 0 ? { ...section, notice: pending, noticeLevel: "error" } : section) : sections;
51618
+ published.set(source, withNotice);
51619
+ return withNotice;
51620
+ }
51621
+ function merged() {
51622
+ const all = [];
51623
+ for (const source of contributions.keys()) all.push(...readOwner(source, false));
51624
+ return all.slice().sort((a, b2) => a.order - b2.order || a.source.localeCompare(b2.source) || a.title.localeCompare(b2.title));
51625
+ }
51626
+ return {
51627
+ register(contribution) {
51628
+ if (disposed) throw new Error("kxm tui registry is disposed");
51629
+ const source = normalizeSource(contribution.source);
51630
+ notices.delete(source);
51631
+ published.delete(source);
51632
+ issues.delete(source);
51633
+ contributions.set(source, { ...contribution, source });
51634
+ emit({ reason: "publish", source });
51635
+ },
51636
+ unregister(source) {
51637
+ contributions.delete(source);
51638
+ published.delete(source);
51639
+ issues.delete(source);
51640
+ notices.delete(source);
51641
+ emit({ reason: "publish", source });
51642
+ },
51643
+ getSections() {
51644
+ return merged();
51645
+ },
51646
+ getIssues() {
51647
+ return [...issues.values()].flat();
51648
+ },
51649
+ async invoke(invocation) {
51650
+ if (disposed) return { ok: false, message: "kxm tui registry is disposed" };
51651
+ const contribution = contributions.get(invocation.source);
51652
+ if (!contribution) {
51653
+ emit({ reason: "refused", source: invocation.source, message: "unknown owner" });
51654
+ return { ok: false, message: `${invocation.source} does not own a surface here` };
51655
+ }
51656
+ const handler = contribution.handlers[invocation.action];
51657
+ if (!handler) {
51658
+ const message = `${invocation.source} does not accept "${invocation.action}"`;
51659
+ emit({ reason: "refused", source: invocation.source, message });
51660
+ return { ok: false, message };
51661
+ }
51662
+ const currentEpoch = epoch;
51663
+ try {
51664
+ await handler({
51665
+ sectionId: invocation.sectionId,
51666
+ fieldId: invocation.fieldId,
51667
+ action: invocation.action,
51668
+ ...invocation.value === void 0 ? {} : { value: invocation.value }
51669
+ });
51670
+ } catch (error) {
51671
+ const failure = contribution.describeError?.(error, invocation.action) ?? describe(error);
51672
+ notices.set(invocation.source, failure);
51673
+ emit({ reason: "failed", source: invocation.source, message: failure });
51674
+ readOwner(invocation.source, true);
51675
+ generation += 1;
51676
+ return { ok: false, message: failure };
51677
+ }
51678
+ if (currentEpoch !== epoch) {
51679
+ readOwner(invocation.source, true);
51680
+ return { ok: false, message: "stale write result was discarded" };
51681
+ }
51682
+ notices.delete(invocation.source);
51683
+ readOwner(invocation.source, true);
51684
+ generation += 1;
51685
+ emit({ reason: "publish", source: invocation.source });
51686
+ return { ok: true };
51687
+ },
51688
+ subscribe(listener) {
51689
+ listeners.add(listener);
51690
+ return () => listeners.delete(listener);
51691
+ },
51692
+ getGeneration() {
51693
+ return generation;
51694
+ },
51695
+ hasSource(source) {
51696
+ return contributions.has(source);
51697
+ },
51698
+ listSources() {
51699
+ return [...contributions.keys()];
51700
+ },
51701
+ dispose() {
51702
+ if (disposed) return;
51703
+ disposed = true;
51704
+ epoch += 1;
51705
+ listeners.clear();
51706
+ contributions.clear();
51707
+ published.clear();
51708
+ issues.clear();
51709
+ notices.clear();
51710
+ }
51711
+ };
51712
+ }
51713
+
51714
+ // packages/core/tui/src/adapters/terminal.ts
51715
+ function renderKxmTuiPanelFrame(input) {
51716
+ const theme = input.theme ?? createPlainKxmTuiTheme();
51717
+ const lines = renderKxmTuiPanel(
51718
+ input.registry.getSections(),
51719
+ initialKxmTuiPanelState(input.registry.getSections()),
51720
+ theme,
51721
+ { title: input.title, footer: input.footer ?? "non-interactive frame", nowMs: input.nowMs ?? Date.now() },
51722
+ input.width ?? 100
51723
+ );
51724
+ return `${lines.map((line) => stripTerminalSequences(line)).join("\n")}
51725
+ `;
51726
+ }
51727
+ async function runKxmTuiPanel(input) {
51728
+ const theme = input.theme ?? createDefaultKxmTuiTheme();
51729
+ const tty = input.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
51730
+ if (!tty) {
51731
+ input.stdout(renderKxmTuiPanelFrame({
51732
+ registry: input.registry,
51733
+ title: input.title,
51734
+ theme,
51735
+ ...input.now === void 0 ? {} : { nowMs: input.now() },
51736
+ ...input.breadcrumb ? { footer: input.breadcrumb } : {}
51737
+ }));
51738
+ return 0;
51739
+ }
51740
+ const terminal = input.terminal ?? new ProcessTerminal();
51741
+ const tui = new TuiAltScreen(terminal, false, void 0, { mouse: true });
51742
+ let timer;
51743
+ let stopped = false;
51744
+ let finished = () => void 0;
51745
+ const done = new Promise((resolve35) => {
51746
+ finished = resolve35;
51747
+ });
51748
+ const stop = () => {
51749
+ if (stopped) return;
51750
+ stopped = true;
51751
+ if (timer) clearInterval(timer);
51752
+ input.abort?.removeEventListener("abort", stop);
51753
+ panel.dispose();
51754
+ tui.stop();
51755
+ finished();
51756
+ };
51757
+ const panel = new KxmTuiPanelComponent({
51758
+ registry: input.registry,
51759
+ theme,
51760
+ title: input.title,
51761
+ ...input.breadcrumb ? { breadcrumb: input.breadcrumb } : {},
51762
+ ...input.helpLines ? { helpLines: input.helpLines } : {},
51763
+ requestRender: () => tui.requestRender(),
51764
+ onQuit: stop,
51765
+ getWidth: () => terminal.columns
51766
+ });
51767
+ input.abort?.addEventListener("abort", stop, { once: true });
51768
+ tui.setLayoutRoot(panel.root);
51769
+ tui.setFocus(panel);
51770
+ tui.start();
51771
+ timer = setInterval(() => panel.refresh(), input.pollMs ?? 2e3);
51772
+ timer.unref?.();
51773
+ await done;
51774
+ return stopped && input.abort?.aborted ? 130 : 0;
51775
+ }
50213
51776
 
50214
51777
  // plugins/kxm/src/external-effects.ts
50215
51778
  init_database();
@@ -52885,7 +54448,7 @@ init_routing();
52885
54448
  init_prices();
52886
54449
  import { spawnSync as spawnSync15 } from "node:child_process";
52887
54450
  import { createHash as createHash19 } from "node:crypto";
52888
- import { existsSync as existsSync48, mkdtempSync as mkdtempSync3, readFileSync as readFileSync42, rmSync as rmSync13 } from "node:fs";
54451
+ import { existsSync as existsSync49, mkdtempSync as mkdtempSync3, readFileSync as readFileSync43, rmSync as rmSync13 } from "node:fs";
52889
54452
  import { tmpdir as tmpdir3 } from "node:os";
52890
54453
  import { basename as basename11, extname as extname3, join as join57, resolve as resolve33 } from "node:path";
52891
54454
  import { createInterface as createInterface3 } from "node:readline";
@@ -54130,6 +55693,304 @@ function executeSshRun(params) {
54130
55693
  // plugins/kxm/src/cli/system.ts
54131
55694
  init_config();
54132
55695
 
55696
+ // plugins/kxm/src/config-editor.ts
55697
+ var import_yaml21 = __toESM(require_dist(), 1);
55698
+ import { existsSync as existsSync46, readFileSync as readFileSync41 } from "node:fs";
55699
+ init_config();
55700
+ var KXM_CONFIG_EDITOR_SOURCE = "kxm/config";
55701
+ var INTEGER_TEXT = /^(0|[1-9][0-9]*)$/;
55702
+ var DECIMAL_TEXT = /^(0|[1-9][0-9]*)(\.[0-9]{1,6})?$/;
55703
+ var TEXT_VALUE = /^[A-Za-z0-9@][A-Za-z0-9@._:/-]{0,127}$/;
55704
+ var SCREEN_CHOICES = ["agents", "tasks", "workflows", "plans", "inbox", "procs", "spend"];
55705
+ var SECTIONS = [
55706
+ {
55707
+ id: "user",
55708
+ title: "User",
55709
+ order: 10,
55710
+ fields: [
55711
+ { id: "user-theme", label: "Theme", keyPath: "user.theme", spec: { kind: "enum", choices: ["dark", "light", "minimal"] } },
55712
+ { id: "user-token-budget", label: "Token budget", keyPath: "user.tokenBudget", spec: { kind: "number", integer: true, min: 1, max: 2e6 } }
55713
+ ]
55714
+ },
55715
+ {
55716
+ id: "defaults",
55717
+ title: "Defaults",
55718
+ order: 20,
55719
+ fields: [
55720
+ { id: "defaults-workflow", label: "Workflow", keyPath: "defaults.workflow", spec: { kind: "text" } },
55721
+ { id: "defaults-harness", label: "Harness", keyPath: "defaults.harness", spec: { kind: "text" } }
55722
+ ]
55723
+ },
55724
+ {
55725
+ id: "dash",
55726
+ title: "Dashboard",
55727
+ order: 30,
55728
+ fields: [
55729
+ { id: "dash-screen", label: "Default screen", keyPath: "dash.defaultScreen", spec: { kind: "enum", choices: SCREEN_CHOICES } },
55730
+ { id: "dash-refresh", label: "Refresh interval", keyPath: "dash.refreshIntervalMs", spec: { kind: "number", integer: true, min: 250, max: 36e5 } },
55731
+ { id: "dash-auto-open", label: "Auto-open", keyPath: "dash.autoOpen", spec: { kind: "enum", choices: ["true", "false"] } }
55732
+ ]
55733
+ },
55734
+ {
55735
+ id: "sync",
55736
+ title: "Sync",
55737
+ order: 40,
55738
+ fields: [
55739
+ { id: "sync-tracker", label: "Default tracker", keyPath: "sync.defaultTracker", spec: { kind: "enum", choices: ["github", "jira", "none"] } }
55740
+ ]
55741
+ },
55742
+ {
55743
+ id: "hub",
55744
+ title: "Hub",
55745
+ order: 50,
55746
+ fields: [
55747
+ {
55748
+ id: "hub-auto-start",
55749
+ label: "Auto-start",
55750
+ keyPath: "hub.autoStart",
55751
+ spec: { kind: "enum", choices: ["background", "off"] },
55752
+ note: "Hub URLs and key references stay out of this panel."
55753
+ }
55754
+ ]
55755
+ },
55756
+ {
55757
+ id: "improvement",
55758
+ title: "Improvement",
55759
+ order: 60,
55760
+ fields: [
55761
+ { id: "improvement-policy", label: "Promotion policy", keyPath: "improvement.promotionPolicy", spec: { kind: "enum", choices: ["manual_pr", "critic_quorum", "auto_threshold"] } },
55762
+ { id: "improvement-half-life", label: "Telemetry half-life", keyPath: "improvement.telemetryHalfLifeDays", spec: { kind: "number", integer: false, min: 1e-6, max: 3650 } },
55763
+ { id: "improvement-min-runs", label: "Minimum runs", keyPath: "improvement.autoThreshold.minRuns", spec: { kind: "number", integer: true, min: 1, max: 1e6 } },
55764
+ { id: "improvement-min-pass-rate", label: "Minimum pass rate", keyPath: "improvement.autoThreshold.minPassRate", spec: { kind: "number", integer: false, min: 0, max: 1 } },
55765
+ { id: "improvement-min-cost-savings", label: "Minimum cost savings", keyPath: "improvement.autoThreshold.minCostSavings", spec: { kind: "number", integer: false, min: 0, max: 1e6 } }
55766
+ ]
55767
+ },
55768
+ {
55769
+ id: "routing",
55770
+ title: "Routing",
55771
+ order: 70,
55772
+ fields: [
55773
+ {
55774
+ id: "routing-shadow",
55775
+ label: "Shadow execution",
55776
+ keyPath: "routing.shadowExecution.enabled",
55777
+ spec: { kind: "enum", choices: ["true", "false"] },
55778
+ note: "Strategy, fallback, and context limits stay in role and route files."
55779
+ },
55780
+ { id: "routing-sample-rate", label: "Sample rate", keyPath: "routing.shadowExecution.sampleRate", spec: { kind: "number", integer: false, min: 0, max: 1 } },
55781
+ { id: "routing-breaker-mode", label: "Breaker mode", keyPath: "routing.circuitBreaker.mode", spec: { kind: "enum", choices: ["soft_demotion", "quarantine"] } },
55782
+ { id: "routing-failure-threshold", label: "Failure threshold", keyPath: "routing.circuitBreaker.failureThreshold", spec: { kind: "number", integer: true, min: 1, max: 1e4 } },
55783
+ { id: "routing-window-seconds", label: "Window seconds", keyPath: "routing.circuitBreaker.windowSeconds", spec: { kind: "number", integer: true, min: 1, max: 31536e3 } },
55784
+ { id: "routing-cooldown-seconds", label: "Cooldown seconds", keyPath: "routing.circuitBreaker.cooldownSeconds", spec: { kind: "number", integer: true, min: 0, max: 31536e3 } },
55785
+ { id: "routing-penalty", label: "Penalty multiplier", keyPath: "routing.circuitBreaker.penaltyMultiplier", spec: { kind: "number", integer: false, min: 1e-6, max: 1e3 } }
55786
+ ]
55787
+ },
55788
+ {
55789
+ id: "telemetry",
55790
+ title: "Telemetry",
55791
+ order: 80,
55792
+ fields: [
55793
+ { id: "telemetry-federated", label: "Federated", keyPath: "telemetry.federated", spec: { kind: "enum", choices: ["true", "false"] } },
55794
+ { id: "telemetry-anonymize", label: "Anonymize", keyPath: "telemetry.anonymize", spec: { kind: "enum", choices: ["true", "false"] } }
55795
+ ]
55796
+ }
55797
+ ];
55798
+ var KxmConfigEditorError = class extends Error {
55799
+ code = "config_edit_refused";
55800
+ constructor(message) {
55801
+ super(message);
55802
+ this.name = "KxmConfigEditorError";
55803
+ }
55804
+ };
55805
+ function createKxmConfigEditorContribution(target) {
55806
+ return {
55807
+ source: KXM_CONFIG_EDITOR_SOURCE,
55808
+ republishAfterWrite: true,
55809
+ listSections: () => listConfigEditorSections(target),
55810
+ handlers: {
55811
+ [KXM_TUI_SET_ACTION]: (input) => {
55812
+ writeConfigEditorValue(target, input.sectionId, input.fieldId, input.value);
55813
+ },
55814
+ [KXM_TUI_CLEAR_ACTION]: (input) => {
55815
+ clearConfigEditorValue(target, input.sectionId, input.fieldId);
55816
+ }
55817
+ },
55818
+ describeError: (error) => error instanceof Error && error.message ? error.message : "config edit failed"
55819
+ };
55820
+ }
55821
+ function scopeFile(target) {
55822
+ return kxmConfigFileForScope(target.cwd, target.scope, target.userConfigDir);
55823
+ }
55824
+ function scopePhrase(scope) {
55825
+ return scope === "project" ? "the project file .kxm/config.yaml" : "the user file config.yaml";
55826
+ }
55827
+ function listConfigEditorSections(target) {
55828
+ const config = loadKxmConfig(target.cwd, {
55829
+ ...target.userConfigDir === void 0 ? {} : { userConfigDir: target.userConfigDir },
55830
+ layers: target.scope === "user" ? "user" : "all"
55831
+ });
55832
+ const owned = readScopeDocument(scopeFile(target));
55833
+ const where = scopePhrase(target.scope);
55834
+ return SECTIONS.map((section) => {
55835
+ const fields = section.fields.map((field) => publishField(field, config, owned, where));
55836
+ const diagnosis = fields.find((field) => field.statusText)?.statusText;
55837
+ return {
55838
+ source: KXM_CONFIG_EDITOR_SOURCE,
55839
+ id: section.id,
55840
+ title: section.title,
55841
+ order: section.order,
55842
+ detail: `kxm.config.v1 \xB7 ${target.scope} scope. Re-read from disk after each write.`,
55843
+ ...diagnosis === void 0 ? {} : { notice: diagnosis, noticeLevel: "error" },
55844
+ fields
55845
+ };
55846
+ });
55847
+ }
55848
+ function publishField(field, config, owned, where) {
55849
+ const effective = getKxmConfigValue(config, field.keyPath);
55850
+ const stored = ownValue(owned, field.keyPath);
55851
+ const shown = displayScalar(effective);
55852
+ const inherited = !stored.present;
55853
+ const diagnosis = storedDiagnosis(field, stored, effective);
55854
+ const detail = [
55855
+ `Writes ${where}.`,
55856
+ inherited ? "Inherited." : "Set in this scope.",
55857
+ "Clear removes only this override.",
55858
+ field.note ?? "",
55859
+ diagnosis ?? ""
55860
+ ].filter((part) => part.length > 0).join(" ");
55861
+ const choices = field.spec.kind === "enum" ? field.spec.choices.map((choice) => ({ id: choice, label: choice })) : void 0;
55862
+ return {
55863
+ id: field.id,
55864
+ label: field.label,
55865
+ kind: field.spec.kind === "enum" ? "enum" : "text",
55866
+ keyPath: field.keyPath,
55867
+ detail,
55868
+ status: "ready",
55869
+ ...shown === void 0 ? {} : { value: shown },
55870
+ ...choices === void 0 ? {} : { choices },
55871
+ ...diagnosis === void 0 ? {} : { statusText: diagnosis }
55872
+ };
55873
+ }
55874
+ function displayScalar(value) {
55875
+ if (typeof value === "string" && value.length > 0) return value;
55876
+ if (typeof value === "boolean") return value ? "true" : "false";
55877
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
55878
+ return void 0;
55879
+ }
55880
+ function isBooleanEnum(spec) {
55881
+ return spec.kind === "enum" && spec.choices.length === 2 && spec.choices.every((choice) => choice === "true" || choice === "false");
55882
+ }
55883
+ function storedTypeMatches(field, value) {
55884
+ if (field.spec.kind === "number") {
55885
+ return typeof value === "number" && Number.isFinite(value) && (!field.spec.integer || Number.isInteger(value));
55886
+ }
55887
+ if (isBooleanEnum(field.spec)) return typeof value === "boolean";
55888
+ return typeof value === "string";
55889
+ }
55890
+ function storedDiagnosis(field, stored, effective) {
55891
+ if (!stored.present) return void 0;
55892
+ if (!storedTypeMatches(field, stored.value)) return "stored type does not match this field";
55893
+ if (field.spec.kind === "enum" && !isBooleanEnum(field.spec) && !field.spec.choices.includes(stored.value)) {
55894
+ return "stored value is not a supported scalar";
55895
+ }
55896
+ if (field.spec.kind === "number") {
55897
+ const value = stored.value;
55898
+ if (value < field.spec.min || value > field.spec.max) return "stored value is not a supported scalar";
55899
+ }
55900
+ if (stored.value !== effective) return "stored value is not a supported scalar";
55901
+ return void 0;
55902
+ }
55903
+ function writeConfigEditorValue(target, sectionId, fieldId, raw) {
55904
+ const field = requireField(sectionId, fieldId);
55905
+ const value = parseEditorValue(field, raw);
55906
+ setKxmConfigValue(target.cwd, field.keyPath, value, {
55907
+ scope: target.scope,
55908
+ ...target.userConfigDir === void 0 ? {} : { userConfigDir: target.userConfigDir }
55909
+ });
55910
+ }
55911
+ function clearConfigEditorValue(target, sectionId, fieldId) {
55912
+ const field = requireField(sectionId, fieldId);
55913
+ deleteKxmConfigValue(target.cwd, field.keyPath, {
55914
+ scope: target.scope,
55915
+ ...target.userConfigDir === void 0 ? {} : { userConfigDir: target.userConfigDir }
55916
+ });
55917
+ }
55918
+ function requireField(sectionId, fieldId) {
55919
+ const section = SECTIONS.find((item) => item.id === sectionId);
55920
+ const field = section?.fields.find((item) => item.id === fieldId);
55921
+ if (!field) throw new KxmConfigEditorError("unknown configuration field");
55922
+ return field;
55923
+ }
55924
+ function parseEditorValue(field, raw) {
55925
+ if (raw === void 0) throw new KxmConfigEditorError("a value is required");
55926
+ if (raw.length === 0 || raw.length > 128 || /[\u0000-\u001f\u007f]/u.test(raw)) {
55927
+ throw new KxmConfigEditorError("value must be a single bounded line");
55928
+ }
55929
+ if (field.spec.kind === "enum") {
55930
+ if (!field.spec.choices.includes(raw)) throw new KxmConfigEditorError("value is not a supported choice");
55931
+ if (isBooleanEnum(field.spec)) return raw === "true";
55932
+ return raw;
55933
+ }
55934
+ if (field.spec.kind === "text") {
55935
+ if (!TEXT_VALUE.test(raw)) throw new KxmConfigEditorError("value must be a single bounded line");
55936
+ return raw;
55937
+ }
55938
+ if (!(field.spec.integer ? INTEGER_TEXT : DECIMAL_TEXT).test(raw)) {
55939
+ throw new KxmConfigEditorError("value must be a finite number in range");
55940
+ }
55941
+ const parsed = Number(raw);
55942
+ if (!Number.isFinite(parsed) || field.spec.integer && !Number.isInteger(parsed) || parsed < field.spec.min || parsed > field.spec.max) {
55943
+ throw new KxmConfigEditorError("value must be a finite number in range");
55944
+ }
55945
+ return parsed;
55946
+ }
55947
+ function readScopeDocument(file) {
55948
+ if (!existsSync46(file)) return void 0;
55949
+ const parsed = (0, import_yaml21.parse)(readFileSync41(file, "utf8"));
55950
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
55951
+ throw new KxmConfigEditorError(`config file ${file} must be a mapping`);
55952
+ }
55953
+ return parsed;
55954
+ }
55955
+ function ownValue(root, keyPath) {
55956
+ if (!root) return { present: false, value: void 0 };
55957
+ let cursor2 = root;
55958
+ for (const part of keyPath.split(".")) {
55959
+ if (!cursor2 || typeof cursor2 !== "object" || Array.isArray(cursor2)) return { present: false, value: void 0 };
55960
+ const record = cursor2;
55961
+ if (!Object.prototype.hasOwnProperty.call(record, part)) return { present: false, value: void 0 };
55962
+ cursor2 = record[part];
55963
+ }
55964
+ return { present: true, value: cursor2 };
55965
+ }
55966
+ var EDITOR_HELP = [
55967
+ "\u2191\u2193 move \xB7 enter edits \xB7 x clears this scope's override \xB7 q quits",
55968
+ "Writes are checked, saved, then re-read. A refused edit leaves the file unchanged."
55969
+ ];
55970
+ var FRAME_NOW_MS = Date.UTC(2026, 9, 11);
55971
+ async function runKxmConfigEditor(input) {
55972
+ const registry = createKxmTuiRegistry();
55973
+ try {
55974
+ registry.register(createKxmConfigEditorContribution(input.target));
55975
+ registry.getSections();
55976
+ const failed2 = registry.getIssues()[0];
55977
+ if (failed2) throw new KxmConfigEditorError(failed2.message);
55978
+ const interactive = input.isTty && !input.dryRun;
55979
+ return await runKxmTuiPanel({
55980
+ registry,
55981
+ title: "KXM configuration",
55982
+ breadcrumb: `kxm.config.v1 \xB7 ${input.target.scope} scope`,
55983
+ helpLines: EDITOR_HELP,
55984
+ stdout: input.stdout,
55985
+ isTty: interactive,
55986
+ ...interactive ? {} : { theme: createPlainKxmTuiTheme() },
55987
+ now: () => FRAME_NOW_MS
55988
+ });
55989
+ } finally {
55990
+ registry.dispose();
55991
+ }
55992
+ }
55993
+
54133
55994
  // plugins/kxm/src/autocomplete.ts
54134
55995
  var TOP_LEVEL_COMMANDS = [
54135
55996
  "init",
@@ -54183,7 +56044,7 @@ var SUBCOMMANDS = {
54183
56044
  memory: ["brief", "note", "sync"],
54184
56045
  routing: ["report", "benchmark"],
54185
56046
  hub: ["view", "start", "stop", "bind", "unbind"],
54186
- config: ["get", "set", "list"],
56047
+ config: ["get", "set", "edit", "list"],
54187
56048
  goal: ["create", "list", "get"],
54188
56049
  task: ["create", "list", "get", "run", "sync"],
54189
56050
  studio: ["layout", "serve"],
@@ -54305,7 +56166,7 @@ _kxm() {
54305
56166
  _arguments '1:shell:(bash zsh fish)'
54306
56167
  ;;
54307
56168
  config)
54308
- _arguments '1:action:(get set list)'
56169
+ _arguments '1:action:(get set edit list)'
54309
56170
  ;;
54310
56171
  runs)
54311
56172
  _arguments '1:subcommand:(status cancel list)'
@@ -54349,7 +56210,7 @@ complete -c kxm -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
54349
56210
  complete -c kxm -n "__fish_seen_subcommand_from dash" -l screen -a "agents tasks workflows plans inbox procs spend"
54350
56211
 
54351
56212
  # Config subcommand
54352
- complete -c kxm -n "__fish_seen_subcommand_from config" -a "get set list"
56213
+ complete -c kxm -n "__fish_seen_subcommand_from config" -a "get set edit list"
54353
56214
 
54354
56215
  # Runs subcommand
54355
56216
  complete -c kxm -n "__fish_seen_subcommand_from runs" -a "status cancel list"
@@ -54369,7 +56230,7 @@ complete -c kxm -n "__fish_seen_subcommand_from goal" -a "create list get"
54369
56230
  }
54370
56231
 
54371
56232
  // plugins/kxm/src/completion-install.ts
54372
- import { existsSync as existsSync46, mkdirSync as mkdirSync33, readFileSync as readFileSync41, writeFileSync as writeFileSync28 } from "node:fs";
56233
+ import { existsSync as existsSync47, mkdirSync as mkdirSync33, readFileSync as readFileSync42, writeFileSync as writeFileSync28 } from "node:fs";
54373
56234
  import { homedir as homedir9 } from "node:os";
54374
56235
  import { basename as basename10, delimiter, dirname as dirname24, isAbsolute as isAbsolute11, join as join55, resolve as resolve32 } from "node:path";
54375
56236
  var COMPLETION_MARKER = "# kxm completion";
@@ -54398,7 +56259,7 @@ function completionScriptPath(shell, options = {}) {
54398
56259
  function bashRcCandidate(options) {
54399
56260
  const home = effectiveHome(options);
54400
56261
  const candidates = [join55(home, ".bashrc"), join55(home, ".bash_profile")];
54401
- const existing = candidates.find((candidate) => existsSync46(candidate));
56262
+ const existing = candidates.find((candidate) => existsSync47(candidate));
54402
56263
  return existing ?? candidates[0];
54403
56264
  }
54404
56265
  function zshRcCandidate(options) {
@@ -54443,13 +56304,13 @@ function installShellCompletion(shellInput, options = {}) {
54443
56304
  const scriptPath = completionScriptPath(shell, options);
54444
56305
  const { rcFile } = completionRcTarget(shell, options);
54445
56306
  const script = generateShellCompletion(shell);
54446
- const existingScript = existsSync46(scriptPath);
54447
- const existingScriptMatches = existingScript && readFileSync41(scriptPath, "utf8") === script;
56307
+ const existingScript = existsSync47(scriptPath);
56308
+ const existingScriptMatches = existingScript && readFileSync42(scriptPath, "utf8") === script;
54448
56309
  let rcModified = false;
54449
56310
  let alreadyInstalled = false;
54450
56311
  if (shell === "fish") {
54451
56312
  const fishTarget = fishCompletionTarget(shell, options);
54452
- const fishInstalled = existsSync46(fishTarget) && readFileSync41(fishTarget, "utf8") === script;
56313
+ const fishInstalled = existsSync47(fishTarget) && readFileSync42(fishTarget, "utf8") === script;
54453
56314
  alreadyInstalled = fishInstalled;
54454
56315
  if (!options.dryRun && (!fishInstalled || options.overwrite)) {
54455
56316
  mkdirSync33(resolve32(fishTarget, ".."), { recursive: true });
@@ -54466,7 +56327,7 @@ function installShellCompletion(shellInput, options = {}) {
54466
56327
  }
54467
56328
  if (rcFile) {
54468
56329
  const line = sourceLine(shell, scriptPath);
54469
- const rcContent = existsSync46(rcFile) ? readFileSync41(rcFile, "utf8") : "";
56330
+ const rcContent = existsSync47(rcFile) ? readFileSync42(rcFile, "utf8") : "";
54470
56331
  alreadyInstalled = rcContent.includes(line) || rcContent.includes(`${COMPLETION_MARKER}`);
54471
56332
  }
54472
56333
  if (!options.dryRun) {
@@ -54482,7 +56343,7 @@ ${marker}
54482
56343
  ${line}
54483
56344
  `;
54484
56345
  mkdirSync33(resolve32(rcFile, ".."), { recursive: true });
54485
- writeFileSync28(rcFile, (existsSync46(rcFile) ? readFileSync41(rcFile, "utf8") : "") + stanza, { encoding: "utf8" });
56346
+ writeFileSync28(rcFile, (existsSync47(rcFile) ? readFileSync42(rcFile, "utf8") : "") + stanza, { encoding: "utf8" });
54486
56347
  rcModified = true;
54487
56348
  }
54488
56349
  }
@@ -54499,13 +56360,13 @@ function kxmBinDir(env = process.env) {
54499
56360
  const argv1 = env.KXM_ENTRY ?? process.argv[1];
54500
56361
  if (argv1 && isAbsolute11(argv1)) {
54501
56362
  const dir = dirname24(argv1);
54502
- if (existsSync46(join55(dir, process.platform === "win32" ? "kxm.cmd" : "kxm"))) return dir;
56363
+ if (existsSync47(join55(dir, process.platform === "win32" ? "kxm.cmd" : "kxm"))) return dir;
54503
56364
  }
54504
56365
  const pathEnv = env.PATH ?? "";
54505
56366
  for (const part of pathEnv.split(delimiter)) {
54506
56367
  if (!part) continue;
54507
56368
  const candidate = resolve32(part, process.platform === "win32" ? "kxm.cmd" : "kxm");
54508
- if (existsSync46(candidate)) return resolve32(part);
56369
+ if (existsSync47(candidate)) return resolve32(part);
54509
56370
  }
54510
56371
  return void 0;
54511
56372
  }
@@ -54523,7 +56384,7 @@ function installPathEntry(shellInput, options = {}) {
54523
56384
  }
54524
56385
  const { rcFile } = completionRcTarget(shell, options);
54525
56386
  const line = pathLine(binDir);
54526
- const rcContent = existsSync46(rcFile ?? "") ? readFileSync41(rcFile, "utf8") : "";
56387
+ const rcContent = existsSync47(rcFile ?? "") ? readFileSync42(rcFile, "utf8") : "";
54527
56388
  const alreadyInstalled = rcContent.includes(binDir) || (options.env?.PATH ?? process.env.PATH ?? "").split(delimiter).includes(binDir);
54528
56389
  if (!options.dryRun && rcFile && !alreadyInstalled) {
54529
56390
  mkdirSync33(dirname24(rcFile), { recursive: true });
@@ -54536,9 +56397,9 @@ ${line}
54536
56397
  }
54537
56398
 
54538
56399
  // plugins/kxm/src/init-guide-setup.ts
54539
- var import_yaml21 = __toESM(require_dist(), 1);
56400
+ var import_yaml22 = __toESM(require_dist(), 1);
54540
56401
  init_routes();
54541
- import { existsSync as existsSync47, mkdirSync as mkdirSync34, writeFileSync as writeFileSync29 } from "node:fs";
56402
+ import { existsSync as existsSync48, mkdirSync as mkdirSync34, writeFileSync as writeFileSync29 } from "node:fs";
54542
56403
  import { dirname as dirname25, join as join56 } from "node:path";
54543
56404
  var ADMITTED_GUIDE_BINDINGS = Object.freeze({
54544
56405
  "anthropic/claude-fable-5.1": { harness: "claude", provider: "anthropic", model: "fable" },
@@ -54971,7 +56832,7 @@ function renderGuideSetupFiles(projectRoot, plan) {
54971
56832
  if (binding.harness === "pi" && !piMatch) {
54972
56833
  files.push({
54973
56834
  path: join56(projectRoot, ".kxm", "agents", `${role}.yaml`),
54974
- content: (0, import_yaml21.stringify)(agentDocument(role, stage))
56835
+ content: (0, import_yaml22.stringify)(agentDocument(role, stage))
54975
56836
  });
54976
56837
  continue;
54977
56838
  }
@@ -54996,15 +56857,15 @@ function renderGuideSetupFiles(projectRoot, plan) {
54996
56857
  }
54997
56858
  files.push({
54998
56859
  path: join56(projectRoot, ".kxm", "agents", `${role}.yaml`),
54999
- content: (0, import_yaml21.stringify)(agentDocument(role, stage))
56860
+ content: (0, import_yaml22.stringify)(agentDocument(role, stage))
55000
56861
  });
55001
56862
  files.push({
55002
56863
  path: join56(projectRoot, ".kxm", "models", `${routeId2}.yaml`),
55003
- content: (0, import_yaml21.stringify)(modelDocument)
56864
+ content: (0, import_yaml22.stringify)(modelDocument)
55004
56865
  });
55005
56866
  files.push({
55006
56867
  path: join56(projectRoot, ".kxm", "roles", `${role}.yaml`),
55007
- content: (0, import_yaml21.stringify)({
56868
+ content: (0, import_yaml22.stringify)({
55008
56869
  schema: "kxm.role.v2",
55009
56870
  id: role,
55010
56871
  purpose: writer ? "writer" : "experiment",
@@ -55017,7 +56878,7 @@ function renderGuideSetupFiles(projectRoot, plan) {
55017
56878
  for (const workflow of plan.workflows) {
55018
56879
  files.push({
55019
56880
  path: join56(projectRoot, ".kxm", "workflows", `${workflow.slug}.yaml`),
55020
- content: (0, import_yaml21.stringify)(workflowDocument(workflow))
56881
+ content: (0, import_yaml22.stringify)(workflowDocument(workflow))
55021
56882
  });
55022
56883
  }
55023
56884
  return files;
@@ -55035,14 +56896,14 @@ function mergeGuideRouteAdmission(projectRoot, plan) {
55035
56896
  policy.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
55036
56897
  const path4 = join56(projectRoot, ".kxm", "routes.yaml");
55037
56898
  mkdirSync34(dirname25(path4), { recursive: true });
55038
- writeFileSync29(path4, (0, import_yaml21.stringify)(policy), "utf8");
56899
+ writeFileSync29(path4, (0, import_yaml22.stringify)(policy), "utf8");
55039
56900
  return added;
55040
56901
  }
55041
56902
  function writeGuideSetupFiles(files) {
55042
56903
  const written = [];
55043
56904
  const existed = [];
55044
56905
  for (const file of files) {
55045
- if (existsSync47(file.path)) {
56906
+ if (existsSync48(file.path)) {
55046
56907
  existed.push(file.path);
55047
56908
  continue;
55048
56909
  }
@@ -55123,7 +56984,7 @@ function applyKxmPackageUpdate(runtime, notice) {
55123
56984
  for (const step of planned) {
55124
56985
  if (step.kind === "verify") {
55125
56986
  if (!verifyReleaseAssetDigest(step.path, step.sha256)) {
55126
- const actual = existsSync48(step.path) ? createHash19("sha256").update(readFileSync42(step.path)).digest("hex") : "missing";
56987
+ const actual = existsSync49(step.path) ? createHash19("sha256").update(readFileSync43(step.path)).digest("hex") : "missing";
55127
56988
  return {
55128
56989
  ok: false,
55129
56990
  error: "release_digest_mismatch",
@@ -55451,12 +57312,12 @@ async function cmdValidate(runtime, fileFlag) {
55451
57312
  }
55452
57313
  const selectedFile = explicitFile || configuredFile;
55453
57314
  const file = selectedFile ? resolve33(runtime.cwd, selectedFile) : void 0;
55454
- if (file && !existsSync48(file)) {
57315
+ if (file && !existsSync49(file)) {
55455
57316
  printWorker(runtime, worker, { ok: false, command: "validate", error: "file_not_found", file }, `workflow file not found: ${file}`);
55456
57317
  return 1;
55457
57318
  }
55458
57319
  try {
55459
- const raw = file ? readFileSync42(file, "utf8") : inline;
57320
+ const raw = file ? readFileSync43(file, "utf8") : inline;
55460
57321
  if (explicitFile && file && !raw.trimStart().startsWith("[")) {
55461
57322
  const value = parseRestrictedYaml2(raw, file);
55462
57323
  const issues = new KxmSchemaRegistry().validate("workflow", value, file);
@@ -55631,6 +57492,37 @@ async function cmdConfigSet(runtime, key, value, options) {
55631
57492
  return 1;
55632
57493
  }
55633
57494
  }
57495
+ async function cmdConfigEdit(runtime, options, isTty = Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
57496
+ const scope = options.scope;
57497
+ if (scope !== "project" && scope !== "user") {
57498
+ const detail = "config edit requires --scope project or --scope user";
57499
+ print(runtime.io, runtime.json, { ok: false, command: "config edit", error: "config_edit_unsupported", detail }, detail);
57500
+ return 2;
57501
+ }
57502
+ if (runtime.json) {
57503
+ const detail = "config edit does not print machine-readable output";
57504
+ print(runtime.io, runtime.json, { ok: false, command: "config edit", error: "config_edit_unsupported", detail }, detail);
57505
+ return 2;
57506
+ }
57507
+ try {
57508
+ const userConfigDir = userConfigDirForEnv(runtime.env);
57509
+ return await runKxmConfigEditor({
57510
+ target: {
57511
+ cwd: runtime.cwd,
57512
+ scope,
57513
+ ...userConfigDir === void 0 ? {} : { userConfigDir }
57514
+ },
57515
+ stdout: (text) => runtime.io.stdout(text),
57516
+ isTty,
57517
+ dryRun: runtime.dryRun
57518
+ });
57519
+ } catch (error) {
57520
+ const message = error instanceof Error ? error.message : String(error);
57521
+ runtime.io.stderr(`config edit failed: ${message}
57522
+ `);
57523
+ return 1;
57524
+ }
57525
+ }
55634
57526
  async function cmdConfigList(runtime) {
55635
57527
  try {
55636
57528
  const config = loadKxmConfig(runtime.cwd);
@@ -55705,9 +57597,9 @@ async function maybeOfferCompletionInstall(runtime) {
55705
57597
  if (!pathNeeded) {
55706
57598
  const scriptPath = completionScriptPath(shell, { env: runtime.env, configDir: runtime.env.KXM_USER_CONFIG_DIR });
55707
57599
  const { rcFile } = completionRcTarget(shell, { env: runtime.env });
55708
- if (rcFile && existsSync48(rcFile)) {
57600
+ if (rcFile && existsSync49(rcFile)) {
55709
57601
  try {
55710
- if (readFileSync42(rcFile, "utf8").includes(scriptPath)) return;
57602
+ if (readFileSync43(rcFile, "utf8").includes(scriptPath)) return;
55711
57603
  } catch {
55712
57604
  }
55713
57605
  }
@@ -56082,6 +57974,7 @@ var DRY_RUN_COMMANDS = /* @__PURE__ */ new Set([
56082
57974
  "hub unbind",
56083
57975
  "config get",
56084
57976
  "config set",
57977
+ "config edit",
56085
57978
  "config list",
56086
57979
  "goal create",
56087
57980
  "goal list",
@@ -56654,7 +58547,7 @@ function createProgram(ctx, result, argv) {
56654
58547
  });
56655
58548
  const gate = addGlobalOptions(program2.command("gate").description("Validate definitions and operate evidence gates"));
56656
58549
  gate.helpCommand("help", "Show gate help");
56657
- addGlobalOptions(gate.command("validate").description("Parse workflow definitions without printing secrets")).option("--file <path>", "Workflow definition file").action(async function validateAction(options) {
58550
+ addGlobalOptions(gate.command("validate").description("Parse workflow definitions without printing secrets")).option("--file <path>", "Workflow definition file").action(async function validateAction2(options) {
56658
58551
  result.code = await cmdValidate(runtimeFrom(ctx, this), options.file);
56659
58552
  });
56660
58553
  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) {
@@ -56782,6 +58675,9 @@ function createProgram(ctx, result, argv) {
56782
58675
  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) {
56783
58676
  result.code = await cmdConfigSet(runtimeFrom(ctx, this), key, value, options);
56784
58677
  });
58678
+ 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) {
58679
+ result.code = await cmdConfigEdit(runtimeFrom(ctx, this), options);
58680
+ });
56785
58681
  addGlobalOptions(configCmd.command("list", { isDefault: true }).description("List resolved configuration values")).action(async function configListAction() {
56786
58682
  result.code = await cmdConfigList(runtimeFrom(ctx, this));
56787
58683
  });