@quikcommit/cli 6.0.0 → 8.0.0

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.
Files changed (2) hide show
  1. package/dist/index.js +1377 -542
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
32
32
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
33
33
  mod
34
34
  ));
35
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
35
36
 
36
37
  // ../shared/dist/types.js
37
38
  var init_types = __esm({
@@ -41,7 +42,7 @@ var init_types = __esm({
41
42
  });
42
43
 
43
44
  // ../shared/dist/constants.js
44
- var CONFIG_DIR, CREDENTIALS_FILE, CONFIG_FILE, DEFAULT_API_URL, MAX_PR_CURRENT_BRANCH_CHARS, DEVICE_POLL_INTERVAL, DEVICE_FLOW_TIMEOUT;
45
+ var CONFIG_DIR, CREDENTIALS_FILE, CONFIG_FILE, DEFAULT_API_URL, MAX_PR_CURRENT_BRANCH_CHARS, DEVICE_FLOW_TIMEOUT;
45
46
  var init_constants = __esm({
46
47
  "../shared/dist/constants.js"() {
47
48
  "use strict";
@@ -50,7 +51,6 @@ var init_constants = __esm({
50
51
  CONFIG_FILE = "config.json";
51
52
  DEFAULT_API_URL = "https://api.quikcommit.dev";
52
53
  MAX_PR_CURRENT_BRANCH_CHARS = 256;
53
- DEVICE_POLL_INTERVAL = 1e3;
54
54
  DEVICE_FLOW_TIMEOUT = 6e5;
55
55
  }
56
56
  });
@@ -62,6 +62,16 @@ var init_rules = __esm({
62
62
  }
63
63
  });
64
64
 
65
+ // ../shared/dist/tokens.js
66
+ function estimateTokens(text) {
67
+ return Math.ceil(text.length / 2.5);
68
+ }
69
+ var init_tokens = __esm({
70
+ "../shared/dist/tokens.js"() {
71
+ "use strict";
72
+ }
73
+ });
74
+
65
75
  // ../shared/dist/index.js
66
76
  var init_dist = __esm({
67
77
  "../shared/dist/index.js"() {
@@ -69,6 +79,7 @@ var init_dist = __esm({
69
79
  init_types();
70
80
  init_constants();
71
81
  init_rules();
82
+ init_tokens();
72
83
  }
73
84
  });
74
85
 
@@ -126,6 +137,149 @@ var init_config = __esm({
126
137
  }
127
138
  });
128
139
 
140
+ // src/commands/login.ts
141
+ var login_exports = {};
142
+ __export(login_exports, {
143
+ runLogin: () => runLogin
144
+ });
145
+ function openBrowser(url) {
146
+ try {
147
+ if ((0, import_os2.platform)() === "darwin") {
148
+ (0, import_child_process.execFileSync)("open", [url], { stdio: "pipe" });
149
+ return true;
150
+ }
151
+ if ((0, import_os2.platform)() === "linux") {
152
+ (0, import_child_process.execFileSync)("xdg-open", [url], { stdio: "pipe" });
153
+ return true;
154
+ }
155
+ if ((0, import_os2.platform)() === "win32") {
156
+ (0, import_child_process.execFileSync)("cmd", ["/c", "start", "", url], { stdio: "pipe" });
157
+ return true;
158
+ }
159
+ } catch {
160
+ }
161
+ return false;
162
+ }
163
+ async function runLogin() {
164
+ const codeRes = await fetch(`${API_URL}/api/auth/device/code`, {
165
+ method: "POST",
166
+ headers: { "Content-Type": "application/json" },
167
+ body: JSON.stringify({ client_id: CLIENT_ID })
168
+ });
169
+ if (!codeRes.ok) {
170
+ const err = await codeRes.json().catch(() => ({ error: codeRes.statusText }));
171
+ throw new Error(err.error ?? "Failed to start device flow");
172
+ }
173
+ const codeData = await codeRes.json();
174
+ const { device_code, user_code, verification_uri_complete, interval = 5 } = codeData;
175
+ if (!device_code || !user_code) {
176
+ throw new Error("Server did not return device codes");
177
+ }
178
+ console.log("Opening browser to sign in...");
179
+ console.log("");
180
+ console.log(` Your code: ${user_code}`);
181
+ console.log("");
182
+ const authUrl = verification_uri_complete ?? `${DASHBOARD_URL}/device?user_code=${encodeURIComponent(user_code)}`;
183
+ const opened = openBrowser(authUrl);
184
+ if (!opened) {
185
+ console.log("Could not open browser. Please visit:");
186
+ console.log(authUrl);
187
+ console.log("");
188
+ }
189
+ let frame = 0;
190
+ const spinner = setInterval(() => {
191
+ const elapsed = Math.floor((Date.now() - startTime) / 1e3);
192
+ process.stderr.write(
193
+ `\r${SPINNER_FRAMES[frame++ % SPINNER_FRAMES.length]} Waiting for authorization... (${elapsed}s)`
194
+ );
195
+ }, 80);
196
+ let pollingInterval = interval * 1e3;
197
+ const startTime = Date.now();
198
+ try {
199
+ while (Date.now() - startTime < DEVICE_FLOW_TIMEOUT) {
200
+ await new Promise((r) => setTimeout(r, pollingInterval));
201
+ try {
202
+ const tokenRes = await fetch(`${API_URL}/api/auth/device/token`, {
203
+ method: "POST",
204
+ headers: { "Content-Type": "application/json" },
205
+ body: JSON.stringify({
206
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
207
+ device_code,
208
+ client_id: CLIENT_ID
209
+ })
210
+ });
211
+ const tokenData = await tokenRes.json();
212
+ if (tokenData.access_token) {
213
+ saveApiKey(tokenData.access_token);
214
+ process.stderr.write("\r\x1B[2K");
215
+ console.log("Successfully logged in!");
216
+ return;
217
+ }
218
+ if (tokenData.error) {
219
+ switch (tokenData.error) {
220
+ case "authorization_pending":
221
+ break;
222
+ // continue polling
223
+ case "slow_down":
224
+ pollingInterval += 5e3;
225
+ break;
226
+ case "access_denied":
227
+ process.stderr.write("\r\x1B[2K");
228
+ console.error("Authorization was denied.");
229
+ process.exit(1);
230
+ break;
231
+ case "expired_token":
232
+ process.stderr.write("\r\x1B[2K");
233
+ console.error("Device code expired. Please try again.");
234
+ process.exit(1);
235
+ break;
236
+ default:
237
+ process.stderr.write("\r\x1B[2K");
238
+ console.error(`Error: ${tokenData.error_description ?? tokenData.error}`);
239
+ process.exit(1);
240
+ }
241
+ }
242
+ } catch {
243
+ }
244
+ }
245
+ process.stderr.write("\r\x1B[2K");
246
+ console.error("Login timed out. Please try again.");
247
+ process.exit(1);
248
+ } finally {
249
+ clearInterval(spinner);
250
+ }
251
+ }
252
+ var import_child_process, import_os2, API_URL, DASHBOARD_URL, CLIENT_ID, SPINNER_FRAMES;
253
+ var init_login = __esm({
254
+ "src/commands/login.ts"() {
255
+ "use strict";
256
+ import_child_process = require("child_process");
257
+ import_os2 = require("os");
258
+ init_config();
259
+ init_dist();
260
+ API_URL = process.env.QC_API_URL ?? DEFAULT_API_URL;
261
+ DASHBOARD_URL = "https://app.quikcommit.dev";
262
+ CLIENT_ID = "qc-cli";
263
+ SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
264
+ }
265
+ });
266
+
267
+ // src/commands/logout.ts
268
+ var logout_exports = {};
269
+ __export(logout_exports, {
270
+ runLogout: () => runLogout
271
+ });
272
+ function runLogout() {
273
+ clearApiKey();
274
+ console.log("Logged out. Credentials cleared.");
275
+ }
276
+ var init_logout = __esm({
277
+ "src/commands/logout.ts"() {
278
+ "use strict";
279
+ init_config();
280
+ }
281
+ });
282
+
129
283
  // src/api.ts
130
284
  var ApiClient;
131
285
  var init_api = __esm({
@@ -164,8 +318,15 @@ var init_api = __esm({
164
318
  }
165
319
  return res.json();
166
320
  }
167
- async generateCommit(diff, changes, rules, model) {
168
- const body = { diff, changes, rules, model };
321
+ async generateCommit(diff, changes, rules, model, recentCommits, generationHints) {
322
+ const body = {
323
+ diff,
324
+ changes,
325
+ rules,
326
+ model,
327
+ recent_commits: recentCommits,
328
+ ...generationHints && Object.keys(generationHints).length > 0 ? { generation_hints: generationHints } : {}
329
+ };
169
330
  const data = await this.request(
170
331
  "/v1/commit",
171
332
  body
@@ -249,6 +410,37 @@ var init_api = __esm({
249
410
  }
250
411
  });
251
412
 
413
+ // src/commands/status.ts
414
+ var status_exports = {};
415
+ __export(status_exports, {
416
+ runStatus: () => runStatus
417
+ });
418
+ async function runStatus(apiKeyFlag) {
419
+ const apiKey = apiKeyFlag ?? getApiKey();
420
+ if (!apiKey) {
421
+ console.log("Not logged in. Run `qc login` to authenticate.");
422
+ return;
423
+ }
424
+ console.log("Logged in: yes");
425
+ console.log(` API key: ...${apiKey.slice(-4)}`);
426
+ const client = new ApiClient({ apiKey });
427
+ const usage = await client.getUsage();
428
+ if (usage) {
429
+ console.log(`Plan: ${usage.plan}`);
430
+ console.log(`Usage: ${usage.commit_count}/${usage.limit} commits this period`);
431
+ console.log(`Remaining: ${usage.remaining}`);
432
+ } else {
433
+ console.log("Usage: (unable to fetch)");
434
+ }
435
+ }
436
+ var init_status = __esm({
437
+ "src/commands/status.ts"() {
438
+ "use strict";
439
+ init_config();
440
+ init_api();
441
+ }
442
+ });
443
+
252
444
  // ../../node_modules/.pnpm/yaml@2.8.4/node_modules/yaml/dist/nodes/identity.js
253
445
  var require_identity = __commonJS({
254
446
  "../../node_modules/.pnpm/yaml@2.8.4/node_modules/yaml/dist/nodes/identity.js"(exports2) {
@@ -7565,7 +7757,7 @@ function validateRef(ref, name = "ref") {
7565
7757
  }
7566
7758
  function isGitRepo() {
7567
7759
  try {
7568
- (0, import_child_process.execFileSync)("git", ["rev-parse", "--is-inside-work-tree"], {
7760
+ (0, import_child_process2.execFileSync)("git", ["rev-parse", "--is-inside-work-tree"], {
7569
7761
  stdio: "pipe"
7570
7762
  });
7571
7763
  return true;
@@ -7575,7 +7767,7 @@ function isGitRepo() {
7575
7767
  }
7576
7768
  function getGitRoot() {
7577
7769
  try {
7578
- return (0, import_child_process.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
7770
+ return (0, import_child_process2.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
7579
7771
  encoding: "utf-8"
7580
7772
  }).trim();
7581
7773
  } catch {
@@ -7591,37 +7783,37 @@ function getStagedDiff(excludes = []) {
7591
7783
  args.push(`:(exclude)${pattern}`);
7592
7784
  }
7593
7785
  }
7594
- return (0, import_child_process.execFileSync)("git", args, {
7786
+ return (0, import_child_process2.execFileSync)("git", args, {
7595
7787
  encoding: "utf-8",
7596
7788
  maxBuffer: 10 * 1024 * 1024
7597
7789
  });
7598
7790
  }
7599
7791
  function getStagedFiles() {
7600
- return (0, import_child_process.execFileSync)("git", ["diff", "--cached", "--name-only"], {
7792
+ return (0, import_child_process2.execFileSync)("git", ["diff", "--cached", "--name-only"], {
7601
7793
  encoding: "utf-8"
7602
7794
  });
7603
7795
  }
7604
7796
  function hasStagedChanges() {
7605
- const output = (0, import_child_process.execFileSync)("git", ["diff", "--cached", "--name-only"], {
7797
+ const output = (0, import_child_process2.execFileSync)("git", ["diff", "--cached", "--name-only"], {
7606
7798
  encoding: "utf-8"
7607
7799
  });
7608
7800
  return output.trim().length > 0;
7609
7801
  }
7610
7802
  function getUnstagedFiles() {
7611
- const output = (0, import_child_process.execFileSync)("git", ["status", "--porcelain"], {
7803
+ const output = (0, import_child_process2.execFileSync)("git", ["status", "--porcelain"], {
7612
7804
  encoding: "utf-8"
7613
7805
  });
7614
7806
  return output.trim().split("\n").filter(Boolean).filter((line) => !line.startsWith("??"));
7615
7807
  }
7616
7808
  function stageAll() {
7617
- (0, import_child_process.execFileSync)("git", ["add", "-u"], { stdio: "pipe" });
7809
+ (0, import_child_process2.execFileSync)("git", ["add", "-u"], { stdio: "pipe" });
7618
7810
  }
7619
7811
  function gitCommit(message) {
7620
- const tmpDir = (0, import_fs2.mkdtempSync)((0, import_path2.join)((0, import_os2.tmpdir)(), "qc-"));
7812
+ const tmpDir = (0, import_fs2.mkdtempSync)((0, import_path2.join)((0, import_os3.tmpdir)(), "qc-"));
7621
7813
  const tmpFile = (0, import_path2.join)(tmpDir, "commit.txt");
7622
7814
  (0, import_fs2.writeFileSync)(tmpFile, message, { mode: 384 });
7623
7815
  try {
7624
- (0, import_child_process.execFileSync)("git", ["commit", "-F", tmpFile], { stdio: "inherit" });
7816
+ (0, import_child_process2.execFileSync)("git", ["commit", "-F", tmpFile], { stdio: "inherit" });
7625
7817
  } finally {
7626
7818
  try {
7627
7819
  (0, import_fs2.unlinkSync)(tmpFile);
@@ -7631,11 +7823,11 @@ function gitCommit(message) {
7631
7823
  }
7632
7824
  }
7633
7825
  function gitPush() {
7634
- (0, import_child_process.execFileSync)("git", ["push"], { stdio: "inherit" });
7826
+ (0, import_child_process2.execFileSync)("git", ["push"], { stdio: "inherit" });
7635
7827
  }
7636
7828
  function getBranchCommits(base = "main") {
7637
7829
  validateRef(base, "base");
7638
- const output = (0, import_child_process.execFileSync)("git", ["log", `${base}..HEAD`, "--format=%s", "--max-count=1000"], {
7830
+ const output = (0, import_child_process2.execFileSync)("git", ["log", `${base}..HEAD`, "--format=%s", "--max-count=1000"], {
7639
7831
  encoding: "utf-8",
7640
7832
  maxBuffer: 10 * 1024 * 1024
7641
7833
  });
@@ -7643,19 +7835,19 @@ function getBranchCommits(base = "main") {
7643
7835
  }
7644
7836
  function getDiffStat(base = "main") {
7645
7837
  validateRef(base, "base");
7646
- return (0, import_child_process.execFileSync)("git", ["diff", `${base}..HEAD`, "--stat"], {
7838
+ return (0, import_child_process2.execFileSync)("git", ["diff", `${base}..HEAD`, "--stat"], {
7647
7839
  encoding: "utf-8",
7648
7840
  maxBuffer: 10 * 1024 * 1024
7649
7841
  });
7650
7842
  }
7651
7843
  function getCurrentBranch() {
7652
- return (0, import_child_process.execFileSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
7844
+ return (0, import_child_process2.execFileSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
7653
7845
  encoding: "utf-8"
7654
7846
  }).trim();
7655
7847
  }
7656
7848
  function getLatestTag() {
7657
7849
  try {
7658
- return (0, import_child_process.execFileSync)("git", ["describe", "--tags", "--abbrev=0"], {
7850
+ return (0, import_child_process2.execFileSync)("git", ["describe", "--tags", "--abbrev=0"], {
7659
7851
  encoding: "utf-8"
7660
7852
  }).trim();
7661
7853
  } catch {
@@ -7665,7 +7857,7 @@ function getLatestTag() {
7665
7857
  function getCommitsSince(ref, to = "HEAD") {
7666
7858
  validateRef(ref, "from ref");
7667
7859
  validateRef(to, "to ref");
7668
- const output = (0, import_child_process.execFileSync)(
7860
+ const output = (0, import_child_process2.execFileSync)(
7669
7861
  "git",
7670
7862
  ["log", `${ref}..${to}`, "--format=%H %s", "--max-count=1000"],
7671
7863
  { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 }
@@ -7677,7 +7869,7 @@ function getCommitsSince(ref, to = "HEAD") {
7677
7869
  }
7678
7870
  function getChangedFilesSince(base = "main") {
7679
7871
  validateRef(base, "base");
7680
- const output = (0, import_child_process.execFileSync)("git", ["diff", `${base}..HEAD`, "--name-only"], {
7872
+ const output = (0, import_child_process2.execFileSync)("git", ["diff", `${base}..HEAD`, "--name-only"], {
7681
7873
  encoding: "utf-8",
7682
7874
  maxBuffer: 10 * 1024 * 1024
7683
7875
  });
@@ -7685,7 +7877,7 @@ function getChangedFilesSince(base = "main") {
7685
7877
  }
7686
7878
  function getOnlineLog(base = "main") {
7687
7879
  validateRef(base, "base");
7688
- return (0, import_child_process.execFileSync)(
7880
+ return (0, import_child_process2.execFileSync)(
7689
7881
  "git",
7690
7882
  ["log", `${base}..HEAD`, "--oneline", "--max-count=200"],
7691
7883
  {
@@ -7696,19 +7888,65 @@ function getOnlineLog(base = "main") {
7696
7888
  }
7697
7889
  function getFullDiff(base = "main") {
7698
7890
  validateRef(base, "base");
7699
- return (0, import_child_process.execFileSync)("git", ["diff", `${base}..HEAD`], {
7891
+ return (0, import_child_process2.execFileSync)("git", ["diff", `${base}..HEAD`], {
7700
7892
  encoding: "utf-8",
7701
7893
  maxBuffer: 10 * 1024 * 1024
7702
7894
  });
7703
7895
  }
7704
- var import_child_process, import_fs2, import_path2, import_os2, SAFE_GIT_REF;
7896
+ function getShortStagedFiles(max = 3) {
7897
+ const output = (0, import_child_process2.execFileSync)("git", ["diff", "--cached", "--name-only"], {
7898
+ encoding: "utf-8"
7899
+ });
7900
+ const all = output.trim().split("\n").filter(Boolean);
7901
+ return { files: all.slice(0, max), total: all.length };
7902
+ }
7903
+ function getCommitHash() {
7904
+ return (0, import_child_process2.execFileSync)("git", ["rev-parse", "--short", "HEAD"], {
7905
+ encoding: "utf-8"
7906
+ }).trim();
7907
+ }
7908
+ function getPushStats() {
7909
+ try {
7910
+ const branch = (0, import_child_process2.execFileSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
7911
+ encoding: "utf-8"
7912
+ }).trim();
7913
+ const countOutput = (0, import_child_process2.execFileSync)(
7914
+ "git",
7915
+ ["rev-list", "--count", `origin/${branch}..HEAD`],
7916
+ { encoding: "utf-8" }
7917
+ ).trim();
7918
+ const parsedCount = parseInt(countOutput, 10);
7919
+ const commits = Number.isFinite(parsedCount) ? parsedCount : 0;
7920
+ const stat = (0, import_child_process2.execFileSync)(
7921
+ "git",
7922
+ ["diff", "--shortstat", `origin/${branch}..HEAD`],
7923
+ { encoding: "utf-8" }
7924
+ ).trim();
7925
+ return { commits, stat };
7926
+ } catch {
7927
+ return null;
7928
+ }
7929
+ }
7930
+ function getRecentBranchCommits(count = 5) {
7931
+ try {
7932
+ const output = (0, import_child_process2.execFileSync)(
7933
+ "git",
7934
+ ["log", "--format=%s%n%b%n---", `--max-count=${count}`, "HEAD"],
7935
+ { encoding: "utf-8", maxBuffer: 1024 * 1024 }
7936
+ );
7937
+ return output.split("---\n").map((entry) => entry.trim()).filter(Boolean).slice(0, count);
7938
+ } catch {
7939
+ return [];
7940
+ }
7941
+ }
7942
+ var import_child_process2, import_fs2, import_path2, import_os3, SAFE_GIT_REF;
7705
7943
  var init_git = __esm({
7706
7944
  "src/git.ts"() {
7707
7945
  "use strict";
7708
- import_child_process = require("child_process");
7946
+ import_child_process2 = require("child_process");
7709
7947
  import_fs2 = require("fs");
7710
7948
  import_path2 = require("path");
7711
- import_os2 = require("os");
7949
+ import_os3 = require("os");
7712
7950
  SAFE_GIT_REF = /^[a-zA-Z0-9._\-/~:^@]+$/;
7713
7951
  }
7714
7952
  });
@@ -7775,7 +8013,7 @@ function mapRulesToCommitRules(rules) {
7775
8013
  }
7776
8014
  function tryNpxPrintConfig(root) {
7777
8015
  try {
7778
- const output = (0, import_child_process2.execFileSync)("npx", ["--no", "commitlint", "--print-config"], {
8016
+ const output = (0, import_child_process3.execFileSync)("npx", ["--no", "commitlint", "--print-config"], {
7779
8017
  encoding: "utf-8",
7780
8018
  cwd: root,
7781
8019
  timeout: 1e4,
@@ -7792,7 +8030,7 @@ function tryNodeEval(configPath) {
7792
8030
  const fileUrl = (0, import_node_url.pathToFileURL)(configPath).href;
7793
8031
  const script = `import cfg from ${JSON.stringify(fileUrl)}; process.stdout.write(JSON.stringify(cfg.default ?? cfg));`;
7794
8032
  try {
7795
- const output = (0, import_child_process2.execFileSync)("node", ["--input-type=module"], {
8033
+ const output = (0, import_child_process3.execFileSync)("node", ["--input-type=module"], {
7796
8034
  input: script,
7797
8035
  encoding: "utf-8",
7798
8036
  timeout: 1e4,
@@ -7809,7 +8047,7 @@ function tryNodeEvalTs(configPath, root) {
7809
8047
  const fileUrl = (0, import_node_url.pathToFileURL)(configPath).href;
7810
8048
  const script = `import cfg from ${JSON.stringify(fileUrl)}; process.stdout.write(JSON.stringify(cfg.default ?? cfg));`;
7811
8049
  try {
7812
- const output = (0, import_child_process2.execFileSync)("node", ["--experimental-strip-types", "--input-type=module"], {
8050
+ const output = (0, import_child_process3.execFileSync)("node", ["--experimental-strip-types", "--input-type=module"], {
7813
8051
  input: script,
7814
8052
  encoding: "utf-8",
7815
8053
  cwd: root,
@@ -7823,7 +8061,7 @@ function tryNodeEvalTs(configPath, root) {
7823
8061
  }
7824
8062
  try {
7825
8063
  const tsxScript = `import cfg from ${JSON.stringify(fileUrl)}; console.log(JSON.stringify(cfg.default ?? cfg));`;
7826
- const output = (0, import_child_process2.execFileSync)("npx", ["--no", "tsx", "-e", tsxScript], {
8064
+ const output = (0, import_child_process3.execFileSync)("npx", ["--no", "tsx", "-e", tsxScript], {
7827
8065
  encoding: "utf-8",
7828
8066
  cwd: root,
7829
8067
  timeout: 15e3,
@@ -7876,11 +8114,11 @@ async function detectCommitlintRules() {
7876
8114
  return void 0;
7877
8115
  }
7878
8116
  }
7879
- var import_child_process2, import_fs3, import_path3, import_node_url, import_yaml, CONFIG_FILES;
8117
+ var import_child_process3, import_fs3, import_path3, import_node_url, import_yaml, CONFIG_FILES;
7880
8118
  var init_commitlint = __esm({
7881
8119
  "src/commitlint.ts"() {
7882
8120
  "use strict";
7883
- import_child_process2 = require("child_process");
8121
+ import_child_process3 = require("child_process");
7884
8122
  import_fs3 = require("fs");
7885
8123
  import_path3 = require("path");
7886
8124
  import_node_url = require("node:url");
@@ -7901,335 +8139,55 @@ var init_commitlint = __esm({
7901
8139
  }
7902
8140
  });
7903
8141
 
7904
- // src/monorepo.ts
7905
- var monorepo_exports = {};
7906
- __export(monorepo_exports, {
7907
- autoDetectScope: () => autoDetectScope,
7908
- detectWorkspace: () => detectWorkspace,
7909
- getPackageForFile: () => getPackageForFile
8142
+ // src/commands/pr.ts
8143
+ var pr_exports = {};
8144
+ __export(pr_exports, {
8145
+ pr: () => pr
7910
8146
  });
7911
- function findGitRoot(start) {
7912
- try {
7913
- return (0, import_child_process3.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
7914
- encoding: "utf-8",
7915
- cwd: start,
7916
- stdio: ["pipe", "pipe", "pipe"]
7917
- }).trim();
7918
- } catch {
7919
- return start;
7920
- }
7921
- }
7922
- function detectWorkspace(cwd = findGitRoot(process.cwd())) {
7923
- const pnpmWs = (0, import_path4.join)(cwd, "pnpm-workspace.yaml");
7924
- if ((0, import_fs4.existsSync)(pnpmWs)) {
7925
- const content = (0, import_fs4.readFileSync)(pnpmWs, "utf-8");
7926
- const match = content.match(/packages:\s*\n((?:\s+-\s+.+\n?)*)/);
7927
- if (match) {
7928
- const packages = match[1].split("\n").map((l) => l.replace(/^\s+-\s+/, "").replace(/["']/g, "").trim()).filter(Boolean);
7929
- return { type: "pnpm", packages, root: cwd };
7930
- }
7931
- }
7932
- const lerna = (0, import_path4.join)(cwd, "lerna.json");
7933
- if ((0, import_fs4.existsSync)(lerna)) {
8147
+ function findPullRequestTemplate(gitRoot) {
8148
+ const fileCandidates = [
8149
+ (0, import_path4.join)(gitRoot, ".github", "pull_request_template.md"),
8150
+ (0, import_path4.join)(gitRoot, ".github", "PULL_REQUEST_TEMPLATE.md"),
8151
+ (0, import_path4.join)(gitRoot, "pull_request_template.md")
8152
+ ];
8153
+ for (const p of fileCandidates) {
7934
8154
  try {
7935
- const config2 = JSON.parse((0, import_fs4.readFileSync)(lerna, "utf-8"));
7936
- return {
7937
- type: "lerna",
7938
- packages: config2.packages ?? ["packages/*"],
7939
- root: cwd
7940
- };
8155
+ if ((0, import_fs4.existsSync)(p) && (0, import_fs4.statSync)(p).isFile()) {
8156
+ return { path: p, content: (0, import_fs4.readFileSync)(p, "utf-8") };
8157
+ }
7941
8158
  } catch {
7942
8159
  }
7943
8160
  }
7944
- if ((0, import_fs4.existsSync)((0, import_path4.join)(cwd, "nx.json"))) {
7945
- return {
7946
- type: "nx",
7947
- packages: ["packages/*", "apps/*", "libs/*"],
7948
- root: cwd
7949
- };
7950
- }
7951
- if ((0, import_fs4.existsSync)((0, import_path4.join)(cwd, "turbo.json"))) {
7952
- const pkgPath2 = (0, import_path4.join)(cwd, "package.json");
7953
- if ((0, import_fs4.existsSync)(pkgPath2)) {
7954
- try {
7955
- const config2 = JSON.parse((0, import_fs4.readFileSync)(pkgPath2, "utf-8"));
7956
- if (config2.workspaces) {
7957
- const ws = Array.isArray(config2.workspaces) ? config2.workspaces : config2.workspaces.packages ?? [];
7958
- return { type: "turbo", packages: ws, root: cwd };
8161
+ const multiDir = (0, import_path4.join)(gitRoot, ".github", "PULL_REQUEST_TEMPLATE");
8162
+ try {
8163
+ if ((0, import_fs4.existsSync)(multiDir) && (0, import_fs4.statSync)(multiDir).isDirectory()) {
8164
+ const names = (0, import_fs4.readdirSync)(multiDir).filter((f) => f.toLowerCase().endsWith(".md")).sort();
8165
+ if (names.length > 0) {
8166
+ const p = (0, import_path4.join)(multiDir, names[0]);
8167
+ if ((0, import_fs4.statSync)(p).isFile()) {
8168
+ return { path: p, content: (0, import_fs4.readFileSync)(p, "utf-8") };
7959
8169
  }
7960
- } catch {
7961
- }
7962
- }
7963
- }
7964
- const pkgPath = (0, import_path4.join)(cwd, "package.json");
7965
- if ((0, import_fs4.existsSync)(pkgPath)) {
7966
- try {
7967
- const config2 = JSON.parse((0, import_fs4.readFileSync)(pkgPath, "utf-8"));
7968
- if (config2.workspaces) {
7969
- const ws = Array.isArray(config2.workspaces) ? config2.workspaces : config2.workspaces.packages ?? [];
7970
- return { type: "npm", packages: ws, root: cwd };
7971
8170
  }
7972
- } catch {
7973
8171
  }
8172
+ } catch {
7974
8173
  }
7975
- return null;
8174
+ return void 0;
7976
8175
  }
7977
- function matchGlobPattern(rel, pattern) {
7978
- const dir = pattern.replace(/\/?\*\*?$/, "").replace(/\/$/, "");
7979
- if (!dir || dir === "*" || dir === "**") {
7980
- const pkg = rel.split("/")[0];
7981
- return pkg || null;
8176
+ async function pr(options) {
8177
+ const base = options.base ?? "main";
8178
+ const commits = getBranchCommits(base);
8179
+ const diffStat = getDiffStat(base);
8180
+ const gitRoot = getGitRoot();
8181
+ const templateHit = findPullRequestTemplate(gitRoot);
8182
+ let prTemplate;
8183
+ if (templateHit) {
8184
+ prTemplate = templateHit.content.substring(0, 16 * 1024);
8185
+ console.error(`[qc] Using PR template from ${(0, import_path4.relative)(gitRoot, templateHit.path)}`);
7982
8186
  }
7983
- const starIdx = dir.indexOf("*");
7984
- if (starIdx !== -1) {
7985
- const prefix2 = dir.slice(0, starIdx);
7986
- if (rel.startsWith(prefix2)) {
7987
- const rest = rel.slice(prefix2.length);
7988
- const pkg = rest.split("/")[0];
7989
- return pkg || null;
7990
- }
7991
- return null;
7992
- }
7993
- const prefix = dir + "/";
7994
- const hasGlob = /\*/.test(pattern);
7995
- if (hasGlob) {
7996
- if (rel.startsWith(prefix)) {
7997
- const rest = rel.slice(prefix.length);
7998
- const pkg = rest.split("/")[0];
7999
- return pkg || null;
8000
- }
8001
- } else {
8002
- if (rel === dir || rel.startsWith(prefix)) {
8003
- const segments = dir.split("/").filter(Boolean);
8004
- return segments[segments.length - 1] ?? null;
8005
- }
8006
- }
8007
- return null;
8008
- }
8009
- function getPackageForFile(filePath, workspace) {
8010
- const absPath = filePath.startsWith("/") ? filePath : (0, import_path4.join)(workspace.root, filePath);
8011
- const rel = (0, import_path4.relative)(workspace.root, absPath);
8012
- for (const pattern of workspace.packages) {
8013
- const packageName = matchGlobPattern(rel, pattern);
8014
- if (packageName) return packageName;
8015
- }
8016
- return null;
8017
- }
8018
- function autoDetectScope(stagedFiles, workspace) {
8019
- const packages = /* @__PURE__ */ new Set();
8020
- for (const file of stagedFiles) {
8021
- const filePath = file.startsWith("/") ? file : (0, import_path4.join)(workspace.root, file);
8022
- const pkg = getPackageForFile(filePath, workspace);
8023
- if (pkg) packages.add(pkg);
8024
- }
8025
- if (packages.size === 1) return [...packages][0];
8026
- if (packages.size > 1 && packages.size <= 3) return [...packages].join(",");
8027
- if (packages.size > 3) {
8028
- console.error(
8029
- `[qc] Changes span ${packages.size} packages; skipping auto-scope detection.`
8030
- );
8031
- }
8032
- return null;
8033
- }
8034
- var import_child_process3, import_fs4, import_path4;
8035
- var init_monorepo = __esm({
8036
- "src/monorepo.ts"() {
8037
- "use strict";
8038
- import_child_process3 = require("child_process");
8039
- import_fs4 = require("fs");
8040
- import_path4 = require("path");
8041
- }
8042
- });
8043
-
8044
- // src/commands/login.ts
8045
- var login_exports = {};
8046
- __export(login_exports, {
8047
- runLogin: () => runLogin
8048
- });
8049
- function openBrowser(url) {
8050
- try {
8051
- if ((0, import_os3.platform)() === "darwin") {
8052
- (0, import_child_process4.execFileSync)("open", [url], { stdio: "pipe" });
8053
- return true;
8054
- }
8055
- if ((0, import_os3.platform)() === "linux") {
8056
- (0, import_child_process4.execFileSync)("xdg-open", [url], { stdio: "pipe" });
8057
- return true;
8058
- }
8059
- if ((0, import_os3.platform)() === "win32") {
8060
- (0, import_child_process4.execFileSync)("cmd", ["/c", "start", "", url], { stdio: "pipe" });
8061
- return true;
8062
- }
8063
- } catch {
8064
- }
8065
- return false;
8066
- }
8067
- async function runLogin() {
8068
- const startRes = await fetch(`${API_URL}/v1/auth/device/start`, {
8069
- method: "POST",
8070
- headers: { "Content-Type": "application/json" },
8071
- body: JSON.stringify({})
8072
- });
8073
- if (!startRes.ok) {
8074
- const err = await startRes.json().catch(() => ({ error: startRes.statusText }));
8075
- throw new Error(err.error ?? "Failed to start device flow");
8076
- }
8077
- const startData = await startRes.json();
8078
- const code = startData.device_code;
8079
- if (!code) {
8080
- throw new Error("Server did not return a device_code");
8081
- }
8082
- console.log("Opening browser to sign in...");
8083
- console.log("");
8084
- const authUrl = `${DASHBOARD_URL}/auth/cli?code=${encodeURIComponent(code)}`;
8085
- const opened = openBrowser(authUrl);
8086
- if (!opened) {
8087
- console.log("Could not open browser. Please visit:");
8088
- console.log(authUrl);
8089
- console.log("");
8090
- }
8091
- let frame = 0;
8092
- const spinner = setInterval(() => {
8093
- const elapsed = Math.floor((Date.now() - startTime) / 1e3);
8094
- process.stderr.write(
8095
- `\r${SPINNER_FRAMES[frame++ % SPINNER_FRAMES.length]} Waiting for authorization... (${elapsed}s)`
8096
- );
8097
- }, 80);
8098
- const startTime = Date.now();
8099
- try {
8100
- while (Date.now() - startTime < DEVICE_FLOW_TIMEOUT) {
8101
- try {
8102
- const res = await fetch(
8103
- `${API_URL}/v1/auth/device/poll?code=${encodeURIComponent(code)}`
8104
- );
8105
- const data = await res.json();
8106
- if (data.status === "complete" && data.api_key) {
8107
- saveApiKey(data.api_key);
8108
- process.stderr.write("\r\x1B[2K");
8109
- console.log("Successfully logged in!");
8110
- return;
8111
- }
8112
- } catch {
8113
- }
8114
- await new Promise((r) => setTimeout(r, DEVICE_POLL_INTERVAL));
8115
- }
8116
- process.stderr.write("\r\x1B[2K");
8117
- console.error("Login timed out. Please try again.");
8118
- process.exit(1);
8119
- } finally {
8120
- clearInterval(spinner);
8121
- }
8122
- }
8123
- var import_child_process4, import_os3, API_URL, DASHBOARD_URL, SPINNER_FRAMES;
8124
- var init_login = __esm({
8125
- "src/commands/login.ts"() {
8126
- "use strict";
8127
- import_child_process4 = require("child_process");
8128
- import_os3 = require("os");
8129
- init_config();
8130
- init_dist();
8131
- API_URL = process.env.QC_API_URL ?? DEFAULT_API_URL;
8132
- DASHBOARD_URL = "https://app.quikcommit.dev";
8133
- SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
8134
- }
8135
- });
8136
-
8137
- // src/commands/logout.ts
8138
- var logout_exports = {};
8139
- __export(logout_exports, {
8140
- runLogout: () => runLogout
8141
- });
8142
- function runLogout() {
8143
- clearApiKey();
8144
- console.log("Logged out. Credentials cleared.");
8145
- }
8146
- var init_logout = __esm({
8147
- "src/commands/logout.ts"() {
8148
- "use strict";
8149
- init_config();
8150
- }
8151
- });
8152
-
8153
- // src/commands/status.ts
8154
- var status_exports = {};
8155
- __export(status_exports, {
8156
- runStatus: () => runStatus
8157
- });
8158
- async function runStatus(apiKeyFlag) {
8159
- const apiKey = apiKeyFlag ?? getApiKey();
8160
- if (!apiKey) {
8161
- console.log("Not logged in. Run `qc login` to authenticate.");
8162
- return;
8163
- }
8164
- console.log("Logged in: yes");
8165
- console.log(` API key: ...${apiKey.slice(-4)}`);
8166
- const client = new ApiClient({ apiKey });
8167
- const usage = await client.getUsage();
8168
- if (usage) {
8169
- console.log(`Plan: ${usage.plan}`);
8170
- console.log(`Usage: ${usage.commit_count}/${usage.limit} commits this period`);
8171
- console.log(`Remaining: ${usage.remaining}`);
8172
- } else {
8173
- console.log("Usage: (unable to fetch)");
8174
- }
8175
- }
8176
- var init_status = __esm({
8177
- "src/commands/status.ts"() {
8178
- "use strict";
8179
- init_config();
8180
- init_api();
8181
- }
8182
- });
8183
-
8184
- // src/commands/pr.ts
8185
- var pr_exports = {};
8186
- __export(pr_exports, {
8187
- pr: () => pr
8188
- });
8189
- function findPullRequestTemplate(gitRoot) {
8190
- const fileCandidates = [
8191
- (0, import_path5.join)(gitRoot, ".github", "pull_request_template.md"),
8192
- (0, import_path5.join)(gitRoot, ".github", "PULL_REQUEST_TEMPLATE.md"),
8193
- (0, import_path5.join)(gitRoot, "pull_request_template.md")
8194
- ];
8195
- for (const p of fileCandidates) {
8196
- try {
8197
- if ((0, import_fs5.existsSync)(p) && (0, import_fs5.statSync)(p).isFile()) {
8198
- return { path: p, content: (0, import_fs5.readFileSync)(p, "utf-8") };
8199
- }
8200
- } catch {
8201
- }
8202
- }
8203
- const multiDir = (0, import_path5.join)(gitRoot, ".github", "PULL_REQUEST_TEMPLATE");
8204
- try {
8205
- if ((0, import_fs5.existsSync)(multiDir) && (0, import_fs5.statSync)(multiDir).isDirectory()) {
8206
- const names = (0, import_fs5.readdirSync)(multiDir).filter((f) => f.toLowerCase().endsWith(".md")).sort();
8207
- if (names.length > 0) {
8208
- const p = (0, import_path5.join)(multiDir, names[0]);
8209
- if ((0, import_fs5.statSync)(p).isFile()) {
8210
- return { path: p, content: (0, import_fs5.readFileSync)(p, "utf-8") };
8211
- }
8212
- }
8213
- }
8214
- } catch {
8215
- }
8216
- return void 0;
8217
- }
8218
- async function pr(options) {
8219
- const base = options.base ?? "main";
8220
- const commits = getBranchCommits(base);
8221
- const diffStat = getDiffStat(base);
8222
- const gitRoot = getGitRoot();
8223
- const templateHit = findPullRequestTemplate(gitRoot);
8224
- let prTemplate;
8225
- if (templateHit) {
8226
- prTemplate = templateHit.content.substring(0, 16 * 1024);
8227
- console.error(`[qc] Using PR template from ${(0, import_path5.relative)(gitRoot, templateHit.path)}`);
8228
- }
8229
- const currentBranch = getCurrentBranch().slice(0, MAX_PR_CURRENT_BRANCH_CHARS);
8230
- if (commits.length === 0) {
8231
- console.error(`No commits found on this branch vs ${base}`);
8232
- process.exit(1);
8187
+ const currentBranch = getCurrentBranch().slice(0, MAX_PR_CURRENT_BRANCH_CHARS);
8188
+ if (commits.length === 0) {
8189
+ console.error(`No commits found on this branch vs ${base}`);
8190
+ process.exit(1);
8233
8191
  }
8234
8192
  const commitlintRules = await detectCommitlintRules();
8235
8193
  console.error(`Generating PR description from ${commits.length} commits...`);
@@ -8260,7 +8218,7 @@ Title: ${trimmedTitle}
8260
8218
  if (options.create) {
8261
8219
  try {
8262
8220
  const prTitle = trimmedTitle || result.message.split("\n").find((l) => l.trim()) || result.message.substring(0, 72).trim();
8263
- (0, import_child_process5.execFileSync)("gh", ["pr", "create", "--title", prTitle, "--body", result.message], {
8221
+ (0, import_child_process4.execFileSync)("gh", ["pr", "create", "--title", prTitle, "--body", result.message], {
8264
8222
  stdio: "inherit"
8265
8223
  });
8266
8224
  } catch {
@@ -8269,13 +8227,13 @@ Title: ${trimmedTitle}
8269
8227
  }
8270
8228
  }
8271
8229
  }
8272
- var import_child_process5, import_fs5, import_path5;
8230
+ var import_child_process4, import_fs4, import_path4;
8273
8231
  var init_pr = __esm({
8274
8232
  "src/commands/pr.ts"() {
8275
8233
  "use strict";
8276
- import_child_process5 = require("child_process");
8277
- import_fs5 = require("fs");
8278
- import_path5 = require("path");
8234
+ import_child_process4 = require("child_process");
8235
+ import_fs4 = require("fs");
8236
+ import_path4 = require("path");
8279
8237
  init_dist();
8280
8238
  init_config();
8281
8239
  init_api();
@@ -8336,21 +8294,21 @@ async function changelog(options) {
8336
8294
  `;
8337
8295
  const changelogEntry = header + result.message;
8338
8296
  if (options.write) {
8339
- const path = (0, import_path6.join)(getGitRoot(), "CHANGELOG.md");
8340
- const existing = (0, import_fs6.existsSync)(path) ? (0, import_fs6.readFileSync)(path, "utf-8") : "";
8297
+ const path = (0, import_path5.join)(getGitRoot(), "CHANGELOG.md");
8298
+ const existing = (0, import_fs5.existsSync)(path) ? (0, import_fs5.readFileSync)(path, "utf-8") : "";
8341
8299
  const newContent = changelogEntry + (existing ? "\n\n" + existing : "");
8342
- (0, import_fs6.writeFileSync)(path, newContent);
8300
+ (0, import_fs5.writeFileSync)(path, newContent);
8343
8301
  console.error(`Wrote to ${path}`);
8344
8302
  } else {
8345
8303
  console.log(changelogEntry);
8346
8304
  }
8347
8305
  }
8348
- var import_fs6, import_path6, CONVENTIONAL_TYPE_RE;
8306
+ var import_fs5, import_path5, CONVENTIONAL_TYPE_RE;
8349
8307
  var init_changelog = __esm({
8350
8308
  "src/commands/changelog.ts"() {
8351
8309
  "use strict";
8352
- import_fs6 = require("fs");
8353
- import_path6 = require("path");
8310
+ import_fs5 = require("fs");
8311
+ import_path5 = require("path");
8354
8312
  init_config();
8355
8313
  init_api();
8356
8314
  init_git();
@@ -8358,6 +8316,146 @@ var init_changelog = __esm({
8358
8316
  }
8359
8317
  });
8360
8318
 
8319
+ // src/monorepo.ts
8320
+ var monorepo_exports = {};
8321
+ __export(monorepo_exports, {
8322
+ autoDetectScope: () => autoDetectScope,
8323
+ detectWorkspace: () => detectWorkspace,
8324
+ getPackageForFile: () => getPackageForFile
8325
+ });
8326
+ function findGitRoot(start) {
8327
+ try {
8328
+ return (0, import_child_process5.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
8329
+ encoding: "utf-8",
8330
+ cwd: start,
8331
+ stdio: ["pipe", "pipe", "pipe"]
8332
+ }).trim();
8333
+ } catch {
8334
+ return start;
8335
+ }
8336
+ }
8337
+ function detectWorkspace(cwd = findGitRoot(process.cwd())) {
8338
+ const pnpmWs = (0, import_path6.join)(cwd, "pnpm-workspace.yaml");
8339
+ if ((0, import_fs6.existsSync)(pnpmWs)) {
8340
+ const content = (0, import_fs6.readFileSync)(pnpmWs, "utf-8");
8341
+ const match = content.match(/packages:\s*\n((?:\s+-\s+.+\n?)*)/);
8342
+ if (match) {
8343
+ const packages = match[1].split("\n").map((l) => l.replace(/^\s+-\s+/, "").replace(/["']/g, "").trim()).filter(Boolean);
8344
+ return { type: "pnpm", packages, root: cwd };
8345
+ }
8346
+ }
8347
+ const lerna = (0, import_path6.join)(cwd, "lerna.json");
8348
+ if ((0, import_fs6.existsSync)(lerna)) {
8349
+ try {
8350
+ const config2 = JSON.parse((0, import_fs6.readFileSync)(lerna, "utf-8"));
8351
+ return {
8352
+ type: "lerna",
8353
+ packages: config2.packages ?? ["packages/*"],
8354
+ root: cwd
8355
+ };
8356
+ } catch {
8357
+ }
8358
+ }
8359
+ if ((0, import_fs6.existsSync)((0, import_path6.join)(cwd, "nx.json"))) {
8360
+ return {
8361
+ type: "nx",
8362
+ packages: ["packages/*", "apps/*", "libs/*"],
8363
+ root: cwd
8364
+ };
8365
+ }
8366
+ if ((0, import_fs6.existsSync)((0, import_path6.join)(cwd, "turbo.json"))) {
8367
+ const pkgPath2 = (0, import_path6.join)(cwd, "package.json");
8368
+ if ((0, import_fs6.existsSync)(pkgPath2)) {
8369
+ try {
8370
+ const config2 = JSON.parse((0, import_fs6.readFileSync)(pkgPath2, "utf-8"));
8371
+ if (config2.workspaces) {
8372
+ const ws = Array.isArray(config2.workspaces) ? config2.workspaces : config2.workspaces.packages ?? [];
8373
+ return { type: "turbo", packages: ws, root: cwd };
8374
+ }
8375
+ } catch {
8376
+ }
8377
+ }
8378
+ }
8379
+ const pkgPath = (0, import_path6.join)(cwd, "package.json");
8380
+ if ((0, import_fs6.existsSync)(pkgPath)) {
8381
+ try {
8382
+ const config2 = JSON.parse((0, import_fs6.readFileSync)(pkgPath, "utf-8"));
8383
+ if (config2.workspaces) {
8384
+ const ws = Array.isArray(config2.workspaces) ? config2.workspaces : config2.workspaces.packages ?? [];
8385
+ return { type: "npm", packages: ws, root: cwd };
8386
+ }
8387
+ } catch {
8388
+ }
8389
+ }
8390
+ return null;
8391
+ }
8392
+ function matchGlobPattern(rel, pattern) {
8393
+ const dir = pattern.replace(/\/?\*\*?$/, "").replace(/\/$/, "");
8394
+ if (!dir || dir === "*" || dir === "**") {
8395
+ const pkg = rel.split("/")[0];
8396
+ return pkg || null;
8397
+ }
8398
+ const starIdx = dir.indexOf("*");
8399
+ if (starIdx !== -1) {
8400
+ const prefix2 = dir.slice(0, starIdx);
8401
+ if (rel.startsWith(prefix2)) {
8402
+ const rest = rel.slice(prefix2.length);
8403
+ const pkg = rest.split("/")[0];
8404
+ return pkg || null;
8405
+ }
8406
+ return null;
8407
+ }
8408
+ const prefix = dir + "/";
8409
+ const hasGlob = /\*/.test(pattern);
8410
+ if (hasGlob) {
8411
+ if (rel.startsWith(prefix)) {
8412
+ const rest = rel.slice(prefix.length);
8413
+ const pkg = rest.split("/")[0];
8414
+ return pkg || null;
8415
+ }
8416
+ } else {
8417
+ if (rel === dir || rel.startsWith(prefix)) {
8418
+ const segments = dir.split("/").filter(Boolean);
8419
+ return segments[segments.length - 1] ?? null;
8420
+ }
8421
+ }
8422
+ return null;
8423
+ }
8424
+ function getPackageForFile(filePath, workspace) {
8425
+ const absPath = filePath.startsWith("/") ? filePath : (0, import_path6.join)(workspace.root, filePath);
8426
+ const rel = (0, import_path6.relative)(workspace.root, absPath);
8427
+ for (const pattern of workspace.packages) {
8428
+ const packageName = matchGlobPattern(rel, pattern);
8429
+ if (packageName) return packageName;
8430
+ }
8431
+ return null;
8432
+ }
8433
+ function autoDetectScope(stagedFiles, workspace) {
8434
+ const packages = /* @__PURE__ */ new Set();
8435
+ for (const file of stagedFiles) {
8436
+ const filePath = file.startsWith("/") ? file : (0, import_path6.join)(workspace.root, file);
8437
+ const pkg = getPackageForFile(filePath, workspace);
8438
+ if (pkg) packages.add(pkg);
8439
+ }
8440
+ if (packages.size === 1) return [...packages][0];
8441
+ if (packages.size > 1 && packages.size <= 3) return [...packages].join(",");
8442
+ if (packages.size > 3) {
8443
+ console.error(
8444
+ `[qc] Changes span ${packages.size} packages; skipping auto-scope detection.`
8445
+ );
8446
+ }
8447
+ return null;
8448
+ }
8449
+ var import_child_process5, import_fs6, import_path6;
8450
+ var init_monorepo = __esm({
8451
+ "src/monorepo.ts"() {
8452
+ "use strict";
8453
+ import_child_process5 = require("child_process");
8454
+ import_fs6 = require("fs");
8455
+ import_path6 = require("path");
8456
+ }
8457
+ });
8458
+
8361
8459
  // src/commands/changeset.ts
8362
8460
  var changeset_exports = {};
8363
8461
  __export(changeset_exports, {
@@ -8977,23 +9075,411 @@ async function upgrade() {
8977
9075
  Opening ${BILLING_URL}
8978
9076
  `);
8979
9077
  try {
8980
- const { execFileSync: execFileSync7 } = await import("child_process");
8981
- if (process.platform === "darwin") {
8982
- execFileSync7("open", [BILLING_URL]);
8983
- } else if (process.platform === "linux") {
8984
- execFileSync7("xdg-open", [BILLING_URL]);
8985
- } else if (process.platform === "win32") {
8986
- execFileSync7("cmd", ["/c", "start", "", BILLING_URL]);
9078
+ const { execFileSync: execFileSync7 } = await import("child_process");
9079
+ if (process.platform === "darwin") {
9080
+ execFileSync7("open", [BILLING_URL]);
9081
+ } else if (process.platform === "linux") {
9082
+ execFileSync7("xdg-open", [BILLING_URL]);
9083
+ } else if (process.platform === "win32") {
9084
+ execFileSync7("cmd", ["/c", "start", "", BILLING_URL]);
9085
+ }
9086
+ } catch {
9087
+ console.log(`Visit: ${BILLING_URL}`);
9088
+ }
9089
+ }
9090
+ var BILLING_URL;
9091
+ var init_upgrade = __esm({
9092
+ "src/commands/upgrade.ts"() {
9093
+ "use strict";
9094
+ BILLING_URL = "https://app.quikcommit.dev/billing";
9095
+ }
9096
+ });
9097
+
9098
+ // src/smart-diff.ts
9099
+ function sanitizeFilepath(path) {
9100
+ return path.replace(/[\x00-\x1F\x7F[\]`]/g, "_").slice(0, 200);
9101
+ }
9102
+ function classifyFile(filepath) {
9103
+ const basename = filepath.split("/").pop() ?? filepath;
9104
+ if (LOCK_FILES.has(basename)) return "lock";
9105
+ if (filepath.endsWith(".map")) return "sourcemap";
9106
+ if (VENDORED_PREFIXES.some((p) => filepath.startsWith(p))) return "vendored";
9107
+ if (GENERATED_PATTERNS.some((p) => p.test(filepath))) return "generated";
9108
+ return "code";
9109
+ }
9110
+ function parseDiffIntoFiles(diff) {
9111
+ const files = [];
9112
+ const parts = diff.split(/^(diff --git .+)$/m);
9113
+ for (let i = 1; i < parts.length; i += 2) {
9114
+ const header = parts[i];
9115
+ const content = parts[i + 1] ?? "";
9116
+ const match = header.match(/diff --git a\/(.+?) b\/(.+)/);
9117
+ const filepath = match?.[2] ?? "unknown";
9118
+ const lines = content.split("\n");
9119
+ let additions = 0;
9120
+ let deletions = 0;
9121
+ for (const line of lines) {
9122
+ if (line.startsWith("+") && !line.startsWith("+++")) additions++;
9123
+ else if (line.startsWith("-") && !line.startsWith("---")) deletions++;
9124
+ }
9125
+ files.push({ filepath, content: header + content, additions, deletions });
9126
+ }
9127
+ return files;
9128
+ }
9129
+ function isMinified(content) {
9130
+ const lines = content.split("\n").filter(
9131
+ (l) => (l.startsWith("+") || l.startsWith("-")) && !l.startsWith("+++") && !l.startsWith("---")
9132
+ );
9133
+ if (lines.length === 0) return false;
9134
+ return lines.some((l) => l.length > 500);
9135
+ }
9136
+ function preprocessDiff(diff) {
9137
+ const files = parseDiffIntoFiles(diff);
9138
+ if (files.length === 0) return { processedDiff: diff, summarized: [], tokensSaved: 0 };
9139
+ const kept = [];
9140
+ const summarized = [];
9141
+ let tokensSaved = 0;
9142
+ for (const file of files) {
9143
+ const classification = classifyFile(file.filepath);
9144
+ switch (classification) {
9145
+ case "sourcemap":
9146
+ tokensSaved += estimateTokens(file.content);
9147
+ summarized.push(file.filepath);
9148
+ break;
9149
+ case "lock":
9150
+ tokensSaved += estimateTokens(file.content);
9151
+ kept.push(`[lock file updated: ${sanitizeFilepath(file.filepath)} (+${file.additions} \u2212${file.deletions} lines)]
9152
+ `);
9153
+ summarized.push(file.filepath);
9154
+ break;
9155
+ case "generated":
9156
+ tokensSaved += estimateTokens(file.content);
9157
+ kept.push(`[generated: ${sanitizeFilepath(file.filepath)} (+${file.additions} \u2212${file.deletions})]
9158
+ `);
9159
+ summarized.push(file.filepath);
9160
+ break;
9161
+ case "vendored":
9162
+ tokensSaved += estimateTokens(file.content);
9163
+ kept.push(`[vendored: ${sanitizeFilepath(file.filepath)} updated]
9164
+ `);
9165
+ summarized.push(file.filepath);
9166
+ break;
9167
+ case "code":
9168
+ if (isMinified(file.content)) {
9169
+ tokensSaved += estimateTokens(file.content);
9170
+ const sizeKB = Math.round(file.content.length / 1024);
9171
+ kept.push(`[minified asset: ${sanitizeFilepath(file.filepath)} (${sizeKB} KB)]
9172
+ `);
9173
+ summarized.push(file.filepath);
9174
+ } else {
9175
+ kept.push(file.content);
9176
+ }
9177
+ break;
9178
+ }
9179
+ }
9180
+ return {
9181
+ processedDiff: kept.join(""),
9182
+ summarized,
9183
+ tokensSaved
9184
+ };
9185
+ }
9186
+ var LOCK_FILES, GENERATED_PATTERNS, VENDORED_PREFIXES;
9187
+ var init_smart_diff = __esm({
9188
+ "src/smart-diff.ts"() {
9189
+ "use strict";
9190
+ init_dist();
9191
+ LOCK_FILES = /* @__PURE__ */ new Set([
9192
+ "pnpm-lock.yaml",
9193
+ "package-lock.json",
9194
+ "yarn.lock",
9195
+ "Cargo.lock",
9196
+ "Gemfile.lock",
9197
+ "poetry.lock",
9198
+ "composer.lock",
9199
+ "bun.lockb",
9200
+ "shrinkwrap.json"
9201
+ ]);
9202
+ GENERATED_PATTERNS = [
9203
+ /\.generated\.\w+$/,
9204
+ /\.g\.dart$/,
9205
+ /\.pb\.go$/,
9206
+ /\.pb\.ts$/,
9207
+ /(^|\/)\.prisma\/client\//,
9208
+ /\/generated\//
9209
+ ];
9210
+ VENDORED_PREFIXES = ["vendor/", "third_party/", "node_modules/"];
9211
+ }
9212
+ });
9213
+
9214
+ // ../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js
9215
+ var require_picocolors = __commonJS({
9216
+ "../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js"(exports2, module2) {
9217
+ var p = process || {};
9218
+ var argv = p.argv || [];
9219
+ var env = p.env || {};
9220
+ var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
9221
+ var formatter = (open, close, replace = open) => (input) => {
9222
+ let string = "" + input, index = string.indexOf(close, open.length);
9223
+ return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
9224
+ };
9225
+ var replaceClose = (string, close, replace, index) => {
9226
+ let result = "", cursor = 0;
9227
+ do {
9228
+ result += string.substring(cursor, index) + replace;
9229
+ cursor = index + close.length;
9230
+ index = string.indexOf(close, cursor);
9231
+ } while (~index);
9232
+ return result + string.substring(cursor);
9233
+ };
9234
+ var createColors = (enabled = isColorSupported) => {
9235
+ let f = enabled ? formatter : () => String;
9236
+ return {
9237
+ isColorSupported: enabled,
9238
+ reset: f("\x1B[0m", "\x1B[0m"),
9239
+ bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
9240
+ dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
9241
+ italic: f("\x1B[3m", "\x1B[23m"),
9242
+ underline: f("\x1B[4m", "\x1B[24m"),
9243
+ inverse: f("\x1B[7m", "\x1B[27m"),
9244
+ hidden: f("\x1B[8m", "\x1B[28m"),
9245
+ strikethrough: f("\x1B[9m", "\x1B[29m"),
9246
+ black: f("\x1B[30m", "\x1B[39m"),
9247
+ red: f("\x1B[31m", "\x1B[39m"),
9248
+ green: f("\x1B[32m", "\x1B[39m"),
9249
+ yellow: f("\x1B[33m", "\x1B[39m"),
9250
+ blue: f("\x1B[34m", "\x1B[39m"),
9251
+ magenta: f("\x1B[35m", "\x1B[39m"),
9252
+ cyan: f("\x1B[36m", "\x1B[39m"),
9253
+ white: f("\x1B[37m", "\x1B[39m"),
9254
+ gray: f("\x1B[90m", "\x1B[39m"),
9255
+ bgBlack: f("\x1B[40m", "\x1B[49m"),
9256
+ bgRed: f("\x1B[41m", "\x1B[49m"),
9257
+ bgGreen: f("\x1B[42m", "\x1B[49m"),
9258
+ bgYellow: f("\x1B[43m", "\x1B[49m"),
9259
+ bgBlue: f("\x1B[44m", "\x1B[49m"),
9260
+ bgMagenta: f("\x1B[45m", "\x1B[49m"),
9261
+ bgCyan: f("\x1B[46m", "\x1B[49m"),
9262
+ bgWhite: f("\x1B[47m", "\x1B[49m"),
9263
+ blackBright: f("\x1B[90m", "\x1B[39m"),
9264
+ redBright: f("\x1B[91m", "\x1B[39m"),
9265
+ greenBright: f("\x1B[92m", "\x1B[39m"),
9266
+ yellowBright: f("\x1B[93m", "\x1B[39m"),
9267
+ blueBright: f("\x1B[94m", "\x1B[39m"),
9268
+ magentaBright: f("\x1B[95m", "\x1B[39m"),
9269
+ cyanBright: f("\x1B[96m", "\x1B[39m"),
9270
+ whiteBright: f("\x1B[97m", "\x1B[39m"),
9271
+ bgBlackBright: f("\x1B[100m", "\x1B[49m"),
9272
+ bgRedBright: f("\x1B[101m", "\x1B[49m"),
9273
+ bgGreenBright: f("\x1B[102m", "\x1B[49m"),
9274
+ bgYellowBright: f("\x1B[103m", "\x1B[49m"),
9275
+ bgBlueBright: f("\x1B[104m", "\x1B[49m"),
9276
+ bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
9277
+ bgCyanBright: f("\x1B[106m", "\x1B[49m"),
9278
+ bgWhiteBright: f("\x1B[107m", "\x1B[49m")
9279
+ };
9280
+ };
9281
+ module2.exports = createColors();
9282
+ module2.exports.createColors = createColors;
9283
+ }
9284
+ });
9285
+
9286
+ // src/ui.ts
9287
+ function hasCliNoColor() {
9288
+ try {
9289
+ return process.argv.slice(2).includes("--no-color");
9290
+ } catch {
9291
+ return false;
9292
+ }
9293
+ }
9294
+ function createUI(options) {
9295
+ const isColor = options.isTTY && !options.noColor;
9296
+ const wrap = (fn) => (s) => isColor ? fn(s) : s;
9297
+ const format = {
9298
+ step: (msg) => `${isColor ? import_picocolors.default.dim("\u203A") : "\u203A"} ${isColor ? import_picocolors.default.dim(msg) : msg}`,
9299
+ success: (msg) => `${isColor ? import_picocolors.default.green("\u2713") : "\u2713"} ${msg}`,
9300
+ error: (msg) => `${isColor ? import_picocolors.default.red("\u2717") : "\u2717"} ${msg}`,
9301
+ dim: wrap(import_picocolors.default.dim),
9302
+ bold: wrap(import_picocolors.default.bold),
9303
+ commitType: wrap(import_picocolors.default.cyan),
9304
+ commitScope: wrap(import_picocolors.default.yellow)
9305
+ };
9306
+ function createSpinner(message, write = (s) => process.stderr.write(s)) {
9307
+ let frame = 0;
9308
+ let interval = null;
9309
+ return {
9310
+ start() {
9311
+ if (interval) return;
9312
+ if (!options.isTTY) return;
9313
+ interval = setInterval(() => {
9314
+ const f = SPINNER_FRAMES2[frame++ % SPINNER_FRAMES2.length];
9315
+ write(`\r${format.step(message)} ${isColor ? import_picocolors.default.cyan(f) : f}`);
9316
+ }, 80);
9317
+ },
9318
+ stop(finalMessage) {
9319
+ if (interval) {
9320
+ clearInterval(interval);
9321
+ interval = null;
9322
+ }
9323
+ if (options.isTTY) {
9324
+ write("\r\x1B[2K");
9325
+ }
9326
+ if (finalMessage) {
9327
+ write(finalMessage + "\n");
9328
+ }
9329
+ }
9330
+ };
9331
+ }
9332
+ const log = {
9333
+ step: (msg) => process.stderr.write(format.step(msg) + "\n"),
9334
+ success: (msg) => process.stderr.write(format.success(msg) + "\n"),
9335
+ error: (msg) => process.stderr.write(format.error(msg) + "\n"),
9336
+ dim: (msg) => process.stderr.write(format.dim(msg) + "\n")
9337
+ };
9338
+ return { isColor, format, spinner: createSpinner, log };
9339
+ }
9340
+ function getUI() {
9341
+ if (!_defaultUI) {
9342
+ _defaultUI = createUI({
9343
+ isTTY: !!process.stderr.isTTY,
9344
+ noColor: !!process.env.NO_COLOR || hasCliNoColor()
9345
+ });
9346
+ }
9347
+ return _defaultUI;
9348
+ }
9349
+ var import_picocolors, SPINNER_FRAMES2, _defaultUI, ui;
9350
+ var init_ui = __esm({
9351
+ "src/ui.ts"() {
9352
+ "use strict";
9353
+ import_picocolors = __toESM(require_picocolors());
9354
+ SPINNER_FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
9355
+ ui = new Proxy({}, {
9356
+ get(_target, prop) {
9357
+ return getUI()[prop];
9358
+ }
9359
+ });
9360
+ }
9361
+ });
9362
+
9363
+ // src/commit-helpers.ts
9364
+ function applyCliTypeScopeToRules(rules, type, scope) {
9365
+ let next = { ...rules };
9366
+ if (type) {
9367
+ next = { ...next, types: [type] };
9368
+ }
9369
+ if (scope) {
9370
+ next = { ...next, scopes: [scope] };
9371
+ }
9372
+ return next;
9373
+ }
9374
+ function generationHintsFromArgs(split, forceBody) {
9375
+ const h = {};
9376
+ if (split) h.split = true;
9377
+ if (forceBody) h.force_body = true;
9378
+ return Object.keys(h).length > 0 ? h : void 0;
9379
+ }
9380
+ function splitCommitMessageForDisplay(message) {
9381
+ const t = message.replace(/\r\n/g, "\n").trimEnd();
9382
+ const doubleNl = t.indexOf("\n\n");
9383
+ if (doubleNl !== -1) {
9384
+ const head = t.slice(0, doubleNl);
9385
+ const subject = head.split("\n")[0]?.trim() ?? "";
9386
+ return { subject, body: t.slice(doubleNl + 2).trimEnd() };
9387
+ }
9388
+ const firstNl = t.indexOf("\n");
9389
+ if (firstNl === -1) {
9390
+ return { subject: t.trim(), body: "" };
9391
+ }
9392
+ return {
9393
+ subject: t.slice(0, firstNl).trim(),
9394
+ body: t.slice(firstNl + 1).trimEnd()
9395
+ };
9396
+ }
9397
+ function formatVerboseCommitDiagnostics(diagnostics, roundTripMs) {
9398
+ const lines = [`api_round_trip_ms: ${roundTripMs}`];
9399
+ if (diagnostics !== void 0) {
9400
+ lines.push(JSON.stringify(diagnostics, null, 2));
9401
+ }
9402
+ return lines.join("\n");
9403
+ }
9404
+ async function interactiveRefineMessage(initial, opts) {
9405
+ if (opts.skip) return { action: "accept", message: initial };
9406
+ const rl = import_promises.default.createInterface({ input: process.stdin, output: process.stderr });
9407
+ try {
9408
+ process.stderr.write(`
9409
+ ${initial}
9410
+
9411
+ `);
9412
+ const choice = (await rl.question("Keep? [Y/n/e]: ")).trim().toLowerCase();
9413
+ if (choice === "n") {
9414
+ return { action: "abort" };
9415
+ }
9416
+ if (choice === "e") {
9417
+ process.stderr.write("Enter new message (end with a line containing only .):\n");
9418
+ const lines = [];
9419
+ while (true) {
9420
+ const line = await rl.question("");
9421
+ if (line === ".") break;
9422
+ lines.push(line);
9423
+ }
9424
+ const edited = lines.join("\n").trim();
9425
+ return { action: "edit", message: edited.length > 0 ? edited : initial };
9426
+ }
9427
+ return { action: "accept", message: initial };
9428
+ } finally {
9429
+ rl.close();
9430
+ }
9431
+ }
9432
+ async function confirmCommit(prompt2, opts) {
9433
+ if (opts.skip) return { action: "commit" };
9434
+ const rl = import_promises.default.createInterface({ input: process.stdin, output: process.stderr });
9435
+ try {
9436
+ const ans = (await rl.question(prompt2)).trim().toLowerCase();
9437
+ if (ans !== "y" && ans !== "yes") {
9438
+ return { action: "abort" };
8987
9439
  }
8988
- } catch {
8989
- console.log(`Visit: ${BILLING_URL}`);
9440
+ return { action: "commit" };
9441
+ } finally {
9442
+ rl.close();
8990
9443
  }
8991
9444
  }
8992
- var BILLING_URL;
8993
- var init_upgrade = __esm({
8994
- "src/commands/upgrade.ts"() {
9445
+ function shouldSkipTTYInteraction(hookMode) {
9446
+ return hookMode === true || process.stdin.isTTY !== true;
9447
+ }
9448
+ function logVerboseDiagnostics(dim, verbose, quiet, diagnostics, roundTripMs) {
9449
+ if (!verbose || quiet) return;
9450
+ process.stderr.write(
9451
+ `
9452
+ ${formatVerboseCommitDiagnostics(diagnostics, roundTripMs)}
9453
+ `
9454
+ );
9455
+ dim("(verbose diagnostics on stderr)");
9456
+ }
9457
+ function createSilentLog() {
9458
+ return {
9459
+ step: () => {
9460
+ },
9461
+ success: () => {
9462
+ },
9463
+ error: (msg) => console.error(msg),
9464
+ dim: () => {
9465
+ }
9466
+ };
9467
+ }
9468
+ function displayCommitMessage(message, log) {
9469
+ const { subject, body } = splitCommitMessageForDisplay(message);
9470
+ log.success(subject);
9471
+ if (body) {
9472
+ for (const line of body.split("\n")) {
9473
+ log.dim(` ${line}`);
9474
+ }
9475
+ process.stderr.write("\n");
9476
+ }
9477
+ }
9478
+ var import_promises;
9479
+ var init_commit_helpers = __esm({
9480
+ "src/commit-helpers.ts"() {
8995
9481
  "use strict";
8996
- BILLING_URL = "https://app.quikcommit.dev/billing";
9482
+ import_promises = __toESM(require("node:readline/promises"));
8997
9483
  }
8998
9484
  });
8999
9485
 
@@ -9048,7 +9534,7 @@ function getLocalProviderConfig() {
9048
9534
  if (provider === "openrouter" && !apiKey) return null;
9049
9535
  return { provider, baseUrl, model, apiKey };
9050
9536
  }
9051
- function buildUserPrompt(changes, diff, rules) {
9537
+ function buildUserPrompt(changes, diff, rules, recentCommits, hints) {
9052
9538
  let prompt2 = `Generate a commit message for these changes:
9053
9539
 
9054
9540
  ## File changes:
@@ -9062,6 +9548,23 @@ ${diff}
9062
9548
  </diff>
9063
9549
 
9064
9550
  `;
9551
+ if (recentCommits && recentCommits.length > 0) {
9552
+ const history = recentCommits.slice(0, 10).join("\n");
9553
+ prompt2 += `Recent commits on this branch (match style when appropriate):
9554
+ ${history}
9555
+
9556
+ `;
9557
+ }
9558
+ if (hints?.split) {
9559
+ prompt2 += `MULTI-COMMIT MODE: If changes span multiple logical commits, focus the message on the primary change and mention other slices in the body.
9560
+
9561
+ `;
9562
+ }
9563
+ if (hints?.force_body) {
9564
+ prompt2 += `The user requires a BODY section after the subject line, even for small changes.
9565
+
9566
+ `;
9567
+ }
9065
9568
  if (rules && Object.keys(rules).length > 0) {
9066
9569
  prompt2 += `Rules: ${JSON.stringify(rules)}
9067
9570
 
@@ -9072,7 +9575,7 @@ ${diff}
9072
9575
  - Response should be the commit message only, no explanations`;
9073
9576
  return prompt2;
9074
9577
  }
9075
- function buildRequest(provider, baseUrl, userContent, diff, changes, model, apiKey, rules) {
9578
+ function buildRequest(provider, baseUrl, userContent, diff, changes, model, apiKey, rules, recentCommits, hints) {
9076
9579
  const headers = {
9077
9580
  "Content-Type": "application/json"
9078
9581
  };
@@ -9124,10 +9627,18 @@ function buildRequest(provider, baseUrl, userContent, diff, changes, model, apiK
9124
9627
  ]
9125
9628
  };
9126
9629
  return { url, body, headers };
9127
- case "cloudflare":
9630
+ case "cloudflare": {
9128
9631
  url = `${baseUrl.replace(/\/$/, "")}/commit`;
9129
- body = { diff, changes, rules };
9632
+ const payload = { diff, changes, rules };
9633
+ if (recentCommits && recentCommits.length > 0) {
9634
+ payload.recent_commits = recentCommits.slice(0, 10);
9635
+ }
9636
+ if (hints && Object.keys(hints).length > 0) {
9637
+ payload.generation_hints = hints;
9638
+ }
9639
+ body = payload;
9130
9640
  return { url, body, headers: { "Content-Type": "application/json" } };
9641
+ }
9131
9642
  default:
9132
9643
  throw new Error(`Unknown provider: ${provider}`);
9133
9644
  }
@@ -9149,7 +9660,9 @@ function parseResponse(provider, data) {
9149
9660
  return "";
9150
9661
  }
9151
9662
  }
9152
- async function runLocalCommit(messageOnly, push, modelFlag) {
9663
+ async function runLocalCommit(args) {
9664
+ const silent = !!(args.hookMode || args.quiet);
9665
+ const log = silent ? createSilentLog() : getUI().log;
9153
9666
  if (!isGitRepo()) {
9154
9667
  throw new Error("Not a git repository.");
9155
9668
  }
@@ -9163,11 +9676,19 @@ async function runLocalCommit(messageOnly, push, modelFlag) {
9163
9676
  );
9164
9677
  }
9165
9678
  const config2 = getConfig();
9166
- const excludes = config2.excludes ?? [];
9167
- const diff = getStagedDiff(excludes);
9679
+ const excludes = [...config2.excludes ?? [], ...args.exclude];
9680
+ let diff = getStagedDiff(excludes);
9168
9681
  const changes = getStagedFiles();
9169
- const model = modelFlag ?? local.model;
9170
- let rules = config2.rules ?? {};
9682
+ if (!args.noSmartDiff) {
9683
+ const smartResult = preprocessDiff(diff);
9684
+ diff = smartResult.processedDiff;
9685
+ if (smartResult.summarized.length > 0 && !silent) {
9686
+ log.step(
9687
+ `smart-diff: ${smartResult.summarized.length} file(s) summarized (saved ~${Math.round(smartResult.tokensSaved / 1e3)}K tokens)`
9688
+ );
9689
+ }
9690
+ }
9691
+ let rules = { ...await detectCommitlintRules(), ...config2.rules ?? {} };
9171
9692
  const workspace = detectWorkspace();
9172
9693
  if (workspace) {
9173
9694
  const stagedFiles = changes.trim().split("\n").filter(Boolean);
@@ -9177,7 +9698,20 @@ async function runLocalCommit(messageOnly, push, modelFlag) {
9177
9698
  rules = { ...rules, scopes };
9178
9699
  }
9179
9700
  }
9180
- const userContent = buildUserPrompt(changes, diff, rules);
9701
+ rules = applyCliTypeScopeToRules(rules, args.type, args.scope);
9702
+ const recentCommits = args.noContext ? void 0 : getRecentBranchCommits(5);
9703
+ const generationHints = generationHintsFromArgs(args.split, args.forceBody);
9704
+ const skipInteractive = silent || args.quiet || shouldSkipTTYInteraction(args.hookMode);
9705
+ const skipConfirm = args.dryRun || args.messageOnly || silent || args.quiet || shouldSkipTTYInteraction(args.hookMode);
9706
+ const model = args.model ?? local.model;
9707
+ const modelDisplay = model ?? local.model ?? "default";
9708
+ const userContent = buildUserPrompt(
9709
+ changes,
9710
+ diff,
9711
+ Object.keys(rules).length > 0 ? rules : void 0,
9712
+ recentCommits,
9713
+ generationHints
9714
+ );
9181
9715
  const { url, body, headers } = buildRequest(
9182
9716
  local.provider,
9183
9717
  local.baseUrl,
@@ -9186,18 +9720,29 @@ async function runLocalCommit(messageOnly, push, modelFlag) {
9186
9720
  changes,
9187
9721
  model,
9188
9722
  local.apiKey,
9189
- rules
9723
+ rules,
9724
+ recentCommits,
9725
+ generationHints
9190
9726
  );
9191
9727
  if (!url || url.includes("YOUR-WORKER")) {
9192
9728
  throw new Error(
9193
9729
  "Cloudflare provider requires api_url. Run: qc config set api_url https://your-worker.workers.dev"
9194
9730
  );
9195
9731
  }
9196
- const res = await fetch(url, {
9197
- method: "POST",
9198
- headers,
9199
- body: JSON.stringify(body)
9200
- });
9732
+ const spinner = getUI().spinner(`generating commit (${modelDisplay} via ${local.provider})...`);
9733
+ if (!silent) spinner.start();
9734
+ const t0 = Date.now();
9735
+ let res;
9736
+ try {
9737
+ res = await fetch(url, {
9738
+ method: "POST",
9739
+ headers,
9740
+ body: JSON.stringify(body)
9741
+ });
9742
+ } finally {
9743
+ spinner.stop();
9744
+ }
9745
+ const roundTripMs = Date.now() - t0;
9201
9746
  if (!res.ok) {
9202
9747
  const text = await res.text();
9203
9748
  throw new Error(`Provider error (${res.status}): ${text}`);
@@ -9208,12 +9753,37 @@ async function runLocalCommit(messageOnly, push, modelFlag) {
9208
9753
  if (!message) {
9209
9754
  throw new Error("Failed to generate commit message.");
9210
9755
  }
9211
- if (messageOnly) {
9756
+ const diagnostics = local.provider === "cloudflare" && typeof data === "object" && data !== null ? data.diagnostics : void 0;
9757
+ logVerboseDiagnostics((msg) => log.dim(msg), args.verbose, args.quiet, diagnostics, roundTripMs);
9758
+ if (args.interactive) {
9759
+ if (shouldSkipTTYInteraction(args.hookMode)) {
9760
+ if (!silent) log.dim("(--interactive ignored: not running in a TTY)");
9761
+ } else {
9762
+ const refineResult = await interactiveRefineMessage(message, { skip: skipInteractive });
9763
+ if (refineResult.action === "abort") {
9764
+ process.exit(0);
9765
+ }
9766
+ message = refineResult.message;
9767
+ }
9768
+ }
9769
+ if (args.messageOnly) {
9212
9770
  console.log(message);
9213
9771
  return;
9214
9772
  }
9773
+ if (!silent) {
9774
+ displayCommitMessage(message, log);
9775
+ }
9776
+ if (args.dryRun) {
9777
+ return;
9778
+ }
9779
+ if (args.confirm) {
9780
+ const confirmResult = await confirmCommit("Proceed with commit? [y/N]: ", { skip: skipConfirm });
9781
+ if (confirmResult.action === "abort") {
9782
+ process.exit(0);
9783
+ }
9784
+ }
9215
9785
  gitCommit(message);
9216
- if (push) {
9786
+ if (args.push) {
9217
9787
  gitPush();
9218
9788
  }
9219
9789
  }
@@ -9228,6 +9798,10 @@ var init_local = __esm({
9228
9798
  init_dist();
9229
9799
  init_git();
9230
9800
  init_monorepo();
9801
+ init_commitlint();
9802
+ init_smart_diff();
9803
+ init_ui();
9804
+ init_commit_helpers();
9231
9805
  CONFIG_PATH2 = (0, import_path10.join)((0, import_os4.homedir)(), CONFIG_DIR);
9232
9806
  PROVIDER_URLS = {
9233
9807
  ollama: "http://localhost:11434",
@@ -9246,166 +9820,55 @@ var init_local = __esm({
9246
9820
  }
9247
9821
  });
9248
9822
 
9249
- // src/index.ts
9250
- init_config();
9251
- init_api();
9252
- init_commitlint();
9253
- init_git();
9254
- init_monorepo();
9255
- var HELP = `Quikcommit - AI-powered conventional commit messages
9256
-
9257
- Usage:
9258
- qc Generate commit message and commit (default)
9259
- qc --message-only Generate message only, print to stdout
9260
- qc --push Commit and push to origin
9261
- qc pr Generate PR description from branch commits
9262
- qc changelog Generate changelog from commits since last tag
9263
- qc changeset Automate pnpm changeset with AI
9264
- qc init Install prepare-commit-msg hook for auto-generation
9265
- qc login Sign in via browser
9266
- qc logout Clear local credentials
9267
- qc status Show auth, plan, usage
9268
- qc team Team management (info, rules, invite)
9269
-
9270
- Options:
9271
- -h, --help Show this help
9272
- -a, --all Stage all tracked changes before generating
9273
- -m, --message-only Generate message only
9274
- -p, --push Commit and push after generating
9275
- --api-key <key> Use this API key (overrides credentials file)
9276
- --base <branch> Base branch for qc pr, qc changeset (default: main)
9277
- --create Create PR with gh CLI after qc pr
9278
- --from <ref> Start ref for qc changelog (default: latest tag)
9279
- --to <ref> End ref for qc changelog (default: HEAD)
9280
- --write Prepend changelog to CHANGELOG.md
9281
- --version <ver> Version label for changelog header (default: derived from --to or "<from>-next")
9282
- --uninstall Remove Quikcommit hook (qc init --uninstall)
9283
- --model <id> Use specific model (e.g. qwen25-coder-32b, llama-3.3-70b)
9284
-
9285
- Commands:
9286
- qc config Show current config
9287
- qc config set <k> <v> Set config (model, api_url)
9288
- qc config reset Reset to defaults
9289
- qc upgrade Open billing page in browser
9290
- `;
9291
- function parseArgs(args) {
9292
- let command = "commit";
9293
- let all = false;
9294
- let messageOnly = false;
9295
- let push = false;
9296
- let apiKey;
9297
- let model;
9298
- let local = false;
9299
- let base;
9300
- let create = false;
9301
- let from;
9302
- let to;
9303
- let write = false;
9304
- let version;
9305
- let uninstall = false;
9306
- let hookMode = false;
9307
- for (let i = 0; i < args.length; i++) {
9308
- const arg = args[i];
9309
- if (arg === "-h" || arg === "--help") {
9310
- command = "help";
9311
- } else if (arg === "-a" || arg === "--all") {
9312
- all = true;
9313
- } else if (arg === "-m" || arg === "--message-only") {
9314
- messageOnly = true;
9315
- } else if (arg === "-p" || arg === "--push") {
9316
- push = true;
9317
- } else if (arg === "--api-key" && i + 1 < args.length) {
9318
- apiKey = args[++i];
9319
- } else if (arg === "--base" && i + 1 < args.length) {
9320
- base = args[++i];
9321
- } else if (arg === "--create") {
9322
- create = true;
9323
- } else if (arg === "--from" && i + 1 < args.length) {
9324
- from = args[++i];
9325
- } else if (arg === "--to" && i + 1 < args.length) {
9326
- to = args[++i];
9327
- } else if (arg === "--write") {
9328
- write = true;
9329
- } else if (arg === "--version" && i + 1 < args.length) {
9330
- version = args[++i];
9331
- } else if (arg === "--uninstall") {
9332
- uninstall = true;
9333
- } else if (arg === "--hook-mode") {
9334
- hookMode = true;
9335
- } else if (arg === "login") {
9336
- command = "login";
9337
- } else if (arg === "logout") {
9338
- command = "logout";
9339
- } else if (arg === "status") {
9340
- command = "status";
9341
- } else if (arg === "pr") {
9342
- command = "pr";
9343
- } else if (arg === "changelog") {
9344
- command = "changelog";
9345
- } else if (arg === "init") {
9346
- command = "init";
9347
- } else if (arg === "team") {
9348
- command = "team";
9349
- } else if (arg === "config") {
9350
- command = "config";
9351
- } else if (arg === "upgrade") {
9352
- command = "upgrade";
9353
- } else if (arg === "changeset") {
9354
- command = "changeset";
9355
- } else if (arg === "--model" && i + 1 < args.length) {
9356
- model = args[++i];
9357
- } else if (arg === "--local" || arg === "--use-ollama" || arg === "--use-lmstudio" || arg === "--use-openrouter" || arg === "--use-cloudflare") {
9358
- local = true;
9359
- if (arg === "--use-ollama") {
9360
- saveConfig({ ...getConfig(), provider: "ollama", apiUrl: "http://localhost:11434", model: "codellama" });
9361
- } else if (arg === "--use-lmstudio") {
9362
- saveConfig({ ...getConfig(), provider: "lmstudio", apiUrl: "http://localhost:1234/v1", model: "default" });
9363
- } else if (arg === "--use-openrouter") {
9364
- saveConfig({ ...getConfig(), provider: "openrouter", apiUrl: "https://openrouter.ai/api/v1", model: "google/gemini-flash-1.5-8b" });
9365
- } else if (arg === "--use-cloudflare") {
9366
- saveConfig({
9367
- ...getConfig(),
9368
- provider: "cloudflare",
9369
- apiUrl: "https://YOUR-WORKER.workers.dev",
9370
- model: "@cf/qwen/qwen2.5-coder-32b-instruct"
9371
- });
9372
- console.error(
9373
- "[qc] Cloudflare provider set. Run: qc config set api_url https://your-worker.workers.dev"
9374
- );
9375
- }
9376
- }
9377
- }
9378
- return { command, all, messageOnly, push, apiKey, base, create, from, to, write, version, uninstall, hookMode, model, local };
9379
- }
9380
- async function runCommit(messageOnly, push, apiKeyFlag, hookMode = false, modelFlag, stageAll_) {
9381
- const log = hookMode ? () => {
9382
- } : (msg) => console.error(msg);
9823
+ // src/commands/commit.ts
9824
+ var commit_exports = {};
9825
+ __export(commit_exports, {
9826
+ runCommit: () => runCommit
9827
+ });
9828
+ async function runCommit(args) {
9829
+ const { messageOnly, push, apiKey: apiKeyFlag, hookMode, model: modelFlag, all } = args;
9830
+ const silent = !!(hookMode || args.quiet);
9831
+ const log = silent ? createSilentLog() : getUI().log;
9383
9832
  if (!isGitRepo()) {
9384
- log("Error: Not a git repository.");
9833
+ log.error("Not a git repository.");
9385
9834
  process.exit(1);
9386
9835
  }
9387
9836
  const config2 = getConfig();
9388
- if (stageAll_ || config2.autoStage) {
9837
+ if (all || config2.autoStage) {
9389
9838
  stageAll();
9839
+ const { files, total } = getShortStagedFiles();
9840
+ const fileList = total > 3 ? `${files.join(", ")}, +${total - 3} more` : files.join(", ");
9841
+ log.step(`staging working tree (${total} file(s))...`);
9842
+ if (fileList) log.dim(` ${fileList}`);
9390
9843
  }
9391
9844
  if (!hasStagedChanges()) {
9392
9845
  const unstaged = getUnstagedFiles();
9393
9846
  if (unstaged.length > 0) {
9394
- log("Error: No staged changes. Use `qc -a` to stage tracked files, or `git add` manually.");
9847
+ log.error("No staged changes. Use `qc -a` to stage tracked files, or `git add` manually.");
9395
9848
  } else {
9396
- log("Error: No changes to commit.");
9849
+ log.error("No changes to commit.");
9397
9850
  }
9398
9851
  process.exit(1);
9399
9852
  }
9400
9853
  const apiKey = apiKeyFlag ?? getApiKey();
9401
9854
  if (!apiKey) {
9402
- log("Error: Not authenticated. Run `qc login` first.");
9855
+ log.error("Not authenticated. Run `qc login` first.");
9403
9856
  process.exit(1);
9404
9857
  }
9405
9858
  const model = modelFlag ?? config2.model;
9406
- const excludes = config2.excludes ?? [];
9859
+ const excludes = [...config2.excludes ?? [], ...args.exclude];
9407
9860
  const diff = getStagedDiff(excludes);
9408
9861
  const changes = getStagedFiles();
9862
+ let processedDiff = diff;
9863
+ if (!args.noSmartDiff) {
9864
+ const smartResult = preprocessDiff(diff);
9865
+ processedDiff = smartResult.processedDiff;
9866
+ if (smartResult.summarized.length > 0) {
9867
+ log.step(
9868
+ `smart-diff: ${smartResult.summarized.length} file(s) summarized (saved ~${Math.round(smartResult.tokensSaved / 1e3)}K tokens)`
9869
+ );
9870
+ }
9871
+ }
9409
9872
  const commitlintRules = await detectCommitlintRules();
9410
9873
  let rules = { ...commitlintRules, ...config2.rules ?? {} };
9411
9874
  const workspace = detectWorkspace();
@@ -9422,7 +9885,7 @@ async function runCommit(messageOnly, push, apiKeyFlag, hookMode = false, modelF
9422
9885
  try {
9423
9886
  const teamRules = await client.getTeamRules();
9424
9887
  if (teamRules && Object.keys(teamRules).length > 0) {
9425
- log("[qc] Using team rules from org");
9888
+ log.step("using team rules from org");
9426
9889
  rules = { ...rules, ...teamRules };
9427
9890
  if (monorepoScopes && teamRules.scopes && teamRules.scopes.length > 0) {
9428
9891
  const allowed = new Set(teamRules.scopes);
@@ -9432,24 +9895,393 @@ async function runCommit(messageOnly, push, apiKeyFlag, hookMode = false, modelF
9432
9895
  }
9433
9896
  } catch {
9434
9897
  }
9435
- const { message } = await client.generateCommit(diff, changes, rules, model);
9898
+ rules = applyCliTypeScopeToRules(rules, args.type, args.scope);
9899
+ const recentCommits = args.noContext ? void 0 : getRecentBranchCommits(5);
9900
+ const generationHints = generationHintsFromArgs(args.split, args.forceBody);
9901
+ const skipInteractive = silent || args.quiet || shouldSkipTTYInteraction(args.hookMode);
9902
+ const skipConfirm = args.dryRun || messageOnly || silent || args.quiet || shouldSkipTTYInteraction(args.hookMode);
9903
+ const modelDisplay = model ?? "default";
9904
+ const spinner = getUI().spinner(`generating commit (${modelDisplay})...`);
9905
+ if (!silent) spinner.start();
9906
+ const t0 = Date.now();
9907
+ let generatedMessage;
9908
+ let diagnostics;
9909
+ try {
9910
+ ({ message: generatedMessage, diagnostics } = await client.generateCommit(
9911
+ processedDiff,
9912
+ changes,
9913
+ rules,
9914
+ model,
9915
+ recentCommits,
9916
+ generationHints
9917
+ ));
9918
+ } finally {
9919
+ spinner.stop();
9920
+ }
9921
+ const roundTripMs = Date.now() - t0;
9922
+ logVerboseDiagnostics((msg) => log.dim(msg), args.verbose, args.quiet, diagnostics, roundTripMs);
9923
+ let message = generatedMessage;
9924
+ if (args.interactive) {
9925
+ if (shouldSkipTTYInteraction(args.hookMode)) {
9926
+ if (!silent) log.dim("(--interactive ignored: not running in a TTY)");
9927
+ } else {
9928
+ const refineResult = await interactiveRefineMessage(message, { skip: skipInteractive });
9929
+ if (refineResult.action === "abort") {
9930
+ process.exit(0);
9931
+ }
9932
+ message = refineResult.message;
9933
+ }
9934
+ }
9436
9935
  if (messageOnly) {
9437
9936
  console.log(message);
9438
9937
  return;
9439
9938
  }
9939
+ displayCommitMessage(message, log);
9940
+ if (args.dryRun) {
9941
+ return;
9942
+ }
9943
+ if (args.confirm) {
9944
+ const confirmResult = await confirmCommit("Proceed with commit? [y/N]: ", { skip: skipConfirm });
9945
+ if (confirmResult.action === "abort") {
9946
+ process.exit(0);
9947
+ }
9948
+ }
9440
9949
  gitCommit(message);
9950
+ const hash = getCommitHash();
9951
+ const branch = getCurrentBranch();
9952
+ log.step(`[${branch} ${hash}] committed`);
9441
9953
  if (push) {
9954
+ log.step(`pushing to origin/${branch}...`);
9442
9955
  gitPush();
9956
+ const stats = getPushStats();
9957
+ if (stats) {
9958
+ log.success(`pushed ${stats.commits} commit(s) \xB7 ${stats.stat}`);
9959
+ } else {
9960
+ log.success("pushed");
9961
+ }
9962
+ }
9963
+ }
9964
+ var init_commit = __esm({
9965
+ "src/commands/commit.ts"() {
9966
+ "use strict";
9967
+ init_config();
9968
+ init_api();
9969
+ init_commitlint();
9970
+ init_git();
9971
+ init_monorepo();
9972
+ init_ui();
9973
+ init_smart_diff();
9974
+ init_commit_helpers();
9975
+ }
9976
+ });
9977
+
9978
+ // src/index.ts
9979
+ var index_exports = {};
9980
+ __export(index_exports, {
9981
+ parseArgs: () => parseArgs
9982
+ });
9983
+ module.exports = __toCommonJS(index_exports);
9984
+ init_config();
9985
+ var HELP = `Quikcommit - AI-powered conventional commit messages
9986
+
9987
+ Usage:
9988
+ qc Generate commit message and commit (default)
9989
+ qc pr Generate PR description from branch commits
9990
+ qc changelog Generate changelog from commits since last tag
9991
+ qc changeset Automate pnpm changeset with AI
9992
+ qc init Install prepare-commit-msg hook
9993
+ qc login Sign in via browser
9994
+ qc logout Clear local credentials
9995
+ qc status Show auth, plan, usage
9996
+ qc team Team management (info, rules, invite)
9997
+ qc config Show/set config
9998
+
9999
+ Flags:
10000
+ -p, --push Commit and push
10001
+ -a, --all Stage all tracked changes first
10002
+ -m, --message-only Print message only (stdout, no commit)
10003
+ -v, --verbose Show diagnostics (model, token estimates, rules) + API round-trip ms on stderr
10004
+ -q, --quiet Minimal output
10005
+ -n, --dry-run Show message without committing
10006
+ -i, --interactive Interactive refinement mode
10007
+ -s, --split Multi-commit split mode
10008
+ -b, --body Force include body
10009
+ -l, --local Use local provider
10010
+ -c, --confirm Ask before committing
10011
+ -t, --type <type> Force commit type
10012
+ -S, --scope <scope> Force scope
10013
+ -e, --exclude <pat> Exclude files from diff (repeatable)
10014
+
10015
+ --no-context Skip commit history context
10016
+ --no-smart-diff Skip smart diff preprocessing
10017
+ --no-color Disable colors
10018
+ --model <id> Use specific model
10019
+ --base <branch> Base branch for pr/changeset (default: main)
10020
+ --create Create PR with gh CLI (qc pr --create)
10021
+ --from <ref> Start ref for changelog
10022
+ --to <ref> End ref for changelog
10023
+ --write Write changelog to CHANGELOG.md
10024
+ --hook-mode Silent mode for git hooks
10025
+
10026
+ Compose short flags: qc -ap (stage all + push), qc -apv (+ verbose)
10027
+
10028
+ Examples:
10029
+ qc # generate and commit
10030
+ qc -p # commit and push
10031
+ qc -ap # stage all, commit, push
10032
+ qc -m | pbcopy # copy message to clipboard
10033
+ qc -n # preview without committing
10034
+ qc -e "*.lock" # exclude lock files
10035
+ qc -t fix -S auth # force type and scope
10036
+ `;
10037
+ var SHORT_FLAGS = {
10038
+ p: "push",
10039
+ a: "all",
10040
+ m: "messageOnly",
10041
+ v: "verbose",
10042
+ q: "quiet",
10043
+ n: "dryRun",
10044
+ i: "interactive",
10045
+ s: "split",
10046
+ b: "forceBody",
10047
+ l: "local",
10048
+ c: "confirm"
10049
+ };
10050
+ var SHORT_FLAGS_WITH_VALUE = {
10051
+ t: "type",
10052
+ S: "scope",
10053
+ e: "exclude"
10054
+ };
10055
+ function parseArgs(args) {
10056
+ const result = {
10057
+ command: "commit",
10058
+ all: false,
10059
+ messageOnly: false,
10060
+ push: false,
10061
+ verbose: false,
10062
+ quiet: false,
10063
+ dryRun: false,
10064
+ interactive: false,
10065
+ split: false,
10066
+ forceBody: false,
10067
+ confirm: false,
10068
+ noContext: false,
10069
+ noSmartDiff: false,
10070
+ local: false,
10071
+ exclude: [],
10072
+ positionals: []
10073
+ };
10074
+ let subcommandSeen = false;
10075
+ for (let i = 0; i < args.length; i++) {
10076
+ const arg = args[i];
10077
+ if (arg === void 0) continue;
10078
+ if (arg.startsWith("-") && !arg.startsWith("--") && arg.length > 2) {
10079
+ const chars = [...arg.slice(1)];
10080
+ for (let j = 0; j < chars.length; j++) {
10081
+ const ch = chars[j];
10082
+ if (!ch) continue;
10083
+ if (SHORT_FLAGS[ch]) {
10084
+ const key = SHORT_FLAGS[ch];
10085
+ result[key] = true;
10086
+ } else if (SHORT_FLAGS_WITH_VALUE[ch]) {
10087
+ if (j < chars.length - 1) {
10088
+ throw new Error(`Flag -${ch} requires a value and must be last in a composed group`);
10089
+ }
10090
+ const val = args[++i];
10091
+ if (!val || val.startsWith("-") && val.length > 1) throw new Error(`Flag -${ch} requires a value`);
10092
+ const key = SHORT_FLAGS_WITH_VALUE[ch];
10093
+ if (key === "exclude") {
10094
+ result.exclude.push(val);
10095
+ } else {
10096
+ result[key] = val;
10097
+ }
10098
+ } else if (ch === "h") {
10099
+ result.command = "help";
10100
+ } else {
10101
+ throw new Error(`Unknown flag: -${ch}`);
10102
+ }
10103
+ }
10104
+ continue;
10105
+ }
10106
+ if (arg.length === 2 && arg.startsWith("-") && !arg.startsWith("--")) {
10107
+ const ch = arg[1];
10108
+ if (!ch) continue;
10109
+ if (SHORT_FLAGS[ch]) {
10110
+ result[SHORT_FLAGS[ch]] = true;
10111
+ continue;
10112
+ }
10113
+ if (SHORT_FLAGS_WITH_VALUE[ch]) {
10114
+ const val = args[++i];
10115
+ if (!val || val.startsWith("-") && val.length > 1) {
10116
+ throw new Error(`Flag -${ch} requires a value`);
10117
+ }
10118
+ const key = SHORT_FLAGS_WITH_VALUE[ch];
10119
+ if (key === "exclude") {
10120
+ result.exclude.push(val);
10121
+ } else {
10122
+ result[key] = val;
10123
+ }
10124
+ continue;
10125
+ }
10126
+ if (ch === "h") {
10127
+ result.command = "help";
10128
+ continue;
10129
+ }
10130
+ throw new Error(`Unknown flag: -${ch}`);
10131
+ }
10132
+ if (arg === "--help") {
10133
+ result.command = "help";
10134
+ } else if (arg === "--all") {
10135
+ result.all = true;
10136
+ } else if (arg === "--message-only") {
10137
+ result.messageOnly = true;
10138
+ } else if (arg === "--push") {
10139
+ result.push = true;
10140
+ } else if (arg === "--verbose") {
10141
+ result.verbose = true;
10142
+ } else if (arg === "--quiet") {
10143
+ result.quiet = true;
10144
+ } else if (arg === "--dry-run") {
10145
+ result.dryRun = true;
10146
+ } else if (arg === "--interactive") {
10147
+ result.interactive = true;
10148
+ } else if (arg === "--split") {
10149
+ result.split = true;
10150
+ } else if (arg === "--body") {
10151
+ result.forceBody = true;
10152
+ } else if (arg === "--confirm") {
10153
+ result.confirm = true;
10154
+ } else if (arg === "--no-confirm") {
10155
+ result.confirm = false;
10156
+ } else if (arg === "--no-context") {
10157
+ result.noContext = true;
10158
+ } else if (arg === "--no-smart-diff") {
10159
+ result.noSmartDiff = true;
10160
+ } else if (arg === "--local" || arg === "--use-ollama" || arg === "--use-lmstudio" || arg === "--use-openrouter" || arg === "--use-cloudflare") {
10161
+ result.local = true;
10162
+ if (arg === "--use-ollama") {
10163
+ result.setProvider = "ollama";
10164
+ } else if (arg === "--use-lmstudio") {
10165
+ result.setProvider = "lmstudio";
10166
+ } else if (arg === "--use-openrouter") {
10167
+ result.setProvider = "openrouter";
10168
+ } else if (arg === "--use-cloudflare") {
10169
+ result.setProvider = "cloudflare";
10170
+ }
10171
+ } else if (arg === "--api-key" && i + 1 < args.length) {
10172
+ result.apiKey = args[++i];
10173
+ } else if (arg === "--base" && i + 1 < args.length) {
10174
+ result.base = args[++i];
10175
+ } else if (arg === "--create") {
10176
+ result.create = true;
10177
+ } else if (arg === "--from" && i + 1 < args.length) {
10178
+ result.from = args[++i];
10179
+ } else if (arg === "--to" && i + 1 < args.length) {
10180
+ result.to = args[++i];
10181
+ } else if (arg === "--write") {
10182
+ result.write = true;
10183
+ } else if (arg === "--version" && i + 1 < args.length) {
10184
+ result.version = args[++i];
10185
+ } else if (arg === "--uninstall") {
10186
+ result.uninstall = true;
10187
+ } else if (arg === "--hook-mode") {
10188
+ result.hookMode = true;
10189
+ } else if (arg === "--model" && i + 1 < args.length) {
10190
+ result.model = args[++i];
10191
+ } else if (arg === "--type" && i + 1 < args.length) {
10192
+ result.type = args[++i];
10193
+ } else if (arg === "--scope" && i + 1 < args.length) {
10194
+ result.scope = args[++i];
10195
+ } else if (arg === "--exclude" && i + 1 < args.length) {
10196
+ const ex = args[++i];
10197
+ if (ex) result.exclude.push(ex);
10198
+ } else if (arg === "--no-color") {
10199
+ } else if (arg === "login") {
10200
+ result.command = "login";
10201
+ subcommandSeen = true;
10202
+ } else if (arg === "logout") {
10203
+ result.command = "logout";
10204
+ subcommandSeen = true;
10205
+ } else if (arg === "status") {
10206
+ result.command = "status";
10207
+ subcommandSeen = true;
10208
+ } else if (arg === "pr") {
10209
+ result.command = "pr";
10210
+ subcommandSeen = true;
10211
+ } else if (arg === "changelog") {
10212
+ result.command = "changelog";
10213
+ subcommandSeen = true;
10214
+ } else if (arg === "init") {
10215
+ result.command = "init";
10216
+ subcommandSeen = true;
10217
+ } else if (arg === "team") {
10218
+ result.command = "team";
10219
+ subcommandSeen = true;
10220
+ } else if (arg === "config") {
10221
+ result.command = "config";
10222
+ subcommandSeen = true;
10223
+ } else if (arg === "upgrade") {
10224
+ result.command = "upgrade";
10225
+ subcommandSeen = true;
10226
+ } else if (arg === "changeset") {
10227
+ result.command = "changeset";
10228
+ subcommandSeen = true;
10229
+ } else if (subcommandSeen && !arg.startsWith("-")) {
10230
+ result.positionals.push(arg);
10231
+ }
10232
+ }
10233
+ if (result.messageOnly && result.push) {
10234
+ throw new Error("Cannot combine --message-only (-m) with --push (-p)");
10235
+ }
10236
+ if (result.quiet && result.verbose) {
10237
+ throw new Error("Cannot combine --quiet (-q) with --verbose (-v)");
9443
10238
  }
10239
+ if (result.dryRun && result.push) {
10240
+ throw new Error("Cannot combine --dry-run (-n) with --push (-p). Pick one.");
10241
+ }
10242
+ return result;
9444
10243
  }
9445
10244
  async function main() {
9446
10245
  const argv = process.argv.slice(2);
9447
- const values = parseArgs(argv);
9448
- const { command, messageOnly, push, apiKey } = values;
10246
+ let values;
10247
+ try {
10248
+ values = parseArgs(argv);
10249
+ } catch (err) {
10250
+ console.error(err instanceof Error ? err.message : String(err));
10251
+ process.exit(1);
10252
+ }
10253
+ const { command, apiKey } = values;
9449
10254
  if (command === "help") {
9450
10255
  console.log(HELP);
9451
10256
  return;
9452
10257
  }
10258
+ if (values.setProvider) {
10259
+ switch (values.setProvider) {
10260
+ case "ollama":
10261
+ saveConfig({ ...getConfig(), provider: "ollama", apiUrl: "http://localhost:11434", model: "codellama" });
10262
+ break;
10263
+ case "lmstudio":
10264
+ saveConfig({ ...getConfig(), provider: "lmstudio", apiUrl: "http://localhost:1234/v1", model: "default" });
10265
+ break;
10266
+ case "openrouter":
10267
+ saveConfig({
10268
+ ...getConfig(),
10269
+ provider: "openrouter",
10270
+ apiUrl: "https://openrouter.ai/api/v1",
10271
+ model: "google/gemini-flash-1.5-8b"
10272
+ });
10273
+ break;
10274
+ case "cloudflare":
10275
+ saveConfig({
10276
+ ...getConfig(),
10277
+ provider: "cloudflare",
10278
+ apiUrl: "https://YOUR-WORKER.workers.dev",
10279
+ model: "@cf/qwen/qwen2.5-coder-32b-instruct"
10280
+ });
10281
+ console.error("[qc] Cloudflare provider set. Run: qc config set api_url https://your-worker.workers.dev");
10282
+ break;
10283
+ }
10284
+ }
9453
10285
  if (command === "login") {
9454
10286
  const { runLogin: runLogin2 } = await Promise.resolve().then(() => (init_login(), login_exports));
9455
10287
  await runLogin2();
@@ -9500,14 +10332,12 @@ async function main() {
9500
10332
  }
9501
10333
  if (command === "team") {
9502
10334
  const { team: team2 } = await Promise.resolve().then(() => (init_team(), team_exports));
9503
- const positionals = argv.filter((a) => !a.startsWith("-") && a !== "team");
9504
- await team2(positionals[0], positionals.slice(1));
10335
+ await team2(values.positionals[0], values.positionals.slice(1));
9505
10336
  return;
9506
10337
  }
9507
10338
  if (command === "config") {
9508
10339
  const { config: config2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
9509
- const positionals = argv.filter((a) => !a.startsWith("-") && a !== "config");
9510
- config2(positionals);
10340
+ config2(values.positionals);
9511
10341
  return;
9512
10342
  }
9513
10343
  if (command === "upgrade") {
@@ -9517,7 +10347,7 @@ async function main() {
9517
10347
  }
9518
10348
  if (values.local) {
9519
10349
  const { runLocalCommit: runLocalCommit2 } = await Promise.resolve().then(() => (init_local(), local_exports));
9520
- await runLocalCommit2(messageOnly, push, values.model);
10350
+ await runLocalCommit2(values);
9521
10351
  return;
9522
10352
  }
9523
10353
  const apiKeyToUse = apiKey ?? getApiKey();
@@ -9525,11 +10355,12 @@ async function main() {
9525
10355
  const { getLocalProviderConfig: getLocalProviderConfig2 } = await Promise.resolve().then(() => (init_local(), local_exports));
9526
10356
  if (getLocalProviderConfig2()) {
9527
10357
  const { runLocalCommit: runLocalCommit2 } = await Promise.resolve().then(() => (init_local(), local_exports));
9528
- await runLocalCommit2(messageOnly, push, values.model);
10358
+ await runLocalCommit2(values);
9529
10359
  return;
9530
10360
  }
9531
10361
  }
9532
- await runCommit(messageOnly, push, apiKey, values.hookMode, values.model, values.all);
10362
+ const { runCommit: runCommit2 } = await Promise.resolve().then(() => (init_commit(), commit_exports));
10363
+ await runCommit2(values);
9533
10364
  }
9534
10365
  main().catch((err) => {
9535
10366
  const args = process.argv.slice(2);
@@ -9539,3 +10370,7 @@ main().catch((err) => {
9539
10370
  }
9540
10371
  process.exit(1);
9541
10372
  });
10373
+ // Annotate the CommonJS export names for ESM import in node:
10374
+ 0 && (module.exports = {
10375
+ parseArgs
10376
+ });