@qodalis/cli 2.0.0-beta.7 → 2.0.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public-api.js CHANGED
@@ -411,7 +411,7 @@ var CliBackgroundServiceRegistry_TOKEN = "cli-background-service-registry";
411
411
  var CliPackageManagerService_TOKEN = "cli-package-manager-service";
412
412
 
413
413
  // src/lib/version.ts
414
- var LIBRARY_VERSION = "2.0.0-beta.7";
414
+ var LIBRARY_VERSION = "2.0.0-beta.8";
415
415
  var API_VERSION = 2;
416
416
  var CliAliasCommandProcessor = class {
417
417
  constructor() {
@@ -2551,10 +2551,8 @@ var CliUptimeCommandProcessor = class {
2551
2551
  icon: "\u23F1\uFE0F",
2552
2552
  module: "misc"
2553
2553
  };
2554
- this.sessionStartTimes = /* @__PURE__ */ new WeakMap();
2555
2554
  }
2556
2555
  async initialize(context2) {
2557
- this.sessionStartTimes.set(context2.terminal, Date.now());
2558
2556
  try {
2559
2557
  context2.backgroundServices.register({
2560
2558
  name: "uptime-tracker",
@@ -2562,6 +2560,10 @@ var CliUptimeCommandProcessor = class {
2562
2560
  type: "daemon",
2563
2561
  onStart: async (ctx) => {
2564
2562
  ctx.log("Uptime tracking started");
2563
+ await new Promise((resolve) => {
2564
+ ctx.signal.addEventListener("abort", () => resolve(), { once: true });
2565
+ });
2566
+ ctx.log("Uptime tracking stopped");
2565
2567
  }
2566
2568
  });
2567
2569
  await context2.backgroundServices.start("uptime-tracker");
@@ -2570,9 +2572,16 @@ var CliUptimeCommandProcessor = class {
2570
2572
  }
2571
2573
  async processCommand(_, context2) {
2572
2574
  const svcInfo = context2.backgroundServices.getStatus("uptime-tracker");
2573
- const startTime = svcInfo?.status === "running" && svcInfo.startedAt ? svcInfo.startedAt.getTime() : this.sessionStartTimes.get(context2.terminal) ?? Date.now();
2574
- const elapsed = Date.now() - startTime;
2575
2575
  const { writer } = context2;
2576
+ if (!svcInfo || svcInfo.status !== "running") {
2577
+ writer.writeError("Uptime tracker is not running.");
2578
+ writer.writeInfo(
2579
+ `Use ${writer.wrapInColor("services start uptime-tracker", cliCore.CliForegroundColor.Cyan)} to restart.`
2580
+ );
2581
+ return;
2582
+ }
2583
+ const startTime = svcInfo.startedAt.getTime();
2584
+ const elapsed = Date.now() - startTime;
2576
2585
  const seconds = Math.floor(elapsed / 1e3) % 60;
2577
2586
  const minutes = Math.floor(elapsed / (1e3 * 60)) % 60;
2578
2587
  const hours = Math.floor(elapsed / (1e3 * 60 * 60)) % 24;
@@ -6218,6 +6227,9 @@ var CliServerManager = class {
6218
6227
 
6219
6228
  // src/lib/wasm/fallback.ts
6220
6229
  var JsFallbackAccelerator = class {
6230
+ constructor() {
6231
+ this.ruleSets = /* @__PURE__ */ new Map();
6232
+ }
6221
6233
  textSearch(text, needle, startRow, startCol, caseSensitive, wrap) {
6222
6234
  if (!needle) return [-1, -1];
6223
6235
  const lines = text.split("\n");
@@ -6277,6 +6289,60 @@ var JsFallbackAccelerator = class {
6277
6289
  }
6278
6290
  return matches.sort();
6279
6291
  }
6292
+ registerRuleSet(ruleSetId, rules) {
6293
+ const parsed = [];
6294
+ for (const line of rules.split("\n")) {
6295
+ if (!line) continue;
6296
+ const parts = line.split(" ");
6297
+ if (parts.length < 2) continue;
6298
+ try {
6299
+ parsed.push({
6300
+ pattern: new RegExp(parts[0]),
6301
+ tokenType: parts[1],
6302
+ captureGroup: parts[2] ? parseInt(parts[2], 10) : 0
6303
+ });
6304
+ } catch {
6305
+ }
6306
+ }
6307
+ this.ruleSets.set(ruleSetId, parsed);
6308
+ }
6309
+ tokenizeLine(line, ruleSetId) {
6310
+ const rules = this.ruleSets.get(ruleSetId);
6311
+ if (!rules || !line) return "";
6312
+ const tokens = [];
6313
+ let pos = 0;
6314
+ while (pos < line.length) {
6315
+ let matched = false;
6316
+ for (const rule of rules) {
6317
+ rule.pattern.lastIndex = 0;
6318
+ const sliced = line.slice(pos);
6319
+ const m = rule.pattern.exec(sliced);
6320
+ if (m) {
6321
+ let start;
6322
+ let end;
6323
+ if (rule.captureGroup > 0 && m[rule.captureGroup] !== void 0) {
6324
+ const groupText = m[rule.captureGroup];
6325
+ const groupOffset = m[0].indexOf(groupText);
6326
+ start = pos + m.index + groupOffset;
6327
+ end = start + groupText.length;
6328
+ } else {
6329
+ start = pos + m.index;
6330
+ end = pos + m.index + m[0].length;
6331
+ }
6332
+ if (pos + m.index + m[0].length > pos) {
6333
+ tokens.push(`${start},${end},${rule.tokenType}`);
6334
+ pos = pos + m.index + m[0].length;
6335
+ matched = true;
6336
+ break;
6337
+ }
6338
+ }
6339
+ }
6340
+ if (!matched) {
6341
+ pos++;
6342
+ }
6343
+ }
6344
+ return tokens.join("\n");
6345
+ }
6280
6346
  commonPrefix(strings) {
6281
6347
  if (strings.length === 0) return "";
6282
6348
  let prefix = strings[0];
@@ -6449,6 +6515,30 @@ function wasmCommonPrefix(strings) {
6449
6515
  wasm.__wbindgen_export3(d0, d1, 1);
6450
6516
  }
6451
6517
  }
6518
+ function wasmRegisterRuleSet(ruleSetId, rules) {
6519
+ const ptr0 = passStringToWasm0(ruleSetId, wasm.__wbindgen_export, wasm.__wbindgen_export2);
6520
+ const len0 = WASM_VECTOR_LEN;
6521
+ const ptr1 = passStringToWasm0(rules, wasm.__wbindgen_export, wasm.__wbindgen_export2);
6522
+ const len1 = WASM_VECTOR_LEN;
6523
+ wasm.register_rule_set(ptr0, len0, ptr1, len1);
6524
+ }
6525
+ function wasmTokenizeLine(line, ruleSetId) {
6526
+ let d0, d1;
6527
+ try {
6528
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
6529
+ const ptr0 = passStringToWasm0(line, wasm.__wbindgen_export, wasm.__wbindgen_export2);
6530
+ const len0 = WASM_VECTOR_LEN;
6531
+ const ptr1 = passStringToWasm0(ruleSetId, wasm.__wbindgen_export, wasm.__wbindgen_export2);
6532
+ const len1 = WASM_VECTOR_LEN;
6533
+ wasm.tokenize_line(retptr, ptr0, len0, ptr1, len1);
6534
+ d0 = getDataViewMemory0().getInt32(retptr + 0, true);
6535
+ d1 = getDataViewMemory0().getInt32(retptr + 4, true);
6536
+ return getStringFromWasm0(d0, d1);
6537
+ } finally {
6538
+ wasm.__wbindgen_add_to_stack_pointer(16);
6539
+ wasm.__wbindgen_export3(d0, d1, 1);
6540
+ }
6541
+ }
6452
6542
  async function instantiateWasm(wasmBytes) {
6453
6543
  const imports = { __proto__: null, "./qodalis_cli_wasm_bg.js": { __proto__: null } };
6454
6544
  const { instance } = await WebAssembly.instantiate(wasmBytes, imports);
@@ -6489,6 +6579,12 @@ function createWasmAccelerator() {
6489
6579
  },
6490
6580
  commonPrefix(strings) {
6491
6581
  return wasmCommonPrefix(strings.join("\n"));
6582
+ },
6583
+ registerRuleSet(ruleSetId, rules) {
6584
+ wasmRegisterRuleSet(ruleSetId, rules);
6585
+ },
6586
+ tokenizeLine(line, ruleSetId) {
6587
+ return wasmTokenizeLine(line, ruleSetId);
6492
6588
  }
6493
6589
  };
6494
6590
  }
@@ -7415,7 +7511,11 @@ var NanoEditorRenderer = class {
7415
7511
  output2 += "\x1B[2K";
7416
7512
  if (lineIdx < buffer.lines.length) {
7417
7513
  const line = buffer.lines[lineIdx];
7418
- output2 += line.length > cols ? line.slice(0, cols) : line;
7514
+ if (this.highlightEngine) {
7515
+ output2 += this.highlightEngine.renderLine(lineIdx, line, cols);
7516
+ } else {
7517
+ output2 += line.length > cols ? line.slice(0, cols) : line;
7518
+ }
7419
7519
  }
7420
7520
  }
7421
7521
  output2 += `\x1B[${rows - 1};1H`;
@@ -7494,6 +7594,135 @@ var NanoEditorRenderer = class {
7494
7594
  }
7495
7595
  };
7496
7596
 
7597
+ // src/lib/editor/syntax/utils.ts
7598
+ function truncateAnsi(str, maxCols) {
7599
+ let visible = 0;
7600
+ let i = 0;
7601
+ let result = "";
7602
+ let hasActiveColor = false;
7603
+ while (i < str.length && visible < maxCols) {
7604
+ if (str[i] === "\x1B" && i + 1 < str.length && str[i + 1] === "[") {
7605
+ let j = i + 2;
7606
+ while (j < str.length && !(str.charCodeAt(j) >= 64 && str.charCodeAt(j) <= 126)) {
7607
+ j++;
7608
+ }
7609
+ if (j < str.length) {
7610
+ const seq = str.slice(i, j + 1);
7611
+ result += seq;
7612
+ if (seq === "\x1B[0m") {
7613
+ hasActiveColor = false;
7614
+ } else {
7615
+ hasActiveColor = true;
7616
+ }
7617
+ i = j + 1;
7618
+ } else {
7619
+ break;
7620
+ }
7621
+ } else {
7622
+ result += str[i];
7623
+ visible++;
7624
+ i++;
7625
+ }
7626
+ }
7627
+ if (hasActiveColor) {
7628
+ result += "\x1B[0m";
7629
+ }
7630
+ return result;
7631
+ }
7632
+ function parseTokens(raw) {
7633
+ if (!raw) return [];
7634
+ return raw.split("\n").map((entry) => {
7635
+ const [s, e, ...rest] = entry.split(",");
7636
+ return { start: parseInt(s, 10), end: parseInt(e, 10), tokenType: rest.join(",") };
7637
+ });
7638
+ }
7639
+
7640
+ // src/lib/editor/syntax/engine.ts
7641
+ var SyntaxHighlightEngine = class {
7642
+ constructor(highlighter, theme, accelerator2) {
7643
+ this.highlighter = highlighter;
7644
+ this.theme = theme;
7645
+ this.accelerator = accelerator2;
7646
+ this.cache = /* @__PURE__ */ new Map();
7647
+ this.registeredRuleSets = /* @__PURE__ */ new Set();
7648
+ }
7649
+ invalidate(fromLine) {
7650
+ for (const key of this.cache.keys()) {
7651
+ if (key >= fromLine) {
7652
+ this.cache.delete(key);
7653
+ }
7654
+ }
7655
+ }
7656
+ renderLine(lineIdx, line, cols) {
7657
+ const entryState = lineIdx === 0 ? this.highlighter.initialState : this.cache.get(lineIdx - 1)?.exitState ?? this.highlighter.initialState;
7658
+ const cached = this.cache.get(lineIdx);
7659
+ if (cached && cached.lineContent === line && cached.entryState === entryState) {
7660
+ return truncateAnsi(cached.rendered, cols);
7661
+ }
7662
+ try {
7663
+ const ruleSetId = `${this.highlighter.id}:${entryState}`;
7664
+ if (!this.registeredRuleSets.has(ruleSetId)) {
7665
+ const rules = this.highlighter.getRules(entryState);
7666
+ const serialized = rules.map(
7667
+ (r) => `${r.pattern.source} ${r.tokenType} ${r.captureGroup ?? 0}`
7668
+ ).join("\n");
7669
+ this.accelerator.registerRuleSet(ruleSetId, serialized);
7670
+ this.registeredRuleSets.add(ruleSetId);
7671
+ }
7672
+ const raw = this.accelerator.tokenizeLine(line, ruleSetId);
7673
+ const tokens = parseTokens(raw);
7674
+ const exitState = this.highlighter.getNextLineState(line, entryState);
7675
+ const rendered = this.applyTheme(line, tokens);
7676
+ this.cache.set(lineIdx, { lineContent: line, entryState, exitState, rendered });
7677
+ return truncateAnsi(rendered, cols);
7678
+ } catch {
7679
+ return line.length > cols ? line.slice(0, cols) : line;
7680
+ }
7681
+ }
7682
+ applyTheme(line, tokens) {
7683
+ if (tokens.length === 0) return line;
7684
+ let result = "";
7685
+ let pos = 0;
7686
+ for (const token of tokens) {
7687
+ if (token.start > pos) {
7688
+ result += line.slice(pos, token.start);
7689
+ }
7690
+ const color = this.theme.colors[token.tokenType];
7691
+ const text = line.slice(token.start, token.end);
7692
+ if (color) {
7693
+ result += color + text + "\x1B[0m";
7694
+ } else {
7695
+ result += text;
7696
+ }
7697
+ pos = token.end;
7698
+ }
7699
+ if (pos < line.length) {
7700
+ result += line.slice(pos);
7701
+ }
7702
+ return result;
7703
+ }
7704
+ };
7705
+
7706
+ // src/lib/editor/syntax/theme.ts
7707
+ var defaultSyntaxTheme = {
7708
+ name: "default",
7709
+ colors: {
7710
+ keyword: "\x1B[93m",
7711
+ string: "\x1B[32m",
7712
+ number: "\x1B[36m",
7713
+ comment: "\x1B[90m",
7714
+ tag: "\x1B[91m",
7715
+ attribute: "\x1B[33m",
7716
+ value: "\x1B[36m",
7717
+ punctuation: "\x1B[97m",
7718
+ heading: "\x1B[1;93m",
7719
+ link: "\x1B[4;36m",
7720
+ bold: "\x1B[1m",
7721
+ italic: "\x1B[3m",
7722
+ code: "\x1B[90;47m"
7723
+ }
7724
+ };
7725
+
7497
7726
  // src/lib/processors/system/cli-nano-command-processor.ts
7498
7727
  var FS_TOKEN = "cli-file-system-service";
7499
7728
  var CliNanoCommandProcessor = class {
@@ -7561,6 +7790,7 @@ var CliNanoCommandProcessor = class {
7561
7790
  } else {
7562
7791
  this.filePath = null;
7563
7792
  }
7793
+ this.setupHighlighting(this.filePath);
7564
7794
  context2.enterFullScreenMode(this);
7565
7795
  this.renderer.render(this.buffer, this.filePath || "New Buffer");
7566
7796
  this.resizeDisposable = context2.terminal.onResize(() => {
@@ -7689,11 +7919,13 @@ var CliNanoCommandProcessor = class {
7689
7919
  }
7690
7920
  if (data === "\v") {
7691
7921
  this.buffer.deleteLine();
7922
+ this.renderer.highlightEngine?.invalidate(this.buffer.cursorRow);
7692
7923
  this.render();
7693
7924
  return;
7694
7925
  }
7695
7926
  if (data === "") {
7696
7927
  if (this.buffer.uncutLines()) {
7928
+ this.renderer.highlightEngine?.invalidate(this.buffer.cursorRow);
7697
7929
  this.render();
7698
7930
  } else {
7699
7931
  this.showStatus("Clipboard is empty");
@@ -7754,11 +7986,16 @@ var CliNanoCommandProcessor = class {
7754
7986
  }
7755
7987
  if (data === "\r") {
7756
7988
  this.buffer.insertNewline();
7989
+ this.renderer.highlightEngine?.invalidate(this.buffer.cursorRow - 1);
7757
7990
  this.render();
7758
7991
  return;
7759
7992
  }
7760
7993
  if (data === "\x7F") {
7994
+ const wasMerge = this.buffer.cursorCol === 0 && this.buffer.cursorRow > 0;
7761
7995
  this.buffer.deleteCharBefore();
7996
+ if (wasMerge) {
7997
+ this.renderer.highlightEngine?.invalidate(this.buffer.cursorRow);
7998
+ }
7762
7999
  this.render();
7763
8000
  return;
7764
8001
  }
@@ -7851,6 +8088,31 @@ var CliNanoCommandProcessor = class {
7851
8088
  ` ${writer.wrapInColor("^Y", cliCore.CliForegroundColor.Yellow)} Page Up ${writer.wrapInColor("^V", cliCore.CliForegroundColor.Yellow)} Page Down`
7852
8089
  );
7853
8090
  }
8091
+ // ── Syntax highlighting ──
8092
+ setupHighlighting(filePath) {
8093
+ if (!filePath) {
8094
+ this.renderer.highlightEngine = void 0;
8095
+ return;
8096
+ }
8097
+ try {
8098
+ const registry = this.context.services.get(
8099
+ "syntax-highlighter-registry"
8100
+ );
8101
+ const ext = "." + filePath.split(".").pop()?.toLowerCase();
8102
+ const highlighter = registry.getByExtension(ext);
8103
+ if (highlighter) {
8104
+ this.renderer.highlightEngine = new SyntaxHighlightEngine(
8105
+ highlighter,
8106
+ defaultSyntaxTheme,
8107
+ getAccelerator()
8108
+ );
8109
+ } else {
8110
+ this.renderer.highlightEngine = void 0;
8111
+ }
8112
+ } catch {
8113
+ this.renderer.highlightEngine = void 0;
8114
+ }
8115
+ }
7854
8116
  // ── Rendering helpers ──
7855
8117
  render() {
7856
8118
  this.renderer.render(
@@ -8388,17 +8650,23 @@ var CliProcessRegistry = class {
8388
8650
  get currentPid() {
8389
8651
  return this._currentPid;
8390
8652
  }
8391
- register(command2) {
8653
+ register(command2, options) {
8392
8654
  const pid = this.nextPid++;
8393
8655
  const abortController = new AbortController();
8656
+ const type = options?.type ?? "command";
8394
8657
  this.processes.set(pid, {
8395
8658
  pid,
8659
+ name: options?.name ?? command2,
8660
+ type,
8396
8661
  command: command2,
8397
8662
  startTime: Date.now(),
8398
8663
  status: "running",
8399
- abortController
8664
+ abortController,
8665
+ onKill: options?.onKill
8400
8666
  });
8401
- this._currentPid = pid;
8667
+ if (type === "command") {
8668
+ this._currentPid = pid;
8669
+ }
8402
8670
  this.pruneHistory();
8403
8671
  return { pid, abortController };
8404
8672
  }
@@ -8425,6 +8693,10 @@ var CliProcessRegistry = class {
8425
8693
  kill(pid) {
8426
8694
  const entry = this.processes.get(pid);
8427
8695
  if (!entry || entry.status !== "running") return false;
8696
+ if (entry.onKill) {
8697
+ Promise.resolve(entry.onKill()).catch(() => {
8698
+ });
8699
+ }
8428
8700
  entry.abortController.abort();
8429
8701
  entry.status = "killed";
8430
8702
  entry.exitCode = -9;
@@ -8434,7 +8706,9 @@ var CliProcessRegistry = class {
8434
8706
  return true;
8435
8707
  }
8436
8708
  list() {
8437
- return Array.from(this.processes.values()).map(({ abortController, ...entry }) => entry);
8709
+ return Array.from(this.processes.values()).map(
8710
+ ({ abortController, onKill, ...entry }) => entry
8711
+ );
8438
8712
  }
8439
8713
  pruneHistory() {
8440
8714
  const entries = Array.from(this.processes.entries());
@@ -8471,11 +8745,13 @@ var CliPsCommandProcessor = class {
8471
8745
  context2.writer.writeInfo("No processes");
8472
8746
  return;
8473
8747
  }
8474
- const headers = ["PID", "STATUS", "EXIT", "TIME", "COMMAND"];
8748
+ const headers = ["PID", "NAME", "TYPE", "STATUS", "EXIT", "TIME", "COMMAND"];
8475
8749
  const rows = processes.map((p) => {
8476
8750
  const elapsed = `${Math.round((Date.now() - p.startTime) / 1e3)}s`;
8477
8751
  return [
8478
8752
  String(p.pid),
8753
+ p.name,
8754
+ p.type,
8479
8755
  p.status,
8480
8756
  p.exitCode !== void 0 ? String(p.exitCode) : "-",
8481
8757
  elapsed,
@@ -8520,6 +8796,553 @@ var CliKillCommandProcessor = class {
8520
8796
  }
8521
8797
  }
8522
8798
  };
8799
+ var ESC = "\x1B";
8800
+ var CSI = `${ESC}[`;
8801
+ var ansi = {
8802
+ clearScreen: `${CSI}2J`,
8803
+ cursorHome: `${CSI}H`,
8804
+ hideCursor: `${CSI}?25l`,
8805
+ cursorTo: (row, col) => `${CSI}${row};${col}H`,
8806
+ fg: {
8807
+ green: `${CSI}32m`,
8808
+ cyan: `${CSI}36m`,
8809
+ yellow: `${CSI}93m`,
8810
+ white: `${CSI}97m`,
8811
+ red: `${CSI}91m`,
8812
+ gray: `${CSI}90m`
8813
+ },
8814
+ bg: {
8815
+ blue: `${CSI}44m`
8816
+ },
8817
+ bold: `${CSI}1m`,
8818
+ dim: `${CSI}2m`,
8819
+ inverse: `${CSI}7m`,
8820
+ reset: `${CSI}0m`
8821
+ };
8822
+ var BOX = {
8823
+ topLeft: "\u250C",
8824
+ topRight: "\u2510",
8825
+ bottomLeft: "\u2514",
8826
+ bottomRight: "\u2518",
8827
+ horizontal: "\u2500",
8828
+ vertical: "\u2502"
8829
+ };
8830
+ function formatElapsed(ms) {
8831
+ const seconds = Math.floor(ms / 1e3) % 60;
8832
+ const minutes = Math.floor(ms / (1e3 * 60)) % 60;
8833
+ const hours = Math.floor(ms / (1e3 * 60 * 60)) % 24;
8834
+ const days = Math.floor(ms / (1e3 * 60 * 60 * 24));
8835
+ const parts = [];
8836
+ if (days > 0) parts.push(`${days}d`);
8837
+ if (hours > 0) parts.push(`${hours}h`);
8838
+ if (minutes > 0) parts.push(`${minutes}m`);
8839
+ parts.push(`${seconds}s`);
8840
+ return parts.join(" ");
8841
+ }
8842
+ function statusColor(status) {
8843
+ switch (status) {
8844
+ case "running":
8845
+ return ansi.fg.green;
8846
+ case "completed":
8847
+ case "done":
8848
+ return ansi.fg.cyan;
8849
+ case "pending":
8850
+ return ansi.fg.yellow;
8851
+ case "stopped":
8852
+ return ansi.fg.white;
8853
+ case "failed":
8854
+ case "killed":
8855
+ return ansi.fg.red;
8856
+ default:
8857
+ return ansi.fg.white;
8858
+ }
8859
+ }
8860
+ function truncate(str, maxLen) {
8861
+ if (str.length <= maxLen) return str;
8862
+ return str.substring(0, maxLen - 1) + "\u2026";
8863
+ }
8864
+ function pad(str, len) {
8865
+ return str.length >= len ? str.substring(0, len) : str + " ".repeat(len - str.length);
8866
+ }
8867
+ var CliHtopCommandProcessor = class {
8868
+ constructor() {
8869
+ this.command = "htop";
8870
+ this.description = "Interactive process and service monitor";
8871
+ this.author = cliCore.DefaultLibraryAuthor;
8872
+ this.metadata = {
8873
+ icon: "\u{1F4CA}",
8874
+ sealed: true,
8875
+ module: "system"
8876
+ };
8877
+ this.context = null;
8878
+ this.registry = null;
8879
+ this.refreshTimer = null;
8880
+ this.rows = [];
8881
+ this.selectedIndex = 0;
8882
+ this.scrollOffset = 0;
8883
+ this.termCols = 80;
8884
+ this.termRows = 24;
8885
+ this.showingLogs = false;
8886
+ this.errorMessage = null;
8887
+ this.errorTimeout = null;
8888
+ }
8889
+ async processCommand(_command, context2) {
8890
+ try {
8891
+ this.registry = context2.services.get(
8892
+ CliProcessRegistry_TOKEN
8893
+ );
8894
+ } catch {
8895
+ context2.writer.writeError("Process registry not available");
8896
+ return;
8897
+ }
8898
+ this.context = context2;
8899
+ this.termCols = context2.terminal.cols || 80;
8900
+ this.termRows = context2.terminal.rows || 24;
8901
+ this.selectedIndex = 0;
8902
+ this.scrollOffset = 0;
8903
+ this.showingLogs = false;
8904
+ this.errorMessage = null;
8905
+ context2.enterFullScreenMode(this);
8906
+ this.refreshData();
8907
+ this.render();
8908
+ this.refreshTimer = context2.createInterval(() => {
8909
+ if (!this.showingLogs) {
8910
+ this.refreshData();
8911
+ this.render();
8912
+ }
8913
+ }, 1e3);
8914
+ }
8915
+ async onData(data, context2) {
8916
+ if (this.showingLogs) {
8917
+ this.showingLogs = false;
8918
+ this.render();
8919
+ return;
8920
+ }
8921
+ switch (data) {
8922
+ case `${ESC}[A`:
8923
+ this.moveSelection(-1);
8924
+ this.render();
8925
+ break;
8926
+ case `${ESC}[B`:
8927
+ this.moveSelection(1);
8928
+ this.render();
8929
+ break;
8930
+ case "q":
8931
+ case ESC:
8932
+ this.exit(context2);
8933
+ break;
8934
+ case "k":
8935
+ this.actionKill();
8936
+ break;
8937
+ case "s":
8938
+ await this.actionStart();
8939
+ break;
8940
+ case "t":
8941
+ await this.actionStop();
8942
+ break;
8943
+ case "r":
8944
+ await this.actionRestart();
8945
+ break;
8946
+ case "l":
8947
+ this.actionLogs();
8948
+ break;
8949
+ }
8950
+ }
8951
+ onResize(cols, rows, _context) {
8952
+ this.termCols = cols;
8953
+ this.termRows = rows;
8954
+ this.clampScroll();
8955
+ this.render();
8956
+ }
8957
+ onDispose(_context) {
8958
+ this.refreshTimer = null;
8959
+ this.context = null;
8960
+ this.registry = null;
8961
+ this.rows = [];
8962
+ }
8963
+ writeDescription(context2) {
8964
+ const { writer } = context2;
8965
+ writer.writeln("Interactive process and service monitor");
8966
+ writer.writeln();
8967
+ writer.writeln("Usage:");
8968
+ writer.writeln(` ${writer.wrapInColor("htop", 36)}`);
8969
+ }
8970
+ // ── Data ─────────────────────────────────────────────────────────
8971
+ refreshData() {
8972
+ if (!this.registry || !this.context) return;
8973
+ const processEntries = this.registry.list();
8974
+ const serviceEntries = this.context.backgroundServices.list();
8975
+ const servicePids = /* @__PURE__ */ new Set();
8976
+ for (const svc of serviceEntries) {
8977
+ if (svc.pid !== void 0) {
8978
+ servicePids.add(svc.pid);
8979
+ }
8980
+ }
8981
+ const unified = [];
8982
+ for (const p of processEntries) {
8983
+ if (servicePids.has(p.pid)) continue;
8984
+ unified.push({
8985
+ pid: p.pid,
8986
+ name: p.name,
8987
+ type: p.type,
8988
+ status: p.status,
8989
+ time: formatElapsed(Date.now() - p.startTime),
8990
+ mode: "main",
8991
+ source: "process"
8992
+ });
8993
+ }
8994
+ for (const svc of serviceEntries) {
8995
+ let time = "-";
8996
+ if (svc.status === "running" && svc.startedAt) {
8997
+ time = formatElapsed(Date.now() - new Date(svc.startedAt).getTime());
8998
+ }
8999
+ unified.push({
9000
+ pid: svc.pid ?? 0,
9001
+ name: svc.name,
9002
+ type: svc.type,
9003
+ status: svc.status,
9004
+ time,
9005
+ mode: svc.executionMode === "worker" ? "worker" : "main",
9006
+ source: "service",
9007
+ serviceInfo: svc
9008
+ });
9009
+ }
9010
+ unified.sort((a, b) => {
9011
+ const aRunning = a.status === "running" ? 0 : 1;
9012
+ const bRunning = b.status === "running" ? 0 : 1;
9013
+ if (aRunning !== bRunning) return aRunning - bRunning;
9014
+ return b.pid - a.pid;
9015
+ });
9016
+ this.rows = unified;
9017
+ if (this.rows.length === 0) {
9018
+ this.selectedIndex = 0;
9019
+ } else {
9020
+ this.selectedIndex = Math.min(this.selectedIndex, this.rows.length - 1);
9021
+ }
9022
+ this.clampScroll();
9023
+ }
9024
+ // ── Actions ──────────────────────────────────────────────────────
9025
+ actionKill() {
9026
+ const row = this.rows[this.selectedIndex];
9027
+ if (!row || row.status !== "running") return;
9028
+ if (row.name === "htop" && row.source === "process") {
9029
+ if (this.context) this.exit(this.context);
9030
+ return;
9031
+ }
9032
+ try {
9033
+ if (this.registry) {
9034
+ this.registry.kill(row.pid);
9035
+ }
9036
+ } catch (e) {
9037
+ this.showError(e?.message || "Kill failed");
9038
+ }
9039
+ this.refreshData();
9040
+ this.render();
9041
+ }
9042
+ async actionStart() {
9043
+ const row = this.rows[this.selectedIndex];
9044
+ if (!row || row.source !== "service") return;
9045
+ if (row.status !== "stopped" && row.status !== "pending" && row.status !== "failed") return;
9046
+ try {
9047
+ await this.context.backgroundServices.start(row.name);
9048
+ } catch (e) {
9049
+ this.showError(e?.message || "Start failed");
9050
+ }
9051
+ this.refreshData();
9052
+ this.render();
9053
+ }
9054
+ async actionStop() {
9055
+ const row = this.rows[this.selectedIndex];
9056
+ if (!row || row.source !== "service" || row.status !== "running") return;
9057
+ try {
9058
+ await this.context.backgroundServices.stop(row.name);
9059
+ } catch (e) {
9060
+ this.showError(e?.message || "Stop failed");
9061
+ }
9062
+ this.refreshData();
9063
+ this.render();
9064
+ }
9065
+ async actionRestart() {
9066
+ const row = this.rows[this.selectedIndex];
9067
+ if (!row || row.source !== "service") return;
9068
+ try {
9069
+ await this.context.backgroundServices.restart(row.name);
9070
+ } catch (e) {
9071
+ this.showError(e?.message || "Restart failed");
9072
+ }
9073
+ this.refreshData();
9074
+ this.render();
9075
+ }
9076
+ actionLogs() {
9077
+ const row = this.rows[this.selectedIndex];
9078
+ if (!row || row.source !== "service" || !this.context) return;
9079
+ this.showingLogs = true;
9080
+ this.renderLogsOverlay(row.name);
9081
+ }
9082
+ // ── Navigation ───────────────────────────────────────────────────
9083
+ moveSelection(delta) {
9084
+ if (this.rows.length === 0) return;
9085
+ this.selectedIndex += delta;
9086
+ if (this.selectedIndex < 0) {
9087
+ this.selectedIndex = this.rows.length - 1;
9088
+ } else if (this.selectedIndex >= this.rows.length) {
9089
+ this.selectedIndex = 0;
9090
+ }
9091
+ this.clampScroll();
9092
+ }
9093
+ get visibleRows() {
9094
+ return Math.max(1, this.termRows - 4);
9095
+ }
9096
+ clampScroll() {
9097
+ if (this.selectedIndex < this.scrollOffset) {
9098
+ this.scrollOffset = this.selectedIndex;
9099
+ } else if (this.selectedIndex >= this.scrollOffset + this.visibleRows) {
9100
+ this.scrollOffset = this.selectedIndex - this.visibleRows + 1;
9101
+ }
9102
+ this.scrollOffset = Math.max(0, this.scrollOffset);
9103
+ }
9104
+ // ── Rendering ────────────────────────────────────────────────────
9105
+ render() {
9106
+ if (!this.context) return;
9107
+ const buf = [];
9108
+ const cols = this.termCols;
9109
+ buf.push(ansi.clearScreen, ansi.cursorHome, ansi.hideCursor);
9110
+ const runningCount = this.rows.filter((r) => r.status === "running").length;
9111
+ const totalCount = this.rows.length;
9112
+ let headerLeft;
9113
+ if (this.errorMessage) {
9114
+ headerLeft = `${ansi.bold}${ansi.fg.red} htop ${ansi.reset}${ansi.fg.red}${ansi.dim} ${this.errorMessage}${ansi.reset}`;
9115
+ } else {
9116
+ headerLeft = `${ansi.bold}${ansi.fg.cyan} htop${ansi.reset}${ansi.fg.gray} \u2014 ${runningCount} running / ${totalCount} total${ansi.reset}`;
9117
+ }
9118
+ const headerRight = `${ansi.fg.gray}Refresh: 1s${ansi.reset}`;
9119
+ buf.push(ansi.cursorTo(1, 1));
9120
+ buf.push(headerLeft);
9121
+ buf.push(ansi.cursorTo(1, Math.max(1, cols - 11)));
9122
+ buf.push(headerRight);
9123
+ buf.push(ansi.cursorTo(2, 1));
9124
+ buf.push(ansi.fg.gray, BOX.horizontal.repeat(cols), ansi.reset);
9125
+ const colPid = 6;
9126
+ const colType = 9;
9127
+ const colStatus = 11;
9128
+ const colMode = 8;
9129
+ const colTime = 9;
9130
+ const colName = Math.max(10, cols - colPid - colType - colStatus - colMode - colTime - 2);
9131
+ buf.push(ansi.cursorTo(3, 1));
9132
+ buf.push(
9133
+ ansi.bold,
9134
+ ansi.fg.white,
9135
+ " ",
9136
+ pad("PID", colPid),
9137
+ pad("NAME", colName),
9138
+ pad("TYPE", colType),
9139
+ pad("STATUS", colStatus),
9140
+ pad("MODE", colMode),
9141
+ pad("TIME", colTime),
9142
+ ansi.reset
9143
+ );
9144
+ if (this.rows.length === 0) {
9145
+ const emptyMsg = "No processes";
9146
+ const emptyRow = Math.floor(this.visibleRows / 2) + 4;
9147
+ const emptyCol = Math.max(1, Math.floor((cols - emptyMsg.length) / 2));
9148
+ buf.push(ansi.cursorTo(emptyRow, emptyCol));
9149
+ buf.push(ansi.fg.gray, emptyMsg, ansi.reset);
9150
+ } else {
9151
+ const endIdx = Math.min(
9152
+ this.scrollOffset + this.visibleRows,
9153
+ this.rows.length
9154
+ );
9155
+ for (let i = this.scrollOffset; i < endIdx; i++) {
9156
+ const row = this.rows[i];
9157
+ const screenRow = 4 + (i - this.scrollOffset);
9158
+ const isSelected = i === this.selectedIndex;
9159
+ const prefix = isSelected ? ">" : " ";
9160
+ const pidStr = pad(String(row.pid), colPid);
9161
+ const nameStr = pad(truncate(row.name, colName - 1), colName);
9162
+ const typeStr = pad(row.type, colType);
9163
+ const statusStr = pad(row.status, colStatus);
9164
+ const modeStr = pad(row.mode, colMode);
9165
+ const timeStr = pad(row.time, colTime);
9166
+ buf.push(ansi.cursorTo(screenRow, 1));
9167
+ if (isSelected) {
9168
+ buf.push(ansi.inverse);
9169
+ }
9170
+ buf.push(
9171
+ prefix,
9172
+ pidStr,
9173
+ nameStr,
9174
+ typeStr,
9175
+ isSelected ? "" : statusColor(row.status),
9176
+ statusStr,
9177
+ isSelected ? "" : ansi.reset,
9178
+ modeStr,
9179
+ timeStr
9180
+ );
9181
+ const contentLen = 1 + colPid + colName + colType + colStatus + colMode + colTime;
9182
+ if (contentLen < cols) {
9183
+ buf.push(" ".repeat(cols - contentLen));
9184
+ }
9185
+ if (isSelected) {
9186
+ buf.push(ansi.reset);
9187
+ }
9188
+ }
9189
+ if (this.scrollOffset > 0) {
9190
+ buf.push(ansi.cursorTo(4, cols));
9191
+ buf.push(ansi.fg.gray, "\u2191", ansi.reset);
9192
+ }
9193
+ if (endIdx < this.rows.length) {
9194
+ buf.push(ansi.cursorTo(3 + this.visibleRows, cols));
9195
+ buf.push(ansi.fg.gray, "\u2193", ansi.reset);
9196
+ }
9197
+ }
9198
+ const footerSepRow = this.termRows - 1;
9199
+ buf.push(ansi.cursorTo(footerSepRow, 1));
9200
+ buf.push(ansi.fg.gray, BOX.horizontal.repeat(cols), ansi.reset);
9201
+ buf.push(ansi.cursorTo(this.termRows, 1));
9202
+ buf.push(ansi.dim, " ", this.buildFooter(), ansi.reset);
9203
+ this.context.terminal.write(buf.join(""));
9204
+ }
9205
+ buildFooter() {
9206
+ const selected = this.rows[this.selectedIndex];
9207
+ const parts = [];
9208
+ if (selected) {
9209
+ if (selected.status === "running") {
9210
+ parts.push("k:Kill");
9211
+ }
9212
+ if (selected.source === "service" && (selected.status === "stopped" || selected.status === "pending" || selected.status === "failed")) {
9213
+ parts.push("s:Start");
9214
+ }
9215
+ if (selected.source === "service" && selected.status === "running") {
9216
+ parts.push("t:Stop");
9217
+ }
9218
+ if (selected.source === "service") {
9219
+ parts.push("r:Restart");
9220
+ parts.push("l:Logs");
9221
+ }
9222
+ }
9223
+ parts.push("q:Quit");
9224
+ parts.push("\u2191\u2193:Navigate");
9225
+ return parts.join(" ");
9226
+ }
9227
+ // ── Logs overlay ─────────────────────────────────────────────
9228
+ renderLogsOverlay(serviceName) {
9229
+ if (!this.context) return;
9230
+ const logs = this.context.backgroundServices.getLogs(serviceName, 15);
9231
+ const buf = [];
9232
+ const boxWidth = Math.min(this.termCols - 4, 70);
9233
+ const boxHeight = Math.min(this.termRows - 4, logs.length + 4);
9234
+ const startCol = Math.max(1, Math.floor((this.termCols - boxWidth) / 2));
9235
+ const startRow = Math.max(1, Math.floor((this.termRows - boxHeight) / 2));
9236
+ const innerWidth = boxWidth - 2;
9237
+ const title = ` Logs: ${truncate(serviceName, innerWidth - 10)} `;
9238
+ const topBorder = BOX.topLeft + BOX.horizontal + title + BOX.horizontal.repeat(Math.max(0, boxWidth - title.length - 3)) + BOX.topRight;
9239
+ buf.push(ansi.cursorTo(startRow, startCol));
9240
+ buf.push(ansi.fg.cyan, topBorder, ansi.reset);
9241
+ const contentRows = boxHeight - 3;
9242
+ if (logs.length === 0) {
9243
+ const noLogs = "No log entries";
9244
+ buf.push(ansi.cursorTo(startRow + 1, startCol));
9245
+ buf.push(
9246
+ ansi.fg.cyan,
9247
+ BOX.vertical,
9248
+ ansi.reset,
9249
+ " ",
9250
+ ansi.fg.gray,
9251
+ pad(noLogs, innerWidth - 1),
9252
+ ansi.reset,
9253
+ ansi.fg.cyan,
9254
+ BOX.vertical,
9255
+ ansi.reset
9256
+ );
9257
+ for (let r = 2; r <= contentRows; r++) {
9258
+ buf.push(ansi.cursorTo(startRow + r, startCol));
9259
+ buf.push(
9260
+ ansi.fg.cyan,
9261
+ BOX.vertical,
9262
+ ansi.reset,
9263
+ " ".repeat(innerWidth),
9264
+ ansi.fg.cyan,
9265
+ BOX.vertical,
9266
+ ansi.reset
9267
+ );
9268
+ }
9269
+ } else {
9270
+ const displayLogs = logs.slice(-contentRows);
9271
+ let lineIdx = 0;
9272
+ for (const entry of displayLogs) {
9273
+ const ts = new Date(entry.timestamp).toLocaleTimeString();
9274
+ const levelTag = entry.level === "error" ? `${ansi.fg.red}ERR${ansi.reset}` : entry.level === "warn" ? `${ansi.fg.yellow}WRN${ansi.reset}` : `${ansi.fg.cyan}INF${ansi.reset}`;
9275
+ const line = `[${ts}] ${levelTag} ${truncate(entry.message, innerWidth - 18)}`;
9276
+ buf.push(ansi.cursorTo(startRow + 1 + lineIdx, startCol));
9277
+ buf.push(
9278
+ ansi.fg.cyan,
9279
+ BOX.vertical,
9280
+ ansi.reset,
9281
+ " ",
9282
+ line
9283
+ );
9284
+ buf.push(ansi.cursorTo(startRow + 1 + lineIdx, startCol + boxWidth - 1));
9285
+ buf.push(ansi.fg.cyan, BOX.vertical, ansi.reset);
9286
+ lineIdx++;
9287
+ }
9288
+ for (let r = lineIdx; r < contentRows; r++) {
9289
+ buf.push(ansi.cursorTo(startRow + 1 + r, startCol));
9290
+ buf.push(
9291
+ ansi.fg.cyan,
9292
+ BOX.vertical,
9293
+ ansi.reset,
9294
+ " ".repeat(innerWidth),
9295
+ ansi.fg.cyan,
9296
+ BOX.vertical,
9297
+ ansi.reset
9298
+ );
9299
+ }
9300
+ }
9301
+ const pressMsg = "Press any key to close";
9302
+ const pressPad = Math.max(0, Math.floor((innerWidth - pressMsg.length) / 2));
9303
+ buf.push(ansi.cursorTo(startRow + contentRows + 1, startCol));
9304
+ buf.push(
9305
+ ansi.fg.cyan,
9306
+ BOX.vertical,
9307
+ ansi.reset,
9308
+ " ".repeat(pressPad),
9309
+ ansi.fg.gray,
9310
+ pressMsg,
9311
+ ansi.reset,
9312
+ " ".repeat(Math.max(0, innerWidth - pressPad - pressMsg.length)),
9313
+ ansi.fg.cyan,
9314
+ BOX.vertical,
9315
+ ansi.reset
9316
+ );
9317
+ buf.push(ansi.cursorTo(startRow + contentRows + 2, startCol));
9318
+ buf.push(
9319
+ ansi.fg.cyan,
9320
+ BOX.bottomLeft,
9321
+ BOX.horizontal.repeat(boxWidth - 2),
9322
+ BOX.bottomRight,
9323
+ ansi.reset
9324
+ );
9325
+ this.context.terminal.write(buf.join(""));
9326
+ }
9327
+ // ── Utilities ────────────────────────────────────────────────
9328
+ showError(msg) {
9329
+ this.errorMessage = msg;
9330
+ if (this.errorTimeout) {
9331
+ this.errorTimeout.clear();
9332
+ }
9333
+ if (this.context) {
9334
+ this.errorTimeout = this.context.createInterval(() => {
9335
+ this.errorMessage = null;
9336
+ this.errorTimeout?.clear();
9337
+ this.errorTimeout = null;
9338
+ }, 3e3);
9339
+ }
9340
+ }
9341
+ exit(context2) {
9342
+ context2.exitFullScreenMode();
9343
+ this.context = null;
9344
+ }
9345
+ };
8523
9346
 
8524
9347
  // src/lib/processors/system/index.ts
8525
9348
  var systemProcessors = [
@@ -8533,7 +9356,8 @@ var systemProcessors = [
8533
9356
  new CliNanoCommandProcessor(),
8534
9357
  new CliServicesCommandProcessor(),
8535
9358
  new CliPsCommandProcessor(),
8536
- new CliKillCommandProcessor()
9359
+ new CliKillCommandProcessor(),
9360
+ new CliHtopCommandProcessor()
8537
9361
  ];
8538
9362
  var themes = {
8539
9363
  ...cliCore.DefaultThemes
@@ -8729,10 +9553,12 @@ var CliThemeCommandProcessor = class {
8729
9553
  const selected = await context2.reader.readSelect(
8730
9554
  "Select a theme (live preview):",
8731
9555
  options,
8732
- (value) => {
8733
- if (themes[value]) {
8734
- context2.terminal.options.theme = themes[value];
8735
- this.applyStyles(context2);
9556
+ {
9557
+ onChange: (value) => {
9558
+ if (themes[value]) {
9559
+ context2.terminal.options.theme = themes[value];
9560
+ this.applyStyles(context2);
9561
+ }
8736
9562
  }
8737
9563
  }
8738
9564
  );
@@ -9917,6 +10743,10 @@ var CliBackgroundServiceRegistry = class {
9917
10743
  this.isFullScreen = false;
9918
10744
  this.notificationQueue = [];
9919
10745
  }
10746
+ /** Attach the process registry so background services get PIDs */
10747
+ setProcessRegistry(registry) {
10748
+ this.processRegistry = registry;
10749
+ }
9920
10750
  register(service) {
9921
10751
  if (this.entries.has(service.name)) {
9922
10752
  throw new Error(`Service "${service.name}" is already registered`);
@@ -9937,7 +10767,17 @@ var CliBackgroundServiceRegistry = class {
9937
10767
  if (entry.status === "running") {
9938
10768
  throw new Error(`Service "${name}" is already running`);
9939
10769
  }
10770
+ if (this.processRegistry) {
10771
+ const svcType = entry.service.type === "daemon" ? "daemon" : entry.service.type === "job" ? "job" : "service";
10772
+ const proc = this.processRegistry.register(name, {
10773
+ name,
10774
+ type: svcType,
10775
+ onKill: () => this.stop(name)
10776
+ });
10777
+ entry.pid = proc.pid;
10778
+ }
9940
10779
  const useWorker = entry.service.workerCompatible && entry.service.workerFactory && typeof Worker !== "undefined";
10780
+ const isDaemon = entry.service.type === "daemon";
9941
10781
  if (useWorker) {
9942
10782
  const workerRunner = new CliWorkerServiceRunner(
9943
10783
  entry.service,
@@ -9951,15 +10791,19 @@ var CliBackgroundServiceRegistry = class {
9951
10791
  entry.startedAt = /* @__PURE__ */ new Date();
9952
10792
  entry.stoppedAt = void 0;
9953
10793
  entry.error = void 0;
9954
- try {
9955
- await workerRunner.start();
9956
- this.emitLifecycleEvent("service-started", name, {
9957
- name,
9958
- mode: "worker"
9959
- });
9960
- } catch (e) {
9961
- this.handleStartError(entry, name, e);
10794
+ if (isDaemon) {
10795
+ workerRunner.start().catch((e) => this.handleStartError(entry, name, e));
10796
+ } else {
10797
+ try {
10798
+ await workerRunner.start();
10799
+ } catch (e) {
10800
+ this.handleStartError(entry, name, e);
10801
+ }
9962
10802
  }
10803
+ this.emitLifecycleEvent("service-started", name, {
10804
+ name,
10805
+ mode: "worker"
10806
+ });
9963
10807
  } else {
9964
10808
  const mainRunner = new CliMainThreadServiceRunner(
9965
10809
  entry.service,
@@ -9973,21 +10817,25 @@ var CliBackgroundServiceRegistry = class {
9973
10817
  entry.startedAt = /* @__PURE__ */ new Date();
9974
10818
  entry.stoppedAt = void 0;
9975
10819
  entry.error = void 0;
9976
- try {
9977
- await mainRunner.start();
9978
- this.emitLifecycleEvent("service-started", name, {
9979
- name,
9980
- mode: "main-thread"
9981
- });
9982
- } catch (e) {
9983
- this.handleStartError(entry, name, e);
10820
+ if (isDaemon) {
10821
+ mainRunner.start().catch((e) => this.handleStartError(entry, name, e));
10822
+ } else {
10823
+ try {
10824
+ await mainRunner.start();
10825
+ } catch (e) {
10826
+ this.handleStartError(entry, name, e);
10827
+ }
9984
10828
  }
10829
+ this.emitLifecycleEvent("service-started", name, {
10830
+ name,
10831
+ mode: "main-thread"
10832
+ });
9985
10833
  }
9986
10834
  }
9987
10835
  async stop(name) {
9988
10836
  const entry = this.getEntry(name);
9989
10837
  if (entry.status !== "running") {
9990
- throw new Error(`Service "${name}" is not running (status: ${entry.status})`);
10838
+ return;
9991
10839
  }
9992
10840
  if (entry.runner) {
9993
10841
  await entry.runner.stop();
@@ -9995,6 +10843,7 @@ var CliBackgroundServiceRegistry = class {
9995
10843
  entry.status = "stopped";
9996
10844
  entry.stoppedAt = /* @__PURE__ */ new Date();
9997
10845
  entry.runner = null;
10846
+ this.completeInProcessRegistry(entry, 0);
9998
10847
  this.emitLifecycleEvent("service-stopped", name, { name });
9999
10848
  }
10000
10849
  async restart(name) {
@@ -10023,6 +10872,7 @@ var CliBackgroundServiceRegistry = class {
10023
10872
  entry.status = "done";
10024
10873
  entry.stoppedAt = /* @__PURE__ */ new Date();
10025
10874
  entry.runner = null;
10875
+ this.completeInProcessRegistry(entry, 0);
10026
10876
  this.emitLifecycleEvent("service-completed", name, {
10027
10877
  name,
10028
10878
  duration
@@ -10036,6 +10886,7 @@ var CliBackgroundServiceRegistry = class {
10036
10886
  entry.error = error.message;
10037
10887
  entry.stoppedAt = /* @__PURE__ */ new Date();
10038
10888
  entry.runner = null;
10889
+ this.failInProcessRegistry(entry);
10039
10890
  this.emitLifecycleEvent("service-failed", name, {
10040
10891
  name,
10041
10892
  error: error.message
@@ -10110,6 +10961,7 @@ var CliBackgroundServiceRegistry = class {
10110
10961
  const executionMode = entry.runner ? entry.runner.mode : "main-thread";
10111
10962
  const uptime = entry.status === "running" && entry.startedAt ? Date.now() - entry.startedAt.getTime() : void 0;
10112
10963
  return {
10964
+ pid: entry.pid,
10113
10965
  name: entry.service.name,
10114
10966
  description: entry.service.description,
10115
10967
  type: entry.service.type,
@@ -10126,9 +10978,14 @@ var CliBackgroundServiceRegistry = class {
10126
10978
  if (!entry) return;
10127
10979
  const from = entry.status;
10128
10980
  entry.status = status;
10129
- if (status === "stopped" || status === "failed" || status === "done") {
10981
+ if (status === "stopped" || status === "done") {
10982
+ entry.stoppedAt = /* @__PURE__ */ new Date();
10983
+ entry.runner = null;
10984
+ this.completeInProcessRegistry(entry, 0);
10985
+ } else if (status === "failed") {
10130
10986
  entry.stoppedAt = /* @__PURE__ */ new Date();
10131
10987
  entry.runner = null;
10988
+ this.failInProcessRegistry(entry);
10132
10989
  }
10133
10990
  this.emitLifecycleEvent("status-change", name, { name, from, to: status });
10134
10991
  }
@@ -10139,6 +10996,7 @@ var CliBackgroundServiceRegistry = class {
10139
10996
  entry.stoppedAt = /* @__PURE__ */ new Date();
10140
10997
  entry.runner = null;
10141
10998
  entry.logBuffer.add(`Failed to start: ${error.message}`, "error");
10999
+ this.failInProcessRegistry(entry);
10142
11000
  if (entry.service.onError) {
10143
11001
  entry.service.onError(error, {});
10144
11002
  }
@@ -10191,6 +11049,16 @@ var CliBackgroundServiceRegistry = class {
10191
11049
  this.writer.writeln(`\u250C\u2500 [${event.source}] ${message}`);
10192
11050
  this.writer.writeln("\u2514" + "\u2500".repeat(40));
10193
11051
  }
11052
+ completeInProcessRegistry(entry, exitCode) {
11053
+ if (entry.pid !== void 0 && this.processRegistry) {
11054
+ this.processRegistry.complete(entry.pid, exitCode);
11055
+ }
11056
+ }
11057
+ failInProcessRegistry(entry) {
11058
+ if (entry.pid !== void 0 && this.processRegistry) {
11059
+ this.processRegistry.fail(entry.pid);
11060
+ }
11061
+ }
10194
11062
  };
10195
11063
  var ANSI_RE = /\x1b\[[0-9;]*m/g;
10196
11064
  function visibleLength(text) {
@@ -10287,8 +11155,8 @@ var CliTerminalWriter = class {
10287
11155
  }
10288
11156
  writeColumns(items, options) {
10289
11157
  const cols = options?.columns ?? 3;
10290
- const pad = options?.padding ?? 2;
10291
- const colWidth = Math.max(...items.map((s) => s.length)) + pad;
11158
+ const pad2 = options?.padding ?? 2;
11159
+ const colWidth = Math.max(...items.map((s) => s.length)) + pad2;
10292
11160
  for (let i = 0; i < items.length; i += cols) {
10293
11161
  const row = items.slice(i, i + cols);
10294
11162
  this.writeln(row.map((item) => item.padEnd(colWidth)).join(""));
@@ -10516,51 +11384,34 @@ var CliTerminalProgressBar = class {
10516
11384
  }
10517
11385
  updateProgressBar() {
10518
11386
  const displayProgress = Math.round(this.progress);
10519
- const totalBars = 50;
11387
+ const totalBars = 30;
10520
11388
  const filledBars = Math.round(
10521
11389
  displayProgress / this.total * totalBars
10522
11390
  );
10523
11391
  const emptyBars = totalBars - filledBars;
10524
11392
  const RESET4 = "\x1B[0m";
11393
+ const CYAN = "\x1B[36m";
11394
+ const BRIGHT_CYAN = "\x1B[1;36m";
10525
11395
  const DIM4 = "\x1B[2m";
11396
+ const shimmerPos = this.tickCount % (filledBars + 4);
10526
11397
  let filledStr = "";
10527
- const shimmerPos = this.tickCount % (filledBars + 6);
10528
11398
  for (let i = 0; i < filledBars; i++) {
10529
- const ratio = filledBars > 1 ? i / (filledBars - 1) : 1;
10530
- const color = this.progressGradientColor(ratio);
10531
- const isShimmer = i >= shimmerPos - 2 && i <= shimmerPos;
10532
- const bright = isShimmer ? "\x1B[1m" : "";
10533
- filledStr += `${color}${bright}#${RESET4}`;
10534
- }
10535
- const emptyStr = `${DIM4}${".".repeat(emptyBars)}${RESET4}`;
10536
- const progressBar = `[${filledStr}${emptyStr}]`;
10537
- const percentage = `${displayProgress}%`.padStart(4, " ");
10538
- const text = this.text.length > 0 ? ` ${this.text}` : "";
11399
+ const isShimmer = i >= shimmerPos - 1 && i <= shimmerPos;
11400
+ filledStr += `${isShimmer ? BRIGHT_CYAN : CYAN}\u2501${RESET4}`;
11401
+ }
11402
+ const emptyStr = `${DIM4}${"\u2500".repeat(emptyBars)}${RESET4}`;
11403
+ const progressBar = `${CYAN}[${RESET4}${filledStr}${emptyStr}${CYAN}]${RESET4}`;
11404
+ const percentage = `${CYAN}${displayProgress}%${RESET4}`.padStart(4, " ");
11405
+ const text = this.text.length > 0 ? ` ${DIM4}${this.text}${RESET4}` : "";
10539
11406
  this.clearCurrentLine();
10540
11407
  this.progressText = `${progressBar} ${percentage} ${text}`;
10541
- this.progressTextPlainLength = 1 + totalBars + 1 + 1 + 4 + 1 + text.length;
11408
+ this.progressTextPlainLength = 1 + totalBars + 1 + 1 + 4 + 1 + this.text.length + 1;
10542
11409
  this.terminal.write(this.progressText);
10543
11410
  this.lastLineCount = this.calcLineCount(this.progressTextPlainLength);
10544
11411
  if (this.context) {
10545
11412
  this.context.setCurrentLine(this.progressText);
10546
11413
  }
10547
11414
  }
10548
- /**
10549
- * Returns an ANSI 256-color escape for a position in the progress bar.
10550
- * Gradient: red (0%) -> yellow (50%) -> green (100%).
10551
- */
10552
- progressGradientColor(ratio) {
10553
- let r, g;
10554
- if (ratio < 0.5) {
10555
- r = 5;
10556
- g = Math.round(ratio / 0.5 * 5);
10557
- } else {
10558
- r = Math.round((1 - ratio) / 0.5 * 5);
10559
- g = 5;
10560
- }
10561
- const colorIndex = 16 + 36 * r + 6 * g + 0;
10562
- return `\x1B[38;5;${colorIndex}m`;
10563
- }
10564
11415
  clearCurrentLine() {
10565
11416
  const lines = Math.max(
10566
11417
  this.lastLineCount,
@@ -10673,243 +11524,1779 @@ var CliClipboard = class {
10673
11524
  }
10674
11525
  };
10675
11526
 
10676
- // src/lib/services/cli-input-reader.ts
10677
- var CliInputReader = class {
10678
- constructor(host) {
11527
+ // src/lib/input/modes/input-mode-base.ts
11528
+ var InputModeBase = class {
11529
+ constructor(host, resolve) {
10679
11530
  this.host = host;
11531
+ this.resolve = resolve;
11532
+ this._resolved = false;
11533
+ /** Number of extra lines rendered below the prompt (help bar, error, etc.) */
11534
+ this.extraLines = 0;
10680
11535
  }
10681
- readLine(prompt) {
10682
- return this.createInputRequest("line", prompt);
11536
+ /** Whether this mode has already resolved (prevents double-resolve from async operations) */
11537
+ get isResolved() {
11538
+ return this._resolved;
10683
11539
  }
10684
- readPassword(prompt) {
10685
- return this.createInputRequest("password", prompt);
11540
+ /**
11541
+ * Default key event handler. Aborts on Ctrl+C or Escape.
11542
+ * Subclasses may override for custom behavior (e.g., search-enabled modes
11543
+ * clear the filter first, abort on second press).
11544
+ */
11545
+ handleKeyEvent(event) {
11546
+ if (event.code === "KeyC" && event.ctrlKey || event.code === "Escape") {
11547
+ this.abort();
11548
+ return false;
11549
+ }
11550
+ return true;
10686
11551
  }
10687
- readConfirm(prompt, defaultValue = false) {
10688
- const hint = defaultValue ? "(Y/n)" : "(y/N)";
10689
- const displayPrompt = `${prompt} ${hint}: `;
10690
- return new Promise((resolve) => {
10691
- if (this.host.activeInputRequest) {
10692
- throw new Error("Another input request is already active");
10693
- }
10694
- this.host.writeToTerminal(displayPrompt);
10695
- this.host.setActiveInputRequest({
10696
- type: "confirm",
10697
- promptText: displayPrompt,
10698
- resolve,
10699
- buffer: "",
10700
- cursorPosition: 0,
10701
- defaultValue
10702
- });
10703
- });
11552
+ /**
11553
+ * Resolve the input promise with a value and pop this mode from the stack.
11554
+ */
11555
+ resolveAndPop(value) {
11556
+ if (this._resolved) return;
11557
+ this._resolved = true;
11558
+ this.host.terminal.write("\r\n");
11559
+ this.host.popMode();
11560
+ this.resolve(value);
10704
11561
  }
10705
- readSelect(prompt, options, onChange) {
10706
- if (!options || options.length === 0) {
10707
- return Promise.reject(
10708
- new Error("readSelect requires at least one option")
11562
+ /**
11563
+ * Abort the input — resolve with null and pop.
11564
+ */
11565
+ abort() {
11566
+ if (this._resolved) return;
11567
+ this._resolved = true;
11568
+ this.host.terminal.write("\r\n");
11569
+ this.host.popMode();
11570
+ this.resolve(null);
11571
+ }
11572
+ /**
11573
+ * Clear the current line and redraw prompt + display text, positioning
11574
+ * the cursor at the given position.
11575
+ *
11576
+ * All writes are batched into a single `terminal.write()` call to
11577
+ * eliminate visible flicker between clear and redraw.
11578
+ */
11579
+ redrawLine(promptText, displayText, cursorPosition) {
11580
+ let buf = "";
11581
+ if (this.extraLines > 0) {
11582
+ buf += "\x1B[s";
11583
+ for (let i = 0; i < this.extraLines; i++) {
11584
+ buf += "\x1B[B\x1B[2K";
11585
+ }
11586
+ buf += "\x1B[u";
11587
+ this.extraLines = 0;
11588
+ }
11589
+ buf += "\x1B[2K\r" + promptText + displayText;
11590
+ const cursorOffset = displayText.length - cursorPosition;
11591
+ if (cursorOffset > 0) {
11592
+ buf += `\x1B[${cursorOffset}D`;
11593
+ }
11594
+ this.host.terminal.write(buf);
11595
+ }
11596
+ /**
11597
+ * Render a dimmed help bar below the current line.
11598
+ */
11599
+ writeHelp(text) {
11600
+ this.extraLines++;
11601
+ this.host.terminal.write(
11602
+ `\r
11603
+ \x1B[2m${text}\x1B[0m\x1B[${this.extraLines}A`
11604
+ );
11605
+ }
11606
+ /**
11607
+ * Write a validation error below the current line.
11608
+ */
11609
+ writeError(message) {
11610
+ this.extraLines++;
11611
+ this.host.terminal.write(
11612
+ `\r
11613
+ \x1B[31m\u2718 ${message}\x1B[0m\x1B[${this.extraLines}A`
11614
+ );
11615
+ }
11616
+ /**
11617
+ * Clear any extra lines (help bar, errors) rendered below the prompt.
11618
+ */
11619
+ clearExtraLines() {
11620
+ if (this.extraLines > 0) {
11621
+ let buf = "\x1B[s";
11622
+ for (let i = 0; i < this.extraLines; i++) {
11623
+ buf += "\x1B[B\x1B[2K";
11624
+ }
11625
+ buf += "\x1B[u";
11626
+ this.host.terminal.write(buf);
11627
+ this.extraLines = 0;
11628
+ }
11629
+ }
11630
+ };
11631
+
11632
+ // src/lib/input/modes/line-input-mode.ts
11633
+ var LineInputMode = class extends InputModeBase {
11634
+ constructor(host, resolve, promptText, options) {
11635
+ super(host, resolve);
11636
+ this.promptText = promptText;
11637
+ this.options = options;
11638
+ this.hasError = false;
11639
+ this.buffer = options?.default ?? "";
11640
+ this.cursorPosition = this.buffer.length;
11641
+ }
11642
+ activate() {
11643
+ const prompt = `\x1B[36m?\x1B[0m ${this.promptText}`;
11644
+ this.host.writeToTerminal(prompt);
11645
+ if (this.buffer) {
11646
+ this.host.writeToTerminal(this.buffer);
11647
+ } else if (this.options?.placeholder) {
11648
+ this.host.writeToTerminal(`\x1B[2m${this.options.placeholder}\x1B[0m`);
11649
+ this.host.terminal.write(`\x1B[${this.options.placeholder.length}D`);
11650
+ }
11651
+ }
11652
+ async handleInput(data) {
11653
+ if (this.hasError) {
11654
+ this.hasError = false;
11655
+ this.clearExtraLines();
11656
+ }
11657
+ if (data === "\r") {
11658
+ if (this.options?.validate) {
11659
+ const error = this.options.validate(this.buffer);
11660
+ if (error) {
11661
+ this.hasError = true;
11662
+ this.writeError(error);
11663
+ return;
11664
+ }
11665
+ }
11666
+ this.resolveAndPop(this.buffer);
11667
+ } else if (data === "\x7F") {
11668
+ if (this.cursorPosition > 0) {
11669
+ this.buffer = this.buffer.slice(0, this.cursorPosition - 1) + this.buffer.slice(this.cursorPosition);
11670
+ this.cursorPosition--;
11671
+ this.renderLine();
11672
+ }
11673
+ } else if (data === "\x1B[D") {
11674
+ if (this.cursorPosition > 0) {
11675
+ this.cursorPosition--;
11676
+ this.host.terminal.write(data);
11677
+ }
11678
+ } else if (data === "\x1B[C") {
11679
+ if (this.cursorPosition < this.buffer.length) {
11680
+ this.cursorPosition++;
11681
+ this.host.terminal.write(data);
11682
+ }
11683
+ } else if (data.startsWith("\x1B")) ; else {
11684
+ const text = data.replace(/[\r\n]+/g, "");
11685
+ this.buffer = this.buffer.slice(0, this.cursorPosition) + text + this.buffer.slice(this.cursorPosition);
11686
+ this.cursorPosition += text.length;
11687
+ if (this.cursorPosition === this.buffer.length && this.extraLines === 0 && !this.hasError && !(this.options?.placeholder && this.buffer.length === text.length)) {
11688
+ this.host.terminal.write(text);
11689
+ } else {
11690
+ this.renderLine();
11691
+ }
11692
+ }
11693
+ }
11694
+ onResize() {
11695
+ this.renderLine();
11696
+ }
11697
+ renderLine() {
11698
+ const prompt = `\x1B[36m?\x1B[0m ${this.promptText}`;
11699
+ if (this.buffer.length === 0 && this.options?.placeholder) {
11700
+ const placeholder = `\x1B[2m${this.options.placeholder}\x1B[0m`;
11701
+ this.redrawLine(prompt, placeholder, 0);
11702
+ } else {
11703
+ this.redrawLine(prompt, this.buffer, this.cursorPosition);
11704
+ }
11705
+ }
11706
+ };
11707
+
11708
+ // src/lib/input/modes/password-input-mode.ts
11709
+ var PasswordInputMode = class extends InputModeBase {
11710
+ constructor(host, resolve, promptText) {
11711
+ super(host, resolve);
11712
+ this.promptText = promptText;
11713
+ this.buffer = "";
11714
+ }
11715
+ activate() {
11716
+ const prompt = `\x1B[36m?\x1B[0m ${this.promptText}`;
11717
+ this.host.writeToTerminal(prompt);
11718
+ }
11719
+ async handleInput(data) {
11720
+ if (data === "\r") {
11721
+ this.resolveAndPop(this.buffer);
11722
+ } else if (data === "\x7F") {
11723
+ if (this.buffer.length > 0) {
11724
+ this.buffer = this.buffer.slice(0, -1);
11725
+ this.renderLine();
11726
+ }
11727
+ } else if (data.startsWith("\x1B")) ; else {
11728
+ const text = data.replace(/[\r\n]+/g, "");
11729
+ this.buffer += text;
11730
+ if (this.extraLines === 0) {
11731
+ this.host.terminal.write("*".repeat(text.length));
11732
+ } else {
11733
+ this.renderLine();
11734
+ }
11735
+ }
11736
+ }
11737
+ onResize() {
11738
+ this.renderLine();
11739
+ }
11740
+ renderLine() {
11741
+ const prompt = `\x1B[36m?\x1B[0m ${this.promptText}`;
11742
+ const masked = "*".repeat(this.buffer.length);
11743
+ this.redrawLine(prompt, masked, masked.length);
11744
+ }
11745
+ };
11746
+
11747
+ // src/lib/input/modes/confirm-input-mode.ts
11748
+ var ConfirmInputMode = class extends InputModeBase {
11749
+ constructor(host, resolve, promptText, defaultValue = false) {
11750
+ super(host, resolve);
11751
+ this.promptText = promptText;
11752
+ this.defaultValue = defaultValue;
11753
+ this.buffer = "";
11754
+ }
11755
+ activate() {
11756
+ const hint = this.defaultValue ? "(Y/n)" : "(y/N)";
11757
+ const prompt = `\x1B[36m?\x1B[0m ${this.promptText} ${hint}: `;
11758
+ this.host.writeToTerminal(prompt);
11759
+ }
11760
+ async handleInput(data) {
11761
+ if (data === "\r") {
11762
+ if (this.buffer === "") {
11763
+ this.resolveAndPop(this.defaultValue);
11764
+ } else {
11765
+ const lower = this.buffer.toLowerCase();
11766
+ if (lower === "y") {
11767
+ this.resolveAndPop(true);
11768
+ } else if (lower === "n") {
11769
+ this.resolveAndPop(false);
11770
+ }
11771
+ }
11772
+ } else if (data === "\x7F") {
11773
+ if (this.buffer.length > 0) {
11774
+ this.buffer = this.buffer.slice(0, -1);
11775
+ this.renderLine();
11776
+ }
11777
+ } else if (data.startsWith("\x1B")) ; else {
11778
+ const lower = data.toLowerCase();
11779
+ if (lower === "y" || lower === "n") {
11780
+ this.buffer = lower;
11781
+ this.renderLine();
11782
+ }
11783
+ }
11784
+ }
11785
+ onResize() {
11786
+ this.renderLine();
11787
+ }
11788
+ renderLine() {
11789
+ const hint = this.defaultValue ? "(Y/n)" : "(y/N)";
11790
+ const prompt = `\x1B[36m?\x1B[0m ${this.promptText} ${hint}: `;
11791
+ this.redrawLine(prompt, this.buffer, this.buffer.length);
11792
+ }
11793
+ };
11794
+
11795
+ // src/lib/input/modes/number-input-mode.ts
11796
+ var NumberInputMode = class extends InputModeBase {
11797
+ constructor(host, resolve, promptText, options) {
11798
+ super(host, resolve);
11799
+ this.promptText = promptText;
11800
+ this.options = options;
11801
+ this.hasError = false;
11802
+ this.buffer = options?.default != null ? String(options.default) : "";
11803
+ this.cursorPosition = this.buffer.length;
11804
+ }
11805
+ activate() {
11806
+ const prompt = this.buildPrompt();
11807
+ this.host.writeToTerminal(prompt);
11808
+ if (this.buffer) {
11809
+ this.host.writeToTerminal(this.buffer);
11810
+ }
11811
+ }
11812
+ async handleInput(data) {
11813
+ if (this.hasError) {
11814
+ this.hasError = false;
11815
+ this.clearExtraLines();
11816
+ }
11817
+ if (data === "\r") {
11818
+ if (this.buffer === "" || this.buffer === "-") {
11819
+ if (this.options?.default != null) {
11820
+ this.resolveAndPop(this.options.default);
11821
+ }
11822
+ return;
11823
+ }
11824
+ const value = parseInt(this.buffer, 10);
11825
+ if (isNaN(value)) {
11826
+ this.hasError = true;
11827
+ this.writeError("Please enter a valid number");
11828
+ return;
11829
+ }
11830
+ if (this.options?.min != null && value < this.options.min) {
11831
+ this.hasError = true;
11832
+ this.writeError(`Value must be at least ${this.options.min}`);
11833
+ return;
11834
+ }
11835
+ if (this.options?.max != null && value > this.options.max) {
11836
+ this.hasError = true;
11837
+ this.writeError(`Value must be at most ${this.options.max}`);
11838
+ return;
11839
+ }
11840
+ this.resolveAndPop(value);
11841
+ } else if (data === "\x7F") {
11842
+ if (this.cursorPosition > 0) {
11843
+ this.buffer = this.buffer.slice(0, this.cursorPosition - 1) + this.buffer.slice(this.cursorPosition);
11844
+ this.cursorPosition--;
11845
+ this.renderLine();
11846
+ }
11847
+ } else if (data === "\x1B[D") {
11848
+ if (this.cursorPosition > 0) {
11849
+ this.cursorPosition--;
11850
+ this.host.terminal.write(data);
11851
+ }
11852
+ } else if (data === "\x1B[C") {
11853
+ if (this.cursorPosition < this.buffer.length) {
11854
+ this.cursorPosition++;
11855
+ this.host.terminal.write(data);
11856
+ }
11857
+ } else if (data.startsWith("\x1B")) ; else {
11858
+ for (const ch of data) {
11859
+ if (ch >= "0" && ch <= "9") {
11860
+ this.buffer = this.buffer.slice(0, this.cursorPosition) + ch + this.buffer.slice(this.cursorPosition);
11861
+ this.cursorPosition++;
11862
+ } else if (ch === "-" && this.cursorPosition === 0 && !this.buffer.startsWith("-")) {
11863
+ this.buffer = "-" + this.buffer;
11864
+ this.cursorPosition++;
11865
+ }
11866
+ }
11867
+ this.renderLine();
11868
+ }
11869
+ }
11870
+ onResize() {
11871
+ this.renderLine();
11872
+ }
11873
+ buildPrompt() {
11874
+ const { min, max } = this.options ?? {};
11875
+ let hint = "";
11876
+ if (min != null && max != null) {
11877
+ hint = ` \x1B[2m(${min}\u2013${max})\x1B[0m`;
11878
+ } else if (min != null) {
11879
+ hint = ` \x1B[2m(min: ${min})\x1B[0m`;
11880
+ } else if (max != null) {
11881
+ hint = ` \x1B[2m(max: ${max})\x1B[0m`;
11882
+ }
11883
+ return `\x1B[36m?\x1B[0m ${this.promptText}${hint}`;
11884
+ }
11885
+ renderLine() {
11886
+ const prompt = this.buildPrompt();
11887
+ this.redrawLine(prompt, this.buffer, this.cursorPosition);
11888
+ }
11889
+ };
11890
+
11891
+ // src/lib/input/modes/select-input-mode.ts
11892
+ var SelectInputMode = class extends InputModeBase {
11893
+ constructor(host, resolve, promptText, options, selectOptions) {
11894
+ super(host, resolve);
11895
+ this.promptText = promptText;
11896
+ this.options = options;
11897
+ this.selectOptions = selectOptions;
11898
+ this.selectedIndex = 0;
11899
+ this.scrollOffset = 0;
11900
+ this.filter = "";
11901
+ /** Number of lines rendered for the option list + help bar (for clearing) */
11902
+ this.renderedLines = 0;
11903
+ this.filteredOptions = [...options];
11904
+ this.maxVisible = Math.min(options.length, host.getTerminalRows() - 4);
11905
+ }
11906
+ activate() {
11907
+ this.host.terminal.write("\x1B[?25l");
11908
+ if (this.selectOptions?.default) {
11909
+ const idx = this.filteredOptions.findIndex(
11910
+ (o) => o.value === this.selectOptions.default
10709
11911
  );
11912
+ if (idx >= 0) {
11913
+ this.selectedIndex = idx;
11914
+ if (this.selectedIndex >= this.maxVisible) {
11915
+ this.scrollOffset = this.selectedIndex - this.maxVisible + 1;
11916
+ }
11917
+ }
11918
+ } else {
11919
+ this.selectedIndex = this.findNextSelectable(-1, 1);
10710
11920
  }
10711
- return new Promise((resolve) => {
10712
- if (this.host.activeInputRequest) {
10713
- throw new Error("Another input request is already active");
10714
- }
10715
- const termRows = this.host.getTerminalRows?.() ?? 24;
10716
- const maxVisible = Math.max(3, termRows - 2);
10717
- this.host.writeToTerminal(prompt + "\r\n");
10718
- const displayLines = this.renderSelectOptions(options, 0, 0, maxVisible);
10719
- onChange?.(options[0].value);
10720
- this.host.setActiveInputRequest({
10721
- type: "select",
10722
- promptText: prompt,
10723
- resolve,
10724
- buffer: "",
10725
- cursorPosition: 0,
10726
- options,
10727
- selectedIndex: 0,
10728
- onChange,
10729
- scrollOffset: 0,
10730
- maxVisible,
10731
- displayLines
10732
- });
10733
- });
11921
+ this.renderAll();
10734
11922
  }
10735
- readSelectInline(prompt, options, onChange) {
10736
- if (!options || options.length === 0) {
10737
- return Promise.reject(
10738
- new Error("readSelectInline requires at least one option")
11923
+ /**
11924
+ * Handle keyboard events. When searchable and filter is non-empty,
11925
+ * Escape clears the filter instead of aborting.
11926
+ */
11927
+ handleKeyEvent(event) {
11928
+ if (this.selectOptions?.searchable && this.filter.length > 0) {
11929
+ if (event.code === "Escape") {
11930
+ this.clearFilter();
11931
+ return false;
11932
+ }
11933
+ }
11934
+ return super.handleKeyEvent(event);
11935
+ }
11936
+ /**
11937
+ * Clear rendered option lines and show final answer before resolving.
11938
+ */
11939
+ resolveAndPop(value) {
11940
+ this.clearRenderedLines();
11941
+ const selectedOpt = this.options.find((o) => o.value === value);
11942
+ const label = selectedOpt ? selectedOpt.label : value;
11943
+ this.host.terminal.write("\x1B[2K\r");
11944
+ this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.promptText} \x1B[36m${label}\x1B[0m`);
11945
+ this.host.terminal.write("\x1B[?25h");
11946
+ super.resolveAndPop(value);
11947
+ }
11948
+ /**
11949
+ * Clear rendered option lines before aborting.
11950
+ */
11951
+ abort() {
11952
+ this.clearRenderedLines();
11953
+ this.host.terminal.write("\x1B[2K\r");
11954
+ this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.promptText} \x1B[2mcancelled\x1B[0m`);
11955
+ this.host.terminal.write("\x1B[?25h");
11956
+ super.abort();
11957
+ }
11958
+ async handleInput(data) {
11959
+ if (data === "\x1B[A") {
11960
+ this.moveSelection(-1);
11961
+ } else if (data === "\x1B[B") {
11962
+ this.moveSelection(1);
11963
+ } else if (data === "\r") {
11964
+ const items = this.buildDisplayList();
11965
+ const selectableItems = items.filter(
11966
+ (i) => i.type === "option" && !i.option.disabled
10739
11967
  );
11968
+ if (selectableItems.length > 0 && this.selectedIndex < selectableItems.length) {
11969
+ const selected = selectableItems[this.selectedIndex];
11970
+ this.resolveAndPop(selected.option.value);
11971
+ }
11972
+ } else if (data === "\x7F") {
11973
+ if (this.selectOptions?.searchable && this.filter.length > 0) {
11974
+ this.filter = this.filter.slice(0, -1);
11975
+ this.applyFilter();
11976
+ this.renderAll();
11977
+ }
11978
+ } else if (data.startsWith("\x1B")) ; else if (this.selectOptions?.searchable && data.length === 1 && data >= " ") {
11979
+ this.filter += data;
11980
+ this.applyFilter();
11981
+ this.renderAll();
10740
11982
  }
10741
- return new Promise((resolve) => {
10742
- if (this.host.activeInputRequest) {
10743
- throw new Error("Another input request is already active");
10744
- }
10745
- const inlineText = this.renderInlineSelectOptions(options, 0);
10746
- this.host.writeToTerminal(`${prompt} ${inlineText}`);
10747
- onChange?.(options[0].value);
10748
- this.host.setActiveInputRequest({
10749
- type: "select-inline",
10750
- promptText: prompt,
10751
- resolve,
10752
- buffer: "",
10753
- cursorPosition: 0,
10754
- options,
10755
- selectedIndex: 0,
10756
- onChange
11983
+ }
11984
+ onResize(cols, rows) {
11985
+ this.maxVisible = Math.min(this.filteredOptions.length, rows - 4);
11986
+ if (this.scrollOffset + this.maxVisible > this.getSelectableCount()) {
11987
+ this.scrollOffset = Math.max(0, this.getSelectableCount() - this.maxVisible);
11988
+ }
11989
+ this.renderAll();
11990
+ }
11991
+ // ── Private helpers ──────────────────────────────────────────────
11992
+ /**
11993
+ * Build flattened display list with group headers inserted.
11994
+ */
11995
+ buildDisplayList() {
11996
+ const items = [];
11997
+ let lastGroup;
11998
+ for (const opt of this.filteredOptions) {
11999
+ if (opt.group && opt.group !== lastGroup) {
12000
+ items.push({ type: "group-header", groupName: opt.group });
12001
+ lastGroup = opt.group;
12002
+ }
12003
+ items.push({ type: "option", option: opt });
12004
+ }
12005
+ return items;
12006
+ }
12007
+ /**
12008
+ * Get the total count of selectable items (non-disabled options).
12009
+ */
12010
+ getSelectableCount() {
12011
+ return this.filteredOptions.filter((o) => !o.disabled).length;
12012
+ }
12013
+ /**
12014
+ * Move selection in the given direction (+1 or -1), skipping disabled options.
12015
+ * Wraps around at boundaries.
12016
+ */
12017
+ moveSelection(direction) {
12018
+ const selectableCount = this.getSelectableCount();
12019
+ if (selectableCount === 0) return;
12020
+ let next = this.selectedIndex + direction;
12021
+ if (next < 0) next = selectableCount - 1;
12022
+ if (next >= selectableCount) next = 0;
12023
+ this.selectedIndex = next;
12024
+ if (this.selectedIndex < this.scrollOffset) {
12025
+ this.scrollOffset = this.selectedIndex;
12026
+ } else if (this.selectedIndex >= this.scrollOffset + this.maxVisible) {
12027
+ this.scrollOffset = this.selectedIndex - this.maxVisible + 1;
12028
+ }
12029
+ const selectables = this.filteredOptions.filter((o) => !o.disabled);
12030
+ if (this.selectOptions?.onChange && selectables[this.selectedIndex]) {
12031
+ this.selectOptions.onChange(selectables[this.selectedIndex].value);
12032
+ }
12033
+ this.renderAll();
12034
+ }
12035
+ /**
12036
+ * Find the next selectable (non-disabled) option index starting from `from`.
12037
+ * Returns 0 if none found.
12038
+ */
12039
+ findNextSelectable(from, direction) {
12040
+ const len = this.filteredOptions.length;
12041
+ if (len === 0) return 0;
12042
+ let idx = from + direction;
12043
+ for (let i = 0; i < len; i++) {
12044
+ if (idx < 0) idx = len - 1;
12045
+ if (idx >= len) idx = 0;
12046
+ if (!this.filteredOptions[idx].disabled) return idx;
12047
+ idx += direction;
12048
+ }
12049
+ return 0;
12050
+ }
12051
+ /**
12052
+ * Apply the current filter text to the options list.
12053
+ */
12054
+ applyFilter() {
12055
+ if (this.filter.length === 0) {
12056
+ this.filteredOptions = [...this.options];
12057
+ } else {
12058
+ const lower = this.filter.toLowerCase();
12059
+ this.filteredOptions = this.options.filter(
12060
+ (o) => o.label.toLowerCase().includes(lower)
12061
+ );
12062
+ }
12063
+ this.selectedIndex = 0;
12064
+ this.scrollOffset = 0;
12065
+ this.maxVisible = Math.min(
12066
+ this.filteredOptions.length,
12067
+ this.host.getTerminalRows() - 4
12068
+ );
12069
+ const selectables = this.filteredOptions.filter((o) => !o.disabled);
12070
+ if (selectables.length > 0) {
12071
+ this.selectedIndex = 0;
12072
+ }
12073
+ }
12074
+ /**
12075
+ * Clear the filter and restore full option list.
12076
+ */
12077
+ clearFilter() {
12078
+ this.filter = "";
12079
+ this.applyFilter();
12080
+ this.renderAll();
12081
+ }
12082
+ /**
12083
+ * Clear all previously rendered lines and re-render everything.
12084
+ */
12085
+ renderAll() {
12086
+ this.clearRenderedLines();
12087
+ this.renderPrompt();
12088
+ this.renderOptions();
12089
+ this.renderHelpBar();
12090
+ if (this.renderedLines > 0) {
12091
+ this.host.terminal.write(`\x1B[${this.renderedLines}A`);
12092
+ }
12093
+ }
12094
+ /**
12095
+ * Clear all lines we previously rendered (below the cursor).
12096
+ */
12097
+ clearRenderedLines() {
12098
+ if (this.renderedLines > 0) {
12099
+ this.host.terminal.write("\x1B[s");
12100
+ for (let i = 0; i < this.renderedLines; i++) {
12101
+ this.host.terminal.write("\x1B[B\x1B[2K");
12102
+ }
12103
+ this.host.terminal.write("\x1B[u");
12104
+ this.renderedLines = 0;
12105
+ }
12106
+ }
12107
+ /**
12108
+ * Render the prompt line.
12109
+ */
12110
+ renderPrompt() {
12111
+ this.host.terminal.write("\x1B[2K\r");
12112
+ if (this.selectOptions?.searchable) {
12113
+ if (this.filter.length > 0) {
12114
+ this.host.terminal.write(
12115
+ `\x1B[36m?\x1B[0m ${this.promptText}: ${this.filter}`
12116
+ );
12117
+ } else {
12118
+ this.host.terminal.write(
12119
+ `\x1B[36m?\x1B[0m ${this.promptText}: \x1B[2mType to filter...\x1B[0m`
12120
+ );
12121
+ }
12122
+ } else {
12123
+ this.host.terminal.write(`\x1B[36m?\x1B[0m ${this.promptText}:`);
12124
+ }
12125
+ }
12126
+ /**
12127
+ * Render the visible slice of the option list.
12128
+ */
12129
+ renderOptions() {
12130
+ const items = this.buildDisplayList();
12131
+ const selectables = this.filteredOptions.filter((o) => !o.disabled);
12132
+ const visibleStart = this.scrollOffset;
12133
+ const visibleEnd = Math.min(
12134
+ selectables.length,
12135
+ this.scrollOffset + this.maxVisible
12136
+ );
12137
+ if (visibleStart > 0) {
12138
+ this.host.terminal.write(`\r
12139
+ \x1B[2m \u2191 ${visibleStart} more above\x1B[0m`);
12140
+ this.renderedLines++;
12141
+ }
12142
+ let selectableIdx = 0;
12143
+ for (const item of items) {
12144
+ if (item.type === "group-header") {
12145
+ if (this.isGroupVisible(item.groupName, items, visibleStart, visibleEnd)) {
12146
+ this.host.terminal.write(
12147
+ `\r
12148
+ \x1B[35m\u2500\u2500 ${item.groupName} \u2500\u2500\x1B[0m`
12149
+ );
12150
+ this.renderedLines++;
12151
+ }
12152
+ } else {
12153
+ const opt = item.option;
12154
+ if (opt.disabled) {
12155
+ if (this.isDisabledOptionVisible(items, item, visibleStart, visibleEnd)) {
12156
+ this.renderOptionLine(opt, false, false);
12157
+ this.renderedLines++;
12158
+ }
12159
+ } else {
12160
+ if (selectableIdx >= visibleStart && selectableIdx < visibleEnd) {
12161
+ const isSelected = selectableIdx === this.selectedIndex;
12162
+ this.renderOptionLine(opt, isSelected, false);
12163
+ this.renderedLines++;
12164
+ }
12165
+ selectableIdx++;
12166
+ }
12167
+ }
12168
+ }
12169
+ const remaining = selectables.length - visibleEnd;
12170
+ if (remaining > 0) {
12171
+ this.host.terminal.write(`\r
12172
+ \x1B[2m \u2193 ${remaining} more below\x1B[0m`);
12173
+ this.renderedLines++;
12174
+ }
12175
+ }
12176
+ /**
12177
+ * Check if a group header should be visible given the current visible range.
12178
+ */
12179
+ isGroupVisible(groupName, items, visibleStart, visibleEnd) {
12180
+ let selectableIdx = 0;
12181
+ for (const item of items) {
12182
+ if (item.type === "option" && !item.option.disabled) {
12183
+ if (item.option.group === groupName && selectableIdx >= visibleStart && selectableIdx < visibleEnd) {
12184
+ return true;
12185
+ }
12186
+ selectableIdx++;
12187
+ }
12188
+ }
12189
+ return false;
12190
+ }
12191
+ /**
12192
+ * Check if a disabled option should be visible (adjacent to visible selectables).
12193
+ */
12194
+ isDisabledOptionVisible(items, disabledItem, visibleStart, visibleEnd) {
12195
+ const idx = items.indexOf(disabledItem);
12196
+ let selectableIdx = 0;
12197
+ for (let i = 0; i < items.length; i++) {
12198
+ if (items[i].type === "option" && !items[i].option.disabled) {
12199
+ if (i > idx) {
12200
+ return selectableIdx >= visibleStart && selectableIdx < visibleEnd;
12201
+ }
12202
+ selectableIdx++;
12203
+ }
12204
+ }
12205
+ return selectableIdx > 0 && selectableIdx - 1 >= visibleStart && selectableIdx - 1 < visibleEnd;
12206
+ }
12207
+ /**
12208
+ * Render a single option line.
12209
+ */
12210
+ renderOptionLine(opt, isSelected, _isGroupHeader) {
12211
+ const label = this.highlightMatch(opt.label);
12212
+ const desc = opt.description ? ` \x1B[2;3m\u2014 ${opt.description}\x1B[0m` : "";
12213
+ if (opt.disabled) {
12214
+ this.host.terminal.write(
12215
+ `\r
12216
+ \x1B[2m${opt.label}${desc} (disabled)\x1B[0m`
12217
+ );
12218
+ } else if (isSelected) {
12219
+ this.host.terminal.write(
12220
+ `\r
12221
+ \x1B[48;5;24m\x1B[38;5;117m\u276F ${label}${desc}\x1B[0m`
12222
+ );
12223
+ } else {
12224
+ this.host.terminal.write(
12225
+ `\r
12226
+ ${label}${desc}`
12227
+ );
12228
+ }
12229
+ }
12230
+ /**
12231
+ * Highlight the matched filter text in yellow within the label.
12232
+ */
12233
+ highlightMatch(label) {
12234
+ if (!this.filter || this.filter.length === 0) return label;
12235
+ const lower = label.toLowerCase();
12236
+ const filterLower = this.filter.toLowerCase();
12237
+ const idx = lower.indexOf(filterLower);
12238
+ if (idx < 0) return label;
12239
+ const before = label.substring(0, idx);
12240
+ const match = label.substring(idx, idx + this.filter.length);
12241
+ const after = label.substring(idx + this.filter.length);
12242
+ return `${before}\x1B[33m${match}\x1B[0m${after}`;
12243
+ }
12244
+ /**
12245
+ * Render the help bar at the bottom.
12246
+ */
12247
+ renderHelpBar() {
12248
+ let help;
12249
+ if (this.selectOptions?.searchable) {
12250
+ if (this.filter.length > 0) {
12251
+ const matchCount = this.filteredOptions.filter((o) => !o.disabled).length;
12252
+ const totalCount = this.options.filter((o) => !o.disabled).length;
12253
+ help = `${matchCount} of ${totalCount} matches \xB7 backspace to clear filter \xB7 enter to select \xB7 esc to cancel`;
12254
+ } else {
12255
+ help = "\u2191/\u2193 navigate \xB7 type to filter \xB7 enter to select \xB7 esc to cancel";
12256
+ }
12257
+ } else {
12258
+ help = "\u2191/\u2193 navigate \xB7 enter to select \xB7 esc to cancel";
12259
+ }
12260
+ this.host.terminal.write(`\r
12261
+ \x1B[2m${help}\x1B[0m`);
12262
+ this.renderedLines++;
12263
+ }
12264
+ };
12265
+
12266
+ // src/lib/input/modes/inline-select-input-mode.ts
12267
+ var InlineSelectInputMode = class extends InputModeBase {
12268
+ constructor(host, resolve, promptText, options, selectOptions) {
12269
+ super(host, resolve);
12270
+ this.promptText = promptText;
12271
+ this.options = options;
12272
+ this.selectOptions = selectOptions;
12273
+ /** Index into the selectable (non-disabled) options */
12274
+ this.selectedIndex = 0;
12275
+ /** Start index of the visible window (into selectable options) */
12276
+ this.visibleStart = 0;
12277
+ /** End index (exclusive) of the visible window */
12278
+ this.visibleEnd = 0;
12279
+ this.selectableOptions = options.filter((o) => !o.disabled);
12280
+ }
12281
+ activate() {
12282
+ this.host.terminal.write("\x1B[?25l");
12283
+ if (this.selectOptions?.default) {
12284
+ const idx = this.selectableOptions.findIndex(
12285
+ (o) => o.value === this.selectOptions.default
12286
+ );
12287
+ if (idx >= 0) {
12288
+ this.selectedIndex = idx;
12289
+ }
12290
+ }
12291
+ this.calculateVisibleWindow();
12292
+ this.render();
12293
+ }
12294
+ /**
12295
+ * Clear the inline options and help bar, then show final answer before resolving.
12296
+ */
12297
+ resolveAndPop(value) {
12298
+ this.clearExtraLines();
12299
+ const selectedOpt = this.selectableOptions.find((o) => o.value === value);
12300
+ const label = selectedOpt ? selectedOpt.label : value;
12301
+ this.host.terminal.write("\x1B[2K\r");
12302
+ this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.promptText}: \x1B[36m${label}\x1B[0m`);
12303
+ this.host.terminal.write("\x1B[?25h");
12304
+ super.resolveAndPop(value);
12305
+ }
12306
+ /**
12307
+ * Clear the inline options and help bar before aborting.
12308
+ */
12309
+ abort() {
12310
+ this.clearExtraLines();
12311
+ this.host.terminal.write("\x1B[2K\r");
12312
+ this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.promptText}: \x1B[2mcancelled\x1B[0m`);
12313
+ this.host.terminal.write("\x1B[?25h");
12314
+ super.abort();
12315
+ }
12316
+ async handleInput(data) {
12317
+ if (data === "\x1B[C") {
12318
+ this.moveSelection(1);
12319
+ } else if (data === "\x1B[D") {
12320
+ this.moveSelection(-1);
12321
+ } else if (data === "\r") {
12322
+ if (this.selectableOptions.length > 0) {
12323
+ this.resolveAndPop(this.selectableOptions[this.selectedIndex].value);
12324
+ }
12325
+ }
12326
+ }
12327
+ onResize(cols, rows) {
12328
+ this.calculateVisibleWindow();
12329
+ this.render();
12330
+ }
12331
+ // ── Private helpers ──────────────────────────────────────────────
12332
+ /**
12333
+ * Move selection in the given direction, skipping disabled options.
12334
+ * Wraps around at boundaries.
12335
+ */
12336
+ moveSelection(direction) {
12337
+ if (this.selectableOptions.length === 0) return;
12338
+ let next = this.selectedIndex + direction;
12339
+ if (next < 0) next = this.selectableOptions.length - 1;
12340
+ if (next >= this.selectableOptions.length) next = 0;
12341
+ this.selectedIndex = next;
12342
+ this.adjustVisibleWindow();
12343
+ if (this.selectOptions?.onChange) {
12344
+ this.selectOptions.onChange(this.selectableOptions[this.selectedIndex].value);
12345
+ }
12346
+ this.render();
12347
+ }
12348
+ /**
12349
+ * Get the prompt prefix string (without ANSI escapes for width calculation).
12350
+ */
12351
+ getPromptPrefix() {
12352
+ return `? ${this.promptText}: `;
12353
+ }
12354
+ /**
12355
+ * Get the rendered prompt prefix (with ANSI color codes).
12356
+ */
12357
+ getRenderedPromptPrefix() {
12358
+ return `\x1B[36m?\x1B[0m ${this.promptText}: `;
12359
+ }
12360
+ /**
12361
+ * Calculate the width needed for a single option.
12362
+ * Selected option: `[ label ]` (label.length + 4)
12363
+ * Unselected option: ` label ` (label.length + 2)
12364
+ */
12365
+ getOptionWidth(opt, isSelected) {
12366
+ return isSelected ? opt.label.length + 4 : opt.label.length + 2;
12367
+ }
12368
+ /**
12369
+ * Calculate how many options fit in the visible window starting from a given index.
12370
+ */
12371
+ calculateVisibleWindow() {
12372
+ const cols = this.host.getTerminalCols();
12373
+ const promptWidth = this.getPromptPrefix().length;
12374
+ const available = cols - promptWidth;
12375
+ if (this.selectableOptions.length === 0) {
12376
+ this.visibleStart = 0;
12377
+ this.visibleEnd = 0;
12378
+ return;
12379
+ }
12380
+ let totalWidth = 0;
12381
+ for (let i = 0; i < this.selectableOptions.length; i++) {
12382
+ totalWidth += this.getOptionWidth(this.selectableOptions[i], i === this.selectedIndex);
12383
+ }
12384
+ if (totalWidth <= available) {
12385
+ this.visibleStart = 0;
12386
+ this.visibleEnd = this.selectableOptions.length;
12387
+ return;
12388
+ }
12389
+ this.adjustVisibleWindow();
12390
+ }
12391
+ /**
12392
+ * Adjust the visible window so the selected option is visible.
12393
+ * Accounts for scroll indicator widths.
12394
+ */
12395
+ adjustVisibleWindow() {
12396
+ const cols = this.host.getTerminalCols();
12397
+ const promptWidth = this.getPromptPrefix().length;
12398
+ const available = cols - promptWidth;
12399
+ if (this.selectableOptions.length === 0) return;
12400
+ let totalWidth = 0;
12401
+ for (let i = 0; i < this.selectableOptions.length; i++) {
12402
+ totalWidth += this.getOptionWidth(this.selectableOptions[i], i === this.selectedIndex);
12403
+ }
12404
+ if (totalWidth <= available) {
12405
+ this.visibleStart = 0;
12406
+ this.visibleEnd = this.selectableOptions.length;
12407
+ return;
12408
+ }
12409
+ const indicatorWidth = 2;
12410
+ if (this.selectedIndex < this.visibleStart) {
12411
+ this.visibleStart = this.selectedIndex;
12412
+ }
12413
+ let usedWidth = 0;
12414
+ const hasLeftIndicator = this.visibleStart > 0;
12415
+ if (hasLeftIndicator) {
12416
+ usedWidth += indicatorWidth;
12417
+ }
12418
+ let end = this.visibleStart;
12419
+ for (let i = this.visibleStart; i < this.selectableOptions.length; i++) {
12420
+ const optWidth = this.getOptionWidth(this.selectableOptions[i], i === this.selectedIndex);
12421
+ const needsRightIndicator = i < this.selectableOptions.length - 1;
12422
+ const rightReserve = needsRightIndicator ? indicatorWidth : 0;
12423
+ if (usedWidth + optWidth + rightReserve <= available) {
12424
+ usedWidth += optWidth;
12425
+ end = i + 1;
12426
+ } else {
12427
+ break;
12428
+ }
12429
+ }
12430
+ if (this.selectedIndex >= end) {
12431
+ end = this.selectedIndex + 1;
12432
+ usedWidth = this.getOptionWidth(
12433
+ this.selectableOptions[this.selectedIndex],
12434
+ true
12435
+ );
12436
+ const hasRightIndicator = end < this.selectableOptions.length;
12437
+ if (hasRightIndicator) {
12438
+ usedWidth += indicatorWidth;
12439
+ }
12440
+ let start = this.selectedIndex;
12441
+ for (let i = this.selectedIndex - 1; i >= 0; i--) {
12442
+ const optWidth = this.getOptionWidth(this.selectableOptions[i], false);
12443
+ const needsLeftIndicator = i > 0;
12444
+ const leftReserve = needsLeftIndicator ? indicatorWidth : 0;
12445
+ const currentLeftReserve = start > 0 ? indicatorWidth : 0;
12446
+ if (usedWidth - currentLeftReserve + optWidth + leftReserve <= available) {
12447
+ usedWidth = usedWidth - currentLeftReserve + optWidth + leftReserve;
12448
+ start = i;
12449
+ } else {
12450
+ break;
12451
+ }
12452
+ }
12453
+ this.visibleStart = start;
12454
+ end = this.selectedIndex + 1;
12455
+ usedWidth = 0;
12456
+ if (this.visibleStart > 0) usedWidth += indicatorWidth;
12457
+ for (let i = this.visibleStart; i < end; i++) {
12458
+ usedWidth += this.getOptionWidth(this.selectableOptions[i], i === this.selectedIndex);
12459
+ }
12460
+ for (let i = end; i < this.selectableOptions.length; i++) {
12461
+ const optWidth = this.getOptionWidth(this.selectableOptions[i], false);
12462
+ const needsRightIndicator = i < this.selectableOptions.length - 1;
12463
+ const rightReserve = needsRightIndicator ? indicatorWidth : 0;
12464
+ if (usedWidth + optWidth + rightReserve <= available) {
12465
+ usedWidth += optWidth;
12466
+ end = i + 1;
12467
+ } else {
12468
+ break;
12469
+ }
12470
+ }
12471
+ }
12472
+ this.visibleEnd = end;
12473
+ }
12474
+ /**
12475
+ * Render the complete inline select display.
12476
+ */
12477
+ render() {
12478
+ this.clearExtraLines();
12479
+ this.host.terminal.write("\x1B[2K\r");
12480
+ this.host.terminal.write(this.getRenderedPromptPrefix());
12481
+ if (this.selectableOptions.length === 0) return;
12482
+ const hasLeftOverflow = this.visibleStart > 0;
12483
+ const hasRightOverflow = this.visibleEnd < this.selectableOptions.length;
12484
+ if (hasLeftOverflow) {
12485
+ this.host.terminal.write("\x1B[2m\u25C2\x1B[0m ");
12486
+ }
12487
+ for (let i = this.visibleStart; i < this.visibleEnd; i++) {
12488
+ const opt = this.selectableOptions[i];
12489
+ const isSelected = i === this.selectedIndex;
12490
+ if (isSelected) {
12491
+ this.host.terminal.write(`\x1B[36m[ ${opt.label} ]\x1B[0m`);
12492
+ } else {
12493
+ this.host.terminal.write(` ${opt.label} `);
12494
+ }
12495
+ }
12496
+ if (hasRightOverflow) {
12497
+ this.host.terminal.write(" \x1B[2m\u25B8\x1B[0m");
12498
+ }
12499
+ this.writeHelp("\u2190/\u2192 navigate \xB7 enter to select \xB7 esc to cancel");
12500
+ }
12501
+ };
12502
+
12503
+ // src/lib/input/modes/multi-select-input-mode.ts
12504
+ var MultiSelectInputMode = class extends InputModeBase {
12505
+ constructor(host, resolve, promptText, options, selectOptions) {
12506
+ super(host, resolve);
12507
+ this.promptText = promptText;
12508
+ this.options = options;
12509
+ this.selectOptions = selectOptions;
12510
+ this.selectedIndex = 0;
12511
+ this.scrollOffset = 0;
12512
+ this.filter = "";
12513
+ /** Number of lines rendered for the option list + help bar (for clearing) */
12514
+ this.renderedLines = 0;
12515
+ this.filteredOptions = [...options];
12516
+ this.maxVisible = Math.min(options.length, host.getTerminalRows() - 4);
12517
+ this.checkedState = /* @__PURE__ */ new Map();
12518
+ for (const opt of options) {
12519
+ this.checkedState.set(opt.value, opt.checked === true);
12520
+ }
12521
+ }
12522
+ activate() {
12523
+ this.host.terminal.write("\x1B[?25l");
12524
+ this.selectedIndex = 0;
12525
+ const selectables = this.filteredOptions.filter((o) => !o.disabled);
12526
+ if (selectables.length > 0) {
12527
+ this.selectedIndex = 0;
12528
+ }
12529
+ this.renderAll();
12530
+ }
12531
+ /**
12532
+ * Handle keyboard events. When searchable and filter is non-empty,
12533
+ * Escape clears the filter instead of aborting.
12534
+ */
12535
+ handleKeyEvent(event) {
12536
+ if (this.selectOptions?.searchable && this.filter.length > 0) {
12537
+ if (event.code === "Escape") {
12538
+ this.clearFilter();
12539
+ return false;
12540
+ }
12541
+ }
12542
+ return super.handleKeyEvent(event);
12543
+ }
12544
+ /**
12545
+ * Clear rendered option lines and show final answer before resolving.
12546
+ */
12547
+ resolveAndPop(value) {
12548
+ this.clearRenderedLines();
12549
+ const labels = value.map((v) => {
12550
+ const opt = this.options.find((o) => o.value === v);
12551
+ return opt ? opt.label : v;
12552
+ });
12553
+ const summary = labels.length > 0 ? labels.join(", ") : "none";
12554
+ this.host.terminal.write("\x1B[2K\r");
12555
+ this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.promptText} \x1B[36m${summary}\x1B[0m`);
12556
+ this.host.terminal.write("\x1B[?25h");
12557
+ super.resolveAndPop(value);
12558
+ }
12559
+ /**
12560
+ * Clear rendered option lines before aborting.
12561
+ */
12562
+ abort() {
12563
+ this.clearRenderedLines();
12564
+ this.host.terminal.write("\x1B[2K\r");
12565
+ this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.promptText} \x1B[2mcancelled\x1B[0m`);
12566
+ this.host.terminal.write("\x1B[?25h");
12567
+ super.abort();
12568
+ }
12569
+ async handleInput(data) {
12570
+ if (data === "\x1B[A") {
12571
+ this.moveSelection(-1);
12572
+ } else if (data === "\x1B[B") {
12573
+ this.moveSelection(1);
12574
+ } else if (data === " ") {
12575
+ this.toggleCurrent();
12576
+ } else if (data === "a" && (!this.selectOptions?.searchable || this.filter.length === 0)) {
12577
+ this.toggleAll();
12578
+ } else if (data === "\r") {
12579
+ this.resolveAndPop(this.getCheckedValues());
12580
+ } else if (data === "\x7F") {
12581
+ if (this.selectOptions?.searchable && this.filter.length > 0) {
12582
+ this.filter = this.filter.slice(0, -1);
12583
+ this.applyFilter();
12584
+ this.renderAll();
12585
+ }
12586
+ } else if (data.startsWith("\x1B")) ; else if (this.selectOptions?.searchable && data.length === 1 && data >= " ") {
12587
+ this.filter += data;
12588
+ this.applyFilter();
12589
+ this.renderAll();
12590
+ }
12591
+ }
12592
+ onResize(cols, rows) {
12593
+ this.maxVisible = Math.min(this.filteredOptions.length, rows - 4);
12594
+ if (this.scrollOffset + this.maxVisible > this.getSelectableCount()) {
12595
+ this.scrollOffset = Math.max(0, this.getSelectableCount() - this.maxVisible);
12596
+ }
12597
+ this.renderAll();
12598
+ }
12599
+ // ── Private helpers ──────────────────────────────────────────────
12600
+ /**
12601
+ * Build flattened display list with group headers inserted.
12602
+ */
12603
+ buildDisplayList() {
12604
+ const items = [];
12605
+ let lastGroup;
12606
+ for (const opt of this.filteredOptions) {
12607
+ if (opt.group && opt.group !== lastGroup) {
12608
+ items.push({ type: "group-header", groupName: opt.group });
12609
+ lastGroup = opt.group;
12610
+ }
12611
+ items.push({ type: "option", option: opt });
12612
+ }
12613
+ return items;
12614
+ }
12615
+ /**
12616
+ * Get the total count of selectable items (non-disabled options).
12617
+ */
12618
+ getSelectableCount() {
12619
+ return this.filteredOptions.filter((o) => !o.disabled).length;
12620
+ }
12621
+ /**
12622
+ * Get an array of currently checked values (preserving original order).
12623
+ */
12624
+ getCheckedValues() {
12625
+ const result = [];
12626
+ for (const opt of this.options) {
12627
+ if (this.checkedState.get(opt.value) === true) {
12628
+ result.push(opt.value);
12629
+ }
12630
+ }
12631
+ return result;
12632
+ }
12633
+ /**
12634
+ * Move selection in the given direction (+1 or -1), skipping disabled options.
12635
+ * Wraps around at boundaries.
12636
+ */
12637
+ moveSelection(direction) {
12638
+ const selectableCount = this.getSelectableCount();
12639
+ if (selectableCount === 0) return;
12640
+ let next = this.selectedIndex + direction;
12641
+ if (next < 0) next = selectableCount - 1;
12642
+ if (next >= selectableCount) next = 0;
12643
+ this.selectedIndex = next;
12644
+ if (this.selectedIndex < this.scrollOffset) {
12645
+ this.scrollOffset = this.selectedIndex;
12646
+ } else if (this.selectedIndex >= this.scrollOffset + this.maxVisible) {
12647
+ this.scrollOffset = this.selectedIndex - this.maxVisible + 1;
12648
+ }
12649
+ this.renderAll();
12650
+ }
12651
+ /**
12652
+ * Toggle the checked state of the currently selected option.
12653
+ */
12654
+ toggleCurrent() {
12655
+ const selectables = this.filteredOptions.filter((o) => !o.disabled);
12656
+ if (selectables.length === 0 || this.selectedIndex >= selectables.length) return;
12657
+ const opt = selectables[this.selectedIndex];
12658
+ const current = this.checkedState.get(opt.value) === true;
12659
+ this.checkedState.set(opt.value, !current);
12660
+ if (this.selectOptions?.onChange) {
12661
+ this.selectOptions.onChange(this.getCheckedValues());
12662
+ }
12663
+ this.renderAll();
12664
+ }
12665
+ /**
12666
+ * Toggle all non-disabled options: if all are checked, uncheck all; otherwise check all.
12667
+ */
12668
+ toggleAll() {
12669
+ const nonDisabled = this.options.filter((o) => !o.disabled);
12670
+ const allChecked = nonDisabled.every((o) => this.checkedState.get(o.value) === true);
12671
+ for (const opt of nonDisabled) {
12672
+ this.checkedState.set(opt.value, !allChecked);
12673
+ }
12674
+ if (this.selectOptions?.onChange) {
12675
+ this.selectOptions.onChange(this.getCheckedValues());
12676
+ }
12677
+ this.renderAll();
12678
+ }
12679
+ /**
12680
+ * Apply the current filter text to the options list.
12681
+ */
12682
+ applyFilter() {
12683
+ if (this.filter.length === 0) {
12684
+ this.filteredOptions = [...this.options];
12685
+ } else {
12686
+ const lower = this.filter.toLowerCase();
12687
+ this.filteredOptions = this.options.filter(
12688
+ (o) => o.label.toLowerCase().includes(lower)
12689
+ );
12690
+ }
12691
+ this.selectedIndex = 0;
12692
+ this.scrollOffset = 0;
12693
+ this.maxVisible = Math.min(
12694
+ this.filteredOptions.length,
12695
+ this.host.getTerminalRows() - 4
12696
+ );
12697
+ }
12698
+ /**
12699
+ * Clear the filter and restore full option list.
12700
+ */
12701
+ clearFilter() {
12702
+ this.filter = "";
12703
+ this.applyFilter();
12704
+ this.renderAll();
12705
+ }
12706
+ /**
12707
+ * Clear all previously rendered lines and re-render everything.
12708
+ */
12709
+ renderAll() {
12710
+ this.clearRenderedLines();
12711
+ this.renderPrompt();
12712
+ this.renderOptions();
12713
+ this.renderHelpBar();
12714
+ if (this.renderedLines > 0) {
12715
+ this.host.terminal.write(`\x1B[${this.renderedLines}A`);
12716
+ }
12717
+ }
12718
+ /**
12719
+ * Clear all lines we previously rendered (below the cursor).
12720
+ */
12721
+ clearRenderedLines() {
12722
+ if (this.renderedLines > 0) {
12723
+ this.host.terminal.write("\x1B[s");
12724
+ for (let i = 0; i < this.renderedLines; i++) {
12725
+ this.host.terminal.write("\x1B[B\x1B[2K");
12726
+ }
12727
+ this.host.terminal.write("\x1B[u");
12728
+ this.renderedLines = 0;
12729
+ }
12730
+ }
12731
+ /**
12732
+ * Render the prompt line.
12733
+ */
12734
+ renderPrompt() {
12735
+ this.host.terminal.write("\x1B[2K\r");
12736
+ if (this.selectOptions?.searchable) {
12737
+ if (this.filter.length > 0) {
12738
+ this.host.terminal.write(
12739
+ `\x1B[36m?\x1B[0m ${this.promptText}: ${this.filter}`
12740
+ );
12741
+ } else {
12742
+ this.host.terminal.write(
12743
+ `\x1B[36m?\x1B[0m ${this.promptText}: \x1B[2mType to filter...\x1B[0m`
12744
+ );
12745
+ }
12746
+ } else {
12747
+ this.host.terminal.write(`\x1B[36m?\x1B[0m ${this.promptText}:`);
12748
+ }
12749
+ }
12750
+ /**
12751
+ * Render the visible slice of the option list.
12752
+ */
12753
+ renderOptions() {
12754
+ const items = this.buildDisplayList();
12755
+ const selectables = this.filteredOptions.filter((o) => !o.disabled);
12756
+ const visibleStart = this.scrollOffset;
12757
+ const visibleEnd = Math.min(
12758
+ selectables.length,
12759
+ this.scrollOffset + this.maxVisible
12760
+ );
12761
+ if (visibleStart > 0) {
12762
+ this.host.terminal.write(`\r
12763
+ \x1B[2m \u2191 ${visibleStart} more above\x1B[0m`);
12764
+ this.renderedLines++;
12765
+ }
12766
+ let selectableIdx = 0;
12767
+ for (const item of items) {
12768
+ if (item.type === "group-header") {
12769
+ if (this.isGroupVisible(item.groupName, items, visibleStart, visibleEnd)) {
12770
+ this.host.terminal.write(
12771
+ `\r
12772
+ \x1B[35m\u2500\u2500 ${item.groupName} \u2500\u2500\x1B[0m`
12773
+ );
12774
+ this.renderedLines++;
12775
+ }
12776
+ } else {
12777
+ const opt = item.option;
12778
+ if (opt.disabled) {
12779
+ if (this.isDisabledOptionVisible(items, item, visibleStart, visibleEnd)) {
12780
+ this.renderOptionLine(opt, false);
12781
+ this.renderedLines++;
12782
+ }
12783
+ } else {
12784
+ if (selectableIdx >= visibleStart && selectableIdx < visibleEnd) {
12785
+ const isSelected = selectableIdx === this.selectedIndex;
12786
+ this.renderOptionLine(opt, isSelected);
12787
+ this.renderedLines++;
12788
+ }
12789
+ selectableIdx++;
12790
+ }
12791
+ }
12792
+ }
12793
+ const remaining = selectables.length - visibleEnd;
12794
+ if (remaining > 0) {
12795
+ this.host.terminal.write(`\r
12796
+ \x1B[2m \u2193 ${remaining} more below\x1B[0m`);
12797
+ this.renderedLines++;
12798
+ }
12799
+ }
12800
+ /**
12801
+ * Check if a group header should be visible given the current visible range.
12802
+ */
12803
+ isGroupVisible(groupName, items, visibleStart, visibleEnd) {
12804
+ let selectableIdx = 0;
12805
+ for (const item of items) {
12806
+ if (item.type === "option" && !item.option.disabled) {
12807
+ if (item.option.group === groupName && selectableIdx >= visibleStart && selectableIdx < visibleEnd) {
12808
+ return true;
12809
+ }
12810
+ selectableIdx++;
12811
+ }
12812
+ }
12813
+ return false;
12814
+ }
12815
+ /**
12816
+ * Check if a disabled option should be visible (adjacent to visible selectables).
12817
+ */
12818
+ isDisabledOptionVisible(items, disabledItem, visibleStart, visibleEnd) {
12819
+ const idx = items.indexOf(disabledItem);
12820
+ let selectableIdx = 0;
12821
+ for (let i = 0; i < items.length; i++) {
12822
+ if (items[i].type === "option" && !items[i].option.disabled) {
12823
+ if (i > idx) {
12824
+ return selectableIdx >= visibleStart && selectableIdx < visibleEnd;
12825
+ }
12826
+ selectableIdx++;
12827
+ }
12828
+ }
12829
+ return selectableIdx > 0 && selectableIdx - 1 >= visibleStart && selectableIdx - 1 < visibleEnd;
12830
+ }
12831
+ /**
12832
+ * Render a single option line with checkbox indicator.
12833
+ */
12834
+ renderOptionLine(opt, isSelected) {
12835
+ const isChecked = this.checkedState.get(opt.value) === true;
12836
+ const label = this.highlightMatch(opt.label);
12837
+ const desc = opt.description ? ` \x1B[2;3m\u2014 ${opt.description}\x1B[0m` : "";
12838
+ const checkbox = isChecked ? "\x1B[32m\u25C9\x1B[0m" : "\x1B[2m\u25CB\x1B[0m";
12839
+ if (opt.disabled) {
12840
+ const disabledCheckbox = isChecked ? "\x1B[2m\u25C9\x1B[0m" : "\x1B[2m\u25CB\x1B[0m";
12841
+ this.host.terminal.write(
12842
+ `\r
12843
+ ${disabledCheckbox} \x1B[2m${opt.label}${desc} (disabled)\x1B[0m`
12844
+ );
12845
+ } else if (isSelected) {
12846
+ this.host.terminal.write(
12847
+ `\r
12848
+ \x1B[48;5;24m\x1B[38;5;117m\u276F ${checkbox} ${label}${desc}\x1B[0m`
12849
+ );
12850
+ } else {
12851
+ this.host.terminal.write(
12852
+ `\r
12853
+ ${checkbox} ${label}${desc}`
12854
+ );
12855
+ }
12856
+ }
12857
+ /**
12858
+ * Highlight the matched filter text in yellow within the label.
12859
+ */
12860
+ highlightMatch(label) {
12861
+ if (!this.filter || this.filter.length === 0) return label;
12862
+ const lower = label.toLowerCase();
12863
+ const filterLower = this.filter.toLowerCase();
12864
+ const idx = lower.indexOf(filterLower);
12865
+ if (idx < 0) return label;
12866
+ const before = label.substring(0, idx);
12867
+ const match = label.substring(idx, idx + this.filter.length);
12868
+ const after = label.substring(idx + this.filter.length);
12869
+ return `${before}\x1B[33m${match}\x1B[0m${after}`;
12870
+ }
12871
+ /**
12872
+ * Render the help bar at the bottom.
12873
+ */
12874
+ renderHelpBar() {
12875
+ const checkedCount = this.getCheckedValues().length;
12876
+ let help;
12877
+ if (this.selectOptions?.searchable) {
12878
+ if (this.filter.length > 0) {
12879
+ const matchCount = this.filteredOptions.filter((o) => !o.disabled).length;
12880
+ const totalCount = this.options.filter((o) => !o.disabled).length;
12881
+ help = `${checkedCount} selected \xB7 ${matchCount} of ${totalCount} matches \xB7 space to toggle \xB7 enter to confirm`;
12882
+ } else {
12883
+ help = `${checkedCount} selected \xB7 space to toggle \xB7 a to select all \xB7 type to filter \xB7 enter to confirm`;
12884
+ }
12885
+ } else {
12886
+ help = `${checkedCount} selected \xB7 space to toggle \xB7 a to select all \xB7 enter to confirm`;
12887
+ }
12888
+ this.host.terminal.write(`\r
12889
+ \x1B[2m${help}\x1B[0m`);
12890
+ this.renderedLines++;
12891
+ }
12892
+ };
12893
+
12894
+ // src/lib/input/modes/date-input-mode.ts
12895
+ var DateInputMode = class extends InputModeBase {
12896
+ constructor(host, resolve, promptText, options) {
12897
+ super(host, resolve);
12898
+ this.promptText = promptText;
12899
+ this.options = options;
12900
+ this.hasError = false;
12901
+ this.format = options?.format ?? "YYYY-MM-DD";
12902
+ this.separator = this.extractSeparator(this.format);
12903
+ this.buffer = options?.default ?? "";
12904
+ this.cursorPosition = this.buffer.length;
12905
+ }
12906
+ activate() {
12907
+ const prompt = this.buildPrompt();
12908
+ this.host.writeToTerminal(prompt);
12909
+ if (this.buffer) {
12910
+ this.host.writeToTerminal(this.buffer);
12911
+ }
12912
+ }
12913
+ async handleInput(data) {
12914
+ if (this.hasError) {
12915
+ this.hasError = false;
12916
+ this.clearExtraLines();
12917
+ }
12918
+ if (data === "\r") {
12919
+ const error = this.validateBuffer();
12920
+ if (error) {
12921
+ this.hasError = true;
12922
+ this.writeError(error);
12923
+ return;
12924
+ }
12925
+ this.resolveAndPop(this.buffer);
12926
+ } else if (data === "\x7F") {
12927
+ if (this.cursorPosition > 0) {
12928
+ this.buffer = this.buffer.slice(0, this.cursorPosition - 1) + this.buffer.slice(this.cursorPosition);
12929
+ this.cursorPosition--;
12930
+ this.renderLine();
12931
+ }
12932
+ } else if (data === "\x1B[D") {
12933
+ if (this.cursorPosition > 0) {
12934
+ this.cursorPosition--;
12935
+ this.host.terminal.write(data);
12936
+ }
12937
+ } else if (data === "\x1B[C") {
12938
+ if (this.cursorPosition < this.buffer.length) {
12939
+ this.cursorPosition++;
12940
+ this.host.terminal.write(data);
12941
+ }
12942
+ } else if (data.startsWith("\x1B")) ; else {
12943
+ for (const ch of data) {
12944
+ if (ch >= "0" && ch <= "9" || ch === this.separator) {
12945
+ this.buffer = this.buffer.slice(0, this.cursorPosition) + ch + this.buffer.slice(this.cursorPosition);
12946
+ this.cursorPosition++;
12947
+ }
12948
+ }
12949
+ this.renderLine();
12950
+ }
12951
+ }
12952
+ onResize() {
12953
+ this.renderLine();
12954
+ }
12955
+ buildPrompt() {
12956
+ return `\x1B[36m?\x1B[0m ${this.promptText} \x1B[2m(${this.format})\x1B[0m`;
12957
+ }
12958
+ renderLine() {
12959
+ const prompt = this.buildPrompt();
12960
+ this.redrawLine(prompt, this.buffer, this.cursorPosition);
12961
+ }
12962
+ /**
12963
+ * Extract the separator character from a format string.
12964
+ * Returns the first non-alphabetic character found (e.g., '-' from 'YYYY-MM-DD').
12965
+ * Defaults to '-' if none is found.
12966
+ */
12967
+ extractSeparator(format) {
12968
+ for (const ch of format) {
12969
+ if (ch < "A" || ch > "z" || ch > "Z" && ch < "a") {
12970
+ return ch;
12971
+ }
12972
+ }
12973
+ return "-";
12974
+ }
12975
+ /**
12976
+ * Parse the buffer according to the current format string.
12977
+ * Returns { year, month, day } or null if parsing fails.
12978
+ */
12979
+ parseBuffer() {
12980
+ const parts = this.buffer.split(this.separator);
12981
+ const formatParts = this.format.split(this.separator);
12982
+ if (parts.length !== formatParts.length) return null;
12983
+ let year = 0, month = 0, day = 0;
12984
+ for (let i = 0; i < formatParts.length; i++) {
12985
+ const token = formatParts[i].toUpperCase();
12986
+ const value = parseInt(parts[i], 10);
12987
+ if (isNaN(value)) return null;
12988
+ if (token === "YYYY") year = value;
12989
+ else if (token === "MM") month = value;
12990
+ else if (token === "DD") day = value;
12991
+ }
12992
+ return { year, month, day };
12993
+ }
12994
+ /**
12995
+ * Validate the buffer as a real calendar date.
12996
+ * Returns an error message string or null if valid.
12997
+ */
12998
+ validateBuffer() {
12999
+ if (!this.buffer) {
13000
+ return `Please enter a date in ${this.format} format`;
13001
+ }
13002
+ const parsed = this.parseBuffer();
13003
+ if (!parsed) {
13004
+ return `Invalid date format, expected ${this.format}`;
13005
+ }
13006
+ const { year, month, day } = parsed;
13007
+ if (month < 1 || month > 12) {
13008
+ return "Month must be between 1 and 12";
13009
+ }
13010
+ const maxDay = this.daysInMonth(year, month);
13011
+ if (day < 1 || day > maxDay) {
13012
+ return `Day must be between 1 and ${maxDay} for the given month`;
13013
+ }
13014
+ if (this.options?.min) {
13015
+ const minDate = this.parseDate(this.options.min);
13016
+ const current = new Date(year, month - 1, day);
13017
+ if (minDate && current < minDate) {
13018
+ return `Date must not be before ${this.options.min}`;
13019
+ }
13020
+ }
13021
+ if (this.options?.max) {
13022
+ const maxDate = this.parseDate(this.options.max);
13023
+ const current = new Date(year, month - 1, day);
13024
+ if (maxDate && current > maxDate) {
13025
+ return `Date must not be after ${this.options.max}`;
13026
+ }
13027
+ }
13028
+ return null;
13029
+ }
13030
+ /**
13031
+ * Returns the number of days in a given month (accounting for leap years).
13032
+ */
13033
+ daysInMonth(year, month) {
13034
+ return new Date(year, month, 0).getDate();
13035
+ }
13036
+ /**
13037
+ * Parse a date string (in the same format as this mode) into a Date object.
13038
+ */
13039
+ parseDate(dateStr) {
13040
+ const parts = dateStr.split(this.separator);
13041
+ const formatParts = this.format.split(this.separator);
13042
+ if (parts.length !== formatParts.length) return null;
13043
+ let year = 0, month = 0, day = 0;
13044
+ for (let i = 0; i < formatParts.length; i++) {
13045
+ const token = formatParts[i].toUpperCase();
13046
+ const value = parseInt(parts[i], 10);
13047
+ if (isNaN(value)) return null;
13048
+ if (token === "YYYY") year = value;
13049
+ else if (token === "MM") month = value;
13050
+ else if (token === "DD") day = value;
13051
+ }
13052
+ return new Date(year, month - 1, day);
13053
+ }
13054
+ };
13055
+
13056
+ // src/lib/input/modes/file-input-mode.ts
13057
+ var FileInputMode = class extends InputModeBase {
13058
+ constructor(host, resolve, promptText, options) {
13059
+ super(host, resolve);
13060
+ this.promptText = promptText;
13061
+ this.options = options;
13062
+ this.waiting = false;
13063
+ }
13064
+ activate() {
13065
+ const provider = this.host.filePickerProvider;
13066
+ const acceptHint = this.options?.accept ? ` \x1B[2m(${this.options.accept})\x1B[0m` : "";
13067
+ this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}${acceptHint}: `);
13068
+ if (!provider.isSupported) {
13069
+ this.host.writeToTerminal("\x1B[2m(file picker not available)\x1B[0m");
13070
+ this.abort();
13071
+ return;
13072
+ }
13073
+ this.host.writeToTerminal("\x1B[33mOpening file picker...\x1B[0m");
13074
+ this.waiting = true;
13075
+ this.pickFiles(provider);
13076
+ }
13077
+ async pickFiles(provider) {
13078
+ try {
13079
+ let results;
13080
+ if (this.options?.directory) {
13081
+ const dir = await provider.pickDirectory();
13082
+ results = dir ? [dir] : null;
13083
+ } else {
13084
+ results = await provider.pickFiles(this.options);
13085
+ }
13086
+ this.waiting = false;
13087
+ if (!results || this.isResolved) {
13088
+ this.host.terminal.write("\x1B[2K\r");
13089
+ this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}: \x1B[2m(cancelled)\x1B[0m\r
13090
+ `);
13091
+ if (!this.isResolved) this.abort();
13092
+ return;
13093
+ }
13094
+ this.host.terminal.write("\x1B[2K\r");
13095
+ this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}:\r
13096
+ `);
13097
+ let totalSize = 0;
13098
+ for (const file of results) {
13099
+ const sizeStr = this.formatSize(file.size);
13100
+ this.host.writeToTerminal(` \x1B[32m\u2714\x1B[0m ${file.name} \x1B[2m(${sizeStr})\x1B[0m\r
13101
+ `);
13102
+ totalSize += file.size;
13103
+ }
13104
+ if (results.length > 1) {
13105
+ this.host.writeToTerminal(` \x1B[2m${results.length} files selected \xB7 ${this.formatSize(totalSize)} total\x1B[0m\r
13106
+ `);
13107
+ }
13108
+ this.resolveAndPop(results);
13109
+ } catch {
13110
+ this.waiting = false;
13111
+ if (this.isResolved) return;
13112
+ this.host.terminal.write("\x1B[2K\r");
13113
+ this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}: \x1B[31m(error)\x1B[0m\r
13114
+ `);
13115
+ this.abort();
13116
+ }
13117
+ }
13118
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
13119
+ async handleInput(_data) {
13120
+ }
13121
+ formatSize(bytes) {
13122
+ if (bytes < 1024) return `${bytes} B`;
13123
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
13124
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
13125
+ }
13126
+ };
13127
+
13128
+ // src/lib/services/cli-input-reader.ts
13129
+ var CliInputReader = class {
13130
+ constructor(host) {
13131
+ this.host = host;
13132
+ }
13133
+ readLine(prompt, options) {
13134
+ return new Promise((resolve) => {
13135
+ const mode = new LineInputMode(this.host, resolve, prompt, options);
13136
+ this.host.pushMode(mode);
13137
+ });
13138
+ }
13139
+ readPassword(prompt) {
13140
+ return new Promise((resolve) => {
13141
+ const mode = new PasswordInputMode(this.host, resolve, prompt);
13142
+ this.host.pushMode(mode);
13143
+ });
13144
+ }
13145
+ readConfirm(prompt, defaultValue = false) {
13146
+ return new Promise((resolve) => {
13147
+ const mode = new ConfirmInputMode(this.host, resolve, prompt, defaultValue);
13148
+ this.host.pushMode(mode);
13149
+ });
13150
+ }
13151
+ readSelect(prompt, options, selectOptions) {
13152
+ if (!options || options.length === 0) {
13153
+ return Promise.reject(new Error("readSelect requires at least one option"));
13154
+ }
13155
+ return new Promise((resolve) => {
13156
+ const mode = new SelectInputMode(this.host, resolve, prompt, options, selectOptions);
13157
+ this.host.pushMode(mode);
13158
+ });
13159
+ }
13160
+ readSelectInline(prompt, options, selectOptions) {
13161
+ if (!options || options.length === 0) {
13162
+ return Promise.reject(new Error("readSelectInline requires at least one option"));
13163
+ }
13164
+ return new Promise((resolve) => {
13165
+ const mode = new InlineSelectInputMode(this.host, resolve, prompt, options, selectOptions);
13166
+ this.host.pushMode(mode);
13167
+ });
13168
+ }
13169
+ readMultiSelect(prompt, options, selectOptions) {
13170
+ if (!options || options.length === 0) {
13171
+ return Promise.reject(new Error("readMultiSelect requires at least one option"));
13172
+ }
13173
+ return new Promise((resolve) => {
13174
+ const mode = new MultiSelectInputMode(this.host, resolve, prompt, options, selectOptions);
13175
+ this.host.pushMode(mode);
13176
+ });
13177
+ }
13178
+ readNumber(prompt, options) {
13179
+ return new Promise((resolve) => {
13180
+ const mode = new NumberInputMode(this.host, resolve, prompt, options);
13181
+ this.host.pushMode(mode);
13182
+ });
13183
+ }
13184
+ readDate(prompt, options) {
13185
+ return new Promise((resolve) => {
13186
+ const mode = new DateInputMode(this.host, resolve, prompt, options);
13187
+ this.host.pushMode(mode);
13188
+ });
13189
+ }
13190
+ readFile(prompt, options) {
13191
+ return new Promise((resolve) => {
13192
+ const mode = new FileInputMode(this.host, resolve, prompt, options);
13193
+ this.host.pushMode(mode);
13194
+ });
13195
+ }
13196
+ };
13197
+
13198
+ // src/lib/services/file-picker/browser-file-picker-provider.ts
13199
+ var BrowserFilePickerProvider = class {
13200
+ constructor() {
13201
+ this.isSupported = typeof document !== "undefined";
13202
+ }
13203
+ async pickFiles(options) {
13204
+ if (!this.isSupported) return null;
13205
+ if (options?.directory) {
13206
+ const dir = await this.pickDirectory();
13207
+ return dir ? [dir] : null;
13208
+ }
13209
+ return new Promise((resolve) => {
13210
+ const input = document.createElement("input");
13211
+ input.type = "file";
13212
+ input.style.display = "none";
13213
+ if (options?.accept) input.accept = options.accept;
13214
+ if (options?.multiple) input.multiple = true;
13215
+ input.addEventListener("change", async () => {
13216
+ const files = input.files;
13217
+ if (!files || files.length === 0) {
13218
+ resolve(null);
13219
+ input.remove();
13220
+ return;
13221
+ }
13222
+ const readAs = options?.readAs ?? "text";
13223
+ const results = [];
13224
+ for (let i = 0; i < files.length; i++) {
13225
+ const file = files[i];
13226
+ const content = await this.readFile(file, readAs);
13227
+ results.push({
13228
+ name: file.name,
13229
+ content,
13230
+ size: file.size,
13231
+ type: file.type || "application/octet-stream"
13232
+ });
13233
+ }
13234
+ resolve(results);
13235
+ input.remove();
13236
+ });
13237
+ input.addEventListener("cancel", () => {
13238
+ resolve(null);
13239
+ input.remove();
10757
13240
  });
13241
+ document.body.appendChild(input);
13242
+ input.click();
10758
13243
  });
10759
13244
  }
10760
- readMultiSelect(prompt, options) {
10761
- if (!options || options.length === 0) {
10762
- return Promise.reject(
10763
- new Error("readMultiSelect requires at least one option")
10764
- );
10765
- }
13245
+ async pickDirectory() {
13246
+ if (!this.isSupported) return null;
10766
13247
  return new Promise((resolve) => {
10767
- if (this.host.activeInputRequest) {
10768
- throw new Error("Another input request is already active");
10769
- }
10770
- const checkedIndices = /* @__PURE__ */ new Set();
10771
- options.forEach((opt, i) => {
10772
- if (opt.checked) {
10773
- checkedIndices.add(i);
13248
+ const input = document.createElement("input");
13249
+ input.type = "file";
13250
+ input.style.display = "none";
13251
+ input.webkitdirectory = true;
13252
+ input.addEventListener("change", () => {
13253
+ const files = input.files;
13254
+ if (!files || files.length === 0) {
13255
+ resolve(null);
13256
+ } else {
13257
+ const pathParts = files[0].webkitRelativePath?.split("/") ?? [];
13258
+ resolve({
13259
+ name: pathParts[0] || "selected-directory",
13260
+ content: "",
13261
+ size: 0,
13262
+ type: "inode/directory"
13263
+ });
10774
13264
  }
13265
+ input.remove();
10775
13266
  });
10776
- const termRows = this.host.getTerminalRows?.() ?? 24;
10777
- const maxVisible = Math.max(3, termRows - 2);
10778
- this.host.writeToTerminal(prompt + "\r\n");
10779
- const displayLines = this.renderMultiSelectOptions(options, 0, checkedIndices, 0, maxVisible);
10780
- this.host.setActiveInputRequest({
10781
- type: "multi-select",
10782
- promptText: prompt,
10783
- resolve,
10784
- buffer: "",
10785
- cursorPosition: 0,
10786
- options,
10787
- selectedIndex: 0,
10788
- checkedIndices,
10789
- scrollOffset: 0,
10790
- maxVisible,
10791
- displayLines
13267
+ input.addEventListener("cancel", () => {
13268
+ resolve(null);
13269
+ input.remove();
10792
13270
  });
13271
+ document.body.appendChild(input);
13272
+ input.click();
10793
13273
  });
10794
13274
  }
10795
- readNumber(prompt, options) {
10796
- return new Promise((resolve) => {
10797
- if (this.host.activeInputRequest) {
10798
- throw new Error("Another input request is already active");
10799
- }
10800
- let displayPrompt = prompt;
10801
- const hints = [];
10802
- if (options?.min !== void 0 || options?.max !== void 0) {
10803
- const minStr = options?.min !== void 0 ? String(options.min) : "";
10804
- const maxStr = options?.max !== void 0 ? String(options.max) : "";
10805
- if (minStr && maxStr) {
10806
- hints.push(`${minStr}-${maxStr}`);
10807
- } else if (minStr) {
10808
- hints.push(`>=${minStr}`);
10809
- } else if (maxStr) {
10810
- hints.push(`<=${maxStr}`);
10811
- }
10812
- }
10813
- if (options?.default !== void 0) {
10814
- hints.push(`default: ${options.default}`);
10815
- }
10816
- if (hints.length > 0) {
10817
- displayPrompt += ` (${hints.join(", ")}): `;
13275
+ readFile(file, readAs) {
13276
+ return new Promise((resolve, reject) => {
13277
+ const reader = new FileReader();
13278
+ reader.onload = () => resolve(reader.result);
13279
+ reader.onerror = () => reject(reader.error);
13280
+ if (readAs === "arraybuffer") {
13281
+ reader.readAsArrayBuffer(file);
10818
13282
  } else {
10819
- displayPrompt += ": ";
13283
+ reader.readAsText(file);
10820
13284
  }
10821
- this.host.writeToTerminal(displayPrompt);
10822
- this.host.setActiveInputRequest({
10823
- type: "number",
10824
- promptText: displayPrompt,
10825
- resolve,
10826
- buffer: "",
10827
- cursorPosition: 0,
10828
- numberOptions: options
10829
- });
10830
13285
  });
10831
13286
  }
10832
- renderSelectOptions(options, selectedIndex, scrollOffset = 0, maxVisible = options.length) {
10833
- const needsScroll = options.length > maxVisible;
10834
- const itemSlots = needsScroll ? maxVisible - 2 : options.length;
10835
- const visibleCount = Math.max(1, Math.min(itemSlots, options.length));
10836
- const end = Math.min(scrollOffset + visibleCount, options.length);
10837
- let lines = 0;
10838
- if (needsScroll) {
10839
- const upLabel = scrollOffset > 0 ? "\u2191 more" : "";
10840
- this.host.writeToTerminal(` \x1B[2m${upLabel}\x1B[0m\r
10841
- `);
10842
- lines++;
10843
- }
10844
- for (let i = scrollOffset; i < end; i++) {
10845
- const prefix = i === selectedIndex ? " \x1B[36m> " : " ";
10846
- const suffix = i === selectedIndex ? "\x1B[0m" : "";
10847
- this.host.writeToTerminal(
10848
- `${prefix}${options[i].label}${suffix}\r
10849
- `
10850
- );
10851
- lines++;
10852
- }
10853
- if (needsScroll) {
10854
- const downLabel = end < options.length ? "\u2193 more" : "";
10855
- this.host.writeToTerminal(` \x1B[2m${downLabel}\x1B[0m\r
10856
- `);
10857
- lines++;
10858
- }
10859
- return lines;
10860
- }
10861
- renderInlineSelectOptions(options, selectedIndex) {
10862
- return options.map((opt, i) => {
10863
- if (i === selectedIndex) {
10864
- return `\x1B[36m[ ${opt.label} ]\x1B[0m`;
10865
- }
10866
- return ` ${opt.label} `;
10867
- }).join("");
10868
- }
10869
- renderMultiSelectOptions(options, selectedIndex, checkedIndices, scrollOffset = 0, maxVisible = options.length) {
10870
- const needsScroll = options.length > maxVisible;
10871
- const itemSlots = needsScroll ? maxVisible - 2 : options.length;
10872
- const visibleCount = Math.max(1, Math.min(itemSlots, options.length));
10873
- const end = Math.min(scrollOffset + visibleCount, options.length);
10874
- let lines = 0;
10875
- if (needsScroll) {
10876
- const upLabel = scrollOffset > 0 ? "\u2191 more" : "";
10877
- this.host.writeToTerminal(` \x1B[2m${upLabel}\x1B[0m\r
10878
- `);
10879
- lines++;
10880
- }
10881
- for (let i = scrollOffset; i < end; i++) {
10882
- const checkbox = checkedIndices.has(i) ? "[x]" : "[ ]";
10883
- const prefix = i === selectedIndex ? ` \x1B[36m> ${checkbox} ` : ` ${checkbox} `;
10884
- const suffix = i === selectedIndex ? "\x1B[0m" : "";
10885
- this.host.writeToTerminal(
10886
- `${prefix}${options[i].label}${suffix}\r
10887
- `
10888
- );
10889
- lines++;
10890
- }
10891
- if (needsScroll) {
10892
- const downLabel = end < options.length ? "\u2193 more" : "";
10893
- this.host.writeToTerminal(` \x1B[2m${downLabel}\x1B[0m\r
10894
- `);
10895
- lines++;
10896
- }
10897
- return lines;
13287
+ };
13288
+
13289
+ // src/lib/services/file-picker/noop-file-picker-provider.ts
13290
+ var NoopFilePickerProvider = class {
13291
+ constructor() {
13292
+ this.isSupported = false;
10898
13293
  }
10899
- createInputRequest(type, prompt) {
10900
- return new Promise((resolve) => {
10901
- if (this.host.activeInputRequest) {
10902
- throw new Error("Another input request is already active");
10903
- }
10904
- this.host.writeToTerminal(prompt);
10905
- this.host.setActiveInputRequest({
10906
- type,
10907
- promptText: prompt,
10908
- resolve,
10909
- buffer: "",
10910
- cursorPosition: 0
10911
- });
10912
- });
13294
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
13295
+ async pickFiles(_options) {
13296
+ return null;
13297
+ }
13298
+ async pickDirectory() {
13299
+ return null;
10913
13300
  }
10914
13301
  };
10915
13302
 
@@ -11434,400 +13821,6 @@ var CommandLineMode = class {
11434
13821
  }
11435
13822
  };
11436
13823
 
11437
- // src/lib/input/reader-mode.ts
11438
- var ReaderMode = class {
11439
- constructor(host) {
11440
- this.host = host;
11441
- }
11442
- async handleInput(data) {
11443
- const request = this.host.getActiveInputRequest();
11444
- if (!request) {
11445
- return;
11446
- }
11447
- switch (request.type) {
11448
- case "line":
11449
- this.handleLineInput(request, data);
11450
- break;
11451
- case "password":
11452
- this.handlePasswordInput(request, data);
11453
- break;
11454
- case "confirm":
11455
- this.handleConfirmInput(request, data);
11456
- break;
11457
- case "select":
11458
- this.handleSelectInput(request, data);
11459
- break;
11460
- case "select-inline":
11461
- this.handleSelectInlineInput(request, data);
11462
- break;
11463
- case "multi-select":
11464
- this.handleMultiSelectInput(request, data);
11465
- break;
11466
- case "number":
11467
- this.handleNumberInput(request, data);
11468
- break;
11469
- }
11470
- }
11471
- handleKeyEvent(event) {
11472
- if (event.code === "KeyC" && event.ctrlKey) {
11473
- const request = this.host.getActiveInputRequest();
11474
- if (request) {
11475
- request.resolve(null);
11476
- this.host.setActiveInputRequest(null);
11477
- this.host.terminal.writeln("");
11478
- this.host.popMode();
11479
- }
11480
- return false;
11481
- }
11482
- if (event.code === "Escape") {
11483
- const request = this.host.getActiveInputRequest();
11484
- if (request) {
11485
- request.resolve(null);
11486
- this.host.setActiveInputRequest(null);
11487
- this.host.terminal.writeln("");
11488
- this.host.popMode();
11489
- }
11490
- return false;
11491
- }
11492
- return true;
11493
- }
11494
- handleLineInput(request, data) {
11495
- if (data === "\r") {
11496
- this.host.terminal.write("\r\n");
11497
- const value = request.buffer;
11498
- this.host.setActiveInputRequest(null);
11499
- this.host.popMode();
11500
- request.resolve(value);
11501
- } else if (data === "\x7F") {
11502
- if (request.cursorPosition > 0) {
11503
- request.buffer = request.buffer.slice(0, request.cursorPosition - 1) + request.buffer.slice(request.cursorPosition);
11504
- request.cursorPosition--;
11505
- this.redrawReaderLine(request, request.buffer);
11506
- }
11507
- } else if (data === "\x1B[D") {
11508
- if (request.cursorPosition > 0) {
11509
- request.cursorPosition--;
11510
- this.host.terminal.write(data);
11511
- }
11512
- } else if (data === "\x1B[C") {
11513
- if (request.cursorPosition < request.buffer.length) {
11514
- request.cursorPosition++;
11515
- this.host.terminal.write(data);
11516
- }
11517
- } else if (data.startsWith("\x1B")) ; else {
11518
- const text = data.replace(/[\r\n]+/g, "");
11519
- request.buffer = request.buffer.slice(0, request.cursorPosition) + text + request.buffer.slice(request.cursorPosition);
11520
- request.cursorPosition += text.length;
11521
- this.redrawReaderLine(request, request.buffer);
11522
- }
11523
- }
11524
- handlePasswordInput(request, data) {
11525
- if (data === "\r") {
11526
- this.host.terminal.write("\r\n");
11527
- const value = request.buffer;
11528
- this.host.setActiveInputRequest(null);
11529
- this.host.popMode();
11530
- request.resolve(value);
11531
- } else if (data === "\x7F") {
11532
- if (request.cursorPosition > 0) {
11533
- request.buffer = request.buffer.slice(0, request.cursorPosition - 1) + request.buffer.slice(request.cursorPosition);
11534
- request.cursorPosition--;
11535
- this.redrawReaderLine(
11536
- request,
11537
- "*".repeat(request.buffer.length)
11538
- );
11539
- }
11540
- } else if (data.startsWith("\x1B")) ; else {
11541
- const text = data.replace(/[\r\n]+/g, "");
11542
- request.buffer = request.buffer.slice(0, request.cursorPosition) + text + request.buffer.slice(request.cursorPosition);
11543
- request.cursorPosition += text.length;
11544
- this.redrawReaderLine(request, "*".repeat(request.buffer.length));
11545
- }
11546
- }
11547
- handleConfirmInput(request, data) {
11548
- if (data === "\r") {
11549
- this.host.terminal.write("\r\n");
11550
- this.host.setActiveInputRequest(null);
11551
- this.host.popMode();
11552
- const buf = request.buffer.toLowerCase();
11553
- if (buf === "y") {
11554
- request.resolve(true);
11555
- } else if (buf === "n") {
11556
- request.resolve(false);
11557
- } else {
11558
- request.resolve(request.defaultValue ?? false);
11559
- }
11560
- } else if (data === "\x7F") {
11561
- if (request.cursorPosition > 0) {
11562
- request.buffer = request.buffer.slice(0, -1);
11563
- request.cursorPosition--;
11564
- this.redrawReaderLine(request, request.buffer);
11565
- }
11566
- } else if (data.startsWith("\x1B")) ; else {
11567
- const char = data.toLowerCase();
11568
- if (char === "y" || char === "n") {
11569
- request.buffer = data;
11570
- request.cursorPosition = 1;
11571
- this.redrawReaderLine(request, request.buffer);
11572
- }
11573
- }
11574
- }
11575
- handleSelectInput(request, data) {
11576
- const options = request.options;
11577
- const selectedIndex = request.selectedIndex;
11578
- if (data === "\r") {
11579
- this.host.terminal.write("\r\n");
11580
- this.host.setActiveInputRequest(null);
11581
- this.host.popMode();
11582
- request.resolve(options[selectedIndex].value);
11583
- } else if (data === "\x1B[A") {
11584
- if (selectedIndex > 0) {
11585
- request.selectedIndex = selectedIndex - 1;
11586
- this.adjustScrollOffset(request);
11587
- this.redrawSelectOptions(request);
11588
- request.onChange?.(options[request.selectedIndex].value);
11589
- }
11590
- } else if (data === "\x1B[B") {
11591
- if (selectedIndex < options.length - 1) {
11592
- request.selectedIndex = selectedIndex + 1;
11593
- this.adjustScrollOffset(request);
11594
- this.redrawSelectOptions(request);
11595
- request.onChange?.(options[request.selectedIndex].value);
11596
- }
11597
- }
11598
- }
11599
- adjustScrollOffset(request) {
11600
- const options = request.options;
11601
- const maxVisible = request.maxVisible ?? options.length;
11602
- const needsScroll = options.length > maxVisible;
11603
- const itemSlots = needsScroll ? maxVisible - 2 : options.length;
11604
- const visibleCount = Math.max(1, Math.min(itemSlots, options.length));
11605
- const scrollOffset = request.scrollOffset ?? 0;
11606
- const selectedIndex = request.selectedIndex;
11607
- if (selectedIndex < scrollOffset) {
11608
- request.scrollOffset = selectedIndex;
11609
- } else if (selectedIndex >= scrollOffset + visibleCount) {
11610
- request.scrollOffset = selectedIndex - visibleCount + 1;
11611
- }
11612
- }
11613
- redrawReaderLine(request, displayText) {
11614
- this.host.terminal.write("\x1B[2K\r");
11615
- this.host.terminal.write(request.promptText + displayText);
11616
- const cursorOffset = request.buffer.length - request.cursorPosition;
11617
- if (cursorOffset > 0) {
11618
- this.host.terminal.write(`\x1B[${cursorOffset}D`);
11619
- }
11620
- }
11621
- redrawSelectOptions(request) {
11622
- const options = request.options;
11623
- const selectedIndex = request.selectedIndex;
11624
- const maxVisible = request.maxVisible ?? options.length;
11625
- const scrollOffset = request.scrollOffset ?? 0;
11626
- const displayLines = request.displayLines ?? options.length;
11627
- const needsScroll = options.length > maxVisible;
11628
- const itemSlots = needsScroll ? maxVisible - 2 : options.length;
11629
- const visibleCount = Math.max(1, Math.min(itemSlots, options.length));
11630
- const end = Math.min(scrollOffset + visibleCount, options.length);
11631
- if (displayLines > 0) {
11632
- this.host.terminal.write(`\x1B[${displayLines}A`);
11633
- }
11634
- if (needsScroll) {
11635
- this.host.terminal.write("\x1B[2K\r");
11636
- const upLabel = scrollOffset > 0 ? "\u2191 more" : "";
11637
- this.host.terminal.write(` \x1B[2m${upLabel}\x1B[0m\r
11638
- `);
11639
- }
11640
- for (let i = scrollOffset; i < end; i++) {
11641
- this.host.terminal.write("\x1B[2K\r");
11642
- const prefix = i === selectedIndex ? " \x1B[36m> " : " ";
11643
- const suffix = i === selectedIndex ? "\x1B[0m" : "";
11644
- this.host.terminal.write(
11645
- `${prefix}${options[i].label}${suffix}\r
11646
- `
11647
- );
11648
- }
11649
- if (needsScroll) {
11650
- this.host.terminal.write("\x1B[2K\r");
11651
- const downLabel = end < options.length ? "\u2193 more" : "";
11652
- this.host.terminal.write(` \x1B[2m${downLabel}\x1B[0m\r
11653
- `);
11654
- }
11655
- }
11656
- handleSelectInlineInput(request, data) {
11657
- const options = request.options;
11658
- const selectedIndex = request.selectedIndex;
11659
- if (data === "\r") {
11660
- this.host.terminal.write("\r\n");
11661
- this.host.setActiveInputRequest(null);
11662
- this.host.popMode();
11663
- request.resolve(options[selectedIndex].value);
11664
- } else if (data === "\x1B[D") {
11665
- if (selectedIndex > 0) {
11666
- request.selectedIndex = selectedIndex - 1;
11667
- this.redrawInlineSelectOptions(request);
11668
- request.onChange?.(options[request.selectedIndex].value);
11669
- }
11670
- } else if (data === "\x1B[C") {
11671
- if (selectedIndex < options.length - 1) {
11672
- request.selectedIndex = selectedIndex + 1;
11673
- this.redrawInlineSelectOptions(request);
11674
- request.onChange?.(options[request.selectedIndex].value);
11675
- }
11676
- }
11677
- }
11678
- redrawInlineSelectOptions(request) {
11679
- const options = request.options;
11680
- const selectedIndex = request.selectedIndex;
11681
- this.host.terminal.write("\x1B[2K\r");
11682
- const inlineText = options.map((opt, i) => {
11683
- if (i === selectedIndex) {
11684
- return `\x1B[36m[ ${opt.label} ]\x1B[0m`;
11685
- }
11686
- return ` ${opt.label} `;
11687
- }).join("");
11688
- this.host.terminal.write(`${request.promptText} ${inlineText}`);
11689
- }
11690
- handleMultiSelectInput(request, data) {
11691
- const options = request.options;
11692
- const selectedIndex = request.selectedIndex;
11693
- const checkedIndices = request.checkedIndices;
11694
- if (data === "\r") {
11695
- this.host.terminal.write("\r\n");
11696
- this.host.setActiveInputRequest(null);
11697
- this.host.popMode();
11698
- const selected = options.filter((_, i) => checkedIndices.has(i)).map((opt) => opt.value);
11699
- request.resolve(selected);
11700
- } else if (data === " ") {
11701
- if (checkedIndices.has(selectedIndex)) {
11702
- checkedIndices.delete(selectedIndex);
11703
- } else {
11704
- checkedIndices.add(selectedIndex);
11705
- }
11706
- this.redrawMultiSelectOptions(request);
11707
- } else if (data === "\x1B[A") {
11708
- if (selectedIndex > 0) {
11709
- request.selectedIndex = selectedIndex - 1;
11710
- this.adjustScrollOffset(request);
11711
- this.redrawMultiSelectOptions(request);
11712
- }
11713
- } else if (data === "\x1B[B") {
11714
- if (selectedIndex < options.length - 1) {
11715
- request.selectedIndex = selectedIndex + 1;
11716
- this.adjustScrollOffset(request);
11717
- this.redrawMultiSelectOptions(request);
11718
- }
11719
- }
11720
- }
11721
- redrawMultiSelectOptions(request) {
11722
- const options = request.options;
11723
- const selectedIndex = request.selectedIndex;
11724
- const checkedIndices = request.checkedIndices;
11725
- const maxVisible = request.maxVisible ?? options.length;
11726
- const scrollOffset = request.scrollOffset ?? 0;
11727
- const displayLines = request.displayLines ?? options.length;
11728
- const needsScroll = options.length > maxVisible;
11729
- const itemSlots = needsScroll ? maxVisible - 2 : options.length;
11730
- const visibleCount = Math.max(1, Math.min(itemSlots, options.length));
11731
- const end = Math.min(scrollOffset + visibleCount, options.length);
11732
- if (displayLines > 0) {
11733
- this.host.terminal.write(`\x1B[${displayLines}A`);
11734
- }
11735
- if (needsScroll) {
11736
- this.host.terminal.write("\x1B[2K\r");
11737
- const upLabel = scrollOffset > 0 ? "\u2191 more" : "";
11738
- this.host.terminal.write(` \x1B[2m${upLabel}\x1B[0m\r
11739
- `);
11740
- }
11741
- for (let i = scrollOffset; i < end; i++) {
11742
- this.host.terminal.write("\x1B[2K\r");
11743
- const checkbox = checkedIndices.has(i) ? "[x]" : "[ ]";
11744
- const prefix = i === selectedIndex ? ` \x1B[36m> ${checkbox} ` : ` ${checkbox} `;
11745
- const suffix = i === selectedIndex ? "\x1B[0m" : "";
11746
- this.host.terminal.write(
11747
- `${prefix}${options[i].label}${suffix}\r
11748
- `
11749
- );
11750
- }
11751
- if (needsScroll) {
11752
- this.host.terminal.write("\x1B[2K\r");
11753
- const downLabel = end < options.length ? "\u2193 more" : "";
11754
- this.host.terminal.write(` \x1B[2m${downLabel}\x1B[0m\r
11755
- `);
11756
- }
11757
- }
11758
- handleNumberInput(request, data) {
11759
- if (data === "\r") {
11760
- const numOptions = request.numberOptions;
11761
- if (request.buffer === "") {
11762
- if (numOptions?.default !== void 0) {
11763
- this.host.terminal.write("\r\n");
11764
- this.host.setActiveInputRequest(null);
11765
- this.host.popMode();
11766
- request.resolve(numOptions.default);
11767
- } else {
11768
- this.host.terminal.write("\r\n");
11769
- this.host.terminal.write(
11770
- "\x1B[31mPlease enter a number.\x1B[0m\r\n"
11771
- );
11772
- this.host.terminal.write(request.promptText);
11773
- }
11774
- return;
11775
- }
11776
- const value = Number(request.buffer);
11777
- if (isNaN(value)) {
11778
- this.host.terminal.write("\r\n");
11779
- this.host.terminal.write("\x1B[31mInvalid number.\x1B[0m\r\n");
11780
- request.buffer = "";
11781
- request.cursorPosition = 0;
11782
- this.host.terminal.write(request.promptText);
11783
- return;
11784
- }
11785
- if (numOptions?.min !== void 0 && value < numOptions.min) {
11786
- this.host.terminal.write("\r\n");
11787
- this.host.terminal.write(
11788
- `\x1B[31mValue must be at least ${numOptions.min}.\x1B[0m\r
11789
- `
11790
- );
11791
- request.buffer = "";
11792
- request.cursorPosition = 0;
11793
- this.host.terminal.write(request.promptText);
11794
- return;
11795
- }
11796
- if (numOptions?.max !== void 0 && value > numOptions.max) {
11797
- this.host.terminal.write("\r\n");
11798
- this.host.terminal.write(
11799
- `\x1B[31mValue must be at most ${numOptions.max}.\x1B[0m\r
11800
- `
11801
- );
11802
- request.buffer = "";
11803
- request.cursorPosition = 0;
11804
- this.host.terminal.write(request.promptText);
11805
- return;
11806
- }
11807
- this.host.terminal.write("\r\n");
11808
- this.host.setActiveInputRequest(null);
11809
- this.host.popMode();
11810
- request.resolve(value);
11811
- } else if (data === "\x7F") {
11812
- if (request.cursorPosition > 0) {
11813
- request.buffer = request.buffer.slice(0, request.cursorPosition - 1) + request.buffer.slice(request.cursorPosition);
11814
- request.cursorPosition--;
11815
- this.redrawReaderLine(request, request.buffer);
11816
- }
11817
- } else if (data === "-") {
11818
- if (request.cursorPosition === 0 && !request.buffer.includes("-")) {
11819
- request.buffer = "-" + request.buffer;
11820
- request.cursorPosition = 1;
11821
- this.redrawReaderLine(request, request.buffer);
11822
- }
11823
- } else if (/^\d$/.test(data)) {
11824
- request.buffer = request.buffer.slice(0, request.cursorPosition) + data + request.buffer.slice(request.cursorPosition);
11825
- request.cursorPosition += 1;
11826
- this.redrawReaderLine(request, request.buffer);
11827
- }
11828
- }
11829
- };
11830
-
11831
13824
  // src/lib/input/raw-mode.ts
11832
13825
  var RawMode = class {
11833
13826
  constructor(processor, context2, fullScreen = false) {
@@ -11920,7 +13913,6 @@ var CliExecutionContext = class {
11920
13913
  this.completionEngine = new CliCompletionEngine();
11921
13914
  this.promptLength = 0;
11922
13915
  this.lineBuffer = new CliLineBuffer();
11923
- this._activeInputRequest = null;
11924
13916
  this.modeStack = [];
11925
13917
  this.managedTimers = /* @__PURE__ */ new Set();
11926
13918
  this.resizeDisposable = null;
@@ -11959,6 +13951,7 @@ var CliExecutionContext = class {
11959
13951
  this.textAnimator = textAnimator;
11960
13952
  this.clipboard = new CliClipboard(this);
11961
13953
  this.process = new CliExecutionProcess(this);
13954
+ this.filePickerProvider = typeof document !== "undefined" ? new BrowserFilePickerProvider() : new NoopFilePickerProvider();
11962
13955
  this.reader = new CliInputReader(this);
11963
13956
  this.logger = deps.logger;
11964
13957
  this.logger.setCliLogLevel(cliOptions?.logLevel || cliCore.CliLogLevel.ERROR);
@@ -11971,26 +13964,15 @@ var CliExecutionContext = class {
11971
13964
  );
11972
13965
  }
11973
13966
  // -- Public API (ICliExecutionContext) --
11974
- get activeInputRequest() {
11975
- return this._activeInputRequest;
11976
- }
11977
- /**
11978
- * Sets the active input request. When a non-null request is provided,
11979
- * pushes ReaderMode onto the mode stack. ReaderMode pops itself on
11980
- * completion, so passing null here does NOT pop the mode.
11981
- */
11982
- setActiveInputRequest(request) {
11983
- this._activeInputRequest = request;
11984
- if (request !== null) {
11985
- this.pushMode(new ReaderMode(this));
11986
- }
11987
- }
11988
13967
  writeToTerminal(text) {
11989
13968
  this.terminal.write(text);
11990
13969
  }
11991
13970
  getTerminalRows() {
11992
13971
  return this.terminal.rows;
11993
13972
  }
13973
+ getTerminalCols() {
13974
+ return this.terminal.cols;
13975
+ }
11994
13976
  get currentLine() {
11995
13977
  return this.lineBuffer.text;
11996
13978
  }
@@ -12095,7 +14077,12 @@ var CliExecutionContext = class {
12095
14077
  );
12096
14078
  return;
12097
14079
  }
12098
- if (this.isProgressRunning() || this.isRawModeActive() || this._activeInputRequest) {
14080
+ if (this.isProgressRunning() || this.isRawModeActive()) {
14081
+ return;
14082
+ }
14083
+ const mode = this.currentMode;
14084
+ if (mode?.onResize) {
14085
+ mode.onResize(this.terminal.cols, this.terminal.rows);
12099
14086
  return;
12100
14087
  }
12101
14088
  const buf = this.terminal.buffer.active;
@@ -12209,10 +14196,6 @@ var CliExecutionContext = class {
12209
14196
  getExecutionContext() {
12210
14197
  return this;
12211
14198
  }
12212
- // -- ReaderModeHost interface --
12213
- getActiveInputRequest() {
12214
- return this._activeInputRequest;
12215
- }
12216
14199
  // -- Managed timer cleanup --
12217
14200
  clearAllManagedTimers() {
12218
14201
  for (const timer of this.managedTimers) {
@@ -12655,6 +14638,7 @@ var CliCommandExecutor = class {
12655
14638
  const aliasProcessor = this.registry.findProcessor("alias", []);
12656
14639
  const aliases = aliasProcessor?.userAliases ?? {};
12657
14640
  if (aliases[mainCommand]) {
14641
+ this.completeProcess(processEntry, context2, 0);
12658
14642
  return await this.executeSingleCommand(
12659
14643
  aliases[mainCommand],
12660
14644
  data,
@@ -12667,6 +14651,7 @@ var CliCommandExecutor = class {
12667
14651
  context2
12668
14652
  );
12669
14653
  if (executed) {
14654
+ this.completeProcess(processEntry, context2, 0);
12670
14655
  process.end();
12671
14656
  return;
12672
14657
  }
@@ -12681,6 +14666,7 @@ var CliCommandExecutor = class {
12681
14666
  context2.writer.writeInfo(
12682
14667
  `\u{1F4E6} Use ${context2.writer.wrapInColor("pkg add <name>", cliCore.CliForegroundColor.Cyan)} to install additional commands`
12683
14668
  );
14669
+ this.failProcess(processEntry, context2);
12684
14670
  context2.process.exit(-1, {
12685
14671
  silent: true
12686
14672
  });
@@ -12699,6 +14685,7 @@ var CliCommandExecutor = class {
12699
14685
  continue;
12700
14686
  }
12701
14687
  if (await handler.handle(args, processor, commandToProcess, context2)) {
14688
+ this.completeProcess(processEntry, context2, 0);
12702
14689
  process.end();
12703
14690
  return;
12704
14691
  }
@@ -12767,26 +14754,18 @@ var CliCommandExecutor = class {
12767
14754
  if (!process.outputCalled && capturingWriter.hasOutput()) {
12768
14755
  process.data = capturingWriter.getCapturedData();
12769
14756
  }
12770
- if (processEntry) {
12771
- try {
12772
- const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
12773
- procRegistry.complete(processEntry.pid, context2.process.exitCode ?? 0);
12774
- } catch {
12775
- }
12776
- }
14757
+ this.completeProcess(processEntry, context2, context2.process.exitCode ?? 0);
12777
14758
  if (!process.exited) {
12778
14759
  process.end();
12779
14760
  }
12780
14761
  } catch (e) {
12781
14762
  context2.spinner?.hide();
12782
- if (processEntry) {
12783
- try {
12784
- const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
12785
- procRegistry.fail(processEntry.pid);
12786
- } catch {
12787
- }
12788
- }
12789
14763
  if (e instanceof ProcessExitedError) {
14764
+ if (e.code === 0) {
14765
+ this.completeProcess(processEntry, context2, 0);
14766
+ } else {
14767
+ this.failProcess(processEntry, context2);
14768
+ }
12790
14769
  cancellable?.cancel();
12791
14770
  context2.abort?.();
12792
14771
  if (e.code !== 0) {
@@ -12799,6 +14778,7 @@ var CliCommandExecutor = class {
12799
14778
  );
12800
14779
  }
12801
14780
  } else {
14781
+ this.failProcess(processEntry, context2);
12802
14782
  context2.writer.writeError(`Error executing command: ${e}`);
12803
14783
  context2.process.exit(-1);
12804
14784
  }
@@ -12806,6 +14786,22 @@ var CliCommandExecutor = class {
12806
14786
  abortSub.unsubscribe();
12807
14787
  }
12808
14788
  }
14789
+ completeProcess(processEntry, context2, exitCode) {
14790
+ if (!processEntry) return;
14791
+ try {
14792
+ const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
14793
+ procRegistry.complete(processEntry.pid, exitCode);
14794
+ } catch {
14795
+ }
14796
+ }
14797
+ failProcess(processEntry, context2) {
14798
+ if (!processEntry) return;
14799
+ try {
14800
+ const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
14801
+ procRegistry.fail(processEntry.pid);
14802
+ } catch {
14803
+ }
14804
+ }
12809
14805
  async writeStderrToFile(filePath, context2) {
12810
14806
  const stderr = this.lastCapturingWriter?.getCapturedStderr();
12811
14807
  if (!stderr) return;
@@ -13886,6 +15882,167 @@ var CliStateStoreManager = class {
13886
15882
  }
13887
15883
  };
13888
15884
 
15885
+ // src/lib/editor/syntax/registry.ts
15886
+ var SyntaxHighlighterRegistry = class {
15887
+ constructor() {
15888
+ this.highlighters = /* @__PURE__ */ new Map();
15889
+ this.extensionMap = /* @__PURE__ */ new Map();
15890
+ }
15891
+ register(highlighter) {
15892
+ this.highlighters.set(highlighter.id, highlighter);
15893
+ for (const ext of highlighter.extensions) {
15894
+ this.extensionMap.set(ext, highlighter.id);
15895
+ }
15896
+ }
15897
+ unregister(id) {
15898
+ const h = this.highlighters.get(id);
15899
+ if (h) {
15900
+ for (const ext of h.extensions) {
15901
+ this.extensionMap.delete(ext);
15902
+ }
15903
+ this.highlighters.delete(id);
15904
+ }
15905
+ }
15906
+ getByExtension(ext) {
15907
+ const id = this.extensionMap.get(ext);
15908
+ return id ? this.highlighters.get(id) ?? null : null;
15909
+ }
15910
+ getRegisteredLanguages() {
15911
+ return Array.from(this.highlighters.keys());
15912
+ }
15913
+ };
15914
+
15915
+ // src/lib/editor/syntax/highlighters/json-highlighter.ts
15916
+ var JsonHighlighter = class {
15917
+ constructor() {
15918
+ this.id = "json";
15919
+ this.extensions = [".json", ".jsonc"];
15920
+ this.initialState = 0;
15921
+ }
15922
+ getRules(state) {
15923
+ if (state === 1) {
15924
+ return [
15925
+ { pattern: /.*?\*\//, tokenType: "comment" },
15926
+ { pattern: /.*/, tokenType: "comment" }
15927
+ ];
15928
+ }
15929
+ return [
15930
+ { pattern: /\/\/.*$/, tokenType: "comment" },
15931
+ { pattern: /\/\*.*?\*\//, tokenType: "comment" },
15932
+ { pattern: /\/\*.*$/, tokenType: "comment" },
15933
+ { pattern: /"(?:[^"\\]|\\.)*"/, tokenType: "string" },
15934
+ { pattern: /-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/, tokenType: "number" },
15935
+ { pattern: /\b(?:true|false|null)\b/, tokenType: "keyword" },
15936
+ { pattern: /[{}\[\]:,]/, tokenType: "punctuation" }
15937
+ ];
15938
+ }
15939
+ getNextLineState(line, currentState) {
15940
+ if (currentState === 1) {
15941
+ return line.includes("*/") ? 0 : 1;
15942
+ }
15943
+ const stripped = line.replace(/"(?:[^"\\]|\\.)*"/g, "").replace(/\/\/.*$/, "");
15944
+ const openIdx = stripped.lastIndexOf("/*");
15945
+ if (openIdx === -1) return 0;
15946
+ const closeIdx = stripped.indexOf("*/", openIdx);
15947
+ return closeIdx === -1 ? 1 : 0;
15948
+ }
15949
+ };
15950
+
15951
+ // src/lib/editor/syntax/highlighters/html-highlighter.ts
15952
+ var HtmlHighlighter = class {
15953
+ constructor() {
15954
+ this.id = "html";
15955
+ this.extensions = [".html", ".htm", ".xml", ".svg"];
15956
+ this.initialState = 0;
15957
+ }
15958
+ getRules(state) {
15959
+ if (state === 1) {
15960
+ return [
15961
+ { pattern: /.*?-->/, tokenType: "comment" },
15962
+ { pattern: /.*/, tokenType: "comment" }
15963
+ ];
15964
+ }
15965
+ return [
15966
+ { pattern: /<!--.*?-->/, tokenType: "comment" },
15967
+ { pattern: /<!--.*$/, tokenType: "comment" },
15968
+ { pattern: /(<\/?)([\w-]+)/, tokenType: "tag", captureGroup: 2 },
15969
+ { pattern: /"[^"]*"/, tokenType: "value" },
15970
+ { pattern: /'[^']*'/, tokenType: "value" },
15971
+ { pattern: /([\w-]+)(\s*=)/, tokenType: "attribute", captureGroup: 1 },
15972
+ { pattern: /\/?>/, tokenType: "punctuation" },
15973
+ { pattern: /<\/?/, tokenType: "punctuation" },
15974
+ { pattern: /=/, tokenType: "punctuation" }
15975
+ ];
15976
+ }
15977
+ getNextLineState(line, currentState) {
15978
+ if (currentState === 1) {
15979
+ return line.includes("-->") ? 0 : 1;
15980
+ }
15981
+ const openIdx = line.lastIndexOf("<!--");
15982
+ if (openIdx === -1) return 0;
15983
+ const closeIdx = line.indexOf("-->", openIdx);
15984
+ return closeIdx === -1 ? 1 : 0;
15985
+ }
15986
+ };
15987
+
15988
+ // src/lib/editor/syntax/highlighters/markdown-highlighter.ts
15989
+ var MarkdownHighlighter = class {
15990
+ constructor() {
15991
+ this.id = "markdown";
15992
+ this.extensions = [".md", ".mdx", ".markdown"];
15993
+ this.initialState = 0;
15994
+ }
15995
+ getRules(state) {
15996
+ if (state === 1) {
15997
+ return [
15998
+ { pattern: /^```/, tokenType: "string" },
15999
+ { pattern: /.*/, tokenType: "code" }
16000
+ ];
16001
+ }
16002
+ return [
16003
+ { pattern: /^#{1,6}\s+.*$/, tokenType: "heading" },
16004
+ { pattern: /^```/, tokenType: "string" },
16005
+ { pattern: /`[^`]+`/, tokenType: "code" },
16006
+ { pattern: /\[[^\]]+\]\([^)]+\)/, tokenType: "link" },
16007
+ { pattern: /\*\*[^*]+\*\*/, tokenType: "bold" },
16008
+ { pattern: /__[^_]+__/, tokenType: "bold" },
16009
+ { pattern: /\*[^*]+\*/, tokenType: "italic" },
16010
+ { pattern: /_[^_]+_/, tokenType: "italic" },
16011
+ { pattern: /^[-*>]\s/, tokenType: "punctuation" }
16012
+ ];
16013
+ }
16014
+ getNextLineState(line, currentState) {
16015
+ const trimmed = line.trimStart();
16016
+ if (trimmed.startsWith("```")) {
16017
+ return currentState === 0 ? 1 : 0;
16018
+ }
16019
+ return currentState;
16020
+ }
16021
+ };
16022
+
16023
+ // src/lib/editor/syntax/highlighters/yaml-highlighter.ts
16024
+ var YamlHighlighter = class {
16025
+ constructor() {
16026
+ this.id = "yaml";
16027
+ this.extensions = [".yaml", ".yml"];
16028
+ this.initialState = 0;
16029
+ }
16030
+ getRules() {
16031
+ return [
16032
+ { pattern: /#.*$/, tokenType: "comment" },
16033
+ { pattern: /"(?:[^"\\]|\\.)*"/, tokenType: "string" },
16034
+ { pattern: /'[^']*'/, tokenType: "string" },
16035
+ { pattern: /\b(?:true|false|null|~)\b/, tokenType: "keyword" },
16036
+ { pattern: /-?\d+(?:\.\d+)?/, tokenType: "number" },
16037
+ { pattern: /([\w][\w.\-]*)(\s*:)/, tokenType: "tag", captureGroup: 1 },
16038
+ { pattern: /[-:\[\]{}]/, tokenType: "punctuation" }
16039
+ ];
16040
+ }
16041
+ getNextLineState() {
16042
+ return 0;
16043
+ }
16044
+ };
16045
+
13889
16046
  // src/lib/services/cli-translation-service.ts
13890
16047
  var CliTranslationService = class {
13891
16048
  constructor() {
@@ -14864,6 +17021,12 @@ var CliEngine = class {
14864
17021
  }
14865
17022
  ]);
14866
17023
  }
17024
+ const syntaxRegistry = new SyntaxHighlighterRegistry();
17025
+ syntaxRegistry.register(new JsonHighlighter());
17026
+ syntaxRegistry.register(new HtmlHighlighter());
17027
+ syntaxRegistry.register(new MarkdownHighlighter());
17028
+ syntaxRegistry.register(new YamlHighlighter());
17029
+ services.set([{ provide: "syntax-highlighter-registry", useValue: syntaxRegistry }]);
14867
17030
  if (!pendingTokens.has(ICliEnvironment_TOKEN)) {
14868
17031
  services.set([
14869
17032
  {
@@ -14896,6 +17059,7 @@ var CliEngine = class {
14896
17059
  { ...this.options ?? {}, terminalOptions }
14897
17060
  );
14898
17061
  this.executionContext.initializeTerminalListeners();
17062
+ this.executionContext.backgroundServices.setProcessRegistry(processRegistry);
14899
17063
  services.set([{
14900
17064
  provide: CliBackgroundServiceRegistry_TOKEN,
14901
17065
  useValue: this.executionContext.backgroundServices
@@ -15452,7 +17616,9 @@ function createQueuedReader(queue) {
15452
17616
  readSelect: async () => next() ?? "",
15453
17617
  readSelectInline: async () => next() ?? "",
15454
17618
  readMultiSelect: async () => next() ?? [],
15455
- readNumber: async () => next() ?? 0
17619
+ readNumber: async () => next() ?? 0,
17620
+ readDate: async () => next() ?? null,
17621
+ readFile: async () => next() ?? null
15456
17622
  };
15457
17623
  }
15458
17624
  function createNoopBackgroundServices() {
@@ -15528,6 +17694,7 @@ exports.CliFontSizeCommandProcessor = CliFontSizeCommandProcessor;
15528
17694
  exports.CliHelpCommandProcessor = CliHelpCommandProcessor;
15529
17695
  exports.CliHistoryCommandProcessor = CliHistoryCommandProcessor;
15530
17696
  exports.CliHotKeysCommandProcessor = CliHotKeysCommandProcessor;
17697
+ exports.CliHtopCommandProcessor = CliHtopCommandProcessor;
15531
17698
  exports.CliInputReader = CliInputReader;
15532
17699
  exports.CliJsonCommandProcessor = CliJsonCommandProcessor;
15533
17700
  exports.CliKeyValueStore = CliKeyValueStore;