@kyubiware/commit-mint 0.8.0 → 0.8.2

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/dist/cli.mjs CHANGED
@@ -10,12 +10,16 @@ import { execa } from "execa";
10
10
  import picomatch from "picomatch";
11
11
  import ini from "ini";
12
12
  import { createHash } from "node:crypto";
13
- import * as p from "@clack/prompts";
14
- import { intro, isCancel, log, outro, spinner } from "@clack/prompts";
13
+ import * as p$1 from "@clack/prompts";
14
+ import { S_BAR, S_BAR_END, S_RADIO_ACTIVE, S_RADIO_INACTIVE, intro, isCancel, limitOptions, log, outro, spinner, symbol } from "@clack/prompts";
15
15
  import { spawn } from "node:child_process";
16
16
  import semver from "semver";
17
+ import { styleText } from "node:util";
18
+ import { stdin, stdout } from "node:process";
19
+ import E from "node:readline";
17
20
  //#region \0rolldown/runtime.js
18
21
  var __defProp = Object.defineProperty;
22
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
19
23
  var __exportAll = (all, no_symbols) => {
20
24
  let target = {};
21
25
  for (var name in all) __defProp(target, name, {
@@ -29,7 +33,7 @@ var __exportAll = (all, no_symbols) => {
29
33
  //#region package.json
30
34
  var package_default = {
31
35
  name: "@kyubiware/commit-mint",
32
- version: "0.8.0",
36
+ version: "0.8.2",
33
37
  description: "🌿 AI-powered git commit tool — auto-group changed files, generate messages, run pre-commit checks",
34
38
  type: "module",
35
39
  bin: { "cmint": "./dist/cli.mjs" },
@@ -673,7 +677,10 @@ async function runCommand(command, timeout, repoRoot) {
673
677
  timeout,
674
678
  all: true,
675
679
  preferLocal: true,
676
- ...repoRoot ? { localDir: repoRoot } : {}
680
+ ...repoRoot ? {
681
+ localDir: repoRoot,
682
+ cwd: repoRoot
683
+ } : {}
677
684
  });
678
685
  const ok = !result.failed;
679
686
  debug("runCommand: %s — ok=%s", tool, ok);
@@ -924,8 +931,10 @@ var git_exports = /* @__PURE__ */ __exportAll({
924
931
  getHead: () => getHead,
925
932
  getRepoRoot: () => getRepoRoot,
926
933
  getStagedDiff: () => getStagedDiff,
934
+ getStagedFiles: () => getStagedFiles,
927
935
  getStatusShort: () => getStatusShort,
928
936
  resetStaging: () => resetStaging,
937
+ resolveToRepoRoot: () => resolveToRepoRoot,
929
938
  stageAll: () => stageAll,
930
939
  stageFiles: () => stageFiles
931
940
  });
@@ -1043,6 +1052,44 @@ async function getChangedFiles() {
1043
1052
  debug("getChangedFiles:", files.length, "files");
1044
1053
  return files;
1045
1054
  }
1055
+ /**
1056
+ * Return staged file paths relative to the repository root, excluding deletions.
1057
+ *
1058
+ * `git status --short` reports paths relative to the current working directory,
1059
+ * but `.cmintrc` globs are written from the repo root (matching lint-staged
1060
+ * conventions). Use this helper whenever staged paths need to match repo-root
1061
+ * globs. `--diff-filter=d` excludes staged deletions so check commands don't
1062
+ * receive paths whose content no longer exists.
1063
+ */
1064
+ async function getStagedFiles() {
1065
+ const { stdout } = await execa("git", [
1066
+ "diff",
1067
+ "--cached",
1068
+ "--name-only",
1069
+ "--diff-filter=d"
1070
+ ]);
1071
+ const files = stdout.split("\n").map((line) => line.trim()).filter(Boolean);
1072
+ debug("getStagedFiles:", files.length, "files");
1073
+ return files;
1074
+ }
1075
+ /**
1076
+ * Convert cwd-relative file paths to repo-root-relative paths.
1077
+ *
1078
+ * Uses `git rev-parse --show-prefix` to discover the prefix of the current
1079
+ * working directory relative to the repo root (e.g. `"extension/"` when cwd
1080
+ * is `<repo>/extension`, or `""` when at the repo root). Useful when a caller
1081
+ * has cwd-relative paths from `getChangedFiles()` but needs to match them
1082
+ * against repo-root-relative `.cmintrc` globs (e.g. the auto-group flow,
1083
+ * which runs checks BEFORE files are staged — so `getStagedFiles()` can't be
1084
+ * used because the index doesn't yet contain those paths).
1085
+ */
1086
+ async function resolveToRepoRoot(cwdRelativePaths) {
1087
+ if (cwdRelativePaths.length === 0) return [];
1088
+ const { stdout } = await execa("git", ["rev-parse", "--show-prefix"]);
1089
+ const prefix = stdout.trim();
1090
+ if (!prefix) return [...cwdRelativePaths];
1091
+ return cwdRelativePaths.map((p) => `${prefix}${p}`);
1092
+ }
1046
1093
  async function stageFiles(paths) {
1047
1094
  debug("stageFiles:", paths);
1048
1095
  await execa("git", ["add", ...paths]);
@@ -1586,6 +1633,97 @@ async function loadCachedCommit(repoPath) {
1586
1633
  }
1587
1634
  }
1588
1635
  //#endregion
1636
+ //#region src/services/auto-accept.ts
1637
+ /** Parse a stored `auto-accept` INI value into a boolean.
1638
+ * Accepts true variants ("true", "1", "yes" — case-insensitive).
1639
+ * Handles boolean values from ini.parse (which converts unquoted
1640
+ * `true`/`false` to actual booleans).
1641
+ * Everything else (including undefined) returns false. */
1642
+ function parseAutoAcceptValue(value) {
1643
+ if (typeof value === "boolean") return value;
1644
+ if (typeof value !== "string" || !value) return false;
1645
+ return [
1646
+ "true",
1647
+ "1",
1648
+ "yes"
1649
+ ].includes(value.toLowerCase());
1650
+ }
1651
+ /** Read the persisted auto-accept preference from `~/.commit-mint`. */
1652
+ async function getAutoAccept() {
1653
+ const raw = (await readConfig())["auto-accept"];
1654
+ const enabled = parseAutoAcceptValue(raw);
1655
+ debug("getAutoAccept: raw=%s enabled=%s", raw, enabled);
1656
+ return enabled;
1657
+ }
1658
+ /** Persist the auto-accept preference to `~/.commit-mint`. */
1659
+ async function setAutoAccept(enabled) {
1660
+ const value = enabled ? "true" : "false";
1661
+ debug("setAutoAccept: %s", value);
1662
+ await writeConfig({ "auto-accept": value });
1663
+ }
1664
+ //#endregion
1665
+ //#region src/ui/grouping.ts
1666
+ async function showGroupingConfirmation(groups, excluded) {
1667
+ debug("showGroupingConfirmation: %d groups, %d excluded", groups.length, excluded.length);
1668
+ const lines = [];
1669
+ for (const group of groups) {
1670
+ lines.push(bold(group.name));
1671
+ lines.push(` ${dim(group.description)}`);
1672
+ lines.push(` ${green(String(group.files.length))} file${group.files.length !== 1 ? "s" : ""}`);
1673
+ for (const file of group.files) lines.push(` ${dim("•")} ${file}`);
1674
+ lines.push("");
1675
+ }
1676
+ if (excluded.length > 0) {
1677
+ lines.push(dim(`Excluded: ${excluded.length} file${excluded.length !== 1 ? "s" : ""}`));
1678
+ for (const file of excluded) lines.push(` ${dim("•")} ${dim(file)}`);
1679
+ }
1680
+ p$1.note(lines.join("\n"), "Proposed commit groups");
1681
+ const choice = await p$1.select({
1682
+ message: "Proceed with these groupings?",
1683
+ options: [{
1684
+ label: "Yes, commit all groups",
1685
+ value: "yes"
1686
+ }, {
1687
+ label: "No, cancel",
1688
+ value: "no"
1689
+ }]
1690
+ });
1691
+ if (p$1.isCancel(choice) || choice === "no") {
1692
+ debug("showGroupingConfirmation: user cancelled");
1693
+ return false;
1694
+ }
1695
+ debug("showGroupingConfirmation: user confirmed");
1696
+ return true;
1697
+ }
1698
+ function showGroupProgress(current, total, groupName) {
1699
+ p$1.log.info(`Commit group ${current} of ${total}: ${cyan(`"${groupName}"`)}`);
1700
+ }
1701
+ const statusLabel = (status) => {
1702
+ switch (status) {
1703
+ case "M": return yellow("M");
1704
+ case "A": return green("A");
1705
+ case "D": return red("D");
1706
+ case "?":
1707
+ case "??": return cyan("?");
1708
+ default: return dim(status);
1709
+ }
1710
+ };
1711
+ /** Display combined view: files with status indicators grouped by commit group */
1712
+ function showGroupedFiles(groups, changedFiles) {
1713
+ const statusMap = new Map(changedFiles.map((f) => [f.path, f.status]));
1714
+ const lines = [];
1715
+ for (let i = 0; i < groups.length; i++) {
1716
+ const group = groups[i];
1717
+ lines.push(`${bold(group.name)} ${dim("—")} ${group.files.length} file${group.files.length !== 1 ? "s" : ""}`);
1718
+ for (const file of group.files) {
1719
+ const status = statusMap.get(file) ?? "M";
1720
+ lines.push(` ${statusLabel(status)} ${file}`);
1721
+ }
1722
+ if (i < groups.length - 1) lines.push("");
1723
+ }
1724
+ p$1.note(lines.join("\n"), "Commit groups");
1725
+ }
1726
+ //#endregion
1589
1727
  //#region src/services/clipboard.ts
1590
1728
  /** Milliseconds to wait after stdin closes for quick exit failures. */
1591
1729
  const GRACE_PERIOD_MS = 150;
@@ -1664,6 +1802,183 @@ function tryCopy(cmd, args, content) {
1664
1802
  });
1665
1803
  }
1666
1804
  //#endregion
1805
+ //#region src/ui/recovery-menu.ts
1806
+ async function showRecoveryMenu(errors, onRetry, onSkipHooks, onRestage, message, rawStderr) {
1807
+ debug("showRecoveryMenu: %d errors", errors.length);
1808
+ let clipboardCopied = false;
1809
+ let showNote = true;
1810
+ while (true) {
1811
+ if (showNote) {
1812
+ p$1.note(errors.map((e) => ` ${red("•")} [${e.tool}] ${e.message}`).join("\n"), red(bold("Pre-commit hook failed")));
1813
+ showNote = false;
1814
+ }
1815
+ const choice = await p$1.select({
1816
+ message: "What do you want to do?",
1817
+ options: [
1818
+ {
1819
+ label: clipboardCopied ? `${green("✓")} Copy error report to clipboard` : "Copy error report to clipboard",
1820
+ value: "clipboard",
1821
+ hint: clipboardCopied ? "Copied!" : "Paste into another terminal for an AI agent"
1822
+ },
1823
+ {
1824
+ label: "View full error output",
1825
+ value: "view",
1826
+ hint: "Show the raw stderr from hooks"
1827
+ },
1828
+ {
1829
+ label: "Skip hooks and commit (--no-verify)",
1830
+ value: "skip",
1831
+ hint: "Commit anyway, fix later"
1832
+ },
1833
+ {
1834
+ label: "Re-stage files and retry",
1835
+ value: "restage",
1836
+ hint: "Pick up fixes from another terminal"
1837
+ },
1838
+ {
1839
+ label: "Edit commit message",
1840
+ value: "edit",
1841
+ hint: "Modify the message before retrying"
1842
+ },
1843
+ {
1844
+ label: "Cancel",
1845
+ value: "cancel"
1846
+ }
1847
+ ]
1848
+ });
1849
+ if (p$1.isCancel(choice)) {
1850
+ debug("showRecoveryMenu: user cancelled");
1851
+ p$1.outro(yellow("Cancelled. Message cached for --retry."));
1852
+ return "cancelled";
1853
+ }
1854
+ debug("showRecoveryMenu: user chose %s", choice);
1855
+ switch (choice) {
1856
+ case "clipboard":
1857
+ if (await copyToClipboard(rawStderr)) {
1858
+ clipboardCopied = true;
1859
+ p$1.log.step(green("Copied to clipboard."));
1860
+ } else p$1.log.warn(red("No clipboard tool found. Install xclip, wl-copy, or xsel."));
1861
+ continue;
1862
+ case "view":
1863
+ p$1.note(rawStderr.trim() || "(no raw output)", "Full error output");
1864
+ showNote = true;
1865
+ continue;
1866
+ case "skip":
1867
+ p$1.log.info(yellow("Committing with --no-verify..."));
1868
+ if (await onSkipHooks(message)) {
1869
+ p$1.outro(green("Committed (hooks skipped)."));
1870
+ return "committed";
1871
+ } else {
1872
+ p$1.outro(red("Commit failed even with --no-verify."));
1873
+ return "failed";
1874
+ }
1875
+ case "restage":
1876
+ p$1.log.info(cyan("Re-staging and retrying..."));
1877
+ if (await onRestage()) {
1878
+ p$1.outro(green("Committed successfully."));
1879
+ return "committed";
1880
+ }
1881
+ showNote = true;
1882
+ continue;
1883
+ case "edit": {
1884
+ const edited = await p$1.text({
1885
+ message: "Edit commit message:",
1886
+ initialValue: message,
1887
+ validate: (v) => v?.trim() ? void 0 : "Message cannot be empty"
1888
+ });
1889
+ if (p$1.isCancel(edited)) {
1890
+ p$1.outro(yellow("Cancelled. Message cached for --retry."));
1891
+ return "cancelled";
1892
+ }
1893
+ if (await onRetry()) {
1894
+ p$1.outro(green("Committed successfully."));
1895
+ return "committed";
1896
+ } else {
1897
+ p$1.outro(red("Commit failed again."));
1898
+ return "failed";
1899
+ }
1900
+ }
1901
+ case "cancel":
1902
+ p$1.outro(dim("Message cached for --retry."));
1903
+ return "cancelled";
1904
+ }
1905
+ }
1906
+ }
1907
+ //#endregion
1908
+ //#region src/ui/review-message.ts
1909
+ async function handleEdit(message) {
1910
+ const { text } = await import("@clack/prompts");
1911
+ const edited = await text({
1912
+ message: "Edit commit message:",
1913
+ initialValue: message,
1914
+ validate: (v) => v?.trim() ? void 0 : "Message cannot be empty"
1915
+ });
1916
+ if (isCancel(edited)) {
1917
+ debug("User cancelled edit, returning to review menu");
1918
+ return null;
1919
+ }
1920
+ const newMessage = String(edited).trim();
1921
+ debug("Edited message:", newMessage);
1922
+ return newMessage;
1923
+ }
1924
+ async function handleRegenerate(regenerate) {
1925
+ const { log, text } = await import("@clack/prompts");
1926
+ const hint = await text({
1927
+ message: "Describe what this commit is about to guide regeneration:",
1928
+ validate: (v) => v?.trim() ? void 0 : "Hint cannot be empty"
1929
+ });
1930
+ if (isCancel(hint)) {
1931
+ debug("User cancelled hint entry, returning to review menu");
1932
+ return null;
1933
+ }
1934
+ const hintValue = String(hint).trim();
1935
+ debug("Regenerating with hint:", hintValue);
1936
+ try {
1937
+ const newMessage = await regenerate(hintValue);
1938
+ debug("Regenerated message:", newMessage);
1939
+ return newMessage;
1940
+ } catch (err) {
1941
+ const errMsg = err instanceof Error ? err.message : String(err);
1942
+ debug("Regeneration failed:", errMsg);
1943
+ log.warn(red(`Regeneration failed: ${errMsg}`));
1944
+ return null;
1945
+ }
1946
+ }
1947
+ async function reviewCommitMessage(message, options) {
1948
+ const { select } = await import("@clack/prompts");
1949
+ while (true) {
1950
+ const reviewOptions = [{
1951
+ label: "Use as-is",
1952
+ value: "use"
1953
+ }, {
1954
+ label: "Edit",
1955
+ value: "edit"
1956
+ }];
1957
+ if (options?.regenerate) reviewOptions.push({
1958
+ label: "Regenerate with hint",
1959
+ value: "regenerate"
1960
+ });
1961
+ reviewOptions.push({
1962
+ label: "Cancel",
1963
+ value: "cancel"
1964
+ });
1965
+ const review = await select({
1966
+ message: `Review commit message:\n\n ${bold(message)}\n`,
1967
+ options: reviewOptions
1968
+ });
1969
+ if (isCancel(review) || review === "cancel") {
1970
+ debug("User cancelled at review step");
1971
+ return null;
1972
+ }
1973
+ if (review === "use") {
1974
+ debug("User accepted message");
1975
+ return message;
1976
+ }
1977
+ if (review === "edit") message = await handleEdit(message) ?? message;
1978
+ if (review === "regenerate" && options?.regenerate) message = await handleRegenerate(options.regenerate) ?? message;
1979
+ }
1980
+ }
1981
+ //#endregion
1667
1982
  //#region src/ui/check-failure-menu.ts
1668
1983
  const MAX_TSC_DIAGNOSTICS = 3;
1669
1984
  const MAX_ESLINT_DIAGNOSTICS = 3;
@@ -1793,9 +2108,9 @@ function truncate(message, maxLength) {
1793
2108
  async function showCheckFailureMenu(errors, rawStderr, onRetry) {
1794
2109
  debug("showCheckFailureMenu: %d errors", errors.length);
1795
2110
  let clipboardCopied = false;
1796
- p.note(formatCheckFailureSummary(errors), red("Pre-commit check failed"));
2111
+ p$1.note(formatCheckFailureSummary(errors), red("Pre-commit check failed"));
1797
2112
  while (true) {
1798
- const choice = await p.select({
2113
+ const choice = await p$1.select({
1799
2114
  message: "What do you want to do?",
1800
2115
  options: [
1801
2116
  {
@@ -1821,292 +2136,100 @@ async function showCheckFailureMenu(errors, rawStderr, onRetry) {
1821
2136
  value: "cancel"
1822
2137
  }
1823
2138
  ]
1824
- });
1825
- if (p.isCancel(choice)) {
1826
- debug("showCheckFailureMenu: user cancelled");
1827
- return "cancelled";
1828
- }
1829
- debug("showCheckFailureMenu: user chose %s", choice);
1830
- switch (choice) {
1831
- case "copy":
1832
- if (await copyToClipboard(rawStderr)) {
1833
- clipboardCopied = true;
1834
- p.log.step(green("Copied to clipboard."));
1835
- } else p.log.warn(red("No clipboard tool found. Install xclip, wl-copy, or xsel."));
1836
- continue;
1837
- case "view":
1838
- p.note(rawStderr.trim() || "(no raw output)", "Full error output");
1839
- continue;
1840
- case "retry":
1841
- if (onRetry) return "retried";
1842
- return "retried";
1843
- case "skip":
1844
- p.log.info("Skipping checks and proceeding with commit...");
1845
- return "skipped";
1846
- case "cancel":
1847
- p.outro(dim("Cancelled."));
1848
- return "cancelled";
1849
- }
1850
- }
1851
- }
1852
- //#endregion
1853
- //#region src/ui/check-summary.ts
1854
- /**
1855
- * Stop a check spinner with a per-tool summary of the check results.
1856
- *
1857
- * - On success: stops with "All checks passed" and prints a `✓ tool` line
1858
- * for each result.
1859
- * - On failure: stops with "N checks failed" (pluralized). Raw error output
1860
- * is intentionally NOT printed here — callers handle failure display
1861
- * (menu, raw print, etc.).
1862
- */
1863
- function stopCheckSpinner(spinner, results) {
1864
- if (results.ok) {
1865
- spinner.stop("All checks passed");
1866
- if (results.results.length > 0) log.info(results.results.map((r) => ` ${green("✓")} ${r.tool}`).join("\n"));
1867
- } else {
1868
- const failed = results.results.filter((r) => !r.ok);
1869
- spinner.stop(`${failed.length} check${failed.length !== 1 ? "s" : ""} failed`);
1870
- }
1871
- }
1872
- //#endregion
1873
- //#region src/ui/grouping.ts
1874
- async function showGroupingConfirmation(groups, excluded) {
1875
- debug("showGroupingConfirmation: %d groups, %d excluded", groups.length, excluded.length);
1876
- const lines = [];
1877
- for (const group of groups) {
1878
- lines.push(bold(group.name));
1879
- lines.push(` ${dim(group.description)}`);
1880
- lines.push(` ${green(String(group.files.length))} file${group.files.length !== 1 ? "s" : ""}`);
1881
- for (const file of group.files) lines.push(` ${dim("•")} ${file}`);
1882
- lines.push("");
1883
- }
1884
- if (excluded.length > 0) {
1885
- lines.push(dim(`Excluded: ${excluded.length} file${excluded.length !== 1 ? "s" : ""}`));
1886
- for (const file of excluded) lines.push(` ${dim("•")} ${dim(file)}`);
1887
- }
1888
- p.note(lines.join("\n"), "Proposed commit groups");
1889
- const choice = await p.select({
1890
- message: "Proceed with these groupings?",
1891
- options: [{
1892
- label: "Yes, commit all groups",
1893
- value: "yes"
1894
- }, {
1895
- label: "No, cancel",
1896
- value: "no"
1897
- }]
1898
- });
1899
- if (p.isCancel(choice) || choice === "no") {
1900
- debug("showGroupingConfirmation: user cancelled");
1901
- return false;
1902
- }
1903
- debug("showGroupingConfirmation: user confirmed");
1904
- return true;
1905
- }
1906
- function showGroupProgress(current, total, groupName) {
1907
- p.log.info(`Commit group ${current} of ${total}: ${cyan(`"${groupName}"`)}`);
1908
- }
1909
- const statusLabel = (status) => {
1910
- switch (status) {
1911
- case "M": return yellow("M");
1912
- case "A": return green("A");
1913
- case "D": return red("D");
1914
- case "?":
1915
- case "??": return cyan("?");
1916
- default: return dim(status);
1917
- }
1918
- };
1919
- /** Display combined view: files with status indicators grouped by commit group */
1920
- function showGroupedFiles(groups, changedFiles) {
1921
- const statusMap = new Map(changedFiles.map((f) => [f.path, f.status]));
1922
- const lines = [];
1923
- for (let i = 0; i < groups.length; i++) {
1924
- const group = groups[i];
1925
- lines.push(`${bold(group.name)} ${dim("—")} ${group.files.length} file${group.files.length !== 1 ? "s" : ""}`);
1926
- for (const file of group.files) {
1927
- const status = statusMap.get(file) ?? "M";
1928
- lines.push(` ${statusLabel(status)} ${file}`);
1929
- }
1930
- if (i < groups.length - 1) lines.push("");
1931
- }
1932
- p.note(lines.join("\n"), "Commit groups");
1933
- }
1934
- //#endregion
1935
- //#region src/ui/recovery-menu.ts
1936
- async function showRecoveryMenu(errors, onRetry, onSkipHooks, onRestage, message, rawStderr) {
1937
- debug("showRecoveryMenu: %d errors", errors.length);
1938
- let clipboardCopied = false;
1939
- let showNote = true;
1940
- while (true) {
1941
- if (showNote) {
1942
- p.note(errors.map((e) => ` ${red("•")} [${e.tool}] ${e.message}`).join("\n"), red(bold("Pre-commit hook failed")));
1943
- showNote = false;
1944
- }
1945
- const choice = await p.select({
1946
- message: "What do you want to do?",
1947
- options: [
1948
- {
1949
- label: clipboardCopied ? `${green("✓")} Copy error report to clipboard` : "Copy error report to clipboard",
1950
- value: "clipboard",
1951
- hint: clipboardCopied ? "Copied!" : "Paste into another terminal for an AI agent"
1952
- },
1953
- {
1954
- label: "View full error output",
1955
- value: "view",
1956
- hint: "Show the raw stderr from hooks"
1957
- },
1958
- {
1959
- label: "Skip hooks and commit (--no-verify)",
1960
- value: "skip",
1961
- hint: "Commit anyway, fix later"
1962
- },
1963
- {
1964
- label: "Re-stage files and retry",
1965
- value: "restage",
1966
- hint: "Pick up fixes from another terminal"
1967
- },
1968
- {
1969
- label: "Edit commit message",
1970
- value: "edit",
1971
- hint: "Modify the message before retrying"
1972
- },
1973
- {
1974
- label: "Cancel",
1975
- value: "cancel"
1976
- }
1977
- ]
1978
- });
1979
- if (p.isCancel(choice)) {
1980
- debug("showRecoveryMenu: user cancelled");
1981
- p.outro(yellow("Cancelled. Message cached for --retry."));
1982
- return "cancelled";
1983
- }
1984
- debug("showRecoveryMenu: user chose %s", choice);
1985
- switch (choice) {
1986
- case "clipboard":
1987
- if (await copyToClipboard(rawStderr)) {
1988
- clipboardCopied = true;
1989
- p.log.step(green("Copied to clipboard."));
1990
- } else p.log.warn(red("No clipboard tool found. Install xclip, wl-copy, or xsel."));
1991
- continue;
1992
- case "view":
1993
- p.note(rawStderr.trim() || "(no raw output)", "Full error output");
1994
- showNote = true;
1995
- continue;
1996
- case "skip":
1997
- p.log.info(yellow("Committing with --no-verify..."));
1998
- if (await onSkipHooks(message)) {
1999
- p.outro(green("Committed (hooks skipped)."));
2000
- return "committed";
2001
- } else {
2002
- p.outro(red("Commit failed even with --no-verify."));
2003
- return "failed";
2004
- }
2005
- case "restage":
2006
- p.log.info(cyan("Re-staging and retrying..."));
2007
- if (await onRestage()) {
2008
- p.outro(green("Committed successfully."));
2009
- return "committed";
2010
- }
2011
- showNote = true;
2012
- continue;
2013
- case "edit": {
2014
- const edited = await p.text({
2015
- message: "Edit commit message:",
2016
- initialValue: message,
2017
- validate: (v) => v?.trim() ? void 0 : "Message cannot be empty"
2018
- });
2019
- if (p.isCancel(edited)) {
2020
- p.outro(yellow("Cancelled. Message cached for --retry."));
2021
- return "cancelled";
2022
- }
2023
- if (await onRetry()) {
2024
- p.outro(green("Committed successfully."));
2025
- return "committed";
2026
- } else {
2027
- p.outro(red("Commit failed again."));
2028
- return "failed";
2029
- }
2030
- }
2139
+ });
2140
+ if (p$1.isCancel(choice)) {
2141
+ debug("showCheckFailureMenu: user cancelled");
2142
+ return "cancelled";
2143
+ }
2144
+ debug("showCheckFailureMenu: user chose %s", choice);
2145
+ switch (choice) {
2146
+ case "copy":
2147
+ if (await copyToClipboard(rawStderr)) {
2148
+ clipboardCopied = true;
2149
+ p$1.log.step(green("Copied to clipboard."));
2150
+ } else p$1.log.warn(red("No clipboard tool found. Install xclip, wl-copy, or xsel."));
2151
+ continue;
2152
+ case "view":
2153
+ p$1.note(rawStderr.trim() || "(no raw output)", "Full error output");
2154
+ continue;
2155
+ case "retry":
2156
+ if (onRetry) return "retried";
2157
+ return "retried";
2158
+ case "skip":
2159
+ p$1.log.info("Skipping checks and proceeding with commit...");
2160
+ return "skipped";
2031
2161
  case "cancel":
2032
- p.outro(dim("Message cached for --retry."));
2162
+ p$1.outro(dim("Cancelled."));
2033
2163
  return "cancelled";
2034
2164
  }
2035
2165
  }
2036
2166
  }
2037
2167
  //#endregion
2038
- //#region src/ui/review-message.ts
2039
- async function handleEdit(message) {
2040
- const { text } = await import("@clack/prompts");
2041
- const edited = await text({
2042
- message: "Edit commit message:",
2043
- initialValue: message,
2044
- validate: (v) => v?.trim() ? void 0 : "Message cannot be empty"
2045
- });
2046
- if (isCancel(edited)) {
2047
- debug("User cancelled edit, returning to review menu");
2048
- return null;
2049
- }
2050
- const newMessage = String(edited).trim();
2051
- debug("Edited message:", newMessage);
2052
- return newMessage;
2053
- }
2054
- async function handleRegenerate(regenerate) {
2055
- const { log, text } = await import("@clack/prompts");
2056
- const hint = await text({
2057
- message: "Describe what this commit is about to guide regeneration:",
2058
- validate: (v) => v?.trim() ? void 0 : "Hint cannot be empty"
2059
- });
2060
- if (isCancel(hint)) {
2061
- debug("User cancelled hint entry, returning to review menu");
2062
- return null;
2063
- }
2064
- const hintValue = String(hint).trim();
2065
- debug("Regenerating with hint:", hintValue);
2066
- try {
2067
- const newMessage = await regenerate(hintValue);
2068
- debug("Regenerated message:", newMessage);
2069
- return newMessage;
2070
- } catch (err) {
2071
- const errMsg = err instanceof Error ? err.message : String(err);
2072
- debug("Regeneration failed:", errMsg);
2073
- log.warn(red(`Regeneration failed: ${errMsg}`));
2074
- return null;
2168
+ //#region src/ui/check-summary.ts
2169
+ /**
2170
+ * Stop a check spinner with a per-tool summary of the check results.
2171
+ *
2172
+ * - On success: stops with "All checks passed" and prints a `✓ tool` line
2173
+ * for each result.
2174
+ * - On failure: stops with "N checks failed" (pluralized). Raw error output
2175
+ * is intentionally NOT printed here — callers handle failure display
2176
+ * (menu, raw print, etc.).
2177
+ */
2178
+ function stopCheckSpinner(spinner, results) {
2179
+ if (results.ok) {
2180
+ spinner.stop("All checks passed");
2181
+ if (results.results.length > 0) log.info(results.results.map((r) => ` ${green("✓")} ${r.tool}`).join("\n"));
2182
+ } else {
2183
+ const failed = results.results.filter((r) => !r.ok);
2184
+ spinner.stop(`${failed.length} check${failed.length !== 1 ? "s" : ""} failed`);
2075
2185
  }
2076
2186
  }
2077
- async function reviewCommitMessage(message, options) {
2078
- const { select } = await import("@clack/prompts");
2079
- while (true) {
2080
- const reviewOptions = [{
2081
- label: "Use as-is",
2082
- value: "use"
2083
- }, {
2084
- label: "Edit",
2085
- value: "edit"
2086
- }];
2087
- if (options?.regenerate) reviewOptions.push({
2088
- label: "Regenerate with hint",
2089
- value: "regenerate"
2090
- });
2091
- reviewOptions.push({
2092
- label: "Cancel",
2093
- value: "cancel"
2094
- });
2095
- const review = await select({
2096
- message: `Review commit message:\n\n ${bold(message)}\n`,
2097
- options: reviewOptions
2187
+ //#endregion
2188
+ //#region src/commands/check-phase.ts
2189
+ /**
2190
+ * Run user-defined pre-commit checks with an interactive failure menu.
2191
+ *
2192
+ * Single entry point for the check-execution pipeline shared by `runPreCommitChecks`
2193
+ * (post-staging, normal commit flow) and `runAutoGroupFlow` (pre-staging, auto-group
2194
+ * flow). Encapsulates: detectConfig guard → spinner → runAllChecks → retry loop with
2195
+ * `showCheckFailureMenu`.
2196
+ *
2197
+ * Caller responsibilities:
2198
+ * - Skip when `noCheck` is set (caller's policy).
2199
+ * - Skip when there are no files to check (caller has the file list context).
2200
+ * - Derive `files` in **repo-root-relative** form so they match `.cmintrc` globs.
2201
+ * Post-staging callers should use `getStagedFiles()`; pre-staging callers should
2202
+ * use `resolveToRepoRoot()` since the index doesn't yet contain those paths.
2203
+ *
2204
+ * Returns the outcome so the caller can decide how to handle cancellation
2205
+ * (`process.exit(1)` in the commit flow, `return "cancelled"` in auto-group).
2206
+ */
2207
+ async function runCheckPhaseInteractive(repoRoot, files, timeout, onRetry) {
2208
+ if (!await detectConfig(repoRoot)) return "passed";
2209
+ debug("Running user checks on %d files...", files.length);
2210
+ const ck = spinner();
2211
+ ck.start("Running checks...");
2212
+ let checkResults = await runAllChecks(repoRoot, files, timeout);
2213
+ stopCheckSpinner(ck, checkResults);
2214
+ debug("Check results: ok=%s, count=%d", checkResults.ok, checkResults.results.length);
2215
+ while (!checkResults.ok) {
2216
+ const rawOutput = checkResults.results.filter((r) => !r.ok).map((r) => `[${r.tool}]\n${r.stdout}\n${r.stderr}`.trim()).join("\n\n");
2217
+ const menuResult = await showCheckFailureMenu(parseCheckErrors(rawOutput), rawOutput, async () => {
2218
+ return (await runAllChecks(repoRoot, files, timeout)).ok;
2098
2219
  });
2099
- if (isCancel(review) || review === "cancel") {
2100
- debug("User cancelled at review step");
2101
- return null;
2102
- }
2103
- if (review === "use") {
2104
- debug("User accepted message");
2105
- return message;
2220
+ if (menuResult === "cancelled") return "cancelled";
2221
+ if (menuResult === "retried") {
2222
+ debug("Re-running checks after retry...");
2223
+ if (onRetry) await onRetry();
2224
+ ck.start("Running checks...");
2225
+ checkResults = await runAllChecks(repoRoot, files, timeout);
2226
+ stopCheckSpinner(ck, checkResults);
2227
+ debug("Retry check results: ok=%s, count=%d", checkResults.ok, checkResults.results.length);
2228
+ continue;
2106
2229
  }
2107
- if (review === "edit") message = await handleEdit(message) ?? message;
2108
- if (review === "regenerate" && options?.regenerate) message = await handleRegenerate(options.regenerate) ?? message;
2230
+ break;
2109
2231
  }
2232
+ return checkResults.ok ? "passed" : "skipped";
2110
2233
  }
2111
2234
  //#endregion
2112
2235
  //#region src/commands/auto-group.ts
@@ -2137,33 +2260,7 @@ async function runAutoGroupFlow(changedFiles, flags) {
2137
2260
  }
2138
2261
  if (!flags.noCheck) {
2139
2262
  const { getRepoRoot } = await Promise.resolve().then(() => git_exports);
2140
- const repoRoot = await getRepoRoot();
2141
- const allFiles = included.filter((f) => f.status !== "D").map((f) => f.path);
2142
- if (await detectConfig(repoRoot)) {
2143
- debug("Running user checks on %d files...", allFiles.length);
2144
- const ck = spinner();
2145
- ck.start("Running checks...");
2146
- let checkResults = await runAllChecks(repoRoot, allFiles, 6e4);
2147
- debug("Check results: ok=%s, count=%d", checkResults.ok, checkResults.results.length);
2148
- while (!checkResults.ok) {
2149
- const failed = checkResults.results.filter((r) => !r.ok);
2150
- ck.stop(`${failed.length} check(s) failed`);
2151
- const rawOutput = failed.map((r) => `[${r.tool}]\n${r.stdout}\n${r.stderr}`.trim()).join("\n\n");
2152
- const menuResult = await showCheckFailureMenu(parseCheckErrors(rawOutput), rawOutput, async () => {
2153
- return (await runAllChecks(repoRoot, allFiles, 6e4)).ok;
2154
- });
2155
- if (menuResult === "cancelled") return "cancelled";
2156
- if (menuResult === "retried") {
2157
- debug("Re-running checks after retry...");
2158
- ck.start("Running checks...");
2159
- checkResults = await runAllChecks(repoRoot, allFiles, 6e4);
2160
- debug("Retry check results: ok=%s, count=%d", checkResults.ok, checkResults.results.length);
2161
- continue;
2162
- }
2163
- break;
2164
- }
2165
- if (checkResults.ok) stopCheckSpinner(ck, checkResults);
2166
- }
2263
+ if (await runCheckPhaseInteractive(await getRepoRoot(), await resolveToRepoRoot(included.filter((f) => f.status !== "D").map((f) => f.path)), 6e4) === "cancelled") return "cancelled";
2167
2264
  }
2168
2265
  const config = await readConfig();
2169
2266
  const resolvedProvider = config.provider ?? "groq";
@@ -2192,7 +2289,9 @@ async function runAutoGroupFlow(changedFiles, flags) {
2192
2289
  const validatedGroups = validateGroups((await generateGroups(included, await getProviderApiKey(provider), getModelForProvider(config, provider, PROVIDER_CONFIGS[provider].defaultModel), config.timeout ? parseInt(config.timeout, 10) : void 0, provider, config.proxy)).groups, included);
2193
2290
  s.stop("Files analyzed");
2194
2291
  showGroupedFiles(validatedGroups, included);
2195
- if (flags.auto) debug("Auto mode: skipping grouping confirmation");
2292
+ const autoAccept = await getAutoAccept();
2293
+ const skipPrompts = flags.auto || autoAccept;
2294
+ if (skipPrompts) debug("Skipping grouping confirmation (auto=%s autoAccept=%s)", flags.auto, autoAccept);
2196
2295
  else if (!await showGroupingConfirmation(validatedGroups, excluded)) {
2197
2296
  outro(dim("Cancelled."));
2198
2297
  return "cancelled";
@@ -2218,7 +2317,7 @@ async function runAutoGroupFlow(changedFiles, flags) {
2218
2317
  }
2219
2318
  s.stop("Message generated");
2220
2319
  log.info(dim(message));
2221
- if (flags.auto) debug("Auto mode: accepting generated message");
2320
+ if (skipPrompts) debug("Accepting generated message (auto=%s autoAccept=%s)", flags.auto, autoAccept);
2222
2321
  else {
2223
2322
  const reviewed = await reviewCommitMessage(message, { regenerate: async (hint) => {
2224
2323
  const combinedHint = flags.hint ? `${flags.hint}\n${hint}` : hint;
@@ -2401,7 +2500,7 @@ async function agentCommand(flags) {
2401
2500
  const repoRoot = await getRepoRoot();
2402
2501
  if (await detectConfig(repoRoot)) {
2403
2502
  debug("Running user checks on changed files...");
2404
- const checkResults = await runAllChecks(repoRoot, changedFiles.filter((f) => f.status !== "D").map((f) => f.path), 6e4);
2503
+ const checkResults = await runAllChecks(repoRoot, await resolveToRepoRoot(changedFiles.filter((f) => f.status !== "D").map((f) => f.path)), 6e4);
2405
2504
  if (!checkResults.ok) {
2406
2505
  const errorMessages = checkResults.results.filter((r) => !r.ok).map((r) => `[${r.tool}]\n${r.stdout}\n${r.stderr}`.trim()).filter(Boolean);
2407
2506
  const parsed = parseCheckErrors(errorMessages.join("\n\n"));
@@ -3003,30 +3102,30 @@ function formatDetection(tools) {
3003
3102
  async function setupCmintrcCommand(cwd = process.cwd()) {
3004
3103
  debug("setupCmintrcCommand: starting in %s", cwd);
3005
3104
  const tools = await detectTools(cwd);
3006
- p.log.info(`Detected tools in ${bold(cwd)}:`);
3007
- p.log.message(formatDetection(tools));
3008
- if (!Object.values(tools).some(Boolean)) p.log.warn("No recognized tools found. Writing an empty config to fill in manually.");
3009
- else if (tools.biome && tools.eslint) p.log.warn(yellow("Both biome and eslint detected — using biome (remove this line to switch)."));
3105
+ p$1.log.info(`Detected tools in ${bold(cwd)}:`);
3106
+ p$1.log.message(formatDetection(tools));
3107
+ if (!Object.values(tools).some(Boolean)) p$1.log.warn("No recognized tools found. Writing an empty config to fill in manually.");
3108
+ else if (tools.biome && tools.eslint) p$1.log.warn(yellow("Both biome and eslint detected — using biome (remove this line to switch)."));
3010
3109
  const fileName = pickFileName(tools);
3011
3110
  const filePath = join(cwd, fileName);
3012
3111
  if (await exists(filePath)) {
3013
- const overwrite = await p.confirm({ message: `${fileName} already exists. Overwrite?` });
3014
- if (p.isCancel(overwrite) || !overwrite) {
3015
- p.log.info(dim("Cancelled — existing file left untouched."));
3112
+ const overwrite = await p$1.confirm({ message: `${fileName} already exists. Overwrite?` });
3113
+ if (p$1.isCancel(overwrite) || !overwrite) {
3114
+ p$1.log.info(dim("Cancelled — existing file left untouched."));
3016
3115
  return;
3017
3116
  }
3018
3117
  }
3019
3118
  const content = buildCmintrcContent(tools);
3020
- p.log.info(dim(`\nPreview of ${fileName}:`));
3021
- p.log.message(dim(content));
3022
- const confirm = await p.confirm({ message: `Write ${fileName}?` });
3023
- if (p.isCancel(confirm) || !confirm) {
3024
- p.log.info(dim("Cancelled."));
3119
+ p$1.log.info(dim(`\nPreview of ${fileName}:`));
3120
+ p$1.log.message(dim(content));
3121
+ const confirm = await p$1.confirm({ message: `Write ${fileName}?` });
3122
+ if (p$1.isCancel(confirm) || !confirm) {
3123
+ p$1.log.info(dim("Cancelled."));
3025
3124
  return;
3026
3125
  }
3027
3126
  await writeFile(filePath, content, "utf-8");
3028
3127
  debug("setupCmintrcCommand: wrote %s", filePath);
3029
- p.log.success(green(`Wrote ${fileName}`));
3128
+ p$1.log.success(green(`Wrote ${fileName}`));
3030
3129
  }
3031
3130
  /** Project-local marker file that suppresses the preflight prompt forever. */
3032
3131
  const SKIP_SETUP_MARKER = ".cmint-skip-setup";
@@ -3067,7 +3166,7 @@ async function runPreflightSetupPrompt(cwd) {
3067
3166
  debug("preflight: project not auto-configurable, skipping prompt");
3068
3167
  return;
3069
3168
  }
3070
- const choice = await p.select({
3169
+ const choice = await p$1.select({
3071
3170
  message: "No .cmintrc found. Run setup to create one from detected tools?",
3072
3171
  options: [
3073
3172
  {
@@ -3084,23 +3183,698 @@ async function runPreflightSetupPrompt(cwd) {
3084
3183
  }
3085
3184
  ]
3086
3185
  });
3087
- if (p.isCancel(choice)) {
3186
+ if (p$1.isCancel(choice)) {
3088
3187
  debug("preflight: user cancelled prompt");
3089
3188
  return;
3090
3189
  }
3091
3190
  if (choice === "never") {
3092
3191
  await writeSkipSetupMarker(cwd);
3093
- p.log.info(dim(`Won't ask again. Delete ${SKIP_SETUP_MARKER} to re-enable.`));
3192
+ p$1.log.info(dim(`Won't ask again. Delete ${SKIP_SETUP_MARKER} to re-enable.`));
3094
3193
  return;
3095
3194
  }
3096
3195
  if (choice === "no") {
3097
- p.log.info(dim("Skipping .cmintrc setup."));
3196
+ p$1.log.info(dim("Skipping .cmintrc setup."));
3098
3197
  return;
3099
3198
  }
3100
3199
  debug("preflight: user chose yes, running setup");
3101
3200
  await setupCmintrcCommand(cwd);
3102
3201
  }
3103
3202
  //#endregion
3203
+ //#region node_modules/fast-string-truncated-width/dist/utils.js
3204
+ const getCodePointsLength = (() => {
3205
+ const SURROGATE_PAIR_RE = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
3206
+ return (input) => {
3207
+ let surrogatePairsNr = 0;
3208
+ SURROGATE_PAIR_RE.lastIndex = 0;
3209
+ while (SURROGATE_PAIR_RE.test(input)) surrogatePairsNr += 1;
3210
+ return input.length - surrogatePairsNr;
3211
+ };
3212
+ })();
3213
+ const isFullWidth = (x) => {
3214
+ return x === 12288 || x >= 65281 && x <= 65376 || x >= 65504 && x <= 65510;
3215
+ };
3216
+ const isWideNotCJKTNotEmoji = (x) => {
3217
+ return x === 8987 || x === 9001 || x >= 12272 && x <= 12287 || x >= 12289 && x <= 12350 || x >= 12441 && x <= 12543 || x >= 12549 && x <= 12591 || x >= 12593 && x <= 12686 || x >= 12688 && x <= 12771 || x >= 12783 && x <= 12830 || x >= 12832 && x <= 12871 || x >= 12880 && x <= 19903 || x >= 65040 && x <= 65049 || x >= 65072 && x <= 65106 || x >= 65108 && x <= 65126 || x >= 65128 && x <= 65131 || x >= 127488 && x <= 127490 || x >= 127504 && x <= 127547 || x >= 127552 && x <= 127560 || x >= 131072 && x <= 196605 || x >= 196608 && x <= 262141;
3218
+ };
3219
+ //#endregion
3220
+ //#region node_modules/fast-string-truncated-width/dist/index.js
3221
+ const ANSI_RE = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]|\u001b\]8;[^;]*;.*?(?:\u0007|\u001b\u005c)/y;
3222
+ const CONTROL_RE = /[\x00-\x08\x0A-\x1F\x7F-\x9F]{1,1000}/y;
3223
+ const CJKT_WIDE_RE = /(?:(?![\uFF61-\uFF9F\uFF00-\uFFEF])[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Tangut}]){1,1000}/uy;
3224
+ const TAB_RE = /\t{1,1000}/y;
3225
+ const EMOJI_RE = /[\u{1F1E6}-\u{1F1FF}]{2}|\u{1F3F4}[\u{E0061}-\u{E007A}]{2}[\u{E0030}-\u{E0039}\u{E0061}-\u{E007A}]{1,3}\u{E007F}|(?:\p{Emoji}\uFE0F\u20E3?|\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation})(?:\u200D(?:\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F\u20E3?))*/uy;
3226
+ const LATIN_RE = /(?:[\x20-\x7E\xA0-\xFF](?!\uFE0F)){1,1000}/y;
3227
+ const MODIFIER_RE = /\p{M}+/gu;
3228
+ const NO_TRUNCATION$1 = {
3229
+ limit: Infinity,
3230
+ ellipsis: ""
3231
+ };
3232
+ const getStringTruncatedWidth = (input, truncationOptions = {}, widthOptions = {}) => {
3233
+ const LIMIT = truncationOptions.limit ?? Infinity;
3234
+ const ELLIPSIS = truncationOptions.ellipsis ?? "";
3235
+ const ELLIPSIS_WIDTH = truncationOptions?.ellipsisWidth ?? (ELLIPSIS ? getStringTruncatedWidth(ELLIPSIS, NO_TRUNCATION$1, widthOptions).width : 0);
3236
+ const ANSI_WIDTH = 0;
3237
+ const CONTROL_WIDTH = widthOptions.controlWidth ?? 0;
3238
+ const TAB_WIDTH = widthOptions.tabWidth ?? 8;
3239
+ const EMOJI_WIDTH = widthOptions.emojiWidth ?? 2;
3240
+ const FULL_WIDTH_WIDTH = 2;
3241
+ const REGULAR_WIDTH = widthOptions.regularWidth ?? 1;
3242
+ const WIDE_WIDTH = widthOptions.wideWidth ?? FULL_WIDTH_WIDTH;
3243
+ const PARSE_BLOCKS = [
3244
+ [LATIN_RE, REGULAR_WIDTH],
3245
+ [ANSI_RE, ANSI_WIDTH],
3246
+ [CONTROL_RE, CONTROL_WIDTH],
3247
+ [TAB_RE, TAB_WIDTH],
3248
+ [EMOJI_RE, EMOJI_WIDTH],
3249
+ [CJKT_WIDE_RE, WIDE_WIDTH]
3250
+ ];
3251
+ let indexPrev = 0;
3252
+ let index = 0;
3253
+ let length = input.length;
3254
+ let lengthExtra = 0;
3255
+ let truncationEnabled = false;
3256
+ let truncationIndex = length;
3257
+ let truncationLimit = Math.max(0, LIMIT - ELLIPSIS_WIDTH);
3258
+ let unmatchedStart = 0;
3259
+ let unmatchedEnd = 0;
3260
+ let width = 0;
3261
+ let widthExtra = 0;
3262
+ outer: while (true) {
3263
+ if (unmatchedEnd > unmatchedStart || index >= length && index > indexPrev) {
3264
+ const unmatched = input.slice(unmatchedStart, unmatchedEnd) || input.slice(indexPrev, index);
3265
+ lengthExtra = 0;
3266
+ for (const char of unmatched.replaceAll(MODIFIER_RE, "")) {
3267
+ const codePoint = char.codePointAt(0) || 0;
3268
+ if (isFullWidth(codePoint)) widthExtra = FULL_WIDTH_WIDTH;
3269
+ else if (isWideNotCJKTNotEmoji(codePoint)) widthExtra = WIDE_WIDTH;
3270
+ else widthExtra = REGULAR_WIDTH;
3271
+ if (width + widthExtra > truncationLimit) truncationIndex = Math.min(truncationIndex, Math.max(unmatchedStart, indexPrev) + lengthExtra);
3272
+ if (width + widthExtra > LIMIT) {
3273
+ truncationEnabled = true;
3274
+ break outer;
3275
+ }
3276
+ lengthExtra += char.length;
3277
+ width += widthExtra;
3278
+ }
3279
+ unmatchedStart = unmatchedEnd = 0;
3280
+ }
3281
+ if (index >= length) break outer;
3282
+ for (let i = 0, l = PARSE_BLOCKS.length; i < l; i++) {
3283
+ const [BLOCK_RE, BLOCK_WIDTH] = PARSE_BLOCKS[i];
3284
+ BLOCK_RE.lastIndex = index;
3285
+ if (BLOCK_RE.test(input)) {
3286
+ lengthExtra = BLOCK_RE === CJKT_WIDE_RE ? getCodePointsLength(input.slice(index, BLOCK_RE.lastIndex)) : BLOCK_RE === EMOJI_RE ? 1 : BLOCK_RE.lastIndex - index;
3287
+ widthExtra = lengthExtra * BLOCK_WIDTH;
3288
+ if (width + widthExtra > truncationLimit) truncationIndex = Math.min(truncationIndex, index + Math.floor((truncationLimit - width) / BLOCK_WIDTH));
3289
+ if (width + widthExtra > LIMIT) {
3290
+ truncationEnabled = true;
3291
+ break outer;
3292
+ }
3293
+ width += widthExtra;
3294
+ unmatchedStart = indexPrev;
3295
+ unmatchedEnd = index;
3296
+ index = indexPrev = BLOCK_RE.lastIndex;
3297
+ continue outer;
3298
+ }
3299
+ }
3300
+ index += 1;
3301
+ }
3302
+ return {
3303
+ width: truncationEnabled ? truncationLimit : width,
3304
+ index: truncationEnabled ? truncationIndex : length,
3305
+ truncated: truncationEnabled,
3306
+ ellipsed: truncationEnabled && LIMIT >= ELLIPSIS_WIDTH
3307
+ };
3308
+ };
3309
+ //#endregion
3310
+ //#region node_modules/fast-string-width/dist/index.js
3311
+ const NO_TRUNCATION = {
3312
+ limit: Infinity,
3313
+ ellipsis: "",
3314
+ ellipsisWidth: 0
3315
+ };
3316
+ const fastStringWidth = (input, options = {}) => {
3317
+ return getStringTruncatedWidth(input, NO_TRUNCATION, options).width;
3318
+ };
3319
+ //#endregion
3320
+ //#region node_modules/fast-wrap-ansi/lib/main.js
3321
+ const ESC = "\x1B";
3322
+ const CSI = "›";
3323
+ const END_CODE = 39;
3324
+ const ANSI_ESCAPE_BELL = "\x07";
3325
+ const ANSI_CSI = "[";
3326
+ const ANSI_OSC = "]";
3327
+ const ANSI_SGR_TERMINATOR = "m";
3328
+ const ANSI_ESCAPE_LINK = `${ANSI_OSC}8;;`;
3329
+ const GROUP_REGEX = new RegExp(`(?:\\${ANSI_CSI}(?<code>\\d+)m|\\${ANSI_ESCAPE_LINK}(?<uri>.*)${ANSI_ESCAPE_BELL})`, "y");
3330
+ const getClosingCode = (openingCode) => {
3331
+ if (openingCode >= 30 && openingCode <= 37) return 39;
3332
+ if (openingCode >= 90 && openingCode <= 97) return 39;
3333
+ if (openingCode >= 40 && openingCode <= 47) return 49;
3334
+ if (openingCode >= 100 && openingCode <= 107) return 49;
3335
+ if (openingCode === 1 || openingCode === 2) return 22;
3336
+ if (openingCode === 3) return 23;
3337
+ if (openingCode === 4) return 24;
3338
+ if (openingCode === 7) return 27;
3339
+ if (openingCode === 8) return 28;
3340
+ if (openingCode === 9) return 29;
3341
+ if (openingCode === 0) return 0;
3342
+ };
3343
+ const wrapAnsiCode = (code) => `${ESC}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`;
3344
+ const wrapAnsiHyperlink = (url) => `${ESC}${ANSI_ESCAPE_LINK}${url}${ANSI_ESCAPE_BELL}`;
3345
+ const wrapWord = (rows, word, columns) => {
3346
+ const characters = word[Symbol.iterator]();
3347
+ let isInsideEscape = false;
3348
+ let isInsideLinkEscape = false;
3349
+ let lastRow = rows.at(-1);
3350
+ let visible = lastRow === void 0 ? 0 : fastStringWidth(lastRow);
3351
+ let currentCharacter = characters.next();
3352
+ let nextCharacter = characters.next();
3353
+ let rawCharacterIndex = 0;
3354
+ while (!currentCharacter.done) {
3355
+ const character = currentCharacter.value;
3356
+ const characterLength = fastStringWidth(character);
3357
+ if (visible + characterLength <= columns) rows[rows.length - 1] += character;
3358
+ else {
3359
+ rows.push(character);
3360
+ visible = 0;
3361
+ }
3362
+ if (character === ESC || character === CSI) {
3363
+ isInsideEscape = true;
3364
+ isInsideLinkEscape = word.startsWith(ANSI_ESCAPE_LINK, rawCharacterIndex + 1);
3365
+ }
3366
+ if (isInsideEscape) {
3367
+ if (isInsideLinkEscape) {
3368
+ if (character === ANSI_ESCAPE_BELL) {
3369
+ isInsideEscape = false;
3370
+ isInsideLinkEscape = false;
3371
+ }
3372
+ } else if (character === ANSI_SGR_TERMINATOR) isInsideEscape = false;
3373
+ } else {
3374
+ visible += characterLength;
3375
+ if (visible === columns && !nextCharacter.done) {
3376
+ rows.push("");
3377
+ visible = 0;
3378
+ }
3379
+ }
3380
+ currentCharacter = nextCharacter;
3381
+ nextCharacter = characters.next();
3382
+ rawCharacterIndex += character.length;
3383
+ }
3384
+ lastRow = rows.at(-1);
3385
+ if (!visible && lastRow !== void 0 && lastRow.length && rows.length > 1) rows[rows.length - 2] += rows.pop();
3386
+ };
3387
+ const stringVisibleTrimSpacesRight = (string) => {
3388
+ const words = string.split(" ");
3389
+ let last = words.length;
3390
+ while (last) {
3391
+ if (fastStringWidth(words[last - 1])) break;
3392
+ last--;
3393
+ }
3394
+ if (last === words.length) return string;
3395
+ return words.slice(0, last).join(" ") + words.slice(last).join("");
3396
+ };
3397
+ const exec = (string, columns, options = {}) => {
3398
+ if (options.trim !== false && string.trim() === "") return "";
3399
+ let returnValue = "";
3400
+ let escapeCode;
3401
+ let escapeUrl;
3402
+ const words = string.split(" ");
3403
+ let rows = [""];
3404
+ let rowLength = 0;
3405
+ for (let index = 0; index < words.length; index++) {
3406
+ const word = words[index];
3407
+ if (options.trim !== false) {
3408
+ const row = rows.at(-1) ?? "";
3409
+ const trimmed = row.trimStart();
3410
+ if (row.length !== trimmed.length) {
3411
+ rows[rows.length - 1] = trimmed;
3412
+ rowLength = fastStringWidth(trimmed);
3413
+ }
3414
+ }
3415
+ if (index !== 0) {
3416
+ if (rowLength >= columns && (options.wordWrap === false || options.trim === false)) {
3417
+ rows.push("");
3418
+ rowLength = 0;
3419
+ }
3420
+ if (rowLength || options.trim === false) {
3421
+ rows[rows.length - 1] += " ";
3422
+ rowLength++;
3423
+ }
3424
+ }
3425
+ const wordLength = fastStringWidth(word);
3426
+ if (options.hard && wordLength > columns) {
3427
+ const remainingColumns = columns - rowLength;
3428
+ const breaksStartingThisLine = 1 + Math.floor((wordLength - remainingColumns - 1) / columns);
3429
+ if (Math.floor((wordLength - 1) / columns) < breaksStartingThisLine) rows.push("");
3430
+ wrapWord(rows, word, columns);
3431
+ rowLength = fastStringWidth(rows.at(-1) ?? "");
3432
+ continue;
3433
+ }
3434
+ if (rowLength + wordLength > columns && rowLength && wordLength) {
3435
+ if (options.wordWrap === false && rowLength < columns) {
3436
+ wrapWord(rows, word, columns);
3437
+ rowLength = fastStringWidth(rows.at(-1) ?? "");
3438
+ continue;
3439
+ }
3440
+ rows.push("");
3441
+ rowLength = 0;
3442
+ }
3443
+ if (rowLength + wordLength > columns && options.wordWrap === false) {
3444
+ wrapWord(rows, word, columns);
3445
+ rowLength = fastStringWidth(rows.at(-1) ?? "");
3446
+ continue;
3447
+ }
3448
+ rows[rows.length - 1] += word;
3449
+ rowLength += wordLength;
3450
+ }
3451
+ if (options.trim !== false) rows = rows.map((row) => stringVisibleTrimSpacesRight(row));
3452
+ const preString = rows.join("\n");
3453
+ let inSurrogate = false;
3454
+ for (let i = 0; i < preString.length; i++) {
3455
+ const character = preString[i];
3456
+ returnValue += character;
3457
+ if (!inSurrogate) {
3458
+ inSurrogate = character >= "\ud800" && character <= "\udbff";
3459
+ if (inSurrogate) continue;
3460
+ } else inSurrogate = false;
3461
+ if (character === ESC || character === CSI) {
3462
+ GROUP_REGEX.lastIndex = i + 1;
3463
+ const groups = GROUP_REGEX.exec(preString)?.groups;
3464
+ if (groups?.code !== void 0) {
3465
+ const code = Number.parseFloat(groups.code);
3466
+ escapeCode = code === END_CODE ? void 0 : code;
3467
+ } else if (groups?.uri !== void 0) escapeUrl = groups.uri.length === 0 ? void 0 : groups.uri;
3468
+ }
3469
+ if (preString[i + 1] === "\n") {
3470
+ if (escapeUrl) returnValue += wrapAnsiHyperlink("");
3471
+ const closingCode = escapeCode ? getClosingCode(escapeCode) : void 0;
3472
+ if (escapeCode && closingCode) returnValue += wrapAnsiCode(closingCode);
3473
+ } else if (character === "\n") {
3474
+ if (escapeCode && getClosingCode(escapeCode)) returnValue += wrapAnsiCode(escapeCode);
3475
+ if (escapeUrl) returnValue += wrapAnsiHyperlink(escapeUrl);
3476
+ }
3477
+ }
3478
+ return returnValue;
3479
+ };
3480
+ const CRLF_OR_LF = /\r?\n/;
3481
+ function wrapAnsi(string, columns, options) {
3482
+ return String(string).normalize().split(CRLF_OR_LF).map((line) => exec(line, columns, options)).join("\n");
3483
+ }
3484
+ //#endregion
3485
+ //#region node_modules/@clack/core/dist/index.mjs
3486
+ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
3487
+ const ESC = "\x1B";
3488
+ const CSI = `${ESC}[`;
3489
+ const beep = "\x07";
3490
+ const cursor = {
3491
+ to(x, y) {
3492
+ if (!y) return `${CSI}${x + 1}G`;
3493
+ return `${CSI}${y + 1};${x + 1}H`;
3494
+ },
3495
+ move(x, y) {
3496
+ let ret = "";
3497
+ if (x < 0) ret += `${CSI}${-x}D`;
3498
+ else if (x > 0) ret += `${CSI}${x}C`;
3499
+ if (y < 0) ret += `${CSI}${-y}A`;
3500
+ else if (y > 0) ret += `${CSI}${y}B`;
3501
+ return ret;
3502
+ },
3503
+ up: (count = 1) => `${CSI}${count}A`,
3504
+ down: (count = 1) => `${CSI}${count}B`,
3505
+ forward: (count = 1) => `${CSI}${count}C`,
3506
+ backward: (count = 1) => `${CSI}${count}D`,
3507
+ nextLine: (count = 1) => `${CSI}E`.repeat(count),
3508
+ prevLine: (count = 1) => `${CSI}F`.repeat(count),
3509
+ left: `${CSI}G`,
3510
+ hide: `${CSI}?25l`,
3511
+ show: `${CSI}?25h`,
3512
+ save: `${ESC}7`,
3513
+ restore: `${ESC}8`
3514
+ };
3515
+ module.exports = {
3516
+ cursor,
3517
+ scroll: {
3518
+ up: (count = 1) => `${CSI}S`.repeat(count),
3519
+ down: (count = 1) => `${CSI}T`.repeat(count)
3520
+ },
3521
+ erase: {
3522
+ screen: `${CSI}2J`,
3523
+ up: (count = 1) => `${CSI}1J`.repeat(count),
3524
+ down: (count = 1) => `${CSI}J`.repeat(count),
3525
+ line: `${CSI}2K`,
3526
+ lineEnd: `${CSI}K`,
3527
+ lineStart: `${CSI}1K`,
3528
+ lines(count) {
3529
+ let clear = "";
3530
+ for (let i = 0; i < count; i++) clear += this.line + (i < count - 1 ? cursor.up() : "");
3531
+ if (count) clear += cursor.left;
3532
+ return clear;
3533
+ }
3534
+ },
3535
+ beep
3536
+ };
3537
+ })))();
3538
+ function f(r, t, s) {
3539
+ if (!s.some((o) => !o.disabled)) return r;
3540
+ const e = r + t, i = Math.max(s.length - 1, 0), n = e < 0 ? i : e > i ? 0 : e;
3541
+ return s[n].disabled ? f(n, t < 0 ? -1 : 1, s) : n;
3542
+ }
3543
+ const h = {
3544
+ actions: new Set([
3545
+ "up",
3546
+ "down",
3547
+ "left",
3548
+ "right",
3549
+ "space",
3550
+ "enter",
3551
+ "cancel"
3552
+ ]),
3553
+ aliases: new Map([
3554
+ ["k", "up"],
3555
+ ["j", "down"],
3556
+ ["h", "left"],
3557
+ ["l", "right"],
3558
+ ["", "cancel"],
3559
+ ["escape", "cancel"]
3560
+ ]),
3561
+ messages: {
3562
+ cancel: "Canceled",
3563
+ error: "Something went wrong"
3564
+ },
3565
+ withGuide: !0,
3566
+ date: {
3567
+ monthNames: [...[
3568
+ "January",
3569
+ "February",
3570
+ "March",
3571
+ "April",
3572
+ "May",
3573
+ "June",
3574
+ "July",
3575
+ "August",
3576
+ "September",
3577
+ "October",
3578
+ "November",
3579
+ "December"
3580
+ ]],
3581
+ messages: {
3582
+ required: "Please enter a valid date",
3583
+ invalidMonth: "There are only 12 months in a year",
3584
+ invalidDay: (r, t) => `There are only ${r} days in ${t}`,
3585
+ afterMin: (r) => `Date must be on or after ${r.toISOString().slice(0, 10)}`,
3586
+ beforeMax: (r) => `Date must be on or before ${r.toISOString().slice(0, 10)}`
3587
+ }
3588
+ }
3589
+ };
3590
+ function C(r, t) {
3591
+ if (typeof r == "string") return h.aliases.get(r) === t;
3592
+ for (const s of r) if (s !== void 0 && C(s, t)) return !0;
3593
+ return !1;
3594
+ }
3595
+ function z(r, t) {
3596
+ if (r === t) return;
3597
+ const s = r.split(`
3598
+ `), e = t.split(`
3599
+ `), i = Math.max(s.length, e.length), n = [];
3600
+ for (let o = 0; o < i; o++) s[o] !== e[o] && n.push(o);
3601
+ return {
3602
+ lines: n,
3603
+ numLinesBefore: s.length,
3604
+ numLinesAfter: e.length,
3605
+ numLines: i
3606
+ };
3607
+ }
3608
+ globalThis.process.platform.startsWith("win");
3609
+ const k = Symbol("clack:cancel");
3610
+ function q(r) {
3611
+ return r === k;
3612
+ }
3613
+ function w(r, t) {
3614
+ const s = r;
3615
+ s.isTTY && s.setRawMode(t);
3616
+ }
3617
+ const L = (r) => "rows" in r && typeof r.rows == "number" ? r.rows : 20;
3618
+ let m = class {
3619
+ input;
3620
+ output;
3621
+ _abortSignal;
3622
+ rl;
3623
+ opts;
3624
+ _render;
3625
+ _track = !1;
3626
+ _prevFrame = "";
3627
+ _subscribers = /* @__PURE__ */ new Map();
3628
+ _cursor = 0;
3629
+ state = "initial";
3630
+ error = "";
3631
+ value;
3632
+ userInput = "";
3633
+ constructor(t, s = !0) {
3634
+ const { input: e = stdin, output: i = stdout, render: n, signal: o, ...u } = t;
3635
+ this.opts = u, this.onKeypress = this.onKeypress.bind(this), this.close = this.close.bind(this), this.render = this.render.bind(this), this._render = n.bind(this), this._track = s, this._abortSignal = o, this.input = e, this.output = i;
3636
+ }
3637
+ unsubscribe() {
3638
+ this._subscribers.clear();
3639
+ }
3640
+ setSubscriber(t, s) {
3641
+ const e = this._subscribers.get(t) ?? [];
3642
+ e.push(s), this._subscribers.set(t, e);
3643
+ }
3644
+ on(t, s) {
3645
+ this.setSubscriber(t, { cb: s });
3646
+ }
3647
+ once(t, s) {
3648
+ this.setSubscriber(t, {
3649
+ cb: s,
3650
+ once: !0
3651
+ });
3652
+ }
3653
+ emit(t, ...s) {
3654
+ const e = this._subscribers.get(t) ?? [], i = [];
3655
+ for (const n of e) n.cb(...s), n.once && i.push(() => e.splice(e.indexOf(n), 1));
3656
+ for (const n of i) n();
3657
+ }
3658
+ prompt() {
3659
+ return new Promise((t) => {
3660
+ if (this._abortSignal) {
3661
+ if (this._abortSignal.aborted) return this.state = "cancel", this.close(), t(k);
3662
+ this._abortSignal.addEventListener("abort", () => {
3663
+ this.state = "cancel", this.close();
3664
+ }, { once: !0 });
3665
+ }
3666
+ this.rl = E.createInterface({
3667
+ input: this.input,
3668
+ tabSize: 2,
3669
+ prompt: "",
3670
+ escapeCodeTimeout: 50,
3671
+ terminal: !0
3672
+ }), this.rl.prompt(), this.opts.initialUserInput !== void 0 && this._setUserInput(this.opts.initialUserInput, !0), this.input.on("keypress", this.onKeypress), w(this.input, !0), this.output.on("resize", this.render), this.render(), this.once("submit", () => {
3673
+ this.output.write(import_src.cursor.show), this.output.off("resize", this.render), w(this.input, !1), t(this.value);
3674
+ }), this.once("cancel", () => {
3675
+ this.output.write(import_src.cursor.show), this.output.off("resize", this.render), w(this.input, !1), t(k);
3676
+ });
3677
+ });
3678
+ }
3679
+ _isActionKey(t, s) {
3680
+ return t === " ";
3681
+ }
3682
+ _shouldSubmit(t, s) {
3683
+ return !0;
3684
+ }
3685
+ _setValue(t) {
3686
+ this.value = t, this.emit("value", this.value);
3687
+ }
3688
+ _setUserInput(t, s) {
3689
+ this.userInput = t ?? "", this.emit("userInput", this.userInput), s && this._track && this.rl && (this.rl.write(this.userInput), this._cursor = this.rl.cursor);
3690
+ }
3691
+ _clearUserInput() {
3692
+ this.rl?.write(null, {
3693
+ ctrl: !0,
3694
+ name: "u"
3695
+ }), this._setUserInput("");
3696
+ }
3697
+ onKeypress(t, s) {
3698
+ if (this._track && s.name !== "return" && (s.name && this._isActionKey(t, s) && this.rl?.write(null, {
3699
+ ctrl: !0,
3700
+ name: "h"
3701
+ }), this._cursor = this.rl?.cursor ?? 0, this._setUserInput(this.rl?.line)), this.state === "error" && (this.state = "active"), s?.name && (!this._track && h.aliases.has(s.name) && this.emit("cursor", h.aliases.get(s.name)), h.actions.has(s.name) && this.emit("cursor", s.name)), t && (t.toLowerCase() === "y" || t.toLowerCase() === "n") && this.emit("confirm", t.toLowerCase() === "y"), this.emit("key", t?.toLowerCase(), s), s?.name === "return" && this._shouldSubmit(t, s)) {
3702
+ if (this.opts.validate) {
3703
+ const e = this.opts.validate(this.value);
3704
+ e && (this.error = e instanceof Error ? e.message : e, this.state = "error", this.rl?.write(this.userInput));
3705
+ }
3706
+ this.state !== "error" && (this.state = "submit");
3707
+ }
3708
+ C([
3709
+ t,
3710
+ s?.name,
3711
+ s?.sequence
3712
+ ], "cancel") && (this.state = "cancel"), (this.state === "submit" || this.state === "cancel") && this.emit("finalize"), this.render(), (this.state === "submit" || this.state === "cancel") && this.close();
3713
+ }
3714
+ close() {
3715
+ this.input.unpipe(), this.input.removeListener("keypress", this.onKeypress), this.output.write(`
3716
+ `), w(this.input, !1), this.rl?.close(), this.rl = void 0, this.emit(`${this.state}`, this.value), this.unsubscribe();
3717
+ }
3718
+ restoreCursor() {
3719
+ const t = wrapAnsi(this._prevFrame, process.stdout.columns, {
3720
+ hard: !0,
3721
+ trim: !1
3722
+ }).split(`
3723
+ `).length - 1;
3724
+ this.output.write(import_src.cursor.move(-999, t * -1));
3725
+ }
3726
+ render() {
3727
+ const t = wrapAnsi(this._render(this) ?? "", process.stdout.columns, {
3728
+ hard: !0,
3729
+ trim: !1
3730
+ });
3731
+ if (t !== this._prevFrame) {
3732
+ if (this.state === "initial") this.output.write(import_src.cursor.hide);
3733
+ else {
3734
+ const s = z(this._prevFrame, t), e = L(this.output);
3735
+ if (this.restoreCursor(), s) {
3736
+ const i = Math.max(0, s.numLinesAfter - e), n = Math.max(0, s.numLinesBefore - e);
3737
+ let o = s.lines.find((u) => u >= i);
3738
+ if (o === void 0) {
3739
+ this._prevFrame = t;
3740
+ return;
3741
+ }
3742
+ if (s.lines.length === 1) {
3743
+ this.output.write(import_src.cursor.move(0, o - n)), this.output.write(import_src.erase.lines(1));
3744
+ const u = t.split(`
3745
+ `);
3746
+ this.output.write(u[o]), this._prevFrame = t, this.output.write(import_src.cursor.move(0, u.length - o - 1));
3747
+ return;
3748
+ } else if (s.lines.length > 1) {
3749
+ if (i < n) o = i;
3750
+ else {
3751
+ const a = o - n;
3752
+ a > 0 && this.output.write(import_src.cursor.move(0, a));
3753
+ }
3754
+ this.output.write(import_src.erase.down());
3755
+ const u = t.split(`
3756
+ `).slice(o);
3757
+ this.output.write(u.join(`
3758
+ `)), this._prevFrame = t;
3759
+ return;
3760
+ }
3761
+ }
3762
+ this.output.write(import_src.erase.down());
3763
+ }
3764
+ this.output.write(t), this.state === "initial" && (this.state = "active"), this._prevFrame = t;
3765
+ }
3766
+ }
3767
+ };
3768
+ var ut = class extends m {
3769
+ options;
3770
+ cursor = 0;
3771
+ get _selectedValue() {
3772
+ return this.options[this.cursor];
3773
+ }
3774
+ changeValue() {
3775
+ this.value = this._selectedValue.value;
3776
+ }
3777
+ constructor(t) {
3778
+ super(t, !1), this.options = t.options;
3779
+ const s = this.options.findIndex(({ value: i }) => i === t.initialValue), e = s === -1 ? 0 : s;
3780
+ this.cursor = this.options[e].disabled ? f(e, 1, this.options) : e, this.changeValue(), this.on("cursor", (i) => {
3781
+ switch (i) {
3782
+ case "left":
3783
+ case "up":
3784
+ this.cursor = f(this.cursor, -1, this.options);
3785
+ break;
3786
+ case "down":
3787
+ case "right":
3788
+ this.cursor = f(this.cursor, 1, this.options);
3789
+ break;
3790
+ }
3791
+ this.changeValue();
3792
+ });
3793
+ }
3794
+ };
3795
+ //#endregion
3796
+ //#region src/ui/auto-accept-select.ts
3797
+ const ON_LABEL = styleText("green", "ON");
3798
+ const OFF_LABEL = styleText("dim", "OFF");
3799
+ const HOTKEY_HINT = dim("(press `a` to toggle)");
3800
+ function renderStatus(autoAccept) {
3801
+ const label = autoAccept ? ON_LABEL : OFF_LABEL;
3802
+ return `${dim("⚡ Auto-accept:")} ${label} ${HOTKEY_HINT}`;
3803
+ }
3804
+ /** Render a single select option line. */
3805
+ function renderOption(opt, active) {
3806
+ const text = opt.label ?? String(opt.value);
3807
+ if (opt.disabled) return `${dim(S_RADIO_INACTIVE)} ${styleText(["strikethrough", "dim"], text)}`;
3808
+ if (active) {
3809
+ const hint = opt.hint ? ` ${dim(`(${opt.hint})`)}` : "";
3810
+ return `${green(S_RADIO_ACTIVE)} ${text}${hint}`;
3811
+ }
3812
+ return `${dim(S_RADIO_INACTIVE)} ${dim(text)}`;
3813
+ }
3814
+ /**
3815
+ * Select prompt with an inline `a`-hotkey toggle for auto-accept mode.
3816
+ *
3817
+ * Renders a normal select list plus a status line showing the current
3818
+ * auto-accept state. Pressing `a` flips the state in-place (the menu
3819
+ * re-renders) and fires `onToggle` so callers can persist the change.
3820
+ *
3821
+ * Returns `{ value, autoAccept }` on submit, or the clack cancel symbol
3822
+ * on cancel.
3823
+ */
3824
+ async function selectWithAutoAccept(opts) {
3825
+ let autoAccept = opts.initialAutoAccept;
3826
+ const prompt = new ut({
3827
+ options: opts.options,
3828
+ input: opts.input,
3829
+ output: opts.output,
3830
+ render() {
3831
+ const sym = symbol(this.state);
3832
+ const statusLine = renderStatus(autoAccept);
3833
+ const header = `${sym} ${opts.message}\n${dim(S_BAR)} ${statusLine}`;
3834
+ switch (this.state) {
3835
+ case "submit": {
3836
+ const selected = this.options[this.cursor];
3837
+ const text = selected.label ?? String(selected.value);
3838
+ return `${header}\n${dim(S_BAR)} ${dim(text)}`;
3839
+ }
3840
+ case "cancel": {
3841
+ const selected = this.options[this.cursor];
3842
+ const text = selected.label ?? String(selected.value);
3843
+ return `${header}\n${dim(S_BAR)} ${styleText(["strikethrough", "dim"], text)}\n${dim(S_BAR_END)}`;
3844
+ }
3845
+ default: return [
3846
+ header,
3847
+ ...limitOptions({
3848
+ cursor: this.cursor,
3849
+ options: this.options,
3850
+ style: (opt, active) => renderOption(opt, active),
3851
+ maxItems: 7,
3852
+ output: opts.output ?? process.stdout
3853
+ }).map((line) => `${dim(S_BAR)} ${line}`),
3854
+ `${dim(S_BAR_END)} ${dim("↑/↓ navigate • Enter confirm • `a` toggle auto-accept")}`
3855
+ ].join("\n");
3856
+ }
3857
+ }
3858
+ });
3859
+ prompt.on("key", async (char) => {
3860
+ if (char === "a") {
3861
+ autoAccept = !autoAccept;
3862
+ debug("auto-accept toggled to %s", autoAccept);
3863
+ try {
3864
+ await opts.onToggle?.(autoAccept);
3865
+ } catch (err) {
3866
+ debug("onToggle threw (ignored): %s", err instanceof Error ? err.message : String(err));
3867
+ }
3868
+ }
3869
+ });
3870
+ const result = await prompt.prompt();
3871
+ if (q(result)) return result;
3872
+ return {
3873
+ value: result,
3874
+ autoAccept
3875
+ };
3876
+ }
3877
+ //#endregion
3104
3878
  //#region src/ui/staging-menu.ts
3105
3879
  async function showStagingMenu(files, hasChecks) {
3106
3880
  debug("showStagingMenu: %d files", files.length);
@@ -3126,9 +3900,15 @@ async function showStagingMenu(files, hasChecks) {
3126
3900
  if (lines.length > 0) lines.push("");
3127
3901
  lines.push(yellow(bold("Changed:")), ...unstagedFiles.map((f) => ` ${statusLabel(f.status)} ${f.path}`));
3128
3902
  }
3129
- p.note(lines.join("\n"), `${files.length} file${files.length !== 1 ? "s" : ""}`);
3130
- const choice = await p.select({
3903
+ p$1.note(lines.join("\n"), `${files.length} file${files.length !== 1 ? "s" : ""}`);
3904
+ const initialAutoAccept = await getAutoAccept();
3905
+ debug("showStagingMenu: initial auto-accept=%s", initialAutoAccept);
3906
+ const selectResult = await selectWithAutoAccept({
3131
3907
  message: "Stage files for commit:",
3908
+ initialAutoAccept,
3909
+ onToggle: async (next) => {
3910
+ await setAutoAccept(next);
3911
+ },
3132
3912
  options: [
3133
3913
  {
3134
3914
  label: "Auto-group into commits",
@@ -3160,7 +3940,13 @@ async function showStagingMenu(files, hasChecks) {
3160
3940
  }
3161
3941
  ]
3162
3942
  });
3163
- if (p.isCancel(choice) || choice === "cancel") return null;
3943
+ if (typeof selectResult === "symbol") {
3944
+ debug("showStagingMenu: user cancelled (clack cancel symbol)");
3945
+ return null;
3946
+ }
3947
+ const choice = selectResult.value;
3948
+ debug("showStagingMenu: choice=%s autoAccept=%s", choice, selectResult.autoAccept);
3949
+ if (p$1.isCancel(choice) || choice === "cancel") return null;
3164
3950
  if (choice === "autogroup") return "autogroup";
3165
3951
  if (choice === "checks") return "checks";
3166
3952
  if (choice === "staged") return "staged";
@@ -3168,7 +3954,7 @@ async function showStagingMenu(files, hasChecks) {
3168
3954
  files: files.map((f) => f.path),
3169
3955
  all: true
3170
3956
  };
3171
- const selected = await p.multiselect({
3957
+ const selected = await p$1.multiselect({
3172
3958
  message: "Select files to stage:",
3173
3959
  options: sorted.map((f) => ({
3174
3960
  label: `${statusLabel(f.status)} ${f.path}`,
@@ -3176,7 +3962,7 @@ async function showStagingMenu(files, hasChecks) {
3176
3962
  })),
3177
3963
  required: true
3178
3964
  });
3179
- if (p.isCancel(selected)) return null;
3965
+ if (p$1.isCancel(selected)) return null;
3180
3966
  return {
3181
3967
  files: selected,
3182
3968
  all: false
@@ -3206,7 +3992,7 @@ async function handleStaging(changedFiles, flags) {
3206
3992
  }
3207
3993
  if (stagingResult === "checks") {
3208
3994
  await stageAll();
3209
- const allFiles = currentFiles.filter((f) => f.status !== "D").map((f) => f.path);
3995
+ const allFiles = await getStagedFiles();
3210
3996
  if (await detectConfig(repoRoot)) {
3211
3997
  const ckSpinner = spinner();
3212
3998
  ckSpinner.start("Running checks...");
@@ -3245,33 +4031,13 @@ async function handleStaging(changedFiles, flags) {
3245
4031
  async function runPreCommitChecks(changedFiles, noCheck) {
3246
4032
  if (noCheck) return;
3247
4033
  const checkRoot = await getRepoRoot();
3248
- const stagedFileList = changedFiles.filter((f) => f.staged && f.status !== "D").map((f) => f.path);
4034
+ const stagedFileList = await getStagedFiles();
3249
4035
  if (stagedFileList.length === 0) return;
3250
- debug("Running user checks on %d staged files...", stagedFileList.length);
3251
- const ckSpinner = spinner();
3252
- ckSpinner.start("Running checks...");
3253
- let checkResults = await runAllChecks(checkRoot, stagedFileList, 6e4);
3254
- stopCheckSpinner(ckSpinner, checkResults);
3255
- debug("Check results: ok=%s, count=%d", checkResults.ok, checkResults.results.length);
3256
- while (!checkResults.ok) {
3257
- const rawOutput = checkResults.results.filter((r) => !r.ok).map((r) => `[${r.tool}]\n${r.stdout}\n${r.stderr}`.trim()).join("\n\n");
3258
- const menuResult = await showCheckFailureMenu(parseCheckErrors(rawOutput), rawOutput, async () => {
3259
- return (await runAllChecks(checkRoot, stagedFileList, 6e4)).ok;
3260
- });
3261
- if (menuResult === "cancelled") process.exit(1);
3262
- if (menuResult === "retried") {
3263
- debug("Re-staging files and re-running checks after retry...");
3264
- await stageAll();
3265
- const ckSpinner = spinner();
3266
- ckSpinner.start("Running checks...");
3267
- checkResults = await runAllChecks(checkRoot, stagedFileList, 6e4);
3268
- debug("Retry check results: ok=%s, count=%d", checkResults.ok, checkResults.results.length);
3269
- stopCheckSpinner(ckSpinner, checkResults);
3270
- continue;
3271
- }
3272
- break;
3273
- }
3274
- await restageFormatterModifications(stagedFileList);
4036
+ if (await runCheckPhaseInteractive(checkRoot, stagedFileList, 6e4, async () => {
4037
+ debug("Re-staging files before retry...");
4038
+ await stageAll();
4039
+ }) === "cancelled") process.exit(1);
4040
+ await restageFormatterModifications(changedFiles.filter((f) => f.staged && f.status !== "D").map((f) => f.path));
3275
4041
  }
3276
4042
  /**
3277
4043
  * Re-stage staged files whose working-tree content diverged from the index after checks ran.
@@ -3395,24 +4161,29 @@ async function commitCommand(flags, version) {
3395
4161
  }
3396
4162
  s.stop("Message generated");
3397
4163
  }
3398
- const reviewed = await reviewCommitMessage(message, { regenerate: async (hint) => {
3399
- const combinedHint = flags.hint ? `${flags.hint}\n${hint}` : hint;
3400
- debug("Regenerating with combined hint:", combinedHint);
3401
- s.start("Regenerating commit message...");
3402
- try {
3403
- const newMessage = await generateMessage(diffResult.diff, combinedHint);
3404
- s.stop("Message regenerated");
3405
- return newMessage;
3406
- } catch (err) {
3407
- s.stop(red("Regeneration failed"));
3408
- throw err;
4164
+ if (await getAutoAccept()) {
4165
+ debug("Auto-accept ON: skipping review step");
4166
+ log.info(message);
4167
+ } else {
4168
+ const reviewed = await reviewCommitMessage(message, { regenerate: async (hint) => {
4169
+ const combinedHint = flags.hint ? `${flags.hint}\n${hint}` : hint;
4170
+ debug("Regenerating with combined hint:", combinedHint);
4171
+ s.start("Regenerating commit message...");
4172
+ try {
4173
+ const newMessage = await generateMessage(diffResult.diff, combinedHint);
4174
+ s.stop("Message regenerated");
4175
+ return newMessage;
4176
+ } catch (err) {
4177
+ s.stop(red("Regeneration failed"));
4178
+ throw err;
4179
+ }
4180
+ } });
4181
+ if (reviewed === null) {
4182
+ outro(dim("Cancelled."));
4183
+ return;
3409
4184
  }
3410
- } });
3411
- if (reviewed === null) {
3412
- outro(dim("Cancelled."));
3413
- return;
4185
+ message = reviewed;
3414
4186
  }
3415
- message = reviewed;
3416
4187
  await saveCachedCommit(repoRoot, message);
3417
4188
  debug("Message cached for repo:", repoRoot);
3418
4189
  s.start("Running pre-commit hooks...");
@@ -3452,7 +4223,7 @@ function getProvider(config) {
3452
4223
  return isValidProvider(config.provider ?? "groq") ? config.provider : "groq";
3453
4224
  }
3454
4225
  async function promptProvider() {
3455
- return p.select({
4226
+ return p$1.select({
3456
4227
  message: "Select LLM provider:",
3457
4228
  options: [
3458
4229
  {
@@ -3475,24 +4246,24 @@ async function promptProvider() {
3475
4246
  }
3476
4247
  async function promptApiKey(provider) {
3477
4248
  const keyName = PROVIDER_ENV_KEYS[provider];
3478
- const result = await p.text({
4249
+ const result = await p$1.text({
3479
4250
  message: `${formatProviderName(provider)} API key:`,
3480
4251
  placeholder: "Paste your API key",
3481
4252
  validate: (v) => !v?.trim() ? "API key cannot be empty" : void 0
3482
4253
  });
3483
- if (p.isCancel(result)) return result;
4254
+ if (p$1.isCancel(result)) return result;
3484
4255
  await writeConfig({ [keyName]: result.toString().trim() });
3485
4256
  debug("config: %s set", keyName);
3486
4257
  return result;
3487
4258
  }
3488
4259
  async function promptTextSetting(label, configKey, currentValue, validate) {
3489
- const result = await p.text({
4260
+ const result = await p$1.text({
3490
4261
  message: label,
3491
4262
  placeholder: currentValue ?? "",
3492
4263
  initialValue: currentValue ?? "",
3493
4264
  validate
3494
4265
  });
3495
- if (p.isCancel(result)) return result;
4266
+ if (p$1.isCancel(result)) return result;
3496
4267
  await writeConfig({ [configKey]: result.toString().trim() });
3497
4268
  debug("config: %s set to %s", configKey, result);
3498
4269
  return result;
@@ -3506,7 +4277,7 @@ function getSettingHandlers(config) {
3506
4277
  return {
3507
4278
  provider: async () => {
3508
4279
  const result = await promptProvider();
3509
- if (p.isCancel(result)) return result;
4280
+ if (p$1.isCancel(result)) return result;
3510
4281
  const newProvider = result;
3511
4282
  const newDefaultModel = PROVIDER_CONFIGS[newProvider].defaultModel;
3512
4283
  await writeConfig({
@@ -3517,7 +4288,7 @@ function getSettingHandlers(config) {
3517
4288
  const keyName = PROVIDER_ENV_KEYS[newProvider];
3518
4289
  if (!(await readConfig())[keyName]) {
3519
4290
  const keyResult = await promptApiKey(newProvider);
3520
- if (p.isCancel(keyResult)) return keyResult;
4291
+ if (p$1.isCancel(keyResult)) return keyResult;
3521
4292
  }
3522
4293
  },
3523
4294
  apikey: async () => promptApiKey(provider),
@@ -3535,7 +4306,7 @@ async function handleEditSetting(setting, config) {
3535
4306
  const handler = getSettingHandlers(config)[setting];
3536
4307
  if (!handler) return false;
3537
4308
  const result = await handler();
3538
- return !p.isCancel(result);
4309
+ return !p$1.isCancel(result);
3539
4310
  }
3540
4311
  async function editSettingsLoop(initialConfig) {
3541
4312
  let config = initialConfig;
@@ -3543,7 +4314,7 @@ async function editSettingsLoop(initialConfig) {
3543
4314
  config = await readConfig();
3544
4315
  const provider = getProvider(config);
3545
4316
  const effectiveModel = getModelForProvider(config, provider, PROVIDER_CONFIGS[provider].defaultModel);
3546
- const setting = await p.select({
4317
+ const setting = await p$1.select({
3547
4318
  message: "Select a setting to edit:",
3548
4319
  options: [
3549
4320
  {
@@ -3584,17 +4355,17 @@ async function editSettingsLoop(initialConfig) {
3584
4355
  }
3585
4356
  ]
3586
4357
  });
3587
- if (p.isCancel(setting) || setting === "done") break;
3588
- if (await handleEditSetting(setting, config)) p.log.success(green("Updated."));
4358
+ if (p$1.isCancel(setting) || setting === "done") break;
4359
+ if (await handleEditSetting(setting, config)) p$1.log.success(green("Updated."));
3589
4360
  }
3590
4361
  }
3591
4362
  async function configCommand() {
3592
4363
  debug("configCommand: starting");
3593
- p.intro(bold("🌿 commit-mint config"));
4364
+ p$1.intro(bold("🌿 commit-mint config"));
3594
4365
  while (true) {
3595
4366
  const config = await readConfig();
3596
- p.note(buildConfigDisplay(config), "commit-mint config");
3597
- const action = await p.select({
4367
+ p$1.note(buildConfigDisplay(config), "commit-mint config");
4368
+ const action = await p$1.select({
3598
4369
  message: "What would you like to do?",
3599
4370
  options: [
3600
4371
  {
@@ -3611,14 +4382,14 @@ async function configCommand() {
3611
4382
  }
3612
4383
  ]
3613
4384
  });
3614
- if (p.isCancel(action)) {
4385
+ if (p$1.isCancel(action)) {
3615
4386
  debug("configCommand: cancelled at main menu");
3616
- p.outro(dim("Cancelled."));
4387
+ p$1.outro(dim("Cancelled."));
3617
4388
  return;
3618
4389
  }
3619
4390
  if (action === "done") {
3620
4391
  debug("configCommand: done");
3621
- p.outro("Config saved.");
4392
+ p$1.outro("Config saved.");
3622
4393
  return;
3623
4394
  }
3624
4395
  if (action === "setup") {
@@ -3772,43 +4543,53 @@ async function runUpdate(pm, packageName = PACKAGE_NAME) {
3772
4543
  * resolve normally so the CLI exits cleanly.
3773
4544
  */
3774
4545
  async function updateCommand(currentVersion, flags) {
3775
- p.intro("cmint update");
4546
+ p$1.intro("cmint update");
3776
4547
  const pm = detectPackageManager(process.env.npm_config_user_agent);
3777
- p.log.info(`Package manager: ${pm}`);
3778
- p.log.message("Checking latest version...");
4548
+ p$1.log.info(`Package manager: ${pm}`);
4549
+ p$1.log.message("Checking latest version...");
3779
4550
  const latest = await fetchLatestVersion();
3780
4551
  if (latest === null) {
3781
- p.outro(red("Could not reach the npm registry. Check your connection and try again."));
4552
+ p$1.outro(red("Could not reach the npm registry. Check your connection and try again."));
3782
4553
  process.exit(1);
3783
4554
  return;
3784
4555
  }
3785
4556
  if (!isUpdateAvailable(currentVersion, latest)) {
3786
- p.outro(`Already up-to-date: v${currentVersion}`);
4557
+ p$1.outro(`Already up-to-date: v${currentVersion}`);
3787
4558
  return;
3788
4559
  }
3789
- p.log.step(`${dim(currentVersion)} → ${green(latest)}`);
4560
+ p$1.log.step(`${dim(currentVersion)} → ${green(latest)}`);
3790
4561
  const cmd = buildUpdateCommand(pm);
3791
4562
  if (flags?.yes !== true) {
3792
- const confirmed = await p.confirm({
4563
+ const confirmed = await p$1.confirm({
3793
4564
  message: `Run \`${cmd}\`?`,
3794
4565
  initialValue: true
3795
4566
  });
3796
- if (p.isCancel(confirmed) || !confirmed) {
3797
- p.outro("Update cancelled.");
4567
+ if (p$1.isCancel(confirmed) || !confirmed) {
4568
+ p$1.outro("Update cancelled.");
3798
4569
  return;
3799
4570
  }
3800
4571
  }
3801
- p.log.message(`Running ${cyan(cmd)}...`);
4572
+ p$1.log.message(`Running ${cyan(cmd)}...`);
3802
4573
  if (await runUpdate(pm)) {
3803
- p.outro(green(`Updated to v${latest}`));
4574
+ p$1.outro(green(`Updated to v${latest}`));
3804
4575
  return;
3805
4576
  }
3806
- p.outro(red("Update failed. See output above."));
4577
+ p$1.outro(red("Update failed. See output above."));
3807
4578
  process.exit(1);
3808
4579
  }
3809
4580
  //#endregion
3810
4581
  //#region src/cli.ts
3811
4582
  const { version } = package_default;
4583
+ /** `cmint auto` subcommand handler — equivalent to `cmint --auto`. */
4584
+ async function handleAutoSubcommand(version) {
4585
+ writeSessionHeader();
4586
+ setDebug(false);
4587
+ commitCommand({
4588
+ auto: true,
4589
+ retry: false,
4590
+ agent: false
4591
+ }, version);
4592
+ }
3812
4593
  cli({
3813
4594
  name: "cmint",
3814
4595
  version,
@@ -3866,6 +4647,12 @@ cli({
3866
4647
  }, async (argv) => {
3867
4648
  await logsCommand(argv.flags);
3868
4649
  }),
4650
+ command({
4651
+ name: "auto",
4652
+ description: "Auto-group files into logical commits (alias for --auto)"
4653
+ }, async () => {
4654
+ await handleAutoSubcommand(version);
4655
+ }),
3869
4656
  command({ name: "config" }, async () => {
3870
4657
  await configCommand();
3871
4658
  }),
@@ -3889,6 +4676,6 @@ cli({
3889
4676
  else commitCommand(argv.flags, version);
3890
4677
  });
3891
4678
  //#endregion
3892
- export {};
4679
+ export { handleAutoSubcommand };
3893
4680
 
3894
4681
  //# sourceMappingURL=cli.mjs.map