@qodalis/cli 2.0.0-beta.6 → 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.mjs CHANGED
@@ -410,7 +410,7 @@ var CliBackgroundServiceRegistry_TOKEN = "cli-background-service-registry";
410
410
  var CliPackageManagerService_TOKEN = "cli-package-manager-service";
411
411
 
412
412
  // src/lib/version.ts
413
- var LIBRARY_VERSION = "2.0.0-beta.6";
413
+ var LIBRARY_VERSION = "2.0.0-beta.8";
414
414
  var API_VERSION = 2;
415
415
  var CliAliasCommandProcessor = class {
416
416
  constructor() {
@@ -605,105 +605,6 @@ var CliCaptureCommandProcessor = class {
605
605
  setTimeout(() => URL.revokeObjectURL(url), 1e3);
606
606
  }
607
607
  };
608
- var CliBase64CommandProcessor = class {
609
- constructor() {
610
- this.command = "base64";
611
- this.aliases = ["b64"];
612
- this.description = "Encode or decode Base64 strings";
613
- this.author = DefaultLibraryAuthor;
614
- this.processors = [];
615
- this.metadata = {
616
- icon: "\u{1F523}",
617
- module: "misc"
618
- };
619
- this.processors = [
620
- {
621
- command: "encode",
622
- aliases: ["enc", "e"],
623
- description: "Encode text to Base64",
624
- acceptsRawInput: true,
625
- valueRequired: true,
626
- processCommand: async (command2, context2) => {
627
- const text = command2.value || command2.data || "";
628
- const encoded = btoa(
629
- new TextEncoder().encode(text).reduce(
630
- (data, byte) => data + String.fromCharCode(byte),
631
- ""
632
- )
633
- );
634
- context2.writer.writeln(encoded);
635
- context2.process.output(encoded);
636
- },
637
- writeDescription: (context2) => {
638
- const { writer } = context2;
639
- writer.writeln("Encode text to Base64 format");
640
- writer.writeln();
641
- writer.writeln(`\u{1F4CB} ${context2.translator.t("cli.common.usage", "Usage:")}`);
642
- writer.writeln(
643
- ` ${writer.wrapInColor("base64 encode <text>", CliForegroundColor.Cyan)}`
644
- );
645
- writer.writeln();
646
- writer.writeln(`\u{1F4DD} ${context2.translator.t("cli.common.examples", "Examples:")}`);
647
- writer.writeln(
648
- ` base64 encode Hello World ${writer.wrapInColor("# \u2192 SGVsbG8gV29ybGQ=", CliForegroundColor.Green)}`
649
- );
650
- }
651
- },
652
- {
653
- command: "decode",
654
- aliases: ["dec", "d"],
655
- description: "Decode a Base64 string",
656
- acceptsRawInput: true,
657
- valueRequired: true,
658
- processCommand: async (command2, context2) => {
659
- const encoded = command2.value || command2.data || "";
660
- try {
661
- const bytes = Uint8Array.from(
662
- atob(encoded),
663
- (c) => c.charCodeAt(0)
664
- );
665
- const decoded = new TextDecoder().decode(bytes);
666
- context2.writer.writeln(decoded);
667
- context2.process.output(decoded);
668
- } catch {
669
- context2.writer.writeError("Invalid Base64 string");
670
- context2.process.exit(-1);
671
- }
672
- },
673
- writeDescription: (context2) => {
674
- const { writer } = context2;
675
- writer.writeln("Decode a Base64 string back to text");
676
- writer.writeln();
677
- writer.writeln(`\u{1F4CB} ${context2.translator.t("cli.common.usage", "Usage:")}`);
678
- writer.writeln(
679
- ` ${writer.wrapInColor("base64 decode <base64>", CliForegroundColor.Cyan)}`
680
- );
681
- writer.writeln();
682
- writer.writeln(`\u{1F4DD} ${context2.translator.t("cli.common.examples", "Examples:")}`);
683
- writer.writeln(
684
- ` base64 decode SGVsbG8gV29ybGQ= ${writer.wrapInColor("# \u2192 Hello World", CliForegroundColor.Green)}`
685
- );
686
- }
687
- }
688
- ];
689
- }
690
- async processCommand(command2, context2) {
691
- context2.executor.showHelp(command2, context2);
692
- }
693
- writeDescription(context2) {
694
- const { writer, translator: t } = context2;
695
- writer.writeln(t.t("cli.base64.long_description", "Encode and decode Base64 strings"));
696
- writer.writeln(t.t("cli.base64.utf8_note", "Supports UTF-8 text encoding"));
697
- writer.writeln();
698
- writer.writeln(`\u{1F4CB} ${t.t("cli.common.usage", "Usage:")}`);
699
- writer.writeln(
700
- ` ${writer.wrapInColor("base64 encode <text>", CliForegroundColor.Cyan)} ${t.t("cli.base64.encode_desc", "Encode text to Base64")}`
701
- );
702
- writer.writeln(
703
- ` ${writer.wrapInColor("base64 decode <base64>", CliForegroundColor.Cyan)} ${t.t("cli.base64.decode_desc", "Decode Base64 to text")}`
704
- );
705
- }
706
- };
707
608
  var CliCalCommandProcessor = class {
708
609
  constructor() {
709
610
  this.command = "cal";
@@ -1479,255 +1380,6 @@ var CliEvalCommandProcessor = class {
1479
1380
  );
1480
1381
  }
1481
1382
  };
1482
- var CliHashCommandProcessor = class {
1483
- constructor() {
1484
- this.command = "hash";
1485
- this.description = "Generate hash digests of text";
1486
- this.author = DefaultLibraryAuthor;
1487
- this.processors = [];
1488
- this.metadata = {
1489
- icon: "#\uFE0F\u20E3",
1490
- module: "misc"
1491
- };
1492
- const algorithms = [
1493
- { command: "sha256", algo: "SHA-256", aliases: ["sha-256"] },
1494
- { command: "sha1", algo: "SHA-1", aliases: ["sha-1"] },
1495
- { command: "sha384", algo: "SHA-384", aliases: ["sha-384"] },
1496
- { command: "sha512", algo: "SHA-512", aliases: ["sha-512"] }
1497
- ];
1498
- this.processors = algorithms.map(({ command: command2, algo, aliases }) => ({
1499
- command: command2,
1500
- aliases,
1501
- description: `Generate ${algo} hash`,
1502
- acceptsRawInput: true,
1503
- valueRequired: true,
1504
- processCommand: async (cmd, context2) => {
1505
- const text = cmd.value || cmd.data || "";
1506
- try {
1507
- const encoded = new TextEncoder().encode(text);
1508
- const hashBuffer = await crypto.subtle.digest(
1509
- algo,
1510
- encoded
1511
- );
1512
- const hashArray = Array.from(new Uint8Array(hashBuffer));
1513
- const hex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
1514
- context2.writer.writeln(hex);
1515
- context2.process.output(hex);
1516
- } catch {
1517
- context2.writer.writeError(`Failed to compute ${algo} hash`);
1518
- context2.process.exit(-1);
1519
- }
1520
- },
1521
- writeDescription: (context2) => {
1522
- const { writer } = context2;
1523
- writer.writeln(
1524
- `Generate a ${algo} hash digest of the input text`
1525
- );
1526
- writer.writeln();
1527
- writer.writeln(`\u{1F4CB} ${context2.translator.t("cli.common.usage", "Usage:")}`);
1528
- writer.writeln(
1529
- ` ${writer.wrapInColor(`hash ${command2} <text>`, CliForegroundColor.Cyan)}`
1530
- );
1531
- }
1532
- }));
1533
- }
1534
- async processCommand(command2, context2) {
1535
- context2.executor.showHelp(command2, context2);
1536
- }
1537
- writeDescription(context2) {
1538
- const { writer, translator: t } = context2;
1539
- writer.writeln(
1540
- t.t("cli.hash.long_description", "Generate cryptographic hash digests using the Web Crypto API")
1541
- );
1542
- writer.writeln();
1543
- writer.writeln(`\u{1F4CB} ${t.t("cli.common.usage", "Usage:")}`);
1544
- writer.writeln(
1545
- ` ${writer.wrapInColor("hash sha256 <text>", CliForegroundColor.Cyan)} SHA-256 hash`
1546
- );
1547
- writer.writeln(
1548
- ` ${writer.wrapInColor("hash sha1 <text>", CliForegroundColor.Cyan)} SHA-1 hash`
1549
- );
1550
- writer.writeln(
1551
- ` ${writer.wrapInColor("hash sha384 <text>", CliForegroundColor.Cyan)} SHA-384 hash`
1552
- );
1553
- writer.writeln(
1554
- ` ${writer.wrapInColor("hash sha512 <text>", CliForegroundColor.Cyan)} SHA-512 hash`
1555
- );
1556
- writer.writeln();
1557
- writer.writeln(`\u{1F4DD} ${t.t("cli.common.examples", "Examples:")}`);
1558
- writer.writeln(
1559
- ` hash sha256 hello ${writer.wrapInColor("# \u2192 2cf24dba5fb0a30e...", CliForegroundColor.Green)}`
1560
- );
1561
- }
1562
- };
1563
- var CliHexCommandProcessor = class {
1564
- constructor() {
1565
- this.command = "hex";
1566
- this.description = "Hex encode/decode and number base conversions";
1567
- this.author = DefaultLibraryAuthor;
1568
- this.processors = [];
1569
- this.metadata = {
1570
- icon: "0\uFE0F\u20E3",
1571
- module: "misc"
1572
- };
1573
- this.processors = [
1574
- {
1575
- command: "encode",
1576
- aliases: ["enc"],
1577
- description: "Encode text to hexadecimal",
1578
- acceptsRawInput: true,
1579
- valueRequired: true,
1580
- processCommand: async (command2, context2) => {
1581
- const text = command2.value || command2.data || "";
1582
- const hex = Array.from(new TextEncoder().encode(text)).map((b) => b.toString(16).padStart(2, "0")).join("");
1583
- context2.writer.writeln(hex);
1584
- context2.process.output(hex);
1585
- },
1586
- writeDescription: (context2) => {
1587
- const { writer } = context2;
1588
- writer.writeln("Encode text to hexadecimal representation");
1589
- writer.writeln();
1590
- writer.writeln(`\u{1F4CB} ${context2.translator.t("cli.common.usage", "Usage:")}`);
1591
- writer.writeln(
1592
- ` ${writer.wrapInColor("hex encode <text>", CliForegroundColor.Cyan)}`
1593
- );
1594
- }
1595
- },
1596
- {
1597
- command: "decode",
1598
- aliases: ["dec"],
1599
- description: "Decode hexadecimal to text",
1600
- acceptsRawInput: true,
1601
- valueRequired: true,
1602
- processCommand: async (command2, context2) => {
1603
- const hexStr = command2.value || command2.data || "";
1604
- try {
1605
- const bytes = new Uint8Array(
1606
- (hexStr.match(/.{1,2}/g) || []).map(
1607
- (byte) => parseInt(byte, 16)
1608
- )
1609
- );
1610
- const text = new TextDecoder().decode(bytes);
1611
- context2.writer.writeln(text);
1612
- context2.process.output(text);
1613
- } catch {
1614
- context2.writer.writeError("Invalid hex string");
1615
- context2.process.exit(-1);
1616
- }
1617
- },
1618
- writeDescription: (context2) => {
1619
- const { writer } = context2;
1620
- writer.writeln("Decode hexadecimal back to text");
1621
- writer.writeln();
1622
- writer.writeln(`\u{1F4CB} ${context2.translator.t("cli.common.usage", "Usage:")}`);
1623
- writer.writeln(
1624
- ` ${writer.wrapInColor("hex decode <hex>", CliForegroundColor.Cyan)}`
1625
- );
1626
- }
1627
- },
1628
- {
1629
- command: "convert",
1630
- aliases: ["conv"],
1631
- description: "Convert a number between bases",
1632
- acceptsRawInput: true,
1633
- valueRequired: true,
1634
- parameters: [
1635
- {
1636
- name: "from",
1637
- description: "Source base (default: 10)",
1638
- type: "number",
1639
- required: false
1640
- },
1641
- {
1642
- name: "to",
1643
- description: "Target base (default: 16)",
1644
- type: "number",
1645
- required: false
1646
- }
1647
- ],
1648
- processCommand: async (command2, context2) => {
1649
- const { writer } = context2;
1650
- const input = command2.value || "";
1651
- const fromBase = parseInt(command2.args["from"]) || 10;
1652
- const toBase = parseInt(command2.args["to"]) || 16;
1653
- if (fromBase < 2 || fromBase > 36 || toBase < 2 || toBase > 36) {
1654
- writer.writeError("Base must be between 2 and 36");
1655
- context2.process.exit(-1);
1656
- return;
1657
- }
1658
- try {
1659
- const num = parseInt(input, fromBase);
1660
- if (isNaN(num)) {
1661
- writer.writeError(
1662
- `Invalid number for base ${fromBase}`
1663
- );
1664
- context2.process.exit(-1);
1665
- return;
1666
- }
1667
- const result = num.toString(toBase);
1668
- writer.writeln(
1669
- ` ${writer.wrapInColor(`Base ${fromBase}:`, CliForegroundColor.Cyan)} ${input}`
1670
- );
1671
- writer.writeln(
1672
- ` ${writer.wrapInColor(`Base ${toBase}:`, CliForegroundColor.Cyan)} ${result}`
1673
- );
1674
- if (fromBase !== 10 && toBase !== 10) {
1675
- writer.writeln(
1676
- ` ${writer.wrapInColor("Base 10:", CliForegroundColor.Cyan)} ${num}`
1677
- );
1678
- }
1679
- context2.process.output(result);
1680
- } catch {
1681
- writer.writeError("Conversion failed");
1682
- context2.process.exit(-1);
1683
- }
1684
- },
1685
- writeDescription: (context2) => {
1686
- const { writer } = context2;
1687
- writer.writeln(
1688
- "Convert a number between different bases (2-36)"
1689
- );
1690
- writer.writeln();
1691
- writer.writeln(`\u{1F4CB} ${context2.translator.t("cli.common.usage", "Usage:")}`);
1692
- writer.writeln(
1693
- ` ${writer.wrapInColor("hex convert <number> [--from=N] [--to=N]", CliForegroundColor.Cyan)}`
1694
- );
1695
- writer.writeln();
1696
- writer.writeln(`\u{1F4DD} ${context2.translator.t("cli.common.examples", "Examples:")}`);
1697
- writer.writeln(
1698
- ` hex convert 255 ${writer.wrapInColor("# Decimal \u2192 Hex (ff)", CliForegroundColor.Green)}`
1699
- );
1700
- writer.writeln(
1701
- ` hex convert ff --from=16 --to=2 ${writer.wrapInColor("# Hex \u2192 Binary (11111111)", CliForegroundColor.Green)}`
1702
- );
1703
- writer.writeln(
1704
- ` hex convert 1010 --from=2 ${writer.wrapInColor("# Binary \u2192 Hex (a)", CliForegroundColor.Green)}`
1705
- );
1706
- }
1707
- }
1708
- ];
1709
- }
1710
- async processCommand(command2, context2) {
1711
- context2.executor.showHelp(command2, context2);
1712
- }
1713
- writeDescription(context2) {
1714
- const { writer, translator: t } = context2;
1715
- writer.writeln(
1716
- t.t("cli.hex.long_description", "Hex encode/decode text and convert numbers between bases")
1717
- );
1718
- writer.writeln();
1719
- writer.writeln(`\u{1F4CB} ${t.t("cli.common.usage", "Usage:")}`);
1720
- writer.writeln(
1721
- ` ${writer.wrapInColor("hex encode <text>", CliForegroundColor.Cyan)} ${t.t("cli.hex.text_to_hex", "Text to hex")}`
1722
- );
1723
- writer.writeln(
1724
- ` ${writer.wrapInColor("hex decode <hex>", CliForegroundColor.Cyan)} ${t.t("cli.hex.hex_to_text", "Hex to text")}`
1725
- );
1726
- writer.writeln(
1727
- ` ${writer.wrapInColor("hex convert <num> [--from --to]", CliForegroundColor.Cyan)} ${t.t("cli.hex.base_conversion", "Base conversion")}`
1728
- );
1729
- }
1730
- };
1731
1383
  var CliJsonCommandProcessor = class {
1732
1384
  constructor() {
1733
1385
  this.command = "json";
@@ -1854,134 +1506,6 @@ var CliJsonCommandProcessor = class {
1854
1506
  );
1855
1507
  }
1856
1508
  };
1857
- var CliJwtCommandProcessor = class {
1858
- constructor() {
1859
- this.command = "jwt";
1860
- this.description = "Decode and inspect JWT tokens";
1861
- this.author = DefaultLibraryAuthor;
1862
- this.processors = [];
1863
- this.metadata = {
1864
- icon: "\u{1F511}",
1865
- module: "misc"
1866
- };
1867
- this.processors = [
1868
- {
1869
- command: "decode",
1870
- aliases: ["d"],
1871
- description: "Decode a JWT token",
1872
- acceptsRawInput: true,
1873
- valueRequired: true,
1874
- processCommand: async (command2, context2) => {
1875
- const token = command2.value || command2.data || "";
1876
- const { writer } = context2;
1877
- const parts = token.split(".");
1878
- if (parts.length !== 3) {
1879
- writer.writeError(
1880
- "Invalid JWT token (expected 3 parts separated by dots)"
1881
- );
1882
- context2.process.exit(-1);
1883
- return;
1884
- }
1885
- try {
1886
- const header = JSON.parse(
1887
- this.base64UrlDecode(parts[0])
1888
- );
1889
- const payload = JSON.parse(
1890
- this.base64UrlDecode(parts[1])
1891
- );
1892
- writer.writeln(
1893
- writer.wrapInColor(
1894
- "Header:",
1895
- CliForegroundColor.Yellow
1896
- )
1897
- );
1898
- writer.writeln(JSON.stringify(header, null, 2));
1899
- writer.writeln();
1900
- writer.writeln(
1901
- writer.wrapInColor(
1902
- "Payload:",
1903
- CliForegroundColor.Yellow
1904
- )
1905
- );
1906
- writer.writeln(JSON.stringify(payload, null, 2));
1907
- writer.writeln();
1908
- if (payload.exp) {
1909
- const expDate = new Date(payload.exp * 1e3);
1910
- const isExpired = expDate.getTime() < Date.now();
1911
- writer.writeln(
1912
- `${writer.wrapInColor("Expires:", CliForegroundColor.Yellow)} ${expDate.toLocaleString()} ${isExpired ? writer.wrapInColor(
1913
- "(EXPIRED)",
1914
- CliForegroundColor.Red
1915
- ) : writer.wrapInColor(
1916
- "(valid)",
1917
- CliForegroundColor.Green
1918
- )}`
1919
- );
1920
- }
1921
- if (payload.iat) {
1922
- const iatDate = new Date(payload.iat * 1e3);
1923
- writer.writeln(
1924
- `${writer.wrapInColor("Issued:", CliForegroundColor.Yellow)} ${iatDate.toLocaleString()}`
1925
- );
1926
- }
1927
- writer.writeln();
1928
- writer.writeln(
1929
- writer.wrapInColor(
1930
- "Signature:",
1931
- CliForegroundColor.Yellow
1932
- ) + ` ${parts[2].substring(0, 20)}...`
1933
- );
1934
- context2.process.output({ header, payload });
1935
- } catch {
1936
- writer.writeError("Failed to decode JWT token");
1937
- context2.process.exit(-1);
1938
- }
1939
- },
1940
- writeDescription: (context2) => {
1941
- const { writer } = context2;
1942
- writer.writeln(
1943
- "Decode a JWT token and display its header and payload"
1944
- );
1945
- writer.writeln(
1946
- "Shows expiration status and issue date if present"
1947
- );
1948
- writer.writeln();
1949
- writer.writeln(`\u{1F4CB} ${context2.translator.t("cli.common.usage", "Usage:")}`);
1950
- writer.writeln(
1951
- ` ${writer.wrapInColor("jwt decode <token>", CliForegroundColor.Cyan)}`
1952
- );
1953
- }
1954
- }
1955
- ];
1956
- }
1957
- async processCommand(command2, context2) {
1958
- context2.executor.showHelp(command2, context2);
1959
- }
1960
- writeDescription(context2) {
1961
- const { writer, translator: t } = context2;
1962
- writer.writeln(t.t("cli.jwt.long_description", "Decode and inspect JSON Web Tokens (JWT)"));
1963
- writer.writeln();
1964
- writer.writeln(`\u{1F4CB} ${t.t("cli.common.usage", "Usage:")}`);
1965
- writer.writeln(
1966
- ` ${writer.wrapInColor("jwt decode <token>", CliForegroundColor.Cyan)} ${t.t("cli.jwt.decode_display", "Decode and display JWT contents")}`
1967
- );
1968
- writer.writeln();
1969
- writer.writeln(
1970
- `\u{1F4DD} ${t.t("cli.jwt.shows", "Shows: header, payload, expiration status, issue date")}`
1971
- );
1972
- }
1973
- base64UrlDecode(str) {
1974
- str = str.replace(/-/g, "+").replace(/_/g, "/");
1975
- while (str.length % 4) {
1976
- str += "=";
1977
- }
1978
- return decodeURIComponent(
1979
- atob(str).split("").map(
1980
- (c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)
1981
- ).join("")
1982
- );
1983
- }
1984
- };
1985
1509
  var LOREM_WORDS = [
1986
1510
  "lorem",
1987
1511
  "ipsum",
@@ -3026,10 +2550,8 @@ var CliUptimeCommandProcessor = class {
3026
2550
  icon: "\u23F1\uFE0F",
3027
2551
  module: "misc"
3028
2552
  };
3029
- this.sessionStartTimes = /* @__PURE__ */ new WeakMap();
3030
2553
  }
3031
2554
  async initialize(context2) {
3032
- this.sessionStartTimes.set(context2.terminal, Date.now());
3033
2555
  try {
3034
2556
  context2.backgroundServices.register({
3035
2557
  name: "uptime-tracker",
@@ -3037,6 +2559,10 @@ var CliUptimeCommandProcessor = class {
3037
2559
  type: "daemon",
3038
2560
  onStart: async (ctx) => {
3039
2561
  ctx.log("Uptime tracking started");
2562
+ await new Promise((resolve) => {
2563
+ ctx.signal.addEventListener("abort", () => resolve(), { once: true });
2564
+ });
2565
+ ctx.log("Uptime tracking stopped");
3040
2566
  }
3041
2567
  });
3042
2568
  await context2.backgroundServices.start("uptime-tracker");
@@ -3045,9 +2571,16 @@ var CliUptimeCommandProcessor = class {
3045
2571
  }
3046
2572
  async processCommand(_, context2) {
3047
2573
  const svcInfo = context2.backgroundServices.getStatus("uptime-tracker");
3048
- const startTime = svcInfo?.status === "running" && svcInfo.startedAt ? svcInfo.startedAt.getTime() : this.sessionStartTimes.get(context2.terminal) ?? Date.now();
3049
- const elapsed = Date.now() - startTime;
3050
2574
  const { writer } = context2;
2575
+ if (!svcInfo || svcInfo.status !== "running") {
2576
+ writer.writeError("Uptime tracker is not running.");
2577
+ writer.writeInfo(
2578
+ `Use ${writer.wrapInColor("services start uptime-tracker", CliForegroundColor.Cyan)} to restart.`
2579
+ );
2580
+ return;
2581
+ }
2582
+ const startTime = svcInfo.startedAt.getTime();
2583
+ const elapsed = Date.now() - startTime;
3051
2584
  const seconds = Math.floor(elapsed / 1e3) % 60;
3052
2585
  const minutes = Math.floor(elapsed / (1e3 * 60)) % 60;
3053
2586
  const hours = Math.floor(elapsed / (1e3 * 60 * 60)) % 24;
@@ -3078,137 +2611,6 @@ var CliUptimeCommandProcessor = class {
3078
2611
  );
3079
2612
  }
3080
2613
  };
3081
- var CliUrlCommandProcessor = class {
3082
- constructor() {
3083
- this.command = "url";
3084
- this.description = "Encode or decode URLs";
3085
- this.author = DefaultLibraryAuthor;
3086
- this.processors = [];
3087
- this.metadata = {
3088
- icon: "\u{1F517}",
3089
- module: "misc"
3090
- };
3091
- this.processors = [
3092
- {
3093
- command: "encode",
3094
- aliases: ["enc"],
3095
- description: "URL-encode a string",
3096
- acceptsRawInput: true,
3097
- valueRequired: true,
3098
- processCommand: async (command2, context2) => {
3099
- const text = command2.value || command2.data || "";
3100
- const encoded = encodeURIComponent(text);
3101
- context2.writer.writeln(encoded);
3102
- context2.process.output(encoded);
3103
- },
3104
- writeDescription: (context2) => {
3105
- const { writer } = context2;
3106
- writer.writeln("URL-encode a string");
3107
- writer.writeln();
3108
- writer.writeln(`\u{1F4CB} ${context2.translator.t("cli.common.usage", "Usage:")}`);
3109
- writer.writeln(
3110
- ` ${writer.wrapInColor("url encode <text>", CliForegroundColor.Cyan)}`
3111
- );
3112
- writer.writeln();
3113
- writer.writeln(`\u{1F4DD} ${context2.translator.t("cli.common.examples", "Examples:")}`);
3114
- writer.writeln(
3115
- ` url encode hello world ${writer.wrapInColor("# \u2192 hello%20world", CliForegroundColor.Green)}`
3116
- );
3117
- }
3118
- },
3119
- {
3120
- command: "decode",
3121
- aliases: ["dec"],
3122
- description: "URL-decode a string",
3123
- acceptsRawInput: true,
3124
- valueRequired: true,
3125
- processCommand: async (command2, context2) => {
3126
- const text = command2.value || command2.data || "";
3127
- try {
3128
- const decoded = decodeURIComponent(text);
3129
- context2.writer.writeln(decoded);
3130
- context2.process.output(decoded);
3131
- } catch {
3132
- context2.writer.writeError("Invalid URL-encoded string");
3133
- context2.process.exit(-1);
3134
- }
3135
- },
3136
- writeDescription: (context2) => {
3137
- const { writer } = context2;
3138
- writer.writeln("URL-decode a string");
3139
- writer.writeln();
3140
- writer.writeln(`\u{1F4CB} ${context2.translator.t("cli.common.usage", "Usage:")}`);
3141
- writer.writeln(
3142
- ` ${writer.wrapInColor("url decode <text>", CliForegroundColor.Cyan)}`
3143
- );
3144
- writer.writeln();
3145
- writer.writeln(`\u{1F4DD} ${context2.translator.t("cli.common.examples", "Examples:")}`);
3146
- writer.writeln(
3147
- ` url decode hello%20world ${writer.wrapInColor("# \u2192 hello world", CliForegroundColor.Green)}`
3148
- );
3149
- }
3150
- },
3151
- {
3152
- command: "parse",
3153
- description: "Parse a URL into its components",
3154
- acceptsRawInput: true,
3155
- valueRequired: true,
3156
- processCommand: async (command2, context2) => {
3157
- const input = command2.value || command2.data || "";
3158
- try {
3159
- const url = new URL(input);
3160
- const parts = {
3161
- protocol: url.protocol,
3162
- hostname: url.hostname,
3163
- port: url.port || "(default)",
3164
- pathname: url.pathname,
3165
- search: url.search || "(none)",
3166
- hash: url.hash || "(none)",
3167
- origin: url.origin
3168
- };
3169
- const { writer } = context2;
3170
- Object.entries(parts).forEach(([key, value]) => {
3171
- writer.writeln(
3172
- ` ${writer.wrapInColor(key.padEnd(12), CliForegroundColor.Cyan)} ${value}`
3173
- );
3174
- });
3175
- context2.process.output(parts);
3176
- } catch {
3177
- context2.writer.writeError("Invalid URL");
3178
- context2.process.exit(-1);
3179
- }
3180
- },
3181
- writeDescription: (context2) => {
3182
- const { writer } = context2;
3183
- writer.writeln("Parse a URL and display its components");
3184
- writer.writeln();
3185
- writer.writeln(`\u{1F4CB} ${context2.translator.t("cli.common.usage", "Usage:")}`);
3186
- writer.writeln(
3187
- ` ${writer.wrapInColor("url parse <url>", CliForegroundColor.Cyan)}`
3188
- );
3189
- }
3190
- }
3191
- ];
3192
- }
3193
- async processCommand(command2, context2) {
3194
- context2.executor.showHelp(command2, context2);
3195
- }
3196
- writeDescription(context2) {
3197
- const { writer, translator: t } = context2;
3198
- writer.writeln(t.t("cli.url.long_description", "Encode, decode, and parse URLs"));
3199
- writer.writeln();
3200
- writer.writeln(`\u{1F4CB} ${t.t("cli.common.usage", "Usage:")}`);
3201
- writer.writeln(
3202
- ` ${writer.wrapInColor("url encode <text>", CliForegroundColor.Cyan)} ${t.t("cli.url.encode_desc", "URL-encode a string")}`
3203
- );
3204
- writer.writeln(
3205
- ` ${writer.wrapInColor("url decode <text>", CliForegroundColor.Cyan)} ${t.t("cli.url.decode_desc", "URL-decode a string")}`
3206
- );
3207
- writer.writeln(
3208
- ` ${writer.wrapInColor("url parse <url>", CliForegroundColor.Cyan)} ${t.t("cli.url.parse_desc", "Parse URL components")}`
3209
- );
3210
- }
3211
- };
3212
2614
  var CliYesCommandProcessor = class {
3213
2615
  constructor() {
3214
2616
  this.command = "yes";
@@ -6824,6 +6226,9 @@ var CliServerManager = class {
6824
6226
 
6825
6227
  // src/lib/wasm/fallback.ts
6826
6228
  var JsFallbackAccelerator = class {
6229
+ constructor() {
6230
+ this.ruleSets = /* @__PURE__ */ new Map();
6231
+ }
6827
6232
  textSearch(text, needle, startRow, startCol, caseSensitive, wrap) {
6828
6233
  if (!needle) return [-1, -1];
6829
6234
  const lines = text.split("\n");
@@ -6883,6 +6288,60 @@ var JsFallbackAccelerator = class {
6883
6288
  }
6884
6289
  return matches.sort();
6885
6290
  }
6291
+ registerRuleSet(ruleSetId, rules) {
6292
+ const parsed = [];
6293
+ for (const line of rules.split("\n")) {
6294
+ if (!line) continue;
6295
+ const parts = line.split(" ");
6296
+ if (parts.length < 2) continue;
6297
+ try {
6298
+ parsed.push({
6299
+ pattern: new RegExp(parts[0]),
6300
+ tokenType: parts[1],
6301
+ captureGroup: parts[2] ? parseInt(parts[2], 10) : 0
6302
+ });
6303
+ } catch {
6304
+ }
6305
+ }
6306
+ this.ruleSets.set(ruleSetId, parsed);
6307
+ }
6308
+ tokenizeLine(line, ruleSetId) {
6309
+ const rules = this.ruleSets.get(ruleSetId);
6310
+ if (!rules || !line) return "";
6311
+ const tokens = [];
6312
+ let pos = 0;
6313
+ while (pos < line.length) {
6314
+ let matched = false;
6315
+ for (const rule of rules) {
6316
+ rule.pattern.lastIndex = 0;
6317
+ const sliced = line.slice(pos);
6318
+ const m = rule.pattern.exec(sliced);
6319
+ if (m) {
6320
+ let start;
6321
+ let end;
6322
+ if (rule.captureGroup > 0 && m[rule.captureGroup] !== void 0) {
6323
+ const groupText = m[rule.captureGroup];
6324
+ const groupOffset = m[0].indexOf(groupText);
6325
+ start = pos + m.index + groupOffset;
6326
+ end = start + groupText.length;
6327
+ } else {
6328
+ start = pos + m.index;
6329
+ end = pos + m.index + m[0].length;
6330
+ }
6331
+ if (pos + m.index + m[0].length > pos) {
6332
+ tokens.push(`${start},${end},${rule.tokenType}`);
6333
+ pos = pos + m.index + m[0].length;
6334
+ matched = true;
6335
+ break;
6336
+ }
6337
+ }
6338
+ }
6339
+ if (!matched) {
6340
+ pos++;
6341
+ }
6342
+ }
6343
+ return tokens.join("\n");
6344
+ }
6886
6345
  commonPrefix(strings) {
6887
6346
  if (strings.length === 0) return "";
6888
6347
  let prefix = strings[0];
@@ -7055,6 +6514,30 @@ function wasmCommonPrefix(strings) {
7055
6514
  wasm.__wbindgen_export3(d0, d1, 1);
7056
6515
  }
7057
6516
  }
6517
+ function wasmRegisterRuleSet(ruleSetId, rules) {
6518
+ const ptr0 = passStringToWasm0(ruleSetId, wasm.__wbindgen_export, wasm.__wbindgen_export2);
6519
+ const len0 = WASM_VECTOR_LEN;
6520
+ const ptr1 = passStringToWasm0(rules, wasm.__wbindgen_export, wasm.__wbindgen_export2);
6521
+ const len1 = WASM_VECTOR_LEN;
6522
+ wasm.register_rule_set(ptr0, len0, ptr1, len1);
6523
+ }
6524
+ function wasmTokenizeLine(line, ruleSetId) {
6525
+ let d0, d1;
6526
+ try {
6527
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
6528
+ const ptr0 = passStringToWasm0(line, wasm.__wbindgen_export, wasm.__wbindgen_export2);
6529
+ const len0 = WASM_VECTOR_LEN;
6530
+ const ptr1 = passStringToWasm0(ruleSetId, wasm.__wbindgen_export, wasm.__wbindgen_export2);
6531
+ const len1 = WASM_VECTOR_LEN;
6532
+ wasm.tokenize_line(retptr, ptr0, len0, ptr1, len1);
6533
+ d0 = getDataViewMemory0().getInt32(retptr + 0, true);
6534
+ d1 = getDataViewMemory0().getInt32(retptr + 4, true);
6535
+ return getStringFromWasm0(d0, d1);
6536
+ } finally {
6537
+ wasm.__wbindgen_add_to_stack_pointer(16);
6538
+ wasm.__wbindgen_export3(d0, d1, 1);
6539
+ }
6540
+ }
7058
6541
  async function instantiateWasm(wasmBytes) {
7059
6542
  const imports = { __proto__: null, "./qodalis_cli_wasm_bg.js": { __proto__: null } };
7060
6543
  const { instance } = await WebAssembly.instantiate(wasmBytes, imports);
@@ -7095,6 +6578,12 @@ function createWasmAccelerator() {
7095
6578
  },
7096
6579
  commonPrefix(strings) {
7097
6580
  return wasmCommonPrefix(strings.join("\n"));
6581
+ },
6582
+ registerRuleSet(ruleSetId, rules) {
6583
+ wasmRegisterRuleSet(ruleSetId, rules);
6584
+ },
6585
+ tokenizeLine(line, ruleSetId) {
6586
+ return wasmTokenizeLine(line, ruleSetId);
7098
6587
  }
7099
6588
  };
7100
6589
  }
@@ -8021,7 +7510,11 @@ var NanoEditorRenderer = class {
8021
7510
  output2 += "\x1B[2K";
8022
7511
  if (lineIdx < buffer.lines.length) {
8023
7512
  const line = buffer.lines[lineIdx];
8024
- output2 += line.length > cols ? line.slice(0, cols) : line;
7513
+ if (this.highlightEngine) {
7514
+ output2 += this.highlightEngine.renderLine(lineIdx, line, cols);
7515
+ } else {
7516
+ output2 += line.length > cols ? line.slice(0, cols) : line;
7517
+ }
8025
7518
  }
8026
7519
  }
8027
7520
  output2 += `\x1B[${rows - 1};1H`;
@@ -8100,6 +7593,135 @@ var NanoEditorRenderer = class {
8100
7593
  }
8101
7594
  };
8102
7595
 
7596
+ // src/lib/editor/syntax/utils.ts
7597
+ function truncateAnsi(str, maxCols) {
7598
+ let visible = 0;
7599
+ let i = 0;
7600
+ let result = "";
7601
+ let hasActiveColor = false;
7602
+ while (i < str.length && visible < maxCols) {
7603
+ if (str[i] === "\x1B" && i + 1 < str.length && str[i + 1] === "[") {
7604
+ let j = i + 2;
7605
+ while (j < str.length && !(str.charCodeAt(j) >= 64 && str.charCodeAt(j) <= 126)) {
7606
+ j++;
7607
+ }
7608
+ if (j < str.length) {
7609
+ const seq = str.slice(i, j + 1);
7610
+ result += seq;
7611
+ if (seq === "\x1B[0m") {
7612
+ hasActiveColor = false;
7613
+ } else {
7614
+ hasActiveColor = true;
7615
+ }
7616
+ i = j + 1;
7617
+ } else {
7618
+ break;
7619
+ }
7620
+ } else {
7621
+ result += str[i];
7622
+ visible++;
7623
+ i++;
7624
+ }
7625
+ }
7626
+ if (hasActiveColor) {
7627
+ result += "\x1B[0m";
7628
+ }
7629
+ return result;
7630
+ }
7631
+ function parseTokens(raw) {
7632
+ if (!raw) return [];
7633
+ return raw.split("\n").map((entry) => {
7634
+ const [s, e, ...rest] = entry.split(",");
7635
+ return { start: parseInt(s, 10), end: parseInt(e, 10), tokenType: rest.join(",") };
7636
+ });
7637
+ }
7638
+
7639
+ // src/lib/editor/syntax/engine.ts
7640
+ var SyntaxHighlightEngine = class {
7641
+ constructor(highlighter, theme, accelerator2) {
7642
+ this.highlighter = highlighter;
7643
+ this.theme = theme;
7644
+ this.accelerator = accelerator2;
7645
+ this.cache = /* @__PURE__ */ new Map();
7646
+ this.registeredRuleSets = /* @__PURE__ */ new Set();
7647
+ }
7648
+ invalidate(fromLine) {
7649
+ for (const key of this.cache.keys()) {
7650
+ if (key >= fromLine) {
7651
+ this.cache.delete(key);
7652
+ }
7653
+ }
7654
+ }
7655
+ renderLine(lineIdx, line, cols) {
7656
+ const entryState = lineIdx === 0 ? this.highlighter.initialState : this.cache.get(lineIdx - 1)?.exitState ?? this.highlighter.initialState;
7657
+ const cached = this.cache.get(lineIdx);
7658
+ if (cached && cached.lineContent === line && cached.entryState === entryState) {
7659
+ return truncateAnsi(cached.rendered, cols);
7660
+ }
7661
+ try {
7662
+ const ruleSetId = `${this.highlighter.id}:${entryState}`;
7663
+ if (!this.registeredRuleSets.has(ruleSetId)) {
7664
+ const rules = this.highlighter.getRules(entryState);
7665
+ const serialized = rules.map(
7666
+ (r) => `${r.pattern.source} ${r.tokenType} ${r.captureGroup ?? 0}`
7667
+ ).join("\n");
7668
+ this.accelerator.registerRuleSet(ruleSetId, serialized);
7669
+ this.registeredRuleSets.add(ruleSetId);
7670
+ }
7671
+ const raw = this.accelerator.tokenizeLine(line, ruleSetId);
7672
+ const tokens = parseTokens(raw);
7673
+ const exitState = this.highlighter.getNextLineState(line, entryState);
7674
+ const rendered = this.applyTheme(line, tokens);
7675
+ this.cache.set(lineIdx, { lineContent: line, entryState, exitState, rendered });
7676
+ return truncateAnsi(rendered, cols);
7677
+ } catch {
7678
+ return line.length > cols ? line.slice(0, cols) : line;
7679
+ }
7680
+ }
7681
+ applyTheme(line, tokens) {
7682
+ if (tokens.length === 0) return line;
7683
+ let result = "";
7684
+ let pos = 0;
7685
+ for (const token of tokens) {
7686
+ if (token.start > pos) {
7687
+ result += line.slice(pos, token.start);
7688
+ }
7689
+ const color = this.theme.colors[token.tokenType];
7690
+ const text = line.slice(token.start, token.end);
7691
+ if (color) {
7692
+ result += color + text + "\x1B[0m";
7693
+ } else {
7694
+ result += text;
7695
+ }
7696
+ pos = token.end;
7697
+ }
7698
+ if (pos < line.length) {
7699
+ result += line.slice(pos);
7700
+ }
7701
+ return result;
7702
+ }
7703
+ };
7704
+
7705
+ // src/lib/editor/syntax/theme.ts
7706
+ var defaultSyntaxTheme = {
7707
+ name: "default",
7708
+ colors: {
7709
+ keyword: "\x1B[93m",
7710
+ string: "\x1B[32m",
7711
+ number: "\x1B[36m",
7712
+ comment: "\x1B[90m",
7713
+ tag: "\x1B[91m",
7714
+ attribute: "\x1B[33m",
7715
+ value: "\x1B[36m",
7716
+ punctuation: "\x1B[97m",
7717
+ heading: "\x1B[1;93m",
7718
+ link: "\x1B[4;36m",
7719
+ bold: "\x1B[1m",
7720
+ italic: "\x1B[3m",
7721
+ code: "\x1B[90;47m"
7722
+ }
7723
+ };
7724
+
8103
7725
  // src/lib/processors/system/cli-nano-command-processor.ts
8104
7726
  var FS_TOKEN = "cli-file-system-service";
8105
7727
  var CliNanoCommandProcessor = class {
@@ -8167,6 +7789,7 @@ var CliNanoCommandProcessor = class {
8167
7789
  } else {
8168
7790
  this.filePath = null;
8169
7791
  }
7792
+ this.setupHighlighting(this.filePath);
8170
7793
  context2.enterFullScreenMode(this);
8171
7794
  this.renderer.render(this.buffer, this.filePath || "New Buffer");
8172
7795
  this.resizeDisposable = context2.terminal.onResize(() => {
@@ -8295,11 +7918,13 @@ var CliNanoCommandProcessor = class {
8295
7918
  }
8296
7919
  if (data === "\v") {
8297
7920
  this.buffer.deleteLine();
7921
+ this.renderer.highlightEngine?.invalidate(this.buffer.cursorRow);
8298
7922
  this.render();
8299
7923
  return;
8300
7924
  }
8301
7925
  if (data === "") {
8302
7926
  if (this.buffer.uncutLines()) {
7927
+ this.renderer.highlightEngine?.invalidate(this.buffer.cursorRow);
8303
7928
  this.render();
8304
7929
  } else {
8305
7930
  this.showStatus("Clipboard is empty");
@@ -8360,11 +7985,16 @@ var CliNanoCommandProcessor = class {
8360
7985
  }
8361
7986
  if (data === "\r") {
8362
7987
  this.buffer.insertNewline();
7988
+ this.renderer.highlightEngine?.invalidate(this.buffer.cursorRow - 1);
8363
7989
  this.render();
8364
7990
  return;
8365
7991
  }
8366
7992
  if (data === "\x7F") {
7993
+ const wasMerge = this.buffer.cursorCol === 0 && this.buffer.cursorRow > 0;
8367
7994
  this.buffer.deleteCharBefore();
7995
+ if (wasMerge) {
7996
+ this.renderer.highlightEngine?.invalidate(this.buffer.cursorRow);
7997
+ }
8368
7998
  this.render();
8369
7999
  return;
8370
8000
  }
@@ -8457,6 +8087,31 @@ var CliNanoCommandProcessor = class {
8457
8087
  ` ${writer.wrapInColor("^Y", CliForegroundColor.Yellow)} Page Up ${writer.wrapInColor("^V", CliForegroundColor.Yellow)} Page Down`
8458
8088
  );
8459
8089
  }
8090
+ // ── Syntax highlighting ──
8091
+ setupHighlighting(filePath) {
8092
+ if (!filePath) {
8093
+ this.renderer.highlightEngine = void 0;
8094
+ return;
8095
+ }
8096
+ try {
8097
+ const registry = this.context.services.get(
8098
+ "syntax-highlighter-registry"
8099
+ );
8100
+ const ext = "." + filePath.split(".").pop()?.toLowerCase();
8101
+ const highlighter = registry.getByExtension(ext);
8102
+ if (highlighter) {
8103
+ this.renderer.highlightEngine = new SyntaxHighlightEngine(
8104
+ highlighter,
8105
+ defaultSyntaxTheme,
8106
+ getAccelerator()
8107
+ );
8108
+ } else {
8109
+ this.renderer.highlightEngine = void 0;
8110
+ }
8111
+ } catch {
8112
+ this.renderer.highlightEngine = void 0;
8113
+ }
8114
+ }
8460
8115
  // ── Rendering helpers ──
8461
8116
  render() {
8462
8117
  this.renderer.render(
@@ -8994,17 +8649,23 @@ var CliProcessRegistry = class {
8994
8649
  get currentPid() {
8995
8650
  return this._currentPid;
8996
8651
  }
8997
- register(command2) {
8652
+ register(command2, options) {
8998
8653
  const pid = this.nextPid++;
8999
8654
  const abortController = new AbortController();
8655
+ const type = options?.type ?? "command";
9000
8656
  this.processes.set(pid, {
9001
8657
  pid,
8658
+ name: options?.name ?? command2,
8659
+ type,
9002
8660
  command: command2,
9003
8661
  startTime: Date.now(),
9004
8662
  status: "running",
9005
- abortController
8663
+ abortController,
8664
+ onKill: options?.onKill
9006
8665
  });
9007
- this._currentPid = pid;
8666
+ if (type === "command") {
8667
+ this._currentPid = pid;
8668
+ }
9008
8669
  this.pruneHistory();
9009
8670
  return { pid, abortController };
9010
8671
  }
@@ -9031,6 +8692,10 @@ var CliProcessRegistry = class {
9031
8692
  kill(pid) {
9032
8693
  const entry = this.processes.get(pid);
9033
8694
  if (!entry || entry.status !== "running") return false;
8695
+ if (entry.onKill) {
8696
+ Promise.resolve(entry.onKill()).catch(() => {
8697
+ });
8698
+ }
9034
8699
  entry.abortController.abort();
9035
8700
  entry.status = "killed";
9036
8701
  entry.exitCode = -9;
@@ -9040,7 +8705,9 @@ var CliProcessRegistry = class {
9040
8705
  return true;
9041
8706
  }
9042
8707
  list() {
9043
- return Array.from(this.processes.values()).map(({ abortController, ...entry }) => entry);
8708
+ return Array.from(this.processes.values()).map(
8709
+ ({ abortController, onKill, ...entry }) => entry
8710
+ );
9044
8711
  }
9045
8712
  pruneHistory() {
9046
8713
  const entries = Array.from(this.processes.entries());
@@ -9077,11 +8744,13 @@ var CliPsCommandProcessor = class {
9077
8744
  context2.writer.writeInfo("No processes");
9078
8745
  return;
9079
8746
  }
9080
- const headers = ["PID", "STATUS", "EXIT", "TIME", "COMMAND"];
8747
+ const headers = ["PID", "NAME", "TYPE", "STATUS", "EXIT", "TIME", "COMMAND"];
9081
8748
  const rows = processes.map((p) => {
9082
8749
  const elapsed = `${Math.round((Date.now() - p.startTime) / 1e3)}s`;
9083
8750
  return [
9084
8751
  String(p.pid),
8752
+ p.name,
8753
+ p.type,
9085
8754
  p.status,
9086
8755
  p.exitCode !== void 0 ? String(p.exitCode) : "-",
9087
8756
  elapsed,
@@ -9126,37 +8795,585 @@ var CliKillCommandProcessor = class {
9126
8795
  }
9127
8796
  }
9128
8797
  };
9129
-
9130
- // src/lib/processors/system/index.ts
9131
- var systemProcessors = [
9132
- new CliHelpCommandProcessor(),
9133
- new CliVersionCommandProcessor(),
9134
- new CliFeedbackCommandProcessor(),
9135
- new CliHistoryCommandProcessor(),
9136
- new CliPackagesCommandProcessor(),
9137
- new CliHotKeysCommandProcessor(),
9138
- new CliDebugCommandProcessor(),
9139
- new CliNanoCommandProcessor(),
9140
- new CliServicesCommandProcessor(),
9141
- new CliPsCommandProcessor(),
9142
- new CliKillCommandProcessor()
9143
- ];
9144
- var themes = {
9145
- ...DefaultThemes
8798
+ var ESC = "\x1B";
8799
+ var CSI = `${ESC}[`;
8800
+ var ansi = {
8801
+ clearScreen: `${CSI}2J`,
8802
+ cursorHome: `${CSI}H`,
8803
+ hideCursor: `${CSI}?25l`,
8804
+ cursorTo: (row, col) => `${CSI}${row};${col}H`,
8805
+ fg: {
8806
+ green: `${CSI}32m`,
8807
+ cyan: `${CSI}36m`,
8808
+ yellow: `${CSI}93m`,
8809
+ white: `${CSI}97m`,
8810
+ red: `${CSI}91m`,
8811
+ gray: `${CSI}90m`
8812
+ },
8813
+ bg: {
8814
+ blue: `${CSI}44m`
8815
+ },
8816
+ bold: `${CSI}1m`,
8817
+ dim: `${CSI}2m`,
8818
+ inverse: `${CSI}7m`,
8819
+ reset: `${CSI}0m`
9146
8820
  };
9147
- var themeInfos = {
9148
- ...DefaultThemeInfos
8821
+ var BOX = {
8822
+ topLeft: "\u250C",
8823
+ topRight: "\u2510",
8824
+ bottomLeft: "\u2514",
8825
+ bottomRight: "\u2518",
8826
+ horizontal: "\u2500",
8827
+ vertical: "\u2502"
9149
8828
  };
9150
-
9151
- // src/lib/processors/theme/cli-theme-command-processor.ts
9152
- function hexToRgb(hex) {
9153
- const m = /^#?([0-9A-Fa-f]{6})$/.exec(hex);
9154
- if (!m) return null;
9155
- const n = parseInt(m[1], 16);
9156
- return { r: n >> 16 & 255, g: n >> 8 & 255, b: n & 255 };
9157
- }
9158
- function colorSwatch(hex) {
9159
- if (!hex) return " ";
8829
+ function formatElapsed(ms) {
8830
+ const seconds = Math.floor(ms / 1e3) % 60;
8831
+ const minutes = Math.floor(ms / (1e3 * 60)) % 60;
8832
+ const hours = Math.floor(ms / (1e3 * 60 * 60)) % 24;
8833
+ const days = Math.floor(ms / (1e3 * 60 * 60 * 24));
8834
+ const parts = [];
8835
+ if (days > 0) parts.push(`${days}d`);
8836
+ if (hours > 0) parts.push(`${hours}h`);
8837
+ if (minutes > 0) parts.push(`${minutes}m`);
8838
+ parts.push(`${seconds}s`);
8839
+ return parts.join(" ");
8840
+ }
8841
+ function statusColor(status) {
8842
+ switch (status) {
8843
+ case "running":
8844
+ return ansi.fg.green;
8845
+ case "completed":
8846
+ case "done":
8847
+ return ansi.fg.cyan;
8848
+ case "pending":
8849
+ return ansi.fg.yellow;
8850
+ case "stopped":
8851
+ return ansi.fg.white;
8852
+ case "failed":
8853
+ case "killed":
8854
+ return ansi.fg.red;
8855
+ default:
8856
+ return ansi.fg.white;
8857
+ }
8858
+ }
8859
+ function truncate(str, maxLen) {
8860
+ if (str.length <= maxLen) return str;
8861
+ return str.substring(0, maxLen - 1) + "\u2026";
8862
+ }
8863
+ function pad(str, len) {
8864
+ return str.length >= len ? str.substring(0, len) : str + " ".repeat(len - str.length);
8865
+ }
8866
+ var CliHtopCommandProcessor = class {
8867
+ constructor() {
8868
+ this.command = "htop";
8869
+ this.description = "Interactive process and service monitor";
8870
+ this.author = DefaultLibraryAuthor;
8871
+ this.metadata = {
8872
+ icon: "\u{1F4CA}",
8873
+ sealed: true,
8874
+ module: "system"
8875
+ };
8876
+ this.context = null;
8877
+ this.registry = null;
8878
+ this.refreshTimer = null;
8879
+ this.rows = [];
8880
+ this.selectedIndex = 0;
8881
+ this.scrollOffset = 0;
8882
+ this.termCols = 80;
8883
+ this.termRows = 24;
8884
+ this.showingLogs = false;
8885
+ this.errorMessage = null;
8886
+ this.errorTimeout = null;
8887
+ }
8888
+ async processCommand(_command, context2) {
8889
+ try {
8890
+ this.registry = context2.services.get(
8891
+ CliProcessRegistry_TOKEN
8892
+ );
8893
+ } catch {
8894
+ context2.writer.writeError("Process registry not available");
8895
+ return;
8896
+ }
8897
+ this.context = context2;
8898
+ this.termCols = context2.terminal.cols || 80;
8899
+ this.termRows = context2.terminal.rows || 24;
8900
+ this.selectedIndex = 0;
8901
+ this.scrollOffset = 0;
8902
+ this.showingLogs = false;
8903
+ this.errorMessage = null;
8904
+ context2.enterFullScreenMode(this);
8905
+ this.refreshData();
8906
+ this.render();
8907
+ this.refreshTimer = context2.createInterval(() => {
8908
+ if (!this.showingLogs) {
8909
+ this.refreshData();
8910
+ this.render();
8911
+ }
8912
+ }, 1e3);
8913
+ }
8914
+ async onData(data, context2) {
8915
+ if (this.showingLogs) {
8916
+ this.showingLogs = false;
8917
+ this.render();
8918
+ return;
8919
+ }
8920
+ switch (data) {
8921
+ case `${ESC}[A`:
8922
+ this.moveSelection(-1);
8923
+ this.render();
8924
+ break;
8925
+ case `${ESC}[B`:
8926
+ this.moveSelection(1);
8927
+ this.render();
8928
+ break;
8929
+ case "q":
8930
+ case ESC:
8931
+ this.exit(context2);
8932
+ break;
8933
+ case "k":
8934
+ this.actionKill();
8935
+ break;
8936
+ case "s":
8937
+ await this.actionStart();
8938
+ break;
8939
+ case "t":
8940
+ await this.actionStop();
8941
+ break;
8942
+ case "r":
8943
+ await this.actionRestart();
8944
+ break;
8945
+ case "l":
8946
+ this.actionLogs();
8947
+ break;
8948
+ }
8949
+ }
8950
+ onResize(cols, rows, _context) {
8951
+ this.termCols = cols;
8952
+ this.termRows = rows;
8953
+ this.clampScroll();
8954
+ this.render();
8955
+ }
8956
+ onDispose(_context) {
8957
+ this.refreshTimer = null;
8958
+ this.context = null;
8959
+ this.registry = null;
8960
+ this.rows = [];
8961
+ }
8962
+ writeDescription(context2) {
8963
+ const { writer } = context2;
8964
+ writer.writeln("Interactive process and service monitor");
8965
+ writer.writeln();
8966
+ writer.writeln("Usage:");
8967
+ writer.writeln(` ${writer.wrapInColor("htop", 36)}`);
8968
+ }
8969
+ // ── Data ─────────────────────────────────────────────────────────
8970
+ refreshData() {
8971
+ if (!this.registry || !this.context) return;
8972
+ const processEntries = this.registry.list();
8973
+ const serviceEntries = this.context.backgroundServices.list();
8974
+ const servicePids = /* @__PURE__ */ new Set();
8975
+ for (const svc of serviceEntries) {
8976
+ if (svc.pid !== void 0) {
8977
+ servicePids.add(svc.pid);
8978
+ }
8979
+ }
8980
+ const unified = [];
8981
+ for (const p of processEntries) {
8982
+ if (servicePids.has(p.pid)) continue;
8983
+ unified.push({
8984
+ pid: p.pid,
8985
+ name: p.name,
8986
+ type: p.type,
8987
+ status: p.status,
8988
+ time: formatElapsed(Date.now() - p.startTime),
8989
+ mode: "main",
8990
+ source: "process"
8991
+ });
8992
+ }
8993
+ for (const svc of serviceEntries) {
8994
+ let time = "-";
8995
+ if (svc.status === "running" && svc.startedAt) {
8996
+ time = formatElapsed(Date.now() - new Date(svc.startedAt).getTime());
8997
+ }
8998
+ unified.push({
8999
+ pid: svc.pid ?? 0,
9000
+ name: svc.name,
9001
+ type: svc.type,
9002
+ status: svc.status,
9003
+ time,
9004
+ mode: svc.executionMode === "worker" ? "worker" : "main",
9005
+ source: "service",
9006
+ serviceInfo: svc
9007
+ });
9008
+ }
9009
+ unified.sort((a, b) => {
9010
+ const aRunning = a.status === "running" ? 0 : 1;
9011
+ const bRunning = b.status === "running" ? 0 : 1;
9012
+ if (aRunning !== bRunning) return aRunning - bRunning;
9013
+ return b.pid - a.pid;
9014
+ });
9015
+ this.rows = unified;
9016
+ if (this.rows.length === 0) {
9017
+ this.selectedIndex = 0;
9018
+ } else {
9019
+ this.selectedIndex = Math.min(this.selectedIndex, this.rows.length - 1);
9020
+ }
9021
+ this.clampScroll();
9022
+ }
9023
+ // ── Actions ──────────────────────────────────────────────────────
9024
+ actionKill() {
9025
+ const row = this.rows[this.selectedIndex];
9026
+ if (!row || row.status !== "running") return;
9027
+ if (row.name === "htop" && row.source === "process") {
9028
+ if (this.context) this.exit(this.context);
9029
+ return;
9030
+ }
9031
+ try {
9032
+ if (this.registry) {
9033
+ this.registry.kill(row.pid);
9034
+ }
9035
+ } catch (e) {
9036
+ this.showError(e?.message || "Kill failed");
9037
+ }
9038
+ this.refreshData();
9039
+ this.render();
9040
+ }
9041
+ async actionStart() {
9042
+ const row = this.rows[this.selectedIndex];
9043
+ if (!row || row.source !== "service") return;
9044
+ if (row.status !== "stopped" && row.status !== "pending" && row.status !== "failed") return;
9045
+ try {
9046
+ await this.context.backgroundServices.start(row.name);
9047
+ } catch (e) {
9048
+ this.showError(e?.message || "Start failed");
9049
+ }
9050
+ this.refreshData();
9051
+ this.render();
9052
+ }
9053
+ async actionStop() {
9054
+ const row = this.rows[this.selectedIndex];
9055
+ if (!row || row.source !== "service" || row.status !== "running") return;
9056
+ try {
9057
+ await this.context.backgroundServices.stop(row.name);
9058
+ } catch (e) {
9059
+ this.showError(e?.message || "Stop failed");
9060
+ }
9061
+ this.refreshData();
9062
+ this.render();
9063
+ }
9064
+ async actionRestart() {
9065
+ const row = this.rows[this.selectedIndex];
9066
+ if (!row || row.source !== "service") return;
9067
+ try {
9068
+ await this.context.backgroundServices.restart(row.name);
9069
+ } catch (e) {
9070
+ this.showError(e?.message || "Restart failed");
9071
+ }
9072
+ this.refreshData();
9073
+ this.render();
9074
+ }
9075
+ actionLogs() {
9076
+ const row = this.rows[this.selectedIndex];
9077
+ if (!row || row.source !== "service" || !this.context) return;
9078
+ this.showingLogs = true;
9079
+ this.renderLogsOverlay(row.name);
9080
+ }
9081
+ // ── Navigation ───────────────────────────────────────────────────
9082
+ moveSelection(delta) {
9083
+ if (this.rows.length === 0) return;
9084
+ this.selectedIndex += delta;
9085
+ if (this.selectedIndex < 0) {
9086
+ this.selectedIndex = this.rows.length - 1;
9087
+ } else if (this.selectedIndex >= this.rows.length) {
9088
+ this.selectedIndex = 0;
9089
+ }
9090
+ this.clampScroll();
9091
+ }
9092
+ get visibleRows() {
9093
+ return Math.max(1, this.termRows - 4);
9094
+ }
9095
+ clampScroll() {
9096
+ if (this.selectedIndex < this.scrollOffset) {
9097
+ this.scrollOffset = this.selectedIndex;
9098
+ } else if (this.selectedIndex >= this.scrollOffset + this.visibleRows) {
9099
+ this.scrollOffset = this.selectedIndex - this.visibleRows + 1;
9100
+ }
9101
+ this.scrollOffset = Math.max(0, this.scrollOffset);
9102
+ }
9103
+ // ── Rendering ────────────────────────────────────────────────────
9104
+ render() {
9105
+ if (!this.context) return;
9106
+ const buf = [];
9107
+ const cols = this.termCols;
9108
+ buf.push(ansi.clearScreen, ansi.cursorHome, ansi.hideCursor);
9109
+ const runningCount = this.rows.filter((r) => r.status === "running").length;
9110
+ const totalCount = this.rows.length;
9111
+ let headerLeft;
9112
+ if (this.errorMessage) {
9113
+ headerLeft = `${ansi.bold}${ansi.fg.red} htop ${ansi.reset}${ansi.fg.red}${ansi.dim} ${this.errorMessage}${ansi.reset}`;
9114
+ } else {
9115
+ headerLeft = `${ansi.bold}${ansi.fg.cyan} htop${ansi.reset}${ansi.fg.gray} \u2014 ${runningCount} running / ${totalCount} total${ansi.reset}`;
9116
+ }
9117
+ const headerRight = `${ansi.fg.gray}Refresh: 1s${ansi.reset}`;
9118
+ buf.push(ansi.cursorTo(1, 1));
9119
+ buf.push(headerLeft);
9120
+ buf.push(ansi.cursorTo(1, Math.max(1, cols - 11)));
9121
+ buf.push(headerRight);
9122
+ buf.push(ansi.cursorTo(2, 1));
9123
+ buf.push(ansi.fg.gray, BOX.horizontal.repeat(cols), ansi.reset);
9124
+ const colPid = 6;
9125
+ const colType = 9;
9126
+ const colStatus = 11;
9127
+ const colMode = 8;
9128
+ const colTime = 9;
9129
+ const colName = Math.max(10, cols - colPid - colType - colStatus - colMode - colTime - 2);
9130
+ buf.push(ansi.cursorTo(3, 1));
9131
+ buf.push(
9132
+ ansi.bold,
9133
+ ansi.fg.white,
9134
+ " ",
9135
+ pad("PID", colPid),
9136
+ pad("NAME", colName),
9137
+ pad("TYPE", colType),
9138
+ pad("STATUS", colStatus),
9139
+ pad("MODE", colMode),
9140
+ pad("TIME", colTime),
9141
+ ansi.reset
9142
+ );
9143
+ if (this.rows.length === 0) {
9144
+ const emptyMsg = "No processes";
9145
+ const emptyRow = Math.floor(this.visibleRows / 2) + 4;
9146
+ const emptyCol = Math.max(1, Math.floor((cols - emptyMsg.length) / 2));
9147
+ buf.push(ansi.cursorTo(emptyRow, emptyCol));
9148
+ buf.push(ansi.fg.gray, emptyMsg, ansi.reset);
9149
+ } else {
9150
+ const endIdx = Math.min(
9151
+ this.scrollOffset + this.visibleRows,
9152
+ this.rows.length
9153
+ );
9154
+ for (let i = this.scrollOffset; i < endIdx; i++) {
9155
+ const row = this.rows[i];
9156
+ const screenRow = 4 + (i - this.scrollOffset);
9157
+ const isSelected = i === this.selectedIndex;
9158
+ const prefix = isSelected ? ">" : " ";
9159
+ const pidStr = pad(String(row.pid), colPid);
9160
+ const nameStr = pad(truncate(row.name, colName - 1), colName);
9161
+ const typeStr = pad(row.type, colType);
9162
+ const statusStr = pad(row.status, colStatus);
9163
+ const modeStr = pad(row.mode, colMode);
9164
+ const timeStr = pad(row.time, colTime);
9165
+ buf.push(ansi.cursorTo(screenRow, 1));
9166
+ if (isSelected) {
9167
+ buf.push(ansi.inverse);
9168
+ }
9169
+ buf.push(
9170
+ prefix,
9171
+ pidStr,
9172
+ nameStr,
9173
+ typeStr,
9174
+ isSelected ? "" : statusColor(row.status),
9175
+ statusStr,
9176
+ isSelected ? "" : ansi.reset,
9177
+ modeStr,
9178
+ timeStr
9179
+ );
9180
+ const contentLen = 1 + colPid + colName + colType + colStatus + colMode + colTime;
9181
+ if (contentLen < cols) {
9182
+ buf.push(" ".repeat(cols - contentLen));
9183
+ }
9184
+ if (isSelected) {
9185
+ buf.push(ansi.reset);
9186
+ }
9187
+ }
9188
+ if (this.scrollOffset > 0) {
9189
+ buf.push(ansi.cursorTo(4, cols));
9190
+ buf.push(ansi.fg.gray, "\u2191", ansi.reset);
9191
+ }
9192
+ if (endIdx < this.rows.length) {
9193
+ buf.push(ansi.cursorTo(3 + this.visibleRows, cols));
9194
+ buf.push(ansi.fg.gray, "\u2193", ansi.reset);
9195
+ }
9196
+ }
9197
+ const footerSepRow = this.termRows - 1;
9198
+ buf.push(ansi.cursorTo(footerSepRow, 1));
9199
+ buf.push(ansi.fg.gray, BOX.horizontal.repeat(cols), ansi.reset);
9200
+ buf.push(ansi.cursorTo(this.termRows, 1));
9201
+ buf.push(ansi.dim, " ", this.buildFooter(), ansi.reset);
9202
+ this.context.terminal.write(buf.join(""));
9203
+ }
9204
+ buildFooter() {
9205
+ const selected = this.rows[this.selectedIndex];
9206
+ const parts = [];
9207
+ if (selected) {
9208
+ if (selected.status === "running") {
9209
+ parts.push("k:Kill");
9210
+ }
9211
+ if (selected.source === "service" && (selected.status === "stopped" || selected.status === "pending" || selected.status === "failed")) {
9212
+ parts.push("s:Start");
9213
+ }
9214
+ if (selected.source === "service" && selected.status === "running") {
9215
+ parts.push("t:Stop");
9216
+ }
9217
+ if (selected.source === "service") {
9218
+ parts.push("r:Restart");
9219
+ parts.push("l:Logs");
9220
+ }
9221
+ }
9222
+ parts.push("q:Quit");
9223
+ parts.push("\u2191\u2193:Navigate");
9224
+ return parts.join(" ");
9225
+ }
9226
+ // ── Logs overlay ─────────────────────────────────────────────
9227
+ renderLogsOverlay(serviceName) {
9228
+ if (!this.context) return;
9229
+ const logs = this.context.backgroundServices.getLogs(serviceName, 15);
9230
+ const buf = [];
9231
+ const boxWidth = Math.min(this.termCols - 4, 70);
9232
+ const boxHeight = Math.min(this.termRows - 4, logs.length + 4);
9233
+ const startCol = Math.max(1, Math.floor((this.termCols - boxWidth) / 2));
9234
+ const startRow = Math.max(1, Math.floor((this.termRows - boxHeight) / 2));
9235
+ const innerWidth = boxWidth - 2;
9236
+ const title = ` Logs: ${truncate(serviceName, innerWidth - 10)} `;
9237
+ const topBorder = BOX.topLeft + BOX.horizontal + title + BOX.horizontal.repeat(Math.max(0, boxWidth - title.length - 3)) + BOX.topRight;
9238
+ buf.push(ansi.cursorTo(startRow, startCol));
9239
+ buf.push(ansi.fg.cyan, topBorder, ansi.reset);
9240
+ const contentRows = boxHeight - 3;
9241
+ if (logs.length === 0) {
9242
+ const noLogs = "No log entries";
9243
+ buf.push(ansi.cursorTo(startRow + 1, startCol));
9244
+ buf.push(
9245
+ ansi.fg.cyan,
9246
+ BOX.vertical,
9247
+ ansi.reset,
9248
+ " ",
9249
+ ansi.fg.gray,
9250
+ pad(noLogs, innerWidth - 1),
9251
+ ansi.reset,
9252
+ ansi.fg.cyan,
9253
+ BOX.vertical,
9254
+ ansi.reset
9255
+ );
9256
+ for (let r = 2; r <= contentRows; r++) {
9257
+ buf.push(ansi.cursorTo(startRow + r, startCol));
9258
+ buf.push(
9259
+ ansi.fg.cyan,
9260
+ BOX.vertical,
9261
+ ansi.reset,
9262
+ " ".repeat(innerWidth),
9263
+ ansi.fg.cyan,
9264
+ BOX.vertical,
9265
+ ansi.reset
9266
+ );
9267
+ }
9268
+ } else {
9269
+ const displayLogs = logs.slice(-contentRows);
9270
+ let lineIdx = 0;
9271
+ for (const entry of displayLogs) {
9272
+ const ts = new Date(entry.timestamp).toLocaleTimeString();
9273
+ 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}`;
9274
+ const line = `[${ts}] ${levelTag} ${truncate(entry.message, innerWidth - 18)}`;
9275
+ buf.push(ansi.cursorTo(startRow + 1 + lineIdx, startCol));
9276
+ buf.push(
9277
+ ansi.fg.cyan,
9278
+ BOX.vertical,
9279
+ ansi.reset,
9280
+ " ",
9281
+ line
9282
+ );
9283
+ buf.push(ansi.cursorTo(startRow + 1 + lineIdx, startCol + boxWidth - 1));
9284
+ buf.push(ansi.fg.cyan, BOX.vertical, ansi.reset);
9285
+ lineIdx++;
9286
+ }
9287
+ for (let r = lineIdx; r < contentRows; r++) {
9288
+ buf.push(ansi.cursorTo(startRow + 1 + r, startCol));
9289
+ buf.push(
9290
+ ansi.fg.cyan,
9291
+ BOX.vertical,
9292
+ ansi.reset,
9293
+ " ".repeat(innerWidth),
9294
+ ansi.fg.cyan,
9295
+ BOX.vertical,
9296
+ ansi.reset
9297
+ );
9298
+ }
9299
+ }
9300
+ const pressMsg = "Press any key to close";
9301
+ const pressPad = Math.max(0, Math.floor((innerWidth - pressMsg.length) / 2));
9302
+ buf.push(ansi.cursorTo(startRow + contentRows + 1, startCol));
9303
+ buf.push(
9304
+ ansi.fg.cyan,
9305
+ BOX.vertical,
9306
+ ansi.reset,
9307
+ " ".repeat(pressPad),
9308
+ ansi.fg.gray,
9309
+ pressMsg,
9310
+ ansi.reset,
9311
+ " ".repeat(Math.max(0, innerWidth - pressPad - pressMsg.length)),
9312
+ ansi.fg.cyan,
9313
+ BOX.vertical,
9314
+ ansi.reset
9315
+ );
9316
+ buf.push(ansi.cursorTo(startRow + contentRows + 2, startCol));
9317
+ buf.push(
9318
+ ansi.fg.cyan,
9319
+ BOX.bottomLeft,
9320
+ BOX.horizontal.repeat(boxWidth - 2),
9321
+ BOX.bottomRight,
9322
+ ansi.reset
9323
+ );
9324
+ this.context.terminal.write(buf.join(""));
9325
+ }
9326
+ // ── Utilities ────────────────────────────────────────────────
9327
+ showError(msg) {
9328
+ this.errorMessage = msg;
9329
+ if (this.errorTimeout) {
9330
+ this.errorTimeout.clear();
9331
+ }
9332
+ if (this.context) {
9333
+ this.errorTimeout = this.context.createInterval(() => {
9334
+ this.errorMessage = null;
9335
+ this.errorTimeout?.clear();
9336
+ this.errorTimeout = null;
9337
+ }, 3e3);
9338
+ }
9339
+ }
9340
+ exit(context2) {
9341
+ context2.exitFullScreenMode();
9342
+ this.context = null;
9343
+ }
9344
+ };
9345
+
9346
+ // src/lib/processors/system/index.ts
9347
+ var systemProcessors = [
9348
+ new CliHelpCommandProcessor(),
9349
+ new CliVersionCommandProcessor(),
9350
+ new CliFeedbackCommandProcessor(),
9351
+ new CliHistoryCommandProcessor(),
9352
+ new CliPackagesCommandProcessor(),
9353
+ new CliHotKeysCommandProcessor(),
9354
+ new CliDebugCommandProcessor(),
9355
+ new CliNanoCommandProcessor(),
9356
+ new CliServicesCommandProcessor(),
9357
+ new CliPsCommandProcessor(),
9358
+ new CliKillCommandProcessor(),
9359
+ new CliHtopCommandProcessor()
9360
+ ];
9361
+ var themes = {
9362
+ ...DefaultThemes
9363
+ };
9364
+ var themeInfos = {
9365
+ ...DefaultThemeInfos
9366
+ };
9367
+
9368
+ // src/lib/processors/theme/cli-theme-command-processor.ts
9369
+ function hexToRgb(hex) {
9370
+ const m = /^#?([0-9A-Fa-f]{6})$/.exec(hex);
9371
+ if (!m) return null;
9372
+ const n = parseInt(m[1], 16);
9373
+ return { r: n >> 16 & 255, g: n >> 8 & 255, b: n & 255 };
9374
+ }
9375
+ function colorSwatch(hex) {
9376
+ if (!hex) return " ";
9160
9377
  const rgb = hexToRgb(hex);
9161
9378
  if (!rgb) return " ";
9162
9379
  return `\x1B[48;2;${rgb.r};${rgb.g};${rgb.b}m \x1B[0m`;
@@ -9335,10 +9552,12 @@ var CliThemeCommandProcessor = class {
9335
9552
  const selected = await context2.reader.readSelect(
9336
9553
  "Select a theme (live preview):",
9337
9554
  options,
9338
- (value) => {
9339
- if (themes[value]) {
9340
- context2.terminal.options.theme = themes[value];
9341
- this.applyStyles(context2);
9555
+ {
9556
+ onChange: (value) => {
9557
+ if (themes[value]) {
9558
+ context2.terminal.options.theme = themes[value];
9559
+ this.applyStyles(context2);
9560
+ }
9342
9561
  }
9343
9562
  }
9344
9563
  );
@@ -10203,21 +10422,16 @@ var miscProcessors = [
10203
10422
  new CliSleepCommandProcessor(),
10204
10423
  new CliUnameCommandProcessor(),
10205
10424
  new CliTimeCommandProcessor(),
10206
- new CliBase64CommandProcessor(),
10207
10425
  new CliJsonCommandProcessor(),
10208
- new CliUrlCommandProcessor(),
10209
- new CliHashCommandProcessor(),
10210
10426
  new CliRandomCommandProcessor(),
10211
10427
  new CliUptimeCommandProcessor(),
10212
10428
  new CliOpenCommandProcessor(),
10213
10429
  new CliLoremCommandProcessor(),
10214
10430
  new CliTimestampCommandProcessor(),
10215
10431
  new CliColorCommandProcessor(),
10216
- new CliJwtCommandProcessor(),
10217
10432
  new CliCalCommandProcessor(),
10218
10433
  new CliSeqCommandProcessor(),
10219
10434
  new CliScreenCommandProcessor(),
10220
- new CliHexCommandProcessor(),
10221
10435
  new CliClipboardCommandProcessor(),
10222
10436
  new CliConvertCommandProcessor(),
10223
10437
  new CliYesCommandProcessor()
@@ -10528,6 +10742,10 @@ var CliBackgroundServiceRegistry = class {
10528
10742
  this.isFullScreen = false;
10529
10743
  this.notificationQueue = [];
10530
10744
  }
10745
+ /** Attach the process registry so background services get PIDs */
10746
+ setProcessRegistry(registry) {
10747
+ this.processRegistry = registry;
10748
+ }
10531
10749
  register(service) {
10532
10750
  if (this.entries.has(service.name)) {
10533
10751
  throw new Error(`Service "${service.name}" is already registered`);
@@ -10548,7 +10766,17 @@ var CliBackgroundServiceRegistry = class {
10548
10766
  if (entry.status === "running") {
10549
10767
  throw new Error(`Service "${name}" is already running`);
10550
10768
  }
10769
+ if (this.processRegistry) {
10770
+ const svcType = entry.service.type === "daemon" ? "daemon" : entry.service.type === "job" ? "job" : "service";
10771
+ const proc = this.processRegistry.register(name, {
10772
+ name,
10773
+ type: svcType,
10774
+ onKill: () => this.stop(name)
10775
+ });
10776
+ entry.pid = proc.pid;
10777
+ }
10551
10778
  const useWorker = entry.service.workerCompatible && entry.service.workerFactory && typeof Worker !== "undefined";
10779
+ const isDaemon = entry.service.type === "daemon";
10552
10780
  if (useWorker) {
10553
10781
  const workerRunner = new CliWorkerServiceRunner(
10554
10782
  entry.service,
@@ -10562,15 +10790,19 @@ var CliBackgroundServiceRegistry = class {
10562
10790
  entry.startedAt = /* @__PURE__ */ new Date();
10563
10791
  entry.stoppedAt = void 0;
10564
10792
  entry.error = void 0;
10565
- try {
10566
- await workerRunner.start();
10567
- this.emitLifecycleEvent("service-started", name, {
10568
- name,
10569
- mode: "worker"
10570
- });
10571
- } catch (e) {
10572
- this.handleStartError(entry, name, e);
10793
+ if (isDaemon) {
10794
+ workerRunner.start().catch((e) => this.handleStartError(entry, name, e));
10795
+ } else {
10796
+ try {
10797
+ await workerRunner.start();
10798
+ } catch (e) {
10799
+ this.handleStartError(entry, name, e);
10800
+ }
10573
10801
  }
10802
+ this.emitLifecycleEvent("service-started", name, {
10803
+ name,
10804
+ mode: "worker"
10805
+ });
10574
10806
  } else {
10575
10807
  const mainRunner = new CliMainThreadServiceRunner(
10576
10808
  entry.service,
@@ -10584,21 +10816,25 @@ var CliBackgroundServiceRegistry = class {
10584
10816
  entry.startedAt = /* @__PURE__ */ new Date();
10585
10817
  entry.stoppedAt = void 0;
10586
10818
  entry.error = void 0;
10587
- try {
10588
- await mainRunner.start();
10589
- this.emitLifecycleEvent("service-started", name, {
10590
- name,
10591
- mode: "main-thread"
10592
- });
10593
- } catch (e) {
10594
- this.handleStartError(entry, name, e);
10819
+ if (isDaemon) {
10820
+ mainRunner.start().catch((e) => this.handleStartError(entry, name, e));
10821
+ } else {
10822
+ try {
10823
+ await mainRunner.start();
10824
+ } catch (e) {
10825
+ this.handleStartError(entry, name, e);
10826
+ }
10595
10827
  }
10828
+ this.emitLifecycleEvent("service-started", name, {
10829
+ name,
10830
+ mode: "main-thread"
10831
+ });
10596
10832
  }
10597
10833
  }
10598
10834
  async stop(name) {
10599
10835
  const entry = this.getEntry(name);
10600
10836
  if (entry.status !== "running") {
10601
- throw new Error(`Service "${name}" is not running (status: ${entry.status})`);
10837
+ return;
10602
10838
  }
10603
10839
  if (entry.runner) {
10604
10840
  await entry.runner.stop();
@@ -10606,6 +10842,7 @@ var CliBackgroundServiceRegistry = class {
10606
10842
  entry.status = "stopped";
10607
10843
  entry.stoppedAt = /* @__PURE__ */ new Date();
10608
10844
  entry.runner = null;
10845
+ this.completeInProcessRegistry(entry, 0);
10609
10846
  this.emitLifecycleEvent("service-stopped", name, { name });
10610
10847
  }
10611
10848
  async restart(name) {
@@ -10634,6 +10871,7 @@ var CliBackgroundServiceRegistry = class {
10634
10871
  entry.status = "done";
10635
10872
  entry.stoppedAt = /* @__PURE__ */ new Date();
10636
10873
  entry.runner = null;
10874
+ this.completeInProcessRegistry(entry, 0);
10637
10875
  this.emitLifecycleEvent("service-completed", name, {
10638
10876
  name,
10639
10877
  duration
@@ -10647,6 +10885,7 @@ var CliBackgroundServiceRegistry = class {
10647
10885
  entry.error = error.message;
10648
10886
  entry.stoppedAt = /* @__PURE__ */ new Date();
10649
10887
  entry.runner = null;
10888
+ this.failInProcessRegistry(entry);
10650
10889
  this.emitLifecycleEvent("service-failed", name, {
10651
10890
  name,
10652
10891
  error: error.message
@@ -10721,6 +10960,7 @@ var CliBackgroundServiceRegistry = class {
10721
10960
  const executionMode = entry.runner ? entry.runner.mode : "main-thread";
10722
10961
  const uptime = entry.status === "running" && entry.startedAt ? Date.now() - entry.startedAt.getTime() : void 0;
10723
10962
  return {
10963
+ pid: entry.pid,
10724
10964
  name: entry.service.name,
10725
10965
  description: entry.service.description,
10726
10966
  type: entry.service.type,
@@ -10737,9 +10977,14 @@ var CliBackgroundServiceRegistry = class {
10737
10977
  if (!entry) return;
10738
10978
  const from = entry.status;
10739
10979
  entry.status = status;
10740
- if (status === "stopped" || status === "failed" || status === "done") {
10980
+ if (status === "stopped" || status === "done") {
10981
+ entry.stoppedAt = /* @__PURE__ */ new Date();
10982
+ entry.runner = null;
10983
+ this.completeInProcessRegistry(entry, 0);
10984
+ } else if (status === "failed") {
10741
10985
  entry.stoppedAt = /* @__PURE__ */ new Date();
10742
10986
  entry.runner = null;
10987
+ this.failInProcessRegistry(entry);
10743
10988
  }
10744
10989
  this.emitLifecycleEvent("status-change", name, { name, from, to: status });
10745
10990
  }
@@ -10750,6 +10995,7 @@ var CliBackgroundServiceRegistry = class {
10750
10995
  entry.stoppedAt = /* @__PURE__ */ new Date();
10751
10996
  entry.runner = null;
10752
10997
  entry.logBuffer.add(`Failed to start: ${error.message}`, "error");
10998
+ this.failInProcessRegistry(entry);
10753
10999
  if (entry.service.onError) {
10754
11000
  entry.service.onError(error, {});
10755
11001
  }
@@ -10802,6 +11048,16 @@ var CliBackgroundServiceRegistry = class {
10802
11048
  this.writer.writeln(`\u250C\u2500 [${event.source}] ${message}`);
10803
11049
  this.writer.writeln("\u2514" + "\u2500".repeat(40));
10804
11050
  }
11051
+ completeInProcessRegistry(entry, exitCode) {
11052
+ if (entry.pid !== void 0 && this.processRegistry) {
11053
+ this.processRegistry.complete(entry.pid, exitCode);
11054
+ }
11055
+ }
11056
+ failInProcessRegistry(entry) {
11057
+ if (entry.pid !== void 0 && this.processRegistry) {
11058
+ this.processRegistry.fail(entry.pid);
11059
+ }
11060
+ }
10805
11061
  };
10806
11062
  var ANSI_RE = /\x1b\[[0-9;]*m/g;
10807
11063
  function visibleLength(text) {
@@ -10898,8 +11154,8 @@ var CliTerminalWriter = class {
10898
11154
  }
10899
11155
  writeColumns(items, options) {
10900
11156
  const cols = options?.columns ?? 3;
10901
- const pad = options?.padding ?? 2;
10902
- const colWidth = Math.max(...items.map((s) => s.length)) + pad;
11157
+ const pad2 = options?.padding ?? 2;
11158
+ const colWidth = Math.max(...items.map((s) => s.length)) + pad2;
10903
11159
  for (let i = 0; i < items.length; i += cols) {
10904
11160
  const row = items.slice(i, i + cols);
10905
11161
  this.writeln(row.map((item) => item.padEnd(colWidth)).join(""));
@@ -11127,51 +11383,34 @@ var CliTerminalProgressBar = class {
11127
11383
  }
11128
11384
  updateProgressBar() {
11129
11385
  const displayProgress = Math.round(this.progress);
11130
- const totalBars = 50;
11386
+ const totalBars = 30;
11131
11387
  const filledBars = Math.round(
11132
11388
  displayProgress / this.total * totalBars
11133
11389
  );
11134
11390
  const emptyBars = totalBars - filledBars;
11135
11391
  const RESET4 = "\x1B[0m";
11392
+ const CYAN = "\x1B[36m";
11393
+ const BRIGHT_CYAN = "\x1B[1;36m";
11136
11394
  const DIM4 = "\x1B[2m";
11395
+ const shimmerPos = this.tickCount % (filledBars + 4);
11137
11396
  let filledStr = "";
11138
- const shimmerPos = this.tickCount % (filledBars + 6);
11139
11397
  for (let i = 0; i < filledBars; i++) {
11140
- const ratio = filledBars > 1 ? i / (filledBars - 1) : 1;
11141
- const color = this.progressGradientColor(ratio);
11142
- const isShimmer = i >= shimmerPos - 2 && i <= shimmerPos;
11143
- const bright = isShimmer ? "\x1B[1m" : "";
11144
- filledStr += `${color}${bright}#${RESET4}`;
11145
- }
11146
- const emptyStr = `${DIM4}${".".repeat(emptyBars)}${RESET4}`;
11147
- const progressBar = `[${filledStr}${emptyStr}]`;
11148
- const percentage = `${displayProgress}%`.padStart(4, " ");
11149
- const text = this.text.length > 0 ? ` ${this.text}` : "";
11398
+ const isShimmer = i >= shimmerPos - 1 && i <= shimmerPos;
11399
+ filledStr += `${isShimmer ? BRIGHT_CYAN : CYAN}\u2501${RESET4}`;
11400
+ }
11401
+ const emptyStr = `${DIM4}${"\u2500".repeat(emptyBars)}${RESET4}`;
11402
+ const progressBar = `${CYAN}[${RESET4}${filledStr}${emptyStr}${CYAN}]${RESET4}`;
11403
+ const percentage = `${CYAN}${displayProgress}%${RESET4}`.padStart(4, " ");
11404
+ const text = this.text.length > 0 ? ` ${DIM4}${this.text}${RESET4}` : "";
11150
11405
  this.clearCurrentLine();
11151
11406
  this.progressText = `${progressBar} ${percentage} ${text}`;
11152
- this.progressTextPlainLength = 1 + totalBars + 1 + 1 + 4 + 1 + text.length;
11407
+ this.progressTextPlainLength = 1 + totalBars + 1 + 1 + 4 + 1 + this.text.length + 1;
11153
11408
  this.terminal.write(this.progressText);
11154
11409
  this.lastLineCount = this.calcLineCount(this.progressTextPlainLength);
11155
11410
  if (this.context) {
11156
11411
  this.context.setCurrentLine(this.progressText);
11157
11412
  }
11158
11413
  }
11159
- /**
11160
- * Returns an ANSI 256-color escape for a position in the progress bar.
11161
- * Gradient: red (0%) -> yellow (50%) -> green (100%).
11162
- */
11163
- progressGradientColor(ratio) {
11164
- let r, g;
11165
- if (ratio < 0.5) {
11166
- r = 5;
11167
- g = Math.round(ratio / 0.5 * 5);
11168
- } else {
11169
- r = Math.round((1 - ratio) / 0.5 * 5);
11170
- g = 5;
11171
- }
11172
- const colorIndex = 16 + 36 * r + 6 * g + 0;
11173
- return `\x1B[38;5;${colorIndex}m`;
11174
- }
11175
11414
  clearCurrentLine() {
11176
11415
  const lines = Math.max(
11177
11416
  this.lastLineCount,
@@ -11284,243 +11523,1779 @@ var CliClipboard = class {
11284
11523
  }
11285
11524
  };
11286
11525
 
11287
- // src/lib/services/cli-input-reader.ts
11288
- var CliInputReader = class {
11289
- constructor(host) {
11526
+ // src/lib/input/modes/input-mode-base.ts
11527
+ var InputModeBase = class {
11528
+ constructor(host, resolve) {
11290
11529
  this.host = host;
11530
+ this.resolve = resolve;
11531
+ this._resolved = false;
11532
+ /** Number of extra lines rendered below the prompt (help bar, error, etc.) */
11533
+ this.extraLines = 0;
11291
11534
  }
11292
- readLine(prompt) {
11293
- return this.createInputRequest("line", prompt);
11535
+ /** Whether this mode has already resolved (prevents double-resolve from async operations) */
11536
+ get isResolved() {
11537
+ return this._resolved;
11294
11538
  }
11295
- readPassword(prompt) {
11296
- return this.createInputRequest("password", prompt);
11539
+ /**
11540
+ * Default key event handler. Aborts on Ctrl+C or Escape.
11541
+ * Subclasses may override for custom behavior (e.g., search-enabled modes
11542
+ * clear the filter first, abort on second press).
11543
+ */
11544
+ handleKeyEvent(event) {
11545
+ if (event.code === "KeyC" && event.ctrlKey || event.code === "Escape") {
11546
+ this.abort();
11547
+ return false;
11548
+ }
11549
+ return true;
11297
11550
  }
11298
- readConfirm(prompt, defaultValue = false) {
11299
- const hint = defaultValue ? "(Y/n)" : "(y/N)";
11300
- const displayPrompt = `${prompt} ${hint}: `;
11301
- return new Promise((resolve) => {
11302
- if (this.host.activeInputRequest) {
11303
- throw new Error("Another input request is already active");
11304
- }
11305
- this.host.writeToTerminal(displayPrompt);
11306
- this.host.setActiveInputRequest({
11307
- type: "confirm",
11308
- promptText: displayPrompt,
11309
- resolve,
11310
- buffer: "",
11311
- cursorPosition: 0,
11312
- defaultValue
11313
- });
11314
- });
11551
+ /**
11552
+ * Resolve the input promise with a value and pop this mode from the stack.
11553
+ */
11554
+ resolveAndPop(value) {
11555
+ if (this._resolved) return;
11556
+ this._resolved = true;
11557
+ this.host.terminal.write("\r\n");
11558
+ this.host.popMode();
11559
+ this.resolve(value);
11315
11560
  }
11316
- readSelect(prompt, options, onChange) {
11317
- if (!options || options.length === 0) {
11318
- return Promise.reject(
11319
- new Error("readSelect requires at least one option")
11320
- );
11561
+ /**
11562
+ * Abort the input — resolve with null and pop.
11563
+ */
11564
+ abort() {
11565
+ if (this._resolved) return;
11566
+ this._resolved = true;
11567
+ this.host.terminal.write("\r\n");
11568
+ this.host.popMode();
11569
+ this.resolve(null);
11570
+ }
11571
+ /**
11572
+ * Clear the current line and redraw prompt + display text, positioning
11573
+ * the cursor at the given position.
11574
+ *
11575
+ * All writes are batched into a single `terminal.write()` call to
11576
+ * eliminate visible flicker between clear and redraw.
11577
+ */
11578
+ redrawLine(promptText, displayText, cursorPosition) {
11579
+ let buf = "";
11580
+ if (this.extraLines > 0) {
11581
+ buf += "\x1B[s";
11582
+ for (let i = 0; i < this.extraLines; i++) {
11583
+ buf += "\x1B[B\x1B[2K";
11584
+ }
11585
+ buf += "\x1B[u";
11586
+ this.extraLines = 0;
11587
+ }
11588
+ buf += "\x1B[2K\r" + promptText + displayText;
11589
+ const cursorOffset = displayText.length - cursorPosition;
11590
+ if (cursorOffset > 0) {
11591
+ buf += `\x1B[${cursorOffset}D`;
11321
11592
  }
11322
- return new Promise((resolve) => {
11323
- if (this.host.activeInputRequest) {
11324
- throw new Error("Another input request is already active");
11325
- }
11326
- const termRows = this.host.getTerminalRows?.() ?? 24;
11327
- const maxVisible = Math.max(3, termRows - 2);
11328
- this.host.writeToTerminal(prompt + "\r\n");
11329
- const displayLines = this.renderSelectOptions(options, 0, 0, maxVisible);
11330
- onChange?.(options[0].value);
11331
- this.host.setActiveInputRequest({
11332
- type: "select",
11333
- promptText: prompt,
11334
- resolve,
11335
- buffer: "",
11336
- cursorPosition: 0,
11337
- options,
11338
- selectedIndex: 0,
11339
- onChange,
11340
- scrollOffset: 0,
11341
- maxVisible,
11342
- displayLines
11343
- });
11344
- });
11593
+ this.host.terminal.write(buf);
11345
11594
  }
11346
- readSelectInline(prompt, options, onChange) {
11347
- if (!options || options.length === 0) {
11348
- return Promise.reject(
11349
- new Error("readSelectInline requires at least one option")
11350
- );
11595
+ /**
11596
+ * Render a dimmed help bar below the current line.
11597
+ */
11598
+ writeHelp(text) {
11599
+ this.extraLines++;
11600
+ this.host.terminal.write(
11601
+ `\r
11602
+ \x1B[2m${text}\x1B[0m\x1B[${this.extraLines}A`
11603
+ );
11604
+ }
11605
+ /**
11606
+ * Write a validation error below the current line.
11607
+ */
11608
+ writeError(message) {
11609
+ this.extraLines++;
11610
+ this.host.terminal.write(
11611
+ `\r
11612
+ \x1B[31m\u2718 ${message}\x1B[0m\x1B[${this.extraLines}A`
11613
+ );
11614
+ }
11615
+ /**
11616
+ * Clear any extra lines (help bar, errors) rendered below the prompt.
11617
+ */
11618
+ clearExtraLines() {
11619
+ if (this.extraLines > 0) {
11620
+ let buf = "\x1B[s";
11621
+ for (let i = 0; i < this.extraLines; i++) {
11622
+ buf += "\x1B[B\x1B[2K";
11623
+ }
11624
+ buf += "\x1B[u";
11625
+ this.host.terminal.write(buf);
11626
+ this.extraLines = 0;
11351
11627
  }
11352
- return new Promise((resolve) => {
11353
- if (this.host.activeInputRequest) {
11354
- throw new Error("Another input request is already active");
11355
- }
11356
- const inlineText = this.renderInlineSelectOptions(options, 0);
11357
- this.host.writeToTerminal(`${prompt} ${inlineText}`);
11358
- onChange?.(options[0].value);
11359
- this.host.setActiveInputRequest({
11360
- type: "select-inline",
11361
- promptText: prompt,
11362
- resolve,
11363
- buffer: "",
11364
- cursorPosition: 0,
11365
- options,
11366
- selectedIndex: 0,
11367
- onChange
11628
+ }
11629
+ };
11630
+
11631
+ // src/lib/input/modes/line-input-mode.ts
11632
+ var LineInputMode = class extends InputModeBase {
11633
+ constructor(host, resolve, promptText, options) {
11634
+ super(host, resolve);
11635
+ this.promptText = promptText;
11636
+ this.options = options;
11637
+ this.hasError = false;
11638
+ this.buffer = options?.default ?? "";
11639
+ this.cursorPosition = this.buffer.length;
11640
+ }
11641
+ activate() {
11642
+ const prompt = `\x1B[36m?\x1B[0m ${this.promptText}`;
11643
+ this.host.writeToTerminal(prompt);
11644
+ if (this.buffer) {
11645
+ this.host.writeToTerminal(this.buffer);
11646
+ } else if (this.options?.placeholder) {
11647
+ this.host.writeToTerminal(`\x1B[2m${this.options.placeholder}\x1B[0m`);
11648
+ this.host.terminal.write(`\x1B[${this.options.placeholder.length}D`);
11649
+ }
11650
+ }
11651
+ async handleInput(data) {
11652
+ if (this.hasError) {
11653
+ this.hasError = false;
11654
+ this.clearExtraLines();
11655
+ }
11656
+ if (data === "\r") {
11657
+ if (this.options?.validate) {
11658
+ const error = this.options.validate(this.buffer);
11659
+ if (error) {
11660
+ this.hasError = true;
11661
+ this.writeError(error);
11662
+ return;
11663
+ }
11664
+ }
11665
+ this.resolveAndPop(this.buffer);
11666
+ } else if (data === "\x7F") {
11667
+ if (this.cursorPosition > 0) {
11668
+ this.buffer = this.buffer.slice(0, this.cursorPosition - 1) + this.buffer.slice(this.cursorPosition);
11669
+ this.cursorPosition--;
11670
+ this.renderLine();
11671
+ }
11672
+ } else if (data === "\x1B[D") {
11673
+ if (this.cursorPosition > 0) {
11674
+ this.cursorPosition--;
11675
+ this.host.terminal.write(data);
11676
+ }
11677
+ } else if (data === "\x1B[C") {
11678
+ if (this.cursorPosition < this.buffer.length) {
11679
+ this.cursorPosition++;
11680
+ this.host.terminal.write(data);
11681
+ }
11682
+ } else if (data.startsWith("\x1B")) ; else {
11683
+ const text = data.replace(/[\r\n]+/g, "");
11684
+ this.buffer = this.buffer.slice(0, this.cursorPosition) + text + this.buffer.slice(this.cursorPosition);
11685
+ this.cursorPosition += text.length;
11686
+ if (this.cursorPosition === this.buffer.length && this.extraLines === 0 && !this.hasError && !(this.options?.placeholder && this.buffer.length === text.length)) {
11687
+ this.host.terminal.write(text);
11688
+ } else {
11689
+ this.renderLine();
11690
+ }
11691
+ }
11692
+ }
11693
+ onResize() {
11694
+ this.renderLine();
11695
+ }
11696
+ renderLine() {
11697
+ const prompt = `\x1B[36m?\x1B[0m ${this.promptText}`;
11698
+ if (this.buffer.length === 0 && this.options?.placeholder) {
11699
+ const placeholder = `\x1B[2m${this.options.placeholder}\x1B[0m`;
11700
+ this.redrawLine(prompt, placeholder, 0);
11701
+ } else {
11702
+ this.redrawLine(prompt, this.buffer, this.cursorPosition);
11703
+ }
11704
+ }
11705
+ };
11706
+
11707
+ // src/lib/input/modes/password-input-mode.ts
11708
+ var PasswordInputMode = class extends InputModeBase {
11709
+ constructor(host, resolve, promptText) {
11710
+ super(host, resolve);
11711
+ this.promptText = promptText;
11712
+ this.buffer = "";
11713
+ }
11714
+ activate() {
11715
+ const prompt = `\x1B[36m?\x1B[0m ${this.promptText}`;
11716
+ this.host.writeToTerminal(prompt);
11717
+ }
11718
+ async handleInput(data) {
11719
+ if (data === "\r") {
11720
+ this.resolveAndPop(this.buffer);
11721
+ } else if (data === "\x7F") {
11722
+ if (this.buffer.length > 0) {
11723
+ this.buffer = this.buffer.slice(0, -1);
11724
+ this.renderLine();
11725
+ }
11726
+ } else if (data.startsWith("\x1B")) ; else {
11727
+ const text = data.replace(/[\r\n]+/g, "");
11728
+ this.buffer += text;
11729
+ if (this.extraLines === 0) {
11730
+ this.host.terminal.write("*".repeat(text.length));
11731
+ } else {
11732
+ this.renderLine();
11733
+ }
11734
+ }
11735
+ }
11736
+ onResize() {
11737
+ this.renderLine();
11738
+ }
11739
+ renderLine() {
11740
+ const prompt = `\x1B[36m?\x1B[0m ${this.promptText}`;
11741
+ const masked = "*".repeat(this.buffer.length);
11742
+ this.redrawLine(prompt, masked, masked.length);
11743
+ }
11744
+ };
11745
+
11746
+ // src/lib/input/modes/confirm-input-mode.ts
11747
+ var ConfirmInputMode = class extends InputModeBase {
11748
+ constructor(host, resolve, promptText, defaultValue = false) {
11749
+ super(host, resolve);
11750
+ this.promptText = promptText;
11751
+ this.defaultValue = defaultValue;
11752
+ this.buffer = "";
11753
+ }
11754
+ activate() {
11755
+ const hint = this.defaultValue ? "(Y/n)" : "(y/N)";
11756
+ const prompt = `\x1B[36m?\x1B[0m ${this.promptText} ${hint}: `;
11757
+ this.host.writeToTerminal(prompt);
11758
+ }
11759
+ async handleInput(data) {
11760
+ if (data === "\r") {
11761
+ if (this.buffer === "") {
11762
+ this.resolveAndPop(this.defaultValue);
11763
+ } else {
11764
+ const lower = this.buffer.toLowerCase();
11765
+ if (lower === "y") {
11766
+ this.resolveAndPop(true);
11767
+ } else if (lower === "n") {
11768
+ this.resolveAndPop(false);
11769
+ }
11770
+ }
11771
+ } else if (data === "\x7F") {
11772
+ if (this.buffer.length > 0) {
11773
+ this.buffer = this.buffer.slice(0, -1);
11774
+ this.renderLine();
11775
+ }
11776
+ } else if (data.startsWith("\x1B")) ; else {
11777
+ const lower = data.toLowerCase();
11778
+ if (lower === "y" || lower === "n") {
11779
+ this.buffer = lower;
11780
+ this.renderLine();
11781
+ }
11782
+ }
11783
+ }
11784
+ onResize() {
11785
+ this.renderLine();
11786
+ }
11787
+ renderLine() {
11788
+ const hint = this.defaultValue ? "(Y/n)" : "(y/N)";
11789
+ const prompt = `\x1B[36m?\x1B[0m ${this.promptText} ${hint}: `;
11790
+ this.redrawLine(prompt, this.buffer, this.buffer.length);
11791
+ }
11792
+ };
11793
+
11794
+ // src/lib/input/modes/number-input-mode.ts
11795
+ var NumberInputMode = class extends InputModeBase {
11796
+ constructor(host, resolve, promptText, options) {
11797
+ super(host, resolve);
11798
+ this.promptText = promptText;
11799
+ this.options = options;
11800
+ this.hasError = false;
11801
+ this.buffer = options?.default != null ? String(options.default) : "";
11802
+ this.cursorPosition = this.buffer.length;
11803
+ }
11804
+ activate() {
11805
+ const prompt = this.buildPrompt();
11806
+ this.host.writeToTerminal(prompt);
11807
+ if (this.buffer) {
11808
+ this.host.writeToTerminal(this.buffer);
11809
+ }
11810
+ }
11811
+ async handleInput(data) {
11812
+ if (this.hasError) {
11813
+ this.hasError = false;
11814
+ this.clearExtraLines();
11815
+ }
11816
+ if (data === "\r") {
11817
+ if (this.buffer === "" || this.buffer === "-") {
11818
+ if (this.options?.default != null) {
11819
+ this.resolveAndPop(this.options.default);
11820
+ }
11821
+ return;
11822
+ }
11823
+ const value = parseInt(this.buffer, 10);
11824
+ if (isNaN(value)) {
11825
+ this.hasError = true;
11826
+ this.writeError("Please enter a valid number");
11827
+ return;
11828
+ }
11829
+ if (this.options?.min != null && value < this.options.min) {
11830
+ this.hasError = true;
11831
+ this.writeError(`Value must be at least ${this.options.min}`);
11832
+ return;
11833
+ }
11834
+ if (this.options?.max != null && value > this.options.max) {
11835
+ this.hasError = true;
11836
+ this.writeError(`Value must be at most ${this.options.max}`);
11837
+ return;
11838
+ }
11839
+ this.resolveAndPop(value);
11840
+ } else if (data === "\x7F") {
11841
+ if (this.cursorPosition > 0) {
11842
+ this.buffer = this.buffer.slice(0, this.cursorPosition - 1) + this.buffer.slice(this.cursorPosition);
11843
+ this.cursorPosition--;
11844
+ this.renderLine();
11845
+ }
11846
+ } else if (data === "\x1B[D") {
11847
+ if (this.cursorPosition > 0) {
11848
+ this.cursorPosition--;
11849
+ this.host.terminal.write(data);
11850
+ }
11851
+ } else if (data === "\x1B[C") {
11852
+ if (this.cursorPosition < this.buffer.length) {
11853
+ this.cursorPosition++;
11854
+ this.host.terminal.write(data);
11855
+ }
11856
+ } else if (data.startsWith("\x1B")) ; else {
11857
+ for (const ch of data) {
11858
+ if (ch >= "0" && ch <= "9") {
11859
+ this.buffer = this.buffer.slice(0, this.cursorPosition) + ch + this.buffer.slice(this.cursorPosition);
11860
+ this.cursorPosition++;
11861
+ } else if (ch === "-" && this.cursorPosition === 0 && !this.buffer.startsWith("-")) {
11862
+ this.buffer = "-" + this.buffer;
11863
+ this.cursorPosition++;
11864
+ }
11865
+ }
11866
+ this.renderLine();
11867
+ }
11868
+ }
11869
+ onResize() {
11870
+ this.renderLine();
11871
+ }
11872
+ buildPrompt() {
11873
+ const { min, max } = this.options ?? {};
11874
+ let hint = "";
11875
+ if (min != null && max != null) {
11876
+ hint = ` \x1B[2m(${min}\u2013${max})\x1B[0m`;
11877
+ } else if (min != null) {
11878
+ hint = ` \x1B[2m(min: ${min})\x1B[0m`;
11879
+ } else if (max != null) {
11880
+ hint = ` \x1B[2m(max: ${max})\x1B[0m`;
11881
+ }
11882
+ return `\x1B[36m?\x1B[0m ${this.promptText}${hint}`;
11883
+ }
11884
+ renderLine() {
11885
+ const prompt = this.buildPrompt();
11886
+ this.redrawLine(prompt, this.buffer, this.cursorPosition);
11887
+ }
11888
+ };
11889
+
11890
+ // src/lib/input/modes/select-input-mode.ts
11891
+ var SelectInputMode = class extends InputModeBase {
11892
+ constructor(host, resolve, promptText, options, selectOptions) {
11893
+ super(host, resolve);
11894
+ this.promptText = promptText;
11895
+ this.options = options;
11896
+ this.selectOptions = selectOptions;
11897
+ this.selectedIndex = 0;
11898
+ this.scrollOffset = 0;
11899
+ this.filter = "";
11900
+ /** Number of lines rendered for the option list + help bar (for clearing) */
11901
+ this.renderedLines = 0;
11902
+ this.filteredOptions = [...options];
11903
+ this.maxVisible = Math.min(options.length, host.getTerminalRows() - 4);
11904
+ }
11905
+ activate() {
11906
+ this.host.terminal.write("\x1B[?25l");
11907
+ if (this.selectOptions?.default) {
11908
+ const idx = this.filteredOptions.findIndex(
11909
+ (o) => o.value === this.selectOptions.default
11910
+ );
11911
+ if (idx >= 0) {
11912
+ this.selectedIndex = idx;
11913
+ if (this.selectedIndex >= this.maxVisible) {
11914
+ this.scrollOffset = this.selectedIndex - this.maxVisible + 1;
11915
+ }
11916
+ }
11917
+ } else {
11918
+ this.selectedIndex = this.findNextSelectable(-1, 1);
11919
+ }
11920
+ this.renderAll();
11921
+ }
11922
+ /**
11923
+ * Handle keyboard events. When searchable and filter is non-empty,
11924
+ * Escape clears the filter instead of aborting.
11925
+ */
11926
+ handleKeyEvent(event) {
11927
+ if (this.selectOptions?.searchable && this.filter.length > 0) {
11928
+ if (event.code === "Escape") {
11929
+ this.clearFilter();
11930
+ return false;
11931
+ }
11932
+ }
11933
+ return super.handleKeyEvent(event);
11934
+ }
11935
+ /**
11936
+ * Clear rendered option lines and show final answer before resolving.
11937
+ */
11938
+ resolveAndPop(value) {
11939
+ this.clearRenderedLines();
11940
+ const selectedOpt = this.options.find((o) => o.value === value);
11941
+ const label = selectedOpt ? selectedOpt.label : value;
11942
+ this.host.terminal.write("\x1B[2K\r");
11943
+ this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.promptText} \x1B[36m${label}\x1B[0m`);
11944
+ this.host.terminal.write("\x1B[?25h");
11945
+ super.resolveAndPop(value);
11946
+ }
11947
+ /**
11948
+ * Clear rendered option lines before aborting.
11949
+ */
11950
+ abort() {
11951
+ this.clearRenderedLines();
11952
+ this.host.terminal.write("\x1B[2K\r");
11953
+ this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.promptText} \x1B[2mcancelled\x1B[0m`);
11954
+ this.host.terminal.write("\x1B[?25h");
11955
+ super.abort();
11956
+ }
11957
+ async handleInput(data) {
11958
+ if (data === "\x1B[A") {
11959
+ this.moveSelection(-1);
11960
+ } else if (data === "\x1B[B") {
11961
+ this.moveSelection(1);
11962
+ } else if (data === "\r") {
11963
+ const items = this.buildDisplayList();
11964
+ const selectableItems = items.filter(
11965
+ (i) => i.type === "option" && !i.option.disabled
11966
+ );
11967
+ if (selectableItems.length > 0 && this.selectedIndex < selectableItems.length) {
11968
+ const selected = selectableItems[this.selectedIndex];
11969
+ this.resolveAndPop(selected.option.value);
11970
+ }
11971
+ } else if (data === "\x7F") {
11972
+ if (this.selectOptions?.searchable && this.filter.length > 0) {
11973
+ this.filter = this.filter.slice(0, -1);
11974
+ this.applyFilter();
11975
+ this.renderAll();
11976
+ }
11977
+ } else if (data.startsWith("\x1B")) ; else if (this.selectOptions?.searchable && data.length === 1 && data >= " ") {
11978
+ this.filter += data;
11979
+ this.applyFilter();
11980
+ this.renderAll();
11981
+ }
11982
+ }
11983
+ onResize(cols, rows) {
11984
+ this.maxVisible = Math.min(this.filteredOptions.length, rows - 4);
11985
+ if (this.scrollOffset + this.maxVisible > this.getSelectableCount()) {
11986
+ this.scrollOffset = Math.max(0, this.getSelectableCount() - this.maxVisible);
11987
+ }
11988
+ this.renderAll();
11989
+ }
11990
+ // ── Private helpers ──────────────────────────────────────────────
11991
+ /**
11992
+ * Build flattened display list with group headers inserted.
11993
+ */
11994
+ buildDisplayList() {
11995
+ const items = [];
11996
+ let lastGroup;
11997
+ for (const opt of this.filteredOptions) {
11998
+ if (opt.group && opt.group !== lastGroup) {
11999
+ items.push({ type: "group-header", groupName: opt.group });
12000
+ lastGroup = opt.group;
12001
+ }
12002
+ items.push({ type: "option", option: opt });
12003
+ }
12004
+ return items;
12005
+ }
12006
+ /**
12007
+ * Get the total count of selectable items (non-disabled options).
12008
+ */
12009
+ getSelectableCount() {
12010
+ return this.filteredOptions.filter((o) => !o.disabled).length;
12011
+ }
12012
+ /**
12013
+ * Move selection in the given direction (+1 or -1), skipping disabled options.
12014
+ * Wraps around at boundaries.
12015
+ */
12016
+ moveSelection(direction) {
12017
+ const selectableCount = this.getSelectableCount();
12018
+ if (selectableCount === 0) return;
12019
+ let next = this.selectedIndex + direction;
12020
+ if (next < 0) next = selectableCount - 1;
12021
+ if (next >= selectableCount) next = 0;
12022
+ this.selectedIndex = next;
12023
+ if (this.selectedIndex < this.scrollOffset) {
12024
+ this.scrollOffset = this.selectedIndex;
12025
+ } else if (this.selectedIndex >= this.scrollOffset + this.maxVisible) {
12026
+ this.scrollOffset = this.selectedIndex - this.maxVisible + 1;
12027
+ }
12028
+ const selectables = this.filteredOptions.filter((o) => !o.disabled);
12029
+ if (this.selectOptions?.onChange && selectables[this.selectedIndex]) {
12030
+ this.selectOptions.onChange(selectables[this.selectedIndex].value);
12031
+ }
12032
+ this.renderAll();
12033
+ }
12034
+ /**
12035
+ * Find the next selectable (non-disabled) option index starting from `from`.
12036
+ * Returns 0 if none found.
12037
+ */
12038
+ findNextSelectable(from, direction) {
12039
+ const len = this.filteredOptions.length;
12040
+ if (len === 0) return 0;
12041
+ let idx = from + direction;
12042
+ for (let i = 0; i < len; i++) {
12043
+ if (idx < 0) idx = len - 1;
12044
+ if (idx >= len) idx = 0;
12045
+ if (!this.filteredOptions[idx].disabled) return idx;
12046
+ idx += direction;
12047
+ }
12048
+ return 0;
12049
+ }
12050
+ /**
12051
+ * Apply the current filter text to the options list.
12052
+ */
12053
+ applyFilter() {
12054
+ if (this.filter.length === 0) {
12055
+ this.filteredOptions = [...this.options];
12056
+ } else {
12057
+ const lower = this.filter.toLowerCase();
12058
+ this.filteredOptions = this.options.filter(
12059
+ (o) => o.label.toLowerCase().includes(lower)
12060
+ );
12061
+ }
12062
+ this.selectedIndex = 0;
12063
+ this.scrollOffset = 0;
12064
+ this.maxVisible = Math.min(
12065
+ this.filteredOptions.length,
12066
+ this.host.getTerminalRows() - 4
12067
+ );
12068
+ const selectables = this.filteredOptions.filter((o) => !o.disabled);
12069
+ if (selectables.length > 0) {
12070
+ this.selectedIndex = 0;
12071
+ }
12072
+ }
12073
+ /**
12074
+ * Clear the filter and restore full option list.
12075
+ */
12076
+ clearFilter() {
12077
+ this.filter = "";
12078
+ this.applyFilter();
12079
+ this.renderAll();
12080
+ }
12081
+ /**
12082
+ * Clear all previously rendered lines and re-render everything.
12083
+ */
12084
+ renderAll() {
12085
+ this.clearRenderedLines();
12086
+ this.renderPrompt();
12087
+ this.renderOptions();
12088
+ this.renderHelpBar();
12089
+ if (this.renderedLines > 0) {
12090
+ this.host.terminal.write(`\x1B[${this.renderedLines}A`);
12091
+ }
12092
+ }
12093
+ /**
12094
+ * Clear all lines we previously rendered (below the cursor).
12095
+ */
12096
+ clearRenderedLines() {
12097
+ if (this.renderedLines > 0) {
12098
+ this.host.terminal.write("\x1B[s");
12099
+ for (let i = 0; i < this.renderedLines; i++) {
12100
+ this.host.terminal.write("\x1B[B\x1B[2K");
12101
+ }
12102
+ this.host.terminal.write("\x1B[u");
12103
+ this.renderedLines = 0;
12104
+ }
12105
+ }
12106
+ /**
12107
+ * Render the prompt line.
12108
+ */
12109
+ renderPrompt() {
12110
+ this.host.terminal.write("\x1B[2K\r");
12111
+ if (this.selectOptions?.searchable) {
12112
+ if (this.filter.length > 0) {
12113
+ this.host.terminal.write(
12114
+ `\x1B[36m?\x1B[0m ${this.promptText}: ${this.filter}`
12115
+ );
12116
+ } else {
12117
+ this.host.terminal.write(
12118
+ `\x1B[36m?\x1B[0m ${this.promptText}: \x1B[2mType to filter...\x1B[0m`
12119
+ );
12120
+ }
12121
+ } else {
12122
+ this.host.terminal.write(`\x1B[36m?\x1B[0m ${this.promptText}:`);
12123
+ }
12124
+ }
12125
+ /**
12126
+ * Render the visible slice of the option list.
12127
+ */
12128
+ renderOptions() {
12129
+ const items = this.buildDisplayList();
12130
+ const selectables = this.filteredOptions.filter((o) => !o.disabled);
12131
+ const visibleStart = this.scrollOffset;
12132
+ const visibleEnd = Math.min(
12133
+ selectables.length,
12134
+ this.scrollOffset + this.maxVisible
12135
+ );
12136
+ if (visibleStart > 0) {
12137
+ this.host.terminal.write(`\r
12138
+ \x1B[2m \u2191 ${visibleStart} more above\x1B[0m`);
12139
+ this.renderedLines++;
12140
+ }
12141
+ let selectableIdx = 0;
12142
+ for (const item of items) {
12143
+ if (item.type === "group-header") {
12144
+ if (this.isGroupVisible(item.groupName, items, visibleStart, visibleEnd)) {
12145
+ this.host.terminal.write(
12146
+ `\r
12147
+ \x1B[35m\u2500\u2500 ${item.groupName} \u2500\u2500\x1B[0m`
12148
+ );
12149
+ this.renderedLines++;
12150
+ }
12151
+ } else {
12152
+ const opt = item.option;
12153
+ if (opt.disabled) {
12154
+ if (this.isDisabledOptionVisible(items, item, visibleStart, visibleEnd)) {
12155
+ this.renderOptionLine(opt, false, false);
12156
+ this.renderedLines++;
12157
+ }
12158
+ } else {
12159
+ if (selectableIdx >= visibleStart && selectableIdx < visibleEnd) {
12160
+ const isSelected = selectableIdx === this.selectedIndex;
12161
+ this.renderOptionLine(opt, isSelected, false);
12162
+ this.renderedLines++;
12163
+ }
12164
+ selectableIdx++;
12165
+ }
12166
+ }
12167
+ }
12168
+ const remaining = selectables.length - visibleEnd;
12169
+ if (remaining > 0) {
12170
+ this.host.terminal.write(`\r
12171
+ \x1B[2m \u2193 ${remaining} more below\x1B[0m`);
12172
+ this.renderedLines++;
12173
+ }
12174
+ }
12175
+ /**
12176
+ * Check if a group header should be visible given the current visible range.
12177
+ */
12178
+ isGroupVisible(groupName, items, visibleStart, visibleEnd) {
12179
+ let selectableIdx = 0;
12180
+ for (const item of items) {
12181
+ if (item.type === "option" && !item.option.disabled) {
12182
+ if (item.option.group === groupName && selectableIdx >= visibleStart && selectableIdx < visibleEnd) {
12183
+ return true;
12184
+ }
12185
+ selectableIdx++;
12186
+ }
12187
+ }
12188
+ return false;
12189
+ }
12190
+ /**
12191
+ * Check if a disabled option should be visible (adjacent to visible selectables).
12192
+ */
12193
+ isDisabledOptionVisible(items, disabledItem, visibleStart, visibleEnd) {
12194
+ const idx = items.indexOf(disabledItem);
12195
+ let selectableIdx = 0;
12196
+ for (let i = 0; i < items.length; i++) {
12197
+ if (items[i].type === "option" && !items[i].option.disabled) {
12198
+ if (i > idx) {
12199
+ return selectableIdx >= visibleStart && selectableIdx < visibleEnd;
12200
+ }
12201
+ selectableIdx++;
12202
+ }
12203
+ }
12204
+ return selectableIdx > 0 && selectableIdx - 1 >= visibleStart && selectableIdx - 1 < visibleEnd;
12205
+ }
12206
+ /**
12207
+ * Render a single option line.
12208
+ */
12209
+ renderOptionLine(opt, isSelected, _isGroupHeader) {
12210
+ const label = this.highlightMatch(opt.label);
12211
+ const desc = opt.description ? ` \x1B[2;3m\u2014 ${opt.description}\x1B[0m` : "";
12212
+ if (opt.disabled) {
12213
+ this.host.terminal.write(
12214
+ `\r
12215
+ \x1B[2m${opt.label}${desc} (disabled)\x1B[0m`
12216
+ );
12217
+ } else if (isSelected) {
12218
+ this.host.terminal.write(
12219
+ `\r
12220
+ \x1B[48;5;24m\x1B[38;5;117m\u276F ${label}${desc}\x1B[0m`
12221
+ );
12222
+ } else {
12223
+ this.host.terminal.write(
12224
+ `\r
12225
+ ${label}${desc}`
12226
+ );
12227
+ }
12228
+ }
12229
+ /**
12230
+ * Highlight the matched filter text in yellow within the label.
12231
+ */
12232
+ highlightMatch(label) {
12233
+ if (!this.filter || this.filter.length === 0) return label;
12234
+ const lower = label.toLowerCase();
12235
+ const filterLower = this.filter.toLowerCase();
12236
+ const idx = lower.indexOf(filterLower);
12237
+ if (idx < 0) return label;
12238
+ const before = label.substring(0, idx);
12239
+ const match = label.substring(idx, idx + this.filter.length);
12240
+ const after = label.substring(idx + this.filter.length);
12241
+ return `${before}\x1B[33m${match}\x1B[0m${after}`;
12242
+ }
12243
+ /**
12244
+ * Render the help bar at the bottom.
12245
+ */
12246
+ renderHelpBar() {
12247
+ let help;
12248
+ if (this.selectOptions?.searchable) {
12249
+ if (this.filter.length > 0) {
12250
+ const matchCount = this.filteredOptions.filter((o) => !o.disabled).length;
12251
+ const totalCount = this.options.filter((o) => !o.disabled).length;
12252
+ help = `${matchCount} of ${totalCount} matches \xB7 backspace to clear filter \xB7 enter to select \xB7 esc to cancel`;
12253
+ } else {
12254
+ help = "\u2191/\u2193 navigate \xB7 type to filter \xB7 enter to select \xB7 esc to cancel";
12255
+ }
12256
+ } else {
12257
+ help = "\u2191/\u2193 navigate \xB7 enter to select \xB7 esc to cancel";
12258
+ }
12259
+ this.host.terminal.write(`\r
12260
+ \x1B[2m${help}\x1B[0m`);
12261
+ this.renderedLines++;
12262
+ }
12263
+ };
12264
+
12265
+ // src/lib/input/modes/inline-select-input-mode.ts
12266
+ var InlineSelectInputMode = class extends InputModeBase {
12267
+ constructor(host, resolve, promptText, options, selectOptions) {
12268
+ super(host, resolve);
12269
+ this.promptText = promptText;
12270
+ this.options = options;
12271
+ this.selectOptions = selectOptions;
12272
+ /** Index into the selectable (non-disabled) options */
12273
+ this.selectedIndex = 0;
12274
+ /** Start index of the visible window (into selectable options) */
12275
+ this.visibleStart = 0;
12276
+ /** End index (exclusive) of the visible window */
12277
+ this.visibleEnd = 0;
12278
+ this.selectableOptions = options.filter((o) => !o.disabled);
12279
+ }
12280
+ activate() {
12281
+ this.host.terminal.write("\x1B[?25l");
12282
+ if (this.selectOptions?.default) {
12283
+ const idx = this.selectableOptions.findIndex(
12284
+ (o) => o.value === this.selectOptions.default
12285
+ );
12286
+ if (idx >= 0) {
12287
+ this.selectedIndex = idx;
12288
+ }
12289
+ }
12290
+ this.calculateVisibleWindow();
12291
+ this.render();
12292
+ }
12293
+ /**
12294
+ * Clear the inline options and help bar, then show final answer before resolving.
12295
+ */
12296
+ resolveAndPop(value) {
12297
+ this.clearExtraLines();
12298
+ const selectedOpt = this.selectableOptions.find((o) => o.value === value);
12299
+ const label = selectedOpt ? selectedOpt.label : value;
12300
+ this.host.terminal.write("\x1B[2K\r");
12301
+ this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.promptText}: \x1B[36m${label}\x1B[0m`);
12302
+ this.host.terminal.write("\x1B[?25h");
12303
+ super.resolveAndPop(value);
12304
+ }
12305
+ /**
12306
+ * Clear the inline options and help bar before aborting.
12307
+ */
12308
+ abort() {
12309
+ this.clearExtraLines();
12310
+ this.host.terminal.write("\x1B[2K\r");
12311
+ this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.promptText}: \x1B[2mcancelled\x1B[0m`);
12312
+ this.host.terminal.write("\x1B[?25h");
12313
+ super.abort();
12314
+ }
12315
+ async handleInput(data) {
12316
+ if (data === "\x1B[C") {
12317
+ this.moveSelection(1);
12318
+ } else if (data === "\x1B[D") {
12319
+ this.moveSelection(-1);
12320
+ } else if (data === "\r") {
12321
+ if (this.selectableOptions.length > 0) {
12322
+ this.resolveAndPop(this.selectableOptions[this.selectedIndex].value);
12323
+ }
12324
+ }
12325
+ }
12326
+ onResize(cols, rows) {
12327
+ this.calculateVisibleWindow();
12328
+ this.render();
12329
+ }
12330
+ // ── Private helpers ──────────────────────────────────────────────
12331
+ /**
12332
+ * Move selection in the given direction, skipping disabled options.
12333
+ * Wraps around at boundaries.
12334
+ */
12335
+ moveSelection(direction) {
12336
+ if (this.selectableOptions.length === 0) return;
12337
+ let next = this.selectedIndex + direction;
12338
+ if (next < 0) next = this.selectableOptions.length - 1;
12339
+ if (next >= this.selectableOptions.length) next = 0;
12340
+ this.selectedIndex = next;
12341
+ this.adjustVisibleWindow();
12342
+ if (this.selectOptions?.onChange) {
12343
+ this.selectOptions.onChange(this.selectableOptions[this.selectedIndex].value);
12344
+ }
12345
+ this.render();
12346
+ }
12347
+ /**
12348
+ * Get the prompt prefix string (without ANSI escapes for width calculation).
12349
+ */
12350
+ getPromptPrefix() {
12351
+ return `? ${this.promptText}: `;
12352
+ }
12353
+ /**
12354
+ * Get the rendered prompt prefix (with ANSI color codes).
12355
+ */
12356
+ getRenderedPromptPrefix() {
12357
+ return `\x1B[36m?\x1B[0m ${this.promptText}: `;
12358
+ }
12359
+ /**
12360
+ * Calculate the width needed for a single option.
12361
+ * Selected option: `[ label ]` (label.length + 4)
12362
+ * Unselected option: ` label ` (label.length + 2)
12363
+ */
12364
+ getOptionWidth(opt, isSelected) {
12365
+ return isSelected ? opt.label.length + 4 : opt.label.length + 2;
12366
+ }
12367
+ /**
12368
+ * Calculate how many options fit in the visible window starting from a given index.
12369
+ */
12370
+ calculateVisibleWindow() {
12371
+ const cols = this.host.getTerminalCols();
12372
+ const promptWidth = this.getPromptPrefix().length;
12373
+ const available = cols - promptWidth;
12374
+ if (this.selectableOptions.length === 0) {
12375
+ this.visibleStart = 0;
12376
+ this.visibleEnd = 0;
12377
+ return;
12378
+ }
12379
+ let totalWidth = 0;
12380
+ for (let i = 0; i < this.selectableOptions.length; i++) {
12381
+ totalWidth += this.getOptionWidth(this.selectableOptions[i], i === this.selectedIndex);
12382
+ }
12383
+ if (totalWidth <= available) {
12384
+ this.visibleStart = 0;
12385
+ this.visibleEnd = this.selectableOptions.length;
12386
+ return;
12387
+ }
12388
+ this.adjustVisibleWindow();
12389
+ }
12390
+ /**
12391
+ * Adjust the visible window so the selected option is visible.
12392
+ * Accounts for scroll indicator widths.
12393
+ */
12394
+ adjustVisibleWindow() {
12395
+ const cols = this.host.getTerminalCols();
12396
+ const promptWidth = this.getPromptPrefix().length;
12397
+ const available = cols - promptWidth;
12398
+ if (this.selectableOptions.length === 0) return;
12399
+ let totalWidth = 0;
12400
+ for (let i = 0; i < this.selectableOptions.length; i++) {
12401
+ totalWidth += this.getOptionWidth(this.selectableOptions[i], i === this.selectedIndex);
12402
+ }
12403
+ if (totalWidth <= available) {
12404
+ this.visibleStart = 0;
12405
+ this.visibleEnd = this.selectableOptions.length;
12406
+ return;
12407
+ }
12408
+ const indicatorWidth = 2;
12409
+ if (this.selectedIndex < this.visibleStart) {
12410
+ this.visibleStart = this.selectedIndex;
12411
+ }
12412
+ let usedWidth = 0;
12413
+ const hasLeftIndicator = this.visibleStart > 0;
12414
+ if (hasLeftIndicator) {
12415
+ usedWidth += indicatorWidth;
12416
+ }
12417
+ let end = this.visibleStart;
12418
+ for (let i = this.visibleStart; i < this.selectableOptions.length; i++) {
12419
+ const optWidth = this.getOptionWidth(this.selectableOptions[i], i === this.selectedIndex);
12420
+ const needsRightIndicator = i < this.selectableOptions.length - 1;
12421
+ const rightReserve = needsRightIndicator ? indicatorWidth : 0;
12422
+ if (usedWidth + optWidth + rightReserve <= available) {
12423
+ usedWidth += optWidth;
12424
+ end = i + 1;
12425
+ } else {
12426
+ break;
12427
+ }
12428
+ }
12429
+ if (this.selectedIndex >= end) {
12430
+ end = this.selectedIndex + 1;
12431
+ usedWidth = this.getOptionWidth(
12432
+ this.selectableOptions[this.selectedIndex],
12433
+ true
12434
+ );
12435
+ const hasRightIndicator = end < this.selectableOptions.length;
12436
+ if (hasRightIndicator) {
12437
+ usedWidth += indicatorWidth;
12438
+ }
12439
+ let start = this.selectedIndex;
12440
+ for (let i = this.selectedIndex - 1; i >= 0; i--) {
12441
+ const optWidth = this.getOptionWidth(this.selectableOptions[i], false);
12442
+ const needsLeftIndicator = i > 0;
12443
+ const leftReserve = needsLeftIndicator ? indicatorWidth : 0;
12444
+ const currentLeftReserve = start > 0 ? indicatorWidth : 0;
12445
+ if (usedWidth - currentLeftReserve + optWidth + leftReserve <= available) {
12446
+ usedWidth = usedWidth - currentLeftReserve + optWidth + leftReserve;
12447
+ start = i;
12448
+ } else {
12449
+ break;
12450
+ }
12451
+ }
12452
+ this.visibleStart = start;
12453
+ end = this.selectedIndex + 1;
12454
+ usedWidth = 0;
12455
+ if (this.visibleStart > 0) usedWidth += indicatorWidth;
12456
+ for (let i = this.visibleStart; i < end; i++) {
12457
+ usedWidth += this.getOptionWidth(this.selectableOptions[i], i === this.selectedIndex);
12458
+ }
12459
+ for (let i = end; i < this.selectableOptions.length; i++) {
12460
+ const optWidth = this.getOptionWidth(this.selectableOptions[i], false);
12461
+ const needsRightIndicator = i < this.selectableOptions.length - 1;
12462
+ const rightReserve = needsRightIndicator ? indicatorWidth : 0;
12463
+ if (usedWidth + optWidth + rightReserve <= available) {
12464
+ usedWidth += optWidth;
12465
+ end = i + 1;
12466
+ } else {
12467
+ break;
12468
+ }
12469
+ }
12470
+ }
12471
+ this.visibleEnd = end;
12472
+ }
12473
+ /**
12474
+ * Render the complete inline select display.
12475
+ */
12476
+ render() {
12477
+ this.clearExtraLines();
12478
+ this.host.terminal.write("\x1B[2K\r");
12479
+ this.host.terminal.write(this.getRenderedPromptPrefix());
12480
+ if (this.selectableOptions.length === 0) return;
12481
+ const hasLeftOverflow = this.visibleStart > 0;
12482
+ const hasRightOverflow = this.visibleEnd < this.selectableOptions.length;
12483
+ if (hasLeftOverflow) {
12484
+ this.host.terminal.write("\x1B[2m\u25C2\x1B[0m ");
12485
+ }
12486
+ for (let i = this.visibleStart; i < this.visibleEnd; i++) {
12487
+ const opt = this.selectableOptions[i];
12488
+ const isSelected = i === this.selectedIndex;
12489
+ if (isSelected) {
12490
+ this.host.terminal.write(`\x1B[36m[ ${opt.label} ]\x1B[0m`);
12491
+ } else {
12492
+ this.host.terminal.write(` ${opt.label} `);
12493
+ }
12494
+ }
12495
+ if (hasRightOverflow) {
12496
+ this.host.terminal.write(" \x1B[2m\u25B8\x1B[0m");
12497
+ }
12498
+ this.writeHelp("\u2190/\u2192 navigate \xB7 enter to select \xB7 esc to cancel");
12499
+ }
12500
+ };
12501
+
12502
+ // src/lib/input/modes/multi-select-input-mode.ts
12503
+ var MultiSelectInputMode = class extends InputModeBase {
12504
+ constructor(host, resolve, promptText, options, selectOptions) {
12505
+ super(host, resolve);
12506
+ this.promptText = promptText;
12507
+ this.options = options;
12508
+ this.selectOptions = selectOptions;
12509
+ this.selectedIndex = 0;
12510
+ this.scrollOffset = 0;
12511
+ this.filter = "";
12512
+ /** Number of lines rendered for the option list + help bar (for clearing) */
12513
+ this.renderedLines = 0;
12514
+ this.filteredOptions = [...options];
12515
+ this.maxVisible = Math.min(options.length, host.getTerminalRows() - 4);
12516
+ this.checkedState = /* @__PURE__ */ new Map();
12517
+ for (const opt of options) {
12518
+ this.checkedState.set(opt.value, opt.checked === true);
12519
+ }
12520
+ }
12521
+ activate() {
12522
+ this.host.terminal.write("\x1B[?25l");
12523
+ this.selectedIndex = 0;
12524
+ const selectables = this.filteredOptions.filter((o) => !o.disabled);
12525
+ if (selectables.length > 0) {
12526
+ this.selectedIndex = 0;
12527
+ }
12528
+ this.renderAll();
12529
+ }
12530
+ /**
12531
+ * Handle keyboard events. When searchable and filter is non-empty,
12532
+ * Escape clears the filter instead of aborting.
12533
+ */
12534
+ handleKeyEvent(event) {
12535
+ if (this.selectOptions?.searchable && this.filter.length > 0) {
12536
+ if (event.code === "Escape") {
12537
+ this.clearFilter();
12538
+ return false;
12539
+ }
12540
+ }
12541
+ return super.handleKeyEvent(event);
12542
+ }
12543
+ /**
12544
+ * Clear rendered option lines and show final answer before resolving.
12545
+ */
12546
+ resolveAndPop(value) {
12547
+ this.clearRenderedLines();
12548
+ const labels = value.map((v) => {
12549
+ const opt = this.options.find((o) => o.value === v);
12550
+ return opt ? opt.label : v;
12551
+ });
12552
+ const summary = labels.length > 0 ? labels.join(", ") : "none";
12553
+ this.host.terminal.write("\x1B[2K\r");
12554
+ this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.promptText} \x1B[36m${summary}\x1B[0m`);
12555
+ this.host.terminal.write("\x1B[?25h");
12556
+ super.resolveAndPop(value);
12557
+ }
12558
+ /**
12559
+ * Clear rendered option lines before aborting.
12560
+ */
12561
+ abort() {
12562
+ this.clearRenderedLines();
12563
+ this.host.terminal.write("\x1B[2K\r");
12564
+ this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.promptText} \x1B[2mcancelled\x1B[0m`);
12565
+ this.host.terminal.write("\x1B[?25h");
12566
+ super.abort();
12567
+ }
12568
+ async handleInput(data) {
12569
+ if (data === "\x1B[A") {
12570
+ this.moveSelection(-1);
12571
+ } else if (data === "\x1B[B") {
12572
+ this.moveSelection(1);
12573
+ } else if (data === " ") {
12574
+ this.toggleCurrent();
12575
+ } else if (data === "a" && (!this.selectOptions?.searchable || this.filter.length === 0)) {
12576
+ this.toggleAll();
12577
+ } else if (data === "\r") {
12578
+ this.resolveAndPop(this.getCheckedValues());
12579
+ } else if (data === "\x7F") {
12580
+ if (this.selectOptions?.searchable && this.filter.length > 0) {
12581
+ this.filter = this.filter.slice(0, -1);
12582
+ this.applyFilter();
12583
+ this.renderAll();
12584
+ }
12585
+ } else if (data.startsWith("\x1B")) ; else if (this.selectOptions?.searchable && data.length === 1 && data >= " ") {
12586
+ this.filter += data;
12587
+ this.applyFilter();
12588
+ this.renderAll();
12589
+ }
12590
+ }
12591
+ onResize(cols, rows) {
12592
+ this.maxVisible = Math.min(this.filteredOptions.length, rows - 4);
12593
+ if (this.scrollOffset + this.maxVisible > this.getSelectableCount()) {
12594
+ this.scrollOffset = Math.max(0, this.getSelectableCount() - this.maxVisible);
12595
+ }
12596
+ this.renderAll();
12597
+ }
12598
+ // ── Private helpers ──────────────────────────────────────────────
12599
+ /**
12600
+ * Build flattened display list with group headers inserted.
12601
+ */
12602
+ buildDisplayList() {
12603
+ const items = [];
12604
+ let lastGroup;
12605
+ for (const opt of this.filteredOptions) {
12606
+ if (opt.group && opt.group !== lastGroup) {
12607
+ items.push({ type: "group-header", groupName: opt.group });
12608
+ lastGroup = opt.group;
12609
+ }
12610
+ items.push({ type: "option", option: opt });
12611
+ }
12612
+ return items;
12613
+ }
12614
+ /**
12615
+ * Get the total count of selectable items (non-disabled options).
12616
+ */
12617
+ getSelectableCount() {
12618
+ return this.filteredOptions.filter((o) => !o.disabled).length;
12619
+ }
12620
+ /**
12621
+ * Get an array of currently checked values (preserving original order).
12622
+ */
12623
+ getCheckedValues() {
12624
+ const result = [];
12625
+ for (const opt of this.options) {
12626
+ if (this.checkedState.get(opt.value) === true) {
12627
+ result.push(opt.value);
12628
+ }
12629
+ }
12630
+ return result;
12631
+ }
12632
+ /**
12633
+ * Move selection in the given direction (+1 or -1), skipping disabled options.
12634
+ * Wraps around at boundaries.
12635
+ */
12636
+ moveSelection(direction) {
12637
+ const selectableCount = this.getSelectableCount();
12638
+ if (selectableCount === 0) return;
12639
+ let next = this.selectedIndex + direction;
12640
+ if (next < 0) next = selectableCount - 1;
12641
+ if (next >= selectableCount) next = 0;
12642
+ this.selectedIndex = next;
12643
+ if (this.selectedIndex < this.scrollOffset) {
12644
+ this.scrollOffset = this.selectedIndex;
12645
+ } else if (this.selectedIndex >= this.scrollOffset + this.maxVisible) {
12646
+ this.scrollOffset = this.selectedIndex - this.maxVisible + 1;
12647
+ }
12648
+ this.renderAll();
12649
+ }
12650
+ /**
12651
+ * Toggle the checked state of the currently selected option.
12652
+ */
12653
+ toggleCurrent() {
12654
+ const selectables = this.filteredOptions.filter((o) => !o.disabled);
12655
+ if (selectables.length === 0 || this.selectedIndex >= selectables.length) return;
12656
+ const opt = selectables[this.selectedIndex];
12657
+ const current = this.checkedState.get(opt.value) === true;
12658
+ this.checkedState.set(opt.value, !current);
12659
+ if (this.selectOptions?.onChange) {
12660
+ this.selectOptions.onChange(this.getCheckedValues());
12661
+ }
12662
+ this.renderAll();
12663
+ }
12664
+ /**
12665
+ * Toggle all non-disabled options: if all are checked, uncheck all; otherwise check all.
12666
+ */
12667
+ toggleAll() {
12668
+ const nonDisabled = this.options.filter((o) => !o.disabled);
12669
+ const allChecked = nonDisabled.every((o) => this.checkedState.get(o.value) === true);
12670
+ for (const opt of nonDisabled) {
12671
+ this.checkedState.set(opt.value, !allChecked);
12672
+ }
12673
+ if (this.selectOptions?.onChange) {
12674
+ this.selectOptions.onChange(this.getCheckedValues());
12675
+ }
12676
+ this.renderAll();
12677
+ }
12678
+ /**
12679
+ * Apply the current filter text to the options list.
12680
+ */
12681
+ applyFilter() {
12682
+ if (this.filter.length === 0) {
12683
+ this.filteredOptions = [...this.options];
12684
+ } else {
12685
+ const lower = this.filter.toLowerCase();
12686
+ this.filteredOptions = this.options.filter(
12687
+ (o) => o.label.toLowerCase().includes(lower)
12688
+ );
12689
+ }
12690
+ this.selectedIndex = 0;
12691
+ this.scrollOffset = 0;
12692
+ this.maxVisible = Math.min(
12693
+ this.filteredOptions.length,
12694
+ this.host.getTerminalRows() - 4
12695
+ );
12696
+ }
12697
+ /**
12698
+ * Clear the filter and restore full option list.
12699
+ */
12700
+ clearFilter() {
12701
+ this.filter = "";
12702
+ this.applyFilter();
12703
+ this.renderAll();
12704
+ }
12705
+ /**
12706
+ * Clear all previously rendered lines and re-render everything.
12707
+ */
12708
+ renderAll() {
12709
+ this.clearRenderedLines();
12710
+ this.renderPrompt();
12711
+ this.renderOptions();
12712
+ this.renderHelpBar();
12713
+ if (this.renderedLines > 0) {
12714
+ this.host.terminal.write(`\x1B[${this.renderedLines}A`);
12715
+ }
12716
+ }
12717
+ /**
12718
+ * Clear all lines we previously rendered (below the cursor).
12719
+ */
12720
+ clearRenderedLines() {
12721
+ if (this.renderedLines > 0) {
12722
+ this.host.terminal.write("\x1B[s");
12723
+ for (let i = 0; i < this.renderedLines; i++) {
12724
+ this.host.terminal.write("\x1B[B\x1B[2K");
12725
+ }
12726
+ this.host.terminal.write("\x1B[u");
12727
+ this.renderedLines = 0;
12728
+ }
12729
+ }
12730
+ /**
12731
+ * Render the prompt line.
12732
+ */
12733
+ renderPrompt() {
12734
+ this.host.terminal.write("\x1B[2K\r");
12735
+ if (this.selectOptions?.searchable) {
12736
+ if (this.filter.length > 0) {
12737
+ this.host.terminal.write(
12738
+ `\x1B[36m?\x1B[0m ${this.promptText}: ${this.filter}`
12739
+ );
12740
+ } else {
12741
+ this.host.terminal.write(
12742
+ `\x1B[36m?\x1B[0m ${this.promptText}: \x1B[2mType to filter...\x1B[0m`
12743
+ );
12744
+ }
12745
+ } else {
12746
+ this.host.terminal.write(`\x1B[36m?\x1B[0m ${this.promptText}:`);
12747
+ }
12748
+ }
12749
+ /**
12750
+ * Render the visible slice of the option list.
12751
+ */
12752
+ renderOptions() {
12753
+ const items = this.buildDisplayList();
12754
+ const selectables = this.filteredOptions.filter((o) => !o.disabled);
12755
+ const visibleStart = this.scrollOffset;
12756
+ const visibleEnd = Math.min(
12757
+ selectables.length,
12758
+ this.scrollOffset + this.maxVisible
12759
+ );
12760
+ if (visibleStart > 0) {
12761
+ this.host.terminal.write(`\r
12762
+ \x1B[2m \u2191 ${visibleStart} more above\x1B[0m`);
12763
+ this.renderedLines++;
12764
+ }
12765
+ let selectableIdx = 0;
12766
+ for (const item of items) {
12767
+ if (item.type === "group-header") {
12768
+ if (this.isGroupVisible(item.groupName, items, visibleStart, visibleEnd)) {
12769
+ this.host.terminal.write(
12770
+ `\r
12771
+ \x1B[35m\u2500\u2500 ${item.groupName} \u2500\u2500\x1B[0m`
12772
+ );
12773
+ this.renderedLines++;
12774
+ }
12775
+ } else {
12776
+ const opt = item.option;
12777
+ if (opt.disabled) {
12778
+ if (this.isDisabledOptionVisible(items, item, visibleStart, visibleEnd)) {
12779
+ this.renderOptionLine(opt, false);
12780
+ this.renderedLines++;
12781
+ }
12782
+ } else {
12783
+ if (selectableIdx >= visibleStart && selectableIdx < visibleEnd) {
12784
+ const isSelected = selectableIdx === this.selectedIndex;
12785
+ this.renderOptionLine(opt, isSelected);
12786
+ this.renderedLines++;
12787
+ }
12788
+ selectableIdx++;
12789
+ }
12790
+ }
12791
+ }
12792
+ const remaining = selectables.length - visibleEnd;
12793
+ if (remaining > 0) {
12794
+ this.host.terminal.write(`\r
12795
+ \x1B[2m \u2193 ${remaining} more below\x1B[0m`);
12796
+ this.renderedLines++;
12797
+ }
12798
+ }
12799
+ /**
12800
+ * Check if a group header should be visible given the current visible range.
12801
+ */
12802
+ isGroupVisible(groupName, items, visibleStart, visibleEnd) {
12803
+ let selectableIdx = 0;
12804
+ for (const item of items) {
12805
+ if (item.type === "option" && !item.option.disabled) {
12806
+ if (item.option.group === groupName && selectableIdx >= visibleStart && selectableIdx < visibleEnd) {
12807
+ return true;
12808
+ }
12809
+ selectableIdx++;
12810
+ }
12811
+ }
12812
+ return false;
12813
+ }
12814
+ /**
12815
+ * Check if a disabled option should be visible (adjacent to visible selectables).
12816
+ */
12817
+ isDisabledOptionVisible(items, disabledItem, visibleStart, visibleEnd) {
12818
+ const idx = items.indexOf(disabledItem);
12819
+ let selectableIdx = 0;
12820
+ for (let i = 0; i < items.length; i++) {
12821
+ if (items[i].type === "option" && !items[i].option.disabled) {
12822
+ if (i > idx) {
12823
+ return selectableIdx >= visibleStart && selectableIdx < visibleEnd;
12824
+ }
12825
+ selectableIdx++;
12826
+ }
12827
+ }
12828
+ return selectableIdx > 0 && selectableIdx - 1 >= visibleStart && selectableIdx - 1 < visibleEnd;
12829
+ }
12830
+ /**
12831
+ * Render a single option line with checkbox indicator.
12832
+ */
12833
+ renderOptionLine(opt, isSelected) {
12834
+ const isChecked = this.checkedState.get(opt.value) === true;
12835
+ const label = this.highlightMatch(opt.label);
12836
+ const desc = opt.description ? ` \x1B[2;3m\u2014 ${opt.description}\x1B[0m` : "";
12837
+ const checkbox = isChecked ? "\x1B[32m\u25C9\x1B[0m" : "\x1B[2m\u25CB\x1B[0m";
12838
+ if (opt.disabled) {
12839
+ const disabledCheckbox = isChecked ? "\x1B[2m\u25C9\x1B[0m" : "\x1B[2m\u25CB\x1B[0m";
12840
+ this.host.terminal.write(
12841
+ `\r
12842
+ ${disabledCheckbox} \x1B[2m${opt.label}${desc} (disabled)\x1B[0m`
12843
+ );
12844
+ } else if (isSelected) {
12845
+ this.host.terminal.write(
12846
+ `\r
12847
+ \x1B[48;5;24m\x1B[38;5;117m\u276F ${checkbox} ${label}${desc}\x1B[0m`
12848
+ );
12849
+ } else {
12850
+ this.host.terminal.write(
12851
+ `\r
12852
+ ${checkbox} ${label}${desc}`
12853
+ );
12854
+ }
12855
+ }
12856
+ /**
12857
+ * Highlight the matched filter text in yellow within the label.
12858
+ */
12859
+ highlightMatch(label) {
12860
+ if (!this.filter || this.filter.length === 0) return label;
12861
+ const lower = label.toLowerCase();
12862
+ const filterLower = this.filter.toLowerCase();
12863
+ const idx = lower.indexOf(filterLower);
12864
+ if (idx < 0) return label;
12865
+ const before = label.substring(0, idx);
12866
+ const match = label.substring(idx, idx + this.filter.length);
12867
+ const after = label.substring(idx + this.filter.length);
12868
+ return `${before}\x1B[33m${match}\x1B[0m${after}`;
12869
+ }
12870
+ /**
12871
+ * Render the help bar at the bottom.
12872
+ */
12873
+ renderHelpBar() {
12874
+ const checkedCount = this.getCheckedValues().length;
12875
+ let help;
12876
+ if (this.selectOptions?.searchable) {
12877
+ if (this.filter.length > 0) {
12878
+ const matchCount = this.filteredOptions.filter((o) => !o.disabled).length;
12879
+ const totalCount = this.options.filter((o) => !o.disabled).length;
12880
+ help = `${checkedCount} selected \xB7 ${matchCount} of ${totalCount} matches \xB7 space to toggle \xB7 enter to confirm`;
12881
+ } else {
12882
+ help = `${checkedCount} selected \xB7 space to toggle \xB7 a to select all \xB7 type to filter \xB7 enter to confirm`;
12883
+ }
12884
+ } else {
12885
+ help = `${checkedCount} selected \xB7 space to toggle \xB7 a to select all \xB7 enter to confirm`;
12886
+ }
12887
+ this.host.terminal.write(`\r
12888
+ \x1B[2m${help}\x1B[0m`);
12889
+ this.renderedLines++;
12890
+ }
12891
+ };
12892
+
12893
+ // src/lib/input/modes/date-input-mode.ts
12894
+ var DateInputMode = class extends InputModeBase {
12895
+ constructor(host, resolve, promptText, options) {
12896
+ super(host, resolve);
12897
+ this.promptText = promptText;
12898
+ this.options = options;
12899
+ this.hasError = false;
12900
+ this.format = options?.format ?? "YYYY-MM-DD";
12901
+ this.separator = this.extractSeparator(this.format);
12902
+ this.buffer = options?.default ?? "";
12903
+ this.cursorPosition = this.buffer.length;
12904
+ }
12905
+ activate() {
12906
+ const prompt = this.buildPrompt();
12907
+ this.host.writeToTerminal(prompt);
12908
+ if (this.buffer) {
12909
+ this.host.writeToTerminal(this.buffer);
12910
+ }
12911
+ }
12912
+ async handleInput(data) {
12913
+ if (this.hasError) {
12914
+ this.hasError = false;
12915
+ this.clearExtraLines();
12916
+ }
12917
+ if (data === "\r") {
12918
+ const error = this.validateBuffer();
12919
+ if (error) {
12920
+ this.hasError = true;
12921
+ this.writeError(error);
12922
+ return;
12923
+ }
12924
+ this.resolveAndPop(this.buffer);
12925
+ } else if (data === "\x7F") {
12926
+ if (this.cursorPosition > 0) {
12927
+ this.buffer = this.buffer.slice(0, this.cursorPosition - 1) + this.buffer.slice(this.cursorPosition);
12928
+ this.cursorPosition--;
12929
+ this.renderLine();
12930
+ }
12931
+ } else if (data === "\x1B[D") {
12932
+ if (this.cursorPosition > 0) {
12933
+ this.cursorPosition--;
12934
+ this.host.terminal.write(data);
12935
+ }
12936
+ } else if (data === "\x1B[C") {
12937
+ if (this.cursorPosition < this.buffer.length) {
12938
+ this.cursorPosition++;
12939
+ this.host.terminal.write(data);
12940
+ }
12941
+ } else if (data.startsWith("\x1B")) ; else {
12942
+ for (const ch of data) {
12943
+ if (ch >= "0" && ch <= "9" || ch === this.separator) {
12944
+ this.buffer = this.buffer.slice(0, this.cursorPosition) + ch + this.buffer.slice(this.cursorPosition);
12945
+ this.cursorPosition++;
12946
+ }
12947
+ }
12948
+ this.renderLine();
12949
+ }
12950
+ }
12951
+ onResize() {
12952
+ this.renderLine();
12953
+ }
12954
+ buildPrompt() {
12955
+ return `\x1B[36m?\x1B[0m ${this.promptText} \x1B[2m(${this.format})\x1B[0m`;
12956
+ }
12957
+ renderLine() {
12958
+ const prompt = this.buildPrompt();
12959
+ this.redrawLine(prompt, this.buffer, this.cursorPosition);
12960
+ }
12961
+ /**
12962
+ * Extract the separator character from a format string.
12963
+ * Returns the first non-alphabetic character found (e.g., '-' from 'YYYY-MM-DD').
12964
+ * Defaults to '-' if none is found.
12965
+ */
12966
+ extractSeparator(format) {
12967
+ for (const ch of format) {
12968
+ if (ch < "A" || ch > "z" || ch > "Z" && ch < "a") {
12969
+ return ch;
12970
+ }
12971
+ }
12972
+ return "-";
12973
+ }
12974
+ /**
12975
+ * Parse the buffer according to the current format string.
12976
+ * Returns { year, month, day } or null if parsing fails.
12977
+ */
12978
+ parseBuffer() {
12979
+ const parts = this.buffer.split(this.separator);
12980
+ const formatParts = this.format.split(this.separator);
12981
+ if (parts.length !== formatParts.length) return null;
12982
+ let year = 0, month = 0, day = 0;
12983
+ for (let i = 0; i < formatParts.length; i++) {
12984
+ const token = formatParts[i].toUpperCase();
12985
+ const value = parseInt(parts[i], 10);
12986
+ if (isNaN(value)) return null;
12987
+ if (token === "YYYY") year = value;
12988
+ else if (token === "MM") month = value;
12989
+ else if (token === "DD") day = value;
12990
+ }
12991
+ return { year, month, day };
12992
+ }
12993
+ /**
12994
+ * Validate the buffer as a real calendar date.
12995
+ * Returns an error message string or null if valid.
12996
+ */
12997
+ validateBuffer() {
12998
+ if (!this.buffer) {
12999
+ return `Please enter a date in ${this.format} format`;
13000
+ }
13001
+ const parsed = this.parseBuffer();
13002
+ if (!parsed) {
13003
+ return `Invalid date format, expected ${this.format}`;
13004
+ }
13005
+ const { year, month, day } = parsed;
13006
+ if (month < 1 || month > 12) {
13007
+ return "Month must be between 1 and 12";
13008
+ }
13009
+ const maxDay = this.daysInMonth(year, month);
13010
+ if (day < 1 || day > maxDay) {
13011
+ return `Day must be between 1 and ${maxDay} for the given month`;
13012
+ }
13013
+ if (this.options?.min) {
13014
+ const minDate = this.parseDate(this.options.min);
13015
+ const current = new Date(year, month - 1, day);
13016
+ if (minDate && current < minDate) {
13017
+ return `Date must not be before ${this.options.min}`;
13018
+ }
13019
+ }
13020
+ if (this.options?.max) {
13021
+ const maxDate = this.parseDate(this.options.max);
13022
+ const current = new Date(year, month - 1, day);
13023
+ if (maxDate && current > maxDate) {
13024
+ return `Date must not be after ${this.options.max}`;
13025
+ }
13026
+ }
13027
+ return null;
13028
+ }
13029
+ /**
13030
+ * Returns the number of days in a given month (accounting for leap years).
13031
+ */
13032
+ daysInMonth(year, month) {
13033
+ return new Date(year, month, 0).getDate();
13034
+ }
13035
+ /**
13036
+ * Parse a date string (in the same format as this mode) into a Date object.
13037
+ */
13038
+ parseDate(dateStr) {
13039
+ const parts = dateStr.split(this.separator);
13040
+ const formatParts = this.format.split(this.separator);
13041
+ if (parts.length !== formatParts.length) return null;
13042
+ let year = 0, month = 0, day = 0;
13043
+ for (let i = 0; i < formatParts.length; i++) {
13044
+ const token = formatParts[i].toUpperCase();
13045
+ const value = parseInt(parts[i], 10);
13046
+ if (isNaN(value)) return null;
13047
+ if (token === "YYYY") year = value;
13048
+ else if (token === "MM") month = value;
13049
+ else if (token === "DD") day = value;
13050
+ }
13051
+ return new Date(year, month - 1, day);
13052
+ }
13053
+ };
13054
+
13055
+ // src/lib/input/modes/file-input-mode.ts
13056
+ var FileInputMode = class extends InputModeBase {
13057
+ constructor(host, resolve, promptText, options) {
13058
+ super(host, resolve);
13059
+ this.promptText = promptText;
13060
+ this.options = options;
13061
+ this.waiting = false;
13062
+ }
13063
+ activate() {
13064
+ const provider = this.host.filePickerProvider;
13065
+ const acceptHint = this.options?.accept ? ` \x1B[2m(${this.options.accept})\x1B[0m` : "";
13066
+ this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}${acceptHint}: `);
13067
+ if (!provider.isSupported) {
13068
+ this.host.writeToTerminal("\x1B[2m(file picker not available)\x1B[0m");
13069
+ this.abort();
13070
+ return;
13071
+ }
13072
+ this.host.writeToTerminal("\x1B[33mOpening file picker...\x1B[0m");
13073
+ this.waiting = true;
13074
+ this.pickFiles(provider);
13075
+ }
13076
+ async pickFiles(provider) {
13077
+ try {
13078
+ let results;
13079
+ if (this.options?.directory) {
13080
+ const dir = await provider.pickDirectory();
13081
+ results = dir ? [dir] : null;
13082
+ } else {
13083
+ results = await provider.pickFiles(this.options);
13084
+ }
13085
+ this.waiting = false;
13086
+ if (!results || this.isResolved) {
13087
+ this.host.terminal.write("\x1B[2K\r");
13088
+ this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}: \x1B[2m(cancelled)\x1B[0m\r
13089
+ `);
13090
+ if (!this.isResolved) this.abort();
13091
+ return;
13092
+ }
13093
+ this.host.terminal.write("\x1B[2K\r");
13094
+ this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}:\r
13095
+ `);
13096
+ let totalSize = 0;
13097
+ for (const file of results) {
13098
+ const sizeStr = this.formatSize(file.size);
13099
+ this.host.writeToTerminal(` \x1B[32m\u2714\x1B[0m ${file.name} \x1B[2m(${sizeStr})\x1B[0m\r
13100
+ `);
13101
+ totalSize += file.size;
13102
+ }
13103
+ if (results.length > 1) {
13104
+ this.host.writeToTerminal(` \x1B[2m${results.length} files selected \xB7 ${this.formatSize(totalSize)} total\x1B[0m\r
13105
+ `);
13106
+ }
13107
+ this.resolveAndPop(results);
13108
+ } catch {
13109
+ this.waiting = false;
13110
+ if (this.isResolved) return;
13111
+ this.host.terminal.write("\x1B[2K\r");
13112
+ this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}: \x1B[31m(error)\x1B[0m\r
13113
+ `);
13114
+ this.abort();
13115
+ }
13116
+ }
13117
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
13118
+ async handleInput(_data) {
13119
+ }
13120
+ formatSize(bytes) {
13121
+ if (bytes < 1024) return `${bytes} B`;
13122
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
13123
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
13124
+ }
13125
+ };
13126
+
13127
+ // src/lib/services/cli-input-reader.ts
13128
+ var CliInputReader = class {
13129
+ constructor(host) {
13130
+ this.host = host;
13131
+ }
13132
+ readLine(prompt, options) {
13133
+ return new Promise((resolve) => {
13134
+ const mode = new LineInputMode(this.host, resolve, prompt, options);
13135
+ this.host.pushMode(mode);
13136
+ });
13137
+ }
13138
+ readPassword(prompt) {
13139
+ return new Promise((resolve) => {
13140
+ const mode = new PasswordInputMode(this.host, resolve, prompt);
13141
+ this.host.pushMode(mode);
13142
+ });
13143
+ }
13144
+ readConfirm(prompt, defaultValue = false) {
13145
+ return new Promise((resolve) => {
13146
+ const mode = new ConfirmInputMode(this.host, resolve, prompt, defaultValue);
13147
+ this.host.pushMode(mode);
13148
+ });
13149
+ }
13150
+ readSelect(prompt, options, selectOptions) {
13151
+ if (!options || options.length === 0) {
13152
+ return Promise.reject(new Error("readSelect requires at least one option"));
13153
+ }
13154
+ return new Promise((resolve) => {
13155
+ const mode = new SelectInputMode(this.host, resolve, prompt, options, selectOptions);
13156
+ this.host.pushMode(mode);
13157
+ });
13158
+ }
13159
+ readSelectInline(prompt, options, selectOptions) {
13160
+ if (!options || options.length === 0) {
13161
+ return Promise.reject(new Error("readSelectInline requires at least one option"));
13162
+ }
13163
+ return new Promise((resolve) => {
13164
+ const mode = new InlineSelectInputMode(this.host, resolve, prompt, options, selectOptions);
13165
+ this.host.pushMode(mode);
13166
+ });
13167
+ }
13168
+ readMultiSelect(prompt, options, selectOptions) {
13169
+ if (!options || options.length === 0) {
13170
+ return Promise.reject(new Error("readMultiSelect requires at least one option"));
13171
+ }
13172
+ return new Promise((resolve) => {
13173
+ const mode = new MultiSelectInputMode(this.host, resolve, prompt, options, selectOptions);
13174
+ this.host.pushMode(mode);
13175
+ });
13176
+ }
13177
+ readNumber(prompt, options) {
13178
+ return new Promise((resolve) => {
13179
+ const mode = new NumberInputMode(this.host, resolve, prompt, options);
13180
+ this.host.pushMode(mode);
13181
+ });
13182
+ }
13183
+ readDate(prompt, options) {
13184
+ return new Promise((resolve) => {
13185
+ const mode = new DateInputMode(this.host, resolve, prompt, options);
13186
+ this.host.pushMode(mode);
13187
+ });
13188
+ }
13189
+ readFile(prompt, options) {
13190
+ return new Promise((resolve) => {
13191
+ const mode = new FileInputMode(this.host, resolve, prompt, options);
13192
+ this.host.pushMode(mode);
13193
+ });
13194
+ }
13195
+ };
13196
+
13197
+ // src/lib/services/file-picker/browser-file-picker-provider.ts
13198
+ var BrowserFilePickerProvider = class {
13199
+ constructor() {
13200
+ this.isSupported = typeof document !== "undefined";
13201
+ }
13202
+ async pickFiles(options) {
13203
+ if (!this.isSupported) return null;
13204
+ if (options?.directory) {
13205
+ const dir = await this.pickDirectory();
13206
+ return dir ? [dir] : null;
13207
+ }
13208
+ return new Promise((resolve) => {
13209
+ const input = document.createElement("input");
13210
+ input.type = "file";
13211
+ input.style.display = "none";
13212
+ if (options?.accept) input.accept = options.accept;
13213
+ if (options?.multiple) input.multiple = true;
13214
+ input.addEventListener("change", async () => {
13215
+ const files = input.files;
13216
+ if (!files || files.length === 0) {
13217
+ resolve(null);
13218
+ input.remove();
13219
+ return;
13220
+ }
13221
+ const readAs = options?.readAs ?? "text";
13222
+ const results = [];
13223
+ for (let i = 0; i < files.length; i++) {
13224
+ const file = files[i];
13225
+ const content = await this.readFile(file, readAs);
13226
+ results.push({
13227
+ name: file.name,
13228
+ content,
13229
+ size: file.size,
13230
+ type: file.type || "application/octet-stream"
13231
+ });
13232
+ }
13233
+ resolve(results);
13234
+ input.remove();
13235
+ });
13236
+ input.addEventListener("cancel", () => {
13237
+ resolve(null);
13238
+ input.remove();
11368
13239
  });
13240
+ document.body.appendChild(input);
13241
+ input.click();
11369
13242
  });
11370
13243
  }
11371
- readMultiSelect(prompt, options) {
11372
- if (!options || options.length === 0) {
11373
- return Promise.reject(
11374
- new Error("readMultiSelect requires at least one option")
11375
- );
11376
- }
13244
+ async pickDirectory() {
13245
+ if (!this.isSupported) return null;
11377
13246
  return new Promise((resolve) => {
11378
- if (this.host.activeInputRequest) {
11379
- throw new Error("Another input request is already active");
11380
- }
11381
- const checkedIndices = /* @__PURE__ */ new Set();
11382
- options.forEach((opt, i) => {
11383
- if (opt.checked) {
11384
- checkedIndices.add(i);
13247
+ const input = document.createElement("input");
13248
+ input.type = "file";
13249
+ input.style.display = "none";
13250
+ input.webkitdirectory = true;
13251
+ input.addEventListener("change", () => {
13252
+ const files = input.files;
13253
+ if (!files || files.length === 0) {
13254
+ resolve(null);
13255
+ } else {
13256
+ const pathParts = files[0].webkitRelativePath?.split("/") ?? [];
13257
+ resolve({
13258
+ name: pathParts[0] || "selected-directory",
13259
+ content: "",
13260
+ size: 0,
13261
+ type: "inode/directory"
13262
+ });
11385
13263
  }
13264
+ input.remove();
11386
13265
  });
11387
- const termRows = this.host.getTerminalRows?.() ?? 24;
11388
- const maxVisible = Math.max(3, termRows - 2);
11389
- this.host.writeToTerminal(prompt + "\r\n");
11390
- const displayLines = this.renderMultiSelectOptions(options, 0, checkedIndices, 0, maxVisible);
11391
- this.host.setActiveInputRequest({
11392
- type: "multi-select",
11393
- promptText: prompt,
11394
- resolve,
11395
- buffer: "",
11396
- cursorPosition: 0,
11397
- options,
11398
- selectedIndex: 0,
11399
- checkedIndices,
11400
- scrollOffset: 0,
11401
- maxVisible,
11402
- displayLines
13266
+ input.addEventListener("cancel", () => {
13267
+ resolve(null);
13268
+ input.remove();
11403
13269
  });
13270
+ document.body.appendChild(input);
13271
+ input.click();
11404
13272
  });
11405
13273
  }
11406
- readNumber(prompt, options) {
11407
- return new Promise((resolve) => {
11408
- if (this.host.activeInputRequest) {
11409
- throw new Error("Another input request is already active");
11410
- }
11411
- let displayPrompt = prompt;
11412
- const hints = [];
11413
- if (options?.min !== void 0 || options?.max !== void 0) {
11414
- const minStr = options?.min !== void 0 ? String(options.min) : "";
11415
- const maxStr = options?.max !== void 0 ? String(options.max) : "";
11416
- if (minStr && maxStr) {
11417
- hints.push(`${minStr}-${maxStr}`);
11418
- } else if (minStr) {
11419
- hints.push(`>=${minStr}`);
11420
- } else if (maxStr) {
11421
- hints.push(`<=${maxStr}`);
11422
- }
11423
- }
11424
- if (options?.default !== void 0) {
11425
- hints.push(`default: ${options.default}`);
11426
- }
11427
- if (hints.length > 0) {
11428
- displayPrompt += ` (${hints.join(", ")}): `;
13274
+ readFile(file, readAs) {
13275
+ return new Promise((resolve, reject) => {
13276
+ const reader = new FileReader();
13277
+ reader.onload = () => resolve(reader.result);
13278
+ reader.onerror = () => reject(reader.error);
13279
+ if (readAs === "arraybuffer") {
13280
+ reader.readAsArrayBuffer(file);
11429
13281
  } else {
11430
- displayPrompt += ": ";
13282
+ reader.readAsText(file);
11431
13283
  }
11432
- this.host.writeToTerminal(displayPrompt);
11433
- this.host.setActiveInputRequest({
11434
- type: "number",
11435
- promptText: displayPrompt,
11436
- resolve,
11437
- buffer: "",
11438
- cursorPosition: 0,
11439
- numberOptions: options
11440
- });
11441
13284
  });
11442
13285
  }
11443
- renderSelectOptions(options, selectedIndex, scrollOffset = 0, maxVisible = options.length) {
11444
- const needsScroll = options.length > maxVisible;
11445
- const itemSlots = needsScroll ? maxVisible - 2 : options.length;
11446
- const visibleCount = Math.max(1, Math.min(itemSlots, options.length));
11447
- const end = Math.min(scrollOffset + visibleCount, options.length);
11448
- let lines = 0;
11449
- if (needsScroll) {
11450
- const upLabel = scrollOffset > 0 ? "\u2191 more" : "";
11451
- this.host.writeToTerminal(` \x1B[2m${upLabel}\x1B[0m\r
11452
- `);
11453
- lines++;
11454
- }
11455
- for (let i = scrollOffset; i < end; i++) {
11456
- const prefix = i === selectedIndex ? " \x1B[36m> " : " ";
11457
- const suffix = i === selectedIndex ? "\x1B[0m" : "";
11458
- this.host.writeToTerminal(
11459
- `${prefix}${options[i].label}${suffix}\r
11460
- `
11461
- );
11462
- lines++;
11463
- }
11464
- if (needsScroll) {
11465
- const downLabel = end < options.length ? "\u2193 more" : "";
11466
- this.host.writeToTerminal(` \x1B[2m${downLabel}\x1B[0m\r
11467
- `);
11468
- lines++;
11469
- }
11470
- return lines;
11471
- }
11472
- renderInlineSelectOptions(options, selectedIndex) {
11473
- return options.map((opt, i) => {
11474
- if (i === selectedIndex) {
11475
- return `\x1B[36m[ ${opt.label} ]\x1B[0m`;
11476
- }
11477
- return ` ${opt.label} `;
11478
- }).join("");
11479
- }
11480
- renderMultiSelectOptions(options, selectedIndex, checkedIndices, scrollOffset = 0, maxVisible = options.length) {
11481
- const needsScroll = options.length > maxVisible;
11482
- const itemSlots = needsScroll ? maxVisible - 2 : options.length;
11483
- const visibleCount = Math.max(1, Math.min(itemSlots, options.length));
11484
- const end = Math.min(scrollOffset + visibleCount, options.length);
11485
- let lines = 0;
11486
- if (needsScroll) {
11487
- const upLabel = scrollOffset > 0 ? "\u2191 more" : "";
11488
- this.host.writeToTerminal(` \x1B[2m${upLabel}\x1B[0m\r
11489
- `);
11490
- lines++;
11491
- }
11492
- for (let i = scrollOffset; i < end; i++) {
11493
- const checkbox = checkedIndices.has(i) ? "[x]" : "[ ]";
11494
- const prefix = i === selectedIndex ? ` \x1B[36m> ${checkbox} ` : ` ${checkbox} `;
11495
- const suffix = i === selectedIndex ? "\x1B[0m" : "";
11496
- this.host.writeToTerminal(
11497
- `${prefix}${options[i].label}${suffix}\r
11498
- `
11499
- );
11500
- lines++;
11501
- }
11502
- if (needsScroll) {
11503
- const downLabel = end < options.length ? "\u2193 more" : "";
11504
- this.host.writeToTerminal(` \x1B[2m${downLabel}\x1B[0m\r
11505
- `);
11506
- lines++;
11507
- }
11508
- return lines;
13286
+ };
13287
+
13288
+ // src/lib/services/file-picker/noop-file-picker-provider.ts
13289
+ var NoopFilePickerProvider = class {
13290
+ constructor() {
13291
+ this.isSupported = false;
11509
13292
  }
11510
- createInputRequest(type, prompt) {
11511
- return new Promise((resolve) => {
11512
- if (this.host.activeInputRequest) {
11513
- throw new Error("Another input request is already active");
11514
- }
11515
- this.host.writeToTerminal(prompt);
11516
- this.host.setActiveInputRequest({
11517
- type,
11518
- promptText: prompt,
11519
- resolve,
11520
- buffer: "",
11521
- cursorPosition: 0
11522
- });
11523
- });
13293
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
13294
+ async pickFiles(_options) {
13295
+ return null;
13296
+ }
13297
+ async pickDirectory() {
13298
+ return null;
11524
13299
  }
11525
13300
  };
11526
13301
 
@@ -12045,400 +13820,6 @@ var CommandLineMode = class {
12045
13820
  }
12046
13821
  };
12047
13822
 
12048
- // src/lib/input/reader-mode.ts
12049
- var ReaderMode = class {
12050
- constructor(host) {
12051
- this.host = host;
12052
- }
12053
- async handleInput(data) {
12054
- const request = this.host.getActiveInputRequest();
12055
- if (!request) {
12056
- return;
12057
- }
12058
- switch (request.type) {
12059
- case "line":
12060
- this.handleLineInput(request, data);
12061
- break;
12062
- case "password":
12063
- this.handlePasswordInput(request, data);
12064
- break;
12065
- case "confirm":
12066
- this.handleConfirmInput(request, data);
12067
- break;
12068
- case "select":
12069
- this.handleSelectInput(request, data);
12070
- break;
12071
- case "select-inline":
12072
- this.handleSelectInlineInput(request, data);
12073
- break;
12074
- case "multi-select":
12075
- this.handleMultiSelectInput(request, data);
12076
- break;
12077
- case "number":
12078
- this.handleNumberInput(request, data);
12079
- break;
12080
- }
12081
- }
12082
- handleKeyEvent(event) {
12083
- if (event.code === "KeyC" && event.ctrlKey) {
12084
- const request = this.host.getActiveInputRequest();
12085
- if (request) {
12086
- request.resolve(null);
12087
- this.host.setActiveInputRequest(null);
12088
- this.host.terminal.writeln("");
12089
- this.host.popMode();
12090
- }
12091
- return false;
12092
- }
12093
- if (event.code === "Escape") {
12094
- const request = this.host.getActiveInputRequest();
12095
- if (request) {
12096
- request.resolve(null);
12097
- this.host.setActiveInputRequest(null);
12098
- this.host.terminal.writeln("");
12099
- this.host.popMode();
12100
- }
12101
- return false;
12102
- }
12103
- return true;
12104
- }
12105
- handleLineInput(request, data) {
12106
- if (data === "\r") {
12107
- this.host.terminal.write("\r\n");
12108
- const value = request.buffer;
12109
- this.host.setActiveInputRequest(null);
12110
- this.host.popMode();
12111
- request.resolve(value);
12112
- } else if (data === "\x7F") {
12113
- if (request.cursorPosition > 0) {
12114
- request.buffer = request.buffer.slice(0, request.cursorPosition - 1) + request.buffer.slice(request.cursorPosition);
12115
- request.cursorPosition--;
12116
- this.redrawReaderLine(request, request.buffer);
12117
- }
12118
- } else if (data === "\x1B[D") {
12119
- if (request.cursorPosition > 0) {
12120
- request.cursorPosition--;
12121
- this.host.terminal.write(data);
12122
- }
12123
- } else if (data === "\x1B[C") {
12124
- if (request.cursorPosition < request.buffer.length) {
12125
- request.cursorPosition++;
12126
- this.host.terminal.write(data);
12127
- }
12128
- } else if (data.startsWith("\x1B")) ; else {
12129
- const text = data.replace(/[\r\n]+/g, "");
12130
- request.buffer = request.buffer.slice(0, request.cursorPosition) + text + request.buffer.slice(request.cursorPosition);
12131
- request.cursorPosition += text.length;
12132
- this.redrawReaderLine(request, request.buffer);
12133
- }
12134
- }
12135
- handlePasswordInput(request, data) {
12136
- if (data === "\r") {
12137
- this.host.terminal.write("\r\n");
12138
- const value = request.buffer;
12139
- this.host.setActiveInputRequest(null);
12140
- this.host.popMode();
12141
- request.resolve(value);
12142
- } else if (data === "\x7F") {
12143
- if (request.cursorPosition > 0) {
12144
- request.buffer = request.buffer.slice(0, request.cursorPosition - 1) + request.buffer.slice(request.cursorPosition);
12145
- request.cursorPosition--;
12146
- this.redrawReaderLine(
12147
- request,
12148
- "*".repeat(request.buffer.length)
12149
- );
12150
- }
12151
- } else if (data.startsWith("\x1B")) ; else {
12152
- const text = data.replace(/[\r\n]+/g, "");
12153
- request.buffer = request.buffer.slice(0, request.cursorPosition) + text + request.buffer.slice(request.cursorPosition);
12154
- request.cursorPosition += text.length;
12155
- this.redrawReaderLine(request, "*".repeat(request.buffer.length));
12156
- }
12157
- }
12158
- handleConfirmInput(request, data) {
12159
- if (data === "\r") {
12160
- this.host.terminal.write("\r\n");
12161
- this.host.setActiveInputRequest(null);
12162
- this.host.popMode();
12163
- const buf = request.buffer.toLowerCase();
12164
- if (buf === "y") {
12165
- request.resolve(true);
12166
- } else if (buf === "n") {
12167
- request.resolve(false);
12168
- } else {
12169
- request.resolve(request.defaultValue ?? false);
12170
- }
12171
- } else if (data === "\x7F") {
12172
- if (request.cursorPosition > 0) {
12173
- request.buffer = request.buffer.slice(0, -1);
12174
- request.cursorPosition--;
12175
- this.redrawReaderLine(request, request.buffer);
12176
- }
12177
- } else if (data.startsWith("\x1B")) ; else {
12178
- const char = data.toLowerCase();
12179
- if (char === "y" || char === "n") {
12180
- request.buffer = data;
12181
- request.cursorPosition = 1;
12182
- this.redrawReaderLine(request, request.buffer);
12183
- }
12184
- }
12185
- }
12186
- handleSelectInput(request, data) {
12187
- const options = request.options;
12188
- const selectedIndex = request.selectedIndex;
12189
- if (data === "\r") {
12190
- this.host.terminal.write("\r\n");
12191
- this.host.setActiveInputRequest(null);
12192
- this.host.popMode();
12193
- request.resolve(options[selectedIndex].value);
12194
- } else if (data === "\x1B[A") {
12195
- if (selectedIndex > 0) {
12196
- request.selectedIndex = selectedIndex - 1;
12197
- this.adjustScrollOffset(request);
12198
- this.redrawSelectOptions(request);
12199
- request.onChange?.(options[request.selectedIndex].value);
12200
- }
12201
- } else if (data === "\x1B[B") {
12202
- if (selectedIndex < options.length - 1) {
12203
- request.selectedIndex = selectedIndex + 1;
12204
- this.adjustScrollOffset(request);
12205
- this.redrawSelectOptions(request);
12206
- request.onChange?.(options[request.selectedIndex].value);
12207
- }
12208
- }
12209
- }
12210
- adjustScrollOffset(request) {
12211
- const options = request.options;
12212
- const maxVisible = request.maxVisible ?? options.length;
12213
- const needsScroll = options.length > maxVisible;
12214
- const itemSlots = needsScroll ? maxVisible - 2 : options.length;
12215
- const visibleCount = Math.max(1, Math.min(itemSlots, options.length));
12216
- const scrollOffset = request.scrollOffset ?? 0;
12217
- const selectedIndex = request.selectedIndex;
12218
- if (selectedIndex < scrollOffset) {
12219
- request.scrollOffset = selectedIndex;
12220
- } else if (selectedIndex >= scrollOffset + visibleCount) {
12221
- request.scrollOffset = selectedIndex - visibleCount + 1;
12222
- }
12223
- }
12224
- redrawReaderLine(request, displayText) {
12225
- this.host.terminal.write("\x1B[2K\r");
12226
- this.host.terminal.write(request.promptText + displayText);
12227
- const cursorOffset = request.buffer.length - request.cursorPosition;
12228
- if (cursorOffset > 0) {
12229
- this.host.terminal.write(`\x1B[${cursorOffset}D`);
12230
- }
12231
- }
12232
- redrawSelectOptions(request) {
12233
- const options = request.options;
12234
- const selectedIndex = request.selectedIndex;
12235
- const maxVisible = request.maxVisible ?? options.length;
12236
- const scrollOffset = request.scrollOffset ?? 0;
12237
- const displayLines = request.displayLines ?? options.length;
12238
- const needsScroll = options.length > maxVisible;
12239
- const itemSlots = needsScroll ? maxVisible - 2 : options.length;
12240
- const visibleCount = Math.max(1, Math.min(itemSlots, options.length));
12241
- const end = Math.min(scrollOffset + visibleCount, options.length);
12242
- if (displayLines > 0) {
12243
- this.host.terminal.write(`\x1B[${displayLines}A`);
12244
- }
12245
- if (needsScroll) {
12246
- this.host.terminal.write("\x1B[2K\r");
12247
- const upLabel = scrollOffset > 0 ? "\u2191 more" : "";
12248
- this.host.terminal.write(` \x1B[2m${upLabel}\x1B[0m\r
12249
- `);
12250
- }
12251
- for (let i = scrollOffset; i < end; i++) {
12252
- this.host.terminal.write("\x1B[2K\r");
12253
- const prefix = i === selectedIndex ? " \x1B[36m> " : " ";
12254
- const suffix = i === selectedIndex ? "\x1B[0m" : "";
12255
- this.host.terminal.write(
12256
- `${prefix}${options[i].label}${suffix}\r
12257
- `
12258
- );
12259
- }
12260
- if (needsScroll) {
12261
- this.host.terminal.write("\x1B[2K\r");
12262
- const downLabel = end < options.length ? "\u2193 more" : "";
12263
- this.host.terminal.write(` \x1B[2m${downLabel}\x1B[0m\r
12264
- `);
12265
- }
12266
- }
12267
- handleSelectInlineInput(request, data) {
12268
- const options = request.options;
12269
- const selectedIndex = request.selectedIndex;
12270
- if (data === "\r") {
12271
- this.host.terminal.write("\r\n");
12272
- this.host.setActiveInputRequest(null);
12273
- this.host.popMode();
12274
- request.resolve(options[selectedIndex].value);
12275
- } else if (data === "\x1B[D") {
12276
- if (selectedIndex > 0) {
12277
- request.selectedIndex = selectedIndex - 1;
12278
- this.redrawInlineSelectOptions(request);
12279
- request.onChange?.(options[request.selectedIndex].value);
12280
- }
12281
- } else if (data === "\x1B[C") {
12282
- if (selectedIndex < options.length - 1) {
12283
- request.selectedIndex = selectedIndex + 1;
12284
- this.redrawInlineSelectOptions(request);
12285
- request.onChange?.(options[request.selectedIndex].value);
12286
- }
12287
- }
12288
- }
12289
- redrawInlineSelectOptions(request) {
12290
- const options = request.options;
12291
- const selectedIndex = request.selectedIndex;
12292
- this.host.terminal.write("\x1B[2K\r");
12293
- const inlineText = options.map((opt, i) => {
12294
- if (i === selectedIndex) {
12295
- return `\x1B[36m[ ${opt.label} ]\x1B[0m`;
12296
- }
12297
- return ` ${opt.label} `;
12298
- }).join("");
12299
- this.host.terminal.write(`${request.promptText} ${inlineText}`);
12300
- }
12301
- handleMultiSelectInput(request, data) {
12302
- const options = request.options;
12303
- const selectedIndex = request.selectedIndex;
12304
- const checkedIndices = request.checkedIndices;
12305
- if (data === "\r") {
12306
- this.host.terminal.write("\r\n");
12307
- this.host.setActiveInputRequest(null);
12308
- this.host.popMode();
12309
- const selected = options.filter((_, i) => checkedIndices.has(i)).map((opt) => opt.value);
12310
- request.resolve(selected);
12311
- } else if (data === " ") {
12312
- if (checkedIndices.has(selectedIndex)) {
12313
- checkedIndices.delete(selectedIndex);
12314
- } else {
12315
- checkedIndices.add(selectedIndex);
12316
- }
12317
- this.redrawMultiSelectOptions(request);
12318
- } else if (data === "\x1B[A") {
12319
- if (selectedIndex > 0) {
12320
- request.selectedIndex = selectedIndex - 1;
12321
- this.adjustScrollOffset(request);
12322
- this.redrawMultiSelectOptions(request);
12323
- }
12324
- } else if (data === "\x1B[B") {
12325
- if (selectedIndex < options.length - 1) {
12326
- request.selectedIndex = selectedIndex + 1;
12327
- this.adjustScrollOffset(request);
12328
- this.redrawMultiSelectOptions(request);
12329
- }
12330
- }
12331
- }
12332
- redrawMultiSelectOptions(request) {
12333
- const options = request.options;
12334
- const selectedIndex = request.selectedIndex;
12335
- const checkedIndices = request.checkedIndices;
12336
- const maxVisible = request.maxVisible ?? options.length;
12337
- const scrollOffset = request.scrollOffset ?? 0;
12338
- const displayLines = request.displayLines ?? options.length;
12339
- const needsScroll = options.length > maxVisible;
12340
- const itemSlots = needsScroll ? maxVisible - 2 : options.length;
12341
- const visibleCount = Math.max(1, Math.min(itemSlots, options.length));
12342
- const end = Math.min(scrollOffset + visibleCount, options.length);
12343
- if (displayLines > 0) {
12344
- this.host.terminal.write(`\x1B[${displayLines}A`);
12345
- }
12346
- if (needsScroll) {
12347
- this.host.terminal.write("\x1B[2K\r");
12348
- const upLabel = scrollOffset > 0 ? "\u2191 more" : "";
12349
- this.host.terminal.write(` \x1B[2m${upLabel}\x1B[0m\r
12350
- `);
12351
- }
12352
- for (let i = scrollOffset; i < end; i++) {
12353
- this.host.terminal.write("\x1B[2K\r");
12354
- const checkbox = checkedIndices.has(i) ? "[x]" : "[ ]";
12355
- const prefix = i === selectedIndex ? ` \x1B[36m> ${checkbox} ` : ` ${checkbox} `;
12356
- const suffix = i === selectedIndex ? "\x1B[0m" : "";
12357
- this.host.terminal.write(
12358
- `${prefix}${options[i].label}${suffix}\r
12359
- `
12360
- );
12361
- }
12362
- if (needsScroll) {
12363
- this.host.terminal.write("\x1B[2K\r");
12364
- const downLabel = end < options.length ? "\u2193 more" : "";
12365
- this.host.terminal.write(` \x1B[2m${downLabel}\x1B[0m\r
12366
- `);
12367
- }
12368
- }
12369
- handleNumberInput(request, data) {
12370
- if (data === "\r") {
12371
- const numOptions = request.numberOptions;
12372
- if (request.buffer === "") {
12373
- if (numOptions?.default !== void 0) {
12374
- this.host.terminal.write("\r\n");
12375
- this.host.setActiveInputRequest(null);
12376
- this.host.popMode();
12377
- request.resolve(numOptions.default);
12378
- } else {
12379
- this.host.terminal.write("\r\n");
12380
- this.host.terminal.write(
12381
- "\x1B[31mPlease enter a number.\x1B[0m\r\n"
12382
- );
12383
- this.host.terminal.write(request.promptText);
12384
- }
12385
- return;
12386
- }
12387
- const value = Number(request.buffer);
12388
- if (isNaN(value)) {
12389
- this.host.terminal.write("\r\n");
12390
- this.host.terminal.write("\x1B[31mInvalid number.\x1B[0m\r\n");
12391
- request.buffer = "";
12392
- request.cursorPosition = 0;
12393
- this.host.terminal.write(request.promptText);
12394
- return;
12395
- }
12396
- if (numOptions?.min !== void 0 && value < numOptions.min) {
12397
- this.host.terminal.write("\r\n");
12398
- this.host.terminal.write(
12399
- `\x1B[31mValue must be at least ${numOptions.min}.\x1B[0m\r
12400
- `
12401
- );
12402
- request.buffer = "";
12403
- request.cursorPosition = 0;
12404
- this.host.terminal.write(request.promptText);
12405
- return;
12406
- }
12407
- if (numOptions?.max !== void 0 && value > numOptions.max) {
12408
- this.host.terminal.write("\r\n");
12409
- this.host.terminal.write(
12410
- `\x1B[31mValue must be at most ${numOptions.max}.\x1B[0m\r
12411
- `
12412
- );
12413
- request.buffer = "";
12414
- request.cursorPosition = 0;
12415
- this.host.terminal.write(request.promptText);
12416
- return;
12417
- }
12418
- this.host.terminal.write("\r\n");
12419
- this.host.setActiveInputRequest(null);
12420
- this.host.popMode();
12421
- request.resolve(value);
12422
- } else if (data === "\x7F") {
12423
- if (request.cursorPosition > 0) {
12424
- request.buffer = request.buffer.slice(0, request.cursorPosition - 1) + request.buffer.slice(request.cursorPosition);
12425
- request.cursorPosition--;
12426
- this.redrawReaderLine(request, request.buffer);
12427
- }
12428
- } else if (data === "-") {
12429
- if (request.cursorPosition === 0 && !request.buffer.includes("-")) {
12430
- request.buffer = "-" + request.buffer;
12431
- request.cursorPosition = 1;
12432
- this.redrawReaderLine(request, request.buffer);
12433
- }
12434
- } else if (/^\d$/.test(data)) {
12435
- request.buffer = request.buffer.slice(0, request.cursorPosition) + data + request.buffer.slice(request.cursorPosition);
12436
- request.cursorPosition += 1;
12437
- this.redrawReaderLine(request, request.buffer);
12438
- }
12439
- }
12440
- };
12441
-
12442
13823
  // src/lib/input/raw-mode.ts
12443
13824
  var RawMode = class {
12444
13825
  constructor(processor, context2, fullScreen = false) {
@@ -12531,7 +13912,6 @@ var CliExecutionContext = class {
12531
13912
  this.completionEngine = new CliCompletionEngine();
12532
13913
  this.promptLength = 0;
12533
13914
  this.lineBuffer = new CliLineBuffer();
12534
- this._activeInputRequest = null;
12535
13915
  this.modeStack = [];
12536
13916
  this.managedTimers = /* @__PURE__ */ new Set();
12537
13917
  this.resizeDisposable = null;
@@ -12570,6 +13950,7 @@ var CliExecutionContext = class {
12570
13950
  this.textAnimator = textAnimator;
12571
13951
  this.clipboard = new CliClipboard(this);
12572
13952
  this.process = new CliExecutionProcess(this);
13953
+ this.filePickerProvider = typeof document !== "undefined" ? new BrowserFilePickerProvider() : new NoopFilePickerProvider();
12573
13954
  this.reader = new CliInputReader(this);
12574
13955
  this.logger = deps.logger;
12575
13956
  this.logger.setCliLogLevel(cliOptions?.logLevel || CliLogLevel.ERROR);
@@ -12582,26 +13963,15 @@ var CliExecutionContext = class {
12582
13963
  );
12583
13964
  }
12584
13965
  // -- Public API (ICliExecutionContext) --
12585
- get activeInputRequest() {
12586
- return this._activeInputRequest;
12587
- }
12588
- /**
12589
- * Sets the active input request. When a non-null request is provided,
12590
- * pushes ReaderMode onto the mode stack. ReaderMode pops itself on
12591
- * completion, so passing null here does NOT pop the mode.
12592
- */
12593
- setActiveInputRequest(request) {
12594
- this._activeInputRequest = request;
12595
- if (request !== null) {
12596
- this.pushMode(new ReaderMode(this));
12597
- }
12598
- }
12599
13966
  writeToTerminal(text) {
12600
13967
  this.terminal.write(text);
12601
13968
  }
12602
13969
  getTerminalRows() {
12603
13970
  return this.terminal.rows;
12604
13971
  }
13972
+ getTerminalCols() {
13973
+ return this.terminal.cols;
13974
+ }
12605
13975
  get currentLine() {
12606
13976
  return this.lineBuffer.text;
12607
13977
  }
@@ -12706,7 +14076,12 @@ var CliExecutionContext = class {
12706
14076
  );
12707
14077
  return;
12708
14078
  }
12709
- if (this.isProgressRunning() || this.isRawModeActive() || this._activeInputRequest) {
14079
+ if (this.isProgressRunning() || this.isRawModeActive()) {
14080
+ return;
14081
+ }
14082
+ const mode = this.currentMode;
14083
+ if (mode?.onResize) {
14084
+ mode.onResize(this.terminal.cols, this.terminal.rows);
12710
14085
  return;
12711
14086
  }
12712
14087
  const buf = this.terminal.buffer.active;
@@ -12820,10 +14195,6 @@ var CliExecutionContext = class {
12820
14195
  getExecutionContext() {
12821
14196
  return this;
12822
14197
  }
12823
- // -- ReaderModeHost interface --
12824
- getActiveInputRequest() {
12825
- return this._activeInputRequest;
12826
- }
12827
14198
  // -- Managed timer cleanup --
12828
14199
  clearAllManagedTimers() {
12829
14200
  for (const timer of this.managedTimers) {
@@ -13266,6 +14637,7 @@ var CliCommandExecutor = class {
13266
14637
  const aliasProcessor = this.registry.findProcessor("alias", []);
13267
14638
  const aliases = aliasProcessor?.userAliases ?? {};
13268
14639
  if (aliases[mainCommand]) {
14640
+ this.completeProcess(processEntry, context2, 0);
13269
14641
  return await this.executeSingleCommand(
13270
14642
  aliases[mainCommand],
13271
14643
  data,
@@ -13278,6 +14650,7 @@ var CliCommandExecutor = class {
13278
14650
  context2
13279
14651
  );
13280
14652
  if (executed) {
14653
+ this.completeProcess(processEntry, context2, 0);
13281
14654
  process.end();
13282
14655
  return;
13283
14656
  }
@@ -13292,6 +14665,7 @@ var CliCommandExecutor = class {
13292
14665
  context2.writer.writeInfo(
13293
14666
  `\u{1F4E6} Use ${context2.writer.wrapInColor("pkg add <name>", CliForegroundColor.Cyan)} to install additional commands`
13294
14667
  );
14668
+ this.failProcess(processEntry, context2);
13295
14669
  context2.process.exit(-1, {
13296
14670
  silent: true
13297
14671
  });
@@ -13310,6 +14684,7 @@ var CliCommandExecutor = class {
13310
14684
  continue;
13311
14685
  }
13312
14686
  if (await handler.handle(args, processor, commandToProcess, context2)) {
14687
+ this.completeProcess(processEntry, context2, 0);
13313
14688
  process.end();
13314
14689
  return;
13315
14690
  }
@@ -13378,26 +14753,18 @@ var CliCommandExecutor = class {
13378
14753
  if (!process.outputCalled && capturingWriter.hasOutput()) {
13379
14754
  process.data = capturingWriter.getCapturedData();
13380
14755
  }
13381
- if (processEntry) {
13382
- try {
13383
- const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
13384
- procRegistry.complete(processEntry.pid, context2.process.exitCode ?? 0);
13385
- } catch {
13386
- }
13387
- }
14756
+ this.completeProcess(processEntry, context2, context2.process.exitCode ?? 0);
13388
14757
  if (!process.exited) {
13389
14758
  process.end();
13390
14759
  }
13391
14760
  } catch (e) {
13392
14761
  context2.spinner?.hide();
13393
- if (processEntry) {
13394
- try {
13395
- const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
13396
- procRegistry.fail(processEntry.pid);
13397
- } catch {
13398
- }
13399
- }
13400
14762
  if (e instanceof ProcessExitedError) {
14763
+ if (e.code === 0) {
14764
+ this.completeProcess(processEntry, context2, 0);
14765
+ } else {
14766
+ this.failProcess(processEntry, context2);
14767
+ }
13401
14768
  cancellable?.cancel();
13402
14769
  context2.abort?.();
13403
14770
  if (e.code !== 0) {
@@ -13410,6 +14777,7 @@ var CliCommandExecutor = class {
13410
14777
  );
13411
14778
  }
13412
14779
  } else {
14780
+ this.failProcess(processEntry, context2);
13413
14781
  context2.writer.writeError(`Error executing command: ${e}`);
13414
14782
  context2.process.exit(-1);
13415
14783
  }
@@ -13417,6 +14785,22 @@ var CliCommandExecutor = class {
13417
14785
  abortSub.unsubscribe();
13418
14786
  }
13419
14787
  }
14788
+ completeProcess(processEntry, context2, exitCode) {
14789
+ if (!processEntry) return;
14790
+ try {
14791
+ const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
14792
+ procRegistry.complete(processEntry.pid, exitCode);
14793
+ } catch {
14794
+ }
14795
+ }
14796
+ failProcess(processEntry, context2) {
14797
+ if (!processEntry) return;
14798
+ try {
14799
+ const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
14800
+ procRegistry.fail(processEntry.pid);
14801
+ } catch {
14802
+ }
14803
+ }
13420
14804
  async writeStderrToFile(filePath, context2) {
13421
14805
  const stderr = this.lastCapturingWriter?.getCapturedStderr();
13422
14806
  if (!stderr) return;
@@ -14497,6 +15881,167 @@ var CliStateStoreManager = class {
14497
15881
  }
14498
15882
  };
14499
15883
 
15884
+ // src/lib/editor/syntax/registry.ts
15885
+ var SyntaxHighlighterRegistry = class {
15886
+ constructor() {
15887
+ this.highlighters = /* @__PURE__ */ new Map();
15888
+ this.extensionMap = /* @__PURE__ */ new Map();
15889
+ }
15890
+ register(highlighter) {
15891
+ this.highlighters.set(highlighter.id, highlighter);
15892
+ for (const ext of highlighter.extensions) {
15893
+ this.extensionMap.set(ext, highlighter.id);
15894
+ }
15895
+ }
15896
+ unregister(id) {
15897
+ const h = this.highlighters.get(id);
15898
+ if (h) {
15899
+ for (const ext of h.extensions) {
15900
+ this.extensionMap.delete(ext);
15901
+ }
15902
+ this.highlighters.delete(id);
15903
+ }
15904
+ }
15905
+ getByExtension(ext) {
15906
+ const id = this.extensionMap.get(ext);
15907
+ return id ? this.highlighters.get(id) ?? null : null;
15908
+ }
15909
+ getRegisteredLanguages() {
15910
+ return Array.from(this.highlighters.keys());
15911
+ }
15912
+ };
15913
+
15914
+ // src/lib/editor/syntax/highlighters/json-highlighter.ts
15915
+ var JsonHighlighter = class {
15916
+ constructor() {
15917
+ this.id = "json";
15918
+ this.extensions = [".json", ".jsonc"];
15919
+ this.initialState = 0;
15920
+ }
15921
+ getRules(state) {
15922
+ if (state === 1) {
15923
+ return [
15924
+ { pattern: /.*?\*\//, tokenType: "comment" },
15925
+ { pattern: /.*/, tokenType: "comment" }
15926
+ ];
15927
+ }
15928
+ return [
15929
+ { pattern: /\/\/.*$/, tokenType: "comment" },
15930
+ { pattern: /\/\*.*?\*\//, tokenType: "comment" },
15931
+ { pattern: /\/\*.*$/, tokenType: "comment" },
15932
+ { pattern: /"(?:[^"\\]|\\.)*"/, tokenType: "string" },
15933
+ { pattern: /-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/, tokenType: "number" },
15934
+ { pattern: /\b(?:true|false|null)\b/, tokenType: "keyword" },
15935
+ { pattern: /[{}\[\]:,]/, tokenType: "punctuation" }
15936
+ ];
15937
+ }
15938
+ getNextLineState(line, currentState) {
15939
+ if (currentState === 1) {
15940
+ return line.includes("*/") ? 0 : 1;
15941
+ }
15942
+ const stripped = line.replace(/"(?:[^"\\]|\\.)*"/g, "").replace(/\/\/.*$/, "");
15943
+ const openIdx = stripped.lastIndexOf("/*");
15944
+ if (openIdx === -1) return 0;
15945
+ const closeIdx = stripped.indexOf("*/", openIdx);
15946
+ return closeIdx === -1 ? 1 : 0;
15947
+ }
15948
+ };
15949
+
15950
+ // src/lib/editor/syntax/highlighters/html-highlighter.ts
15951
+ var HtmlHighlighter = class {
15952
+ constructor() {
15953
+ this.id = "html";
15954
+ this.extensions = [".html", ".htm", ".xml", ".svg"];
15955
+ this.initialState = 0;
15956
+ }
15957
+ getRules(state) {
15958
+ if (state === 1) {
15959
+ return [
15960
+ { pattern: /.*?-->/, tokenType: "comment" },
15961
+ { pattern: /.*/, tokenType: "comment" }
15962
+ ];
15963
+ }
15964
+ return [
15965
+ { pattern: /<!--.*?-->/, tokenType: "comment" },
15966
+ { pattern: /<!--.*$/, tokenType: "comment" },
15967
+ { pattern: /(<\/?)([\w-]+)/, tokenType: "tag", captureGroup: 2 },
15968
+ { pattern: /"[^"]*"/, tokenType: "value" },
15969
+ { pattern: /'[^']*'/, tokenType: "value" },
15970
+ { pattern: /([\w-]+)(\s*=)/, tokenType: "attribute", captureGroup: 1 },
15971
+ { pattern: /\/?>/, tokenType: "punctuation" },
15972
+ { pattern: /<\/?/, tokenType: "punctuation" },
15973
+ { pattern: /=/, tokenType: "punctuation" }
15974
+ ];
15975
+ }
15976
+ getNextLineState(line, currentState) {
15977
+ if (currentState === 1) {
15978
+ return line.includes("-->") ? 0 : 1;
15979
+ }
15980
+ const openIdx = line.lastIndexOf("<!--");
15981
+ if (openIdx === -1) return 0;
15982
+ const closeIdx = line.indexOf("-->", openIdx);
15983
+ return closeIdx === -1 ? 1 : 0;
15984
+ }
15985
+ };
15986
+
15987
+ // src/lib/editor/syntax/highlighters/markdown-highlighter.ts
15988
+ var MarkdownHighlighter = class {
15989
+ constructor() {
15990
+ this.id = "markdown";
15991
+ this.extensions = [".md", ".mdx", ".markdown"];
15992
+ this.initialState = 0;
15993
+ }
15994
+ getRules(state) {
15995
+ if (state === 1) {
15996
+ return [
15997
+ { pattern: /^```/, tokenType: "string" },
15998
+ { pattern: /.*/, tokenType: "code" }
15999
+ ];
16000
+ }
16001
+ return [
16002
+ { pattern: /^#{1,6}\s+.*$/, tokenType: "heading" },
16003
+ { pattern: /^```/, tokenType: "string" },
16004
+ { pattern: /`[^`]+`/, tokenType: "code" },
16005
+ { pattern: /\[[^\]]+\]\([^)]+\)/, tokenType: "link" },
16006
+ { pattern: /\*\*[^*]+\*\*/, tokenType: "bold" },
16007
+ { pattern: /__[^_]+__/, tokenType: "bold" },
16008
+ { pattern: /\*[^*]+\*/, tokenType: "italic" },
16009
+ { pattern: /_[^_]+_/, tokenType: "italic" },
16010
+ { pattern: /^[-*>]\s/, tokenType: "punctuation" }
16011
+ ];
16012
+ }
16013
+ getNextLineState(line, currentState) {
16014
+ const trimmed = line.trimStart();
16015
+ if (trimmed.startsWith("```")) {
16016
+ return currentState === 0 ? 1 : 0;
16017
+ }
16018
+ return currentState;
16019
+ }
16020
+ };
16021
+
16022
+ // src/lib/editor/syntax/highlighters/yaml-highlighter.ts
16023
+ var YamlHighlighter = class {
16024
+ constructor() {
16025
+ this.id = "yaml";
16026
+ this.extensions = [".yaml", ".yml"];
16027
+ this.initialState = 0;
16028
+ }
16029
+ getRules() {
16030
+ return [
16031
+ { pattern: /#.*$/, tokenType: "comment" },
16032
+ { pattern: /"(?:[^"\\]|\\.)*"/, tokenType: "string" },
16033
+ { pattern: /'[^']*'/, tokenType: "string" },
16034
+ { pattern: /\b(?:true|false|null|~)\b/, tokenType: "keyword" },
16035
+ { pattern: /-?\d+(?:\.\d+)?/, tokenType: "number" },
16036
+ { pattern: /([\w][\w.\-]*)(\s*:)/, tokenType: "tag", captureGroup: 1 },
16037
+ { pattern: /[-:\[\]{}]/, tokenType: "punctuation" }
16038
+ ];
16039
+ }
16040
+ getNextLineState() {
16041
+ return 0;
16042
+ }
16043
+ };
16044
+
14500
16045
  // src/lib/services/cli-translation-service.ts
14501
16046
  var CliTranslationService = class {
14502
16047
  constructor() {
@@ -15475,6 +17020,12 @@ var CliEngine = class {
15475
17020
  }
15476
17021
  ]);
15477
17022
  }
17023
+ const syntaxRegistry = new SyntaxHighlighterRegistry();
17024
+ syntaxRegistry.register(new JsonHighlighter());
17025
+ syntaxRegistry.register(new HtmlHighlighter());
17026
+ syntaxRegistry.register(new MarkdownHighlighter());
17027
+ syntaxRegistry.register(new YamlHighlighter());
17028
+ services.set([{ provide: "syntax-highlighter-registry", useValue: syntaxRegistry }]);
15478
17029
  if (!pendingTokens.has(ICliEnvironment_TOKEN)) {
15479
17030
  services.set([
15480
17031
  {
@@ -15507,6 +17058,7 @@ var CliEngine = class {
15507
17058
  { ...this.options ?? {}, terminalOptions }
15508
17059
  );
15509
17060
  this.executionContext.initializeTerminalListeners();
17061
+ this.executionContext.backgroundServices.setProcessRegistry(processRegistry);
15510
17062
  services.set([{
15511
17063
  provide: CliBackgroundServiceRegistry_TOKEN,
15512
17064
  useValue: this.executionContext.backgroundServices
@@ -16063,7 +17615,9 @@ function createQueuedReader(queue) {
16063
17615
  readSelect: async () => next() ?? "",
16064
17616
  readSelectInline: async () => next() ?? "",
16065
17617
  readMultiSelect: async () => next() ?? [],
16066
- readNumber: async () => next() ?? 0
17618
+ readNumber: async () => next() ?? 0,
17619
+ readDate: async () => next() ?? null,
17620
+ readFile: async () => next() ?? null
16067
17621
  };
16068
17622
  }
16069
17623
  function createNoopBackgroundServices() {
@@ -16094,6 +17648,6 @@ function createInMemoryKvStore() {
16094
17648
  };
16095
17649
  }
16096
17650
 
16097
- export { API_VERSION, CLi_Name_Art, CapturingTerminalWriter, CliAliasCommandProcessor, CliArgsParser, CliBackgroundServiceRegistry_TOKEN, CliBase64CommandProcessor, CliBoot, CliCalCommandProcessor, CliCaptureCommandProcessor, CliClearCommandProcessor, CliClipboard, CliClipboardCommandProcessor, CliColorCommandProcessor, CliCommandExecutionContext, CliCommandExecutor, CliCommandHistory, CliCommandHistory_TOKEN, CliCommandProcessorRegistry, CliConfigureCommandProcessor, CliConvertCommandProcessor, CliDebugCommandProcessor, CliDefaultPingServerService, CliDiffCommandProcessor, CliDragDropService, CliEchoCommandProcessor, CliEngine, CliEnvCommandProcessor, CliEnvironment, CliEvalCommandProcessor, CliExecutionContext, CliExecutionProcess, CliExportCommandProcessor, CliFeedbackCommandProcessor, CliFontSizeCommandProcessor, CliHashCommandProcessor, CliHelpCommandProcessor, CliHexCommandProcessor, CliHistoryCommandProcessor, CliHotKeysCommandProcessor, CliInputReader, CliJsonCommandProcessor, CliJwtCommandProcessor, CliKeyValueStore, CliKillCommandProcessor, CliLogger, CliLoremCommandProcessor, CliModuleRegistry_TOKEN, CliNanoCommandProcessor, CliOpenCommandProcessor, CliPackageManagerService, CliPackageManagerService_TOKEN, CliPackagesCommandProcessor, CliPingCommandProcessor, CliProcessRegistry, CliProcessRegistry_TOKEN, CliProcessorsRegistry_TOKEN, CliPsCommandProcessor, CliRandomCommandProcessor, CliScreenCommandProcessor, CliSeqCommandProcessor, CliServerCommandProcessor, CliServerConnection, CliServerManager, CliServerManager_TOKEN, CliServerMultiProxyProcessor, CliServerProxyProcessor, CliServiceContainer, CliServicesCommandProcessor, CliSleepCommandProcessor, CliSnippetCommandProcessor, CliSshCommandProcessor, CliStateStore, CliStateStoreManager, CliStateStoreManager_TOKEN, CliTerminalProgressBar, CliTerminalSpinner, CliTerminalTextAnimator, CliTerminalWriter, CliTestHarness, CliThemeCommandProcessor, CliTimeCommandProcessor, CliTimestampCommandProcessor, CliUnAliasCommandProcessor, CliUnameCommandProcessor, CliUnsetCommandProcessor, CliUptimeCommandProcessor, CliUrlCommandProcessor, CliVersionCommandProcessor, CliYesCommandProcessor, CommandParser, ICliEnvironment_TOKEN, ICliPingServerService_TOKEN, JsFallbackAccelerator, LIBRARY_VERSION, OverlayAddon, ProcessExitedError, ScriptLoaderService, builtinProcessors, computeDiff, contextGlobalParameter, createServerModule, executeOnServer, formatDiff, getAccelerator, getCliNameArt, getGreetingBasedOnTime, groupBy, helpGlobalParameter, hotkeysInfo, initWasmAccelerator, isWasmAccelerated, miscProcessors, openLink, reconcileArgs, renderServerResponse, systemProcessors, themeInfos, themes, versionGlobalParameter, welcomeModule };
17651
+ export { API_VERSION, CLi_Name_Art, CapturingTerminalWriter, CliAliasCommandProcessor, CliArgsParser, CliBackgroundServiceRegistry_TOKEN, CliBoot, CliCalCommandProcessor, CliCaptureCommandProcessor, CliClearCommandProcessor, CliClipboard, CliClipboardCommandProcessor, CliColorCommandProcessor, CliCommandExecutionContext, CliCommandExecutor, CliCommandHistory, CliCommandHistory_TOKEN, CliCommandProcessorRegistry, CliConfigureCommandProcessor, CliConvertCommandProcessor, CliDebugCommandProcessor, CliDefaultPingServerService, CliDiffCommandProcessor, CliDragDropService, CliEchoCommandProcessor, CliEngine, CliEnvCommandProcessor, CliEnvironment, CliEvalCommandProcessor, CliExecutionContext, CliExecutionProcess, CliExportCommandProcessor, CliFeedbackCommandProcessor, CliFontSizeCommandProcessor, CliHelpCommandProcessor, CliHistoryCommandProcessor, CliHotKeysCommandProcessor, CliHtopCommandProcessor, CliInputReader, CliJsonCommandProcessor, CliKeyValueStore, CliKillCommandProcessor, CliLogger, CliLoremCommandProcessor, CliModuleRegistry_TOKEN, CliNanoCommandProcessor, CliOpenCommandProcessor, CliPackageManagerService, CliPackageManagerService_TOKEN, CliPackagesCommandProcessor, CliPingCommandProcessor, CliProcessRegistry, CliProcessRegistry_TOKEN, CliProcessorsRegistry_TOKEN, CliPsCommandProcessor, CliRandomCommandProcessor, CliScreenCommandProcessor, CliSeqCommandProcessor, CliServerCommandProcessor, CliServerConnection, CliServerManager, CliServerManager_TOKEN, CliServerMultiProxyProcessor, CliServerProxyProcessor, CliServiceContainer, CliServicesCommandProcessor, CliSleepCommandProcessor, CliSnippetCommandProcessor, CliSshCommandProcessor, CliStateStore, CliStateStoreManager, CliStateStoreManager_TOKEN, CliTerminalProgressBar, CliTerminalSpinner, CliTerminalTextAnimator, CliTerminalWriter, CliTestHarness, CliThemeCommandProcessor, CliTimeCommandProcessor, CliTimestampCommandProcessor, CliUnAliasCommandProcessor, CliUnameCommandProcessor, CliUnsetCommandProcessor, CliUptimeCommandProcessor, CliVersionCommandProcessor, CliYesCommandProcessor, CommandParser, ICliEnvironment_TOKEN, ICliPingServerService_TOKEN, JsFallbackAccelerator, LIBRARY_VERSION, OverlayAddon, ProcessExitedError, ScriptLoaderService, builtinProcessors, computeDiff, contextGlobalParameter, createServerModule, executeOnServer, formatDiff, getAccelerator, getCliNameArt, getGreetingBasedOnTime, groupBy, helpGlobalParameter, hotkeysInfo, initWasmAccelerator, isWasmAccelerated, miscProcessors, openLink, reconcileArgs, renderServerResponse, systemProcessors, themeInfos, themes, versionGlobalParameter, welcomeModule };
16098
17652
  //# sourceMappingURL=public-api.mjs.map
16099
17653
  //# sourceMappingURL=public-api.mjs.map