@quikcommit/cli 7.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 +1362 -560
  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({
@@ -61,6 +62,16 @@ var init_rules = __esm({
61
62
  }
62
63
  });
63
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
+
64
75
  // ../shared/dist/index.js
65
76
  var init_dist = __esm({
66
77
  "../shared/dist/index.js"() {
@@ -68,6 +79,7 @@ var init_dist = __esm({
68
79
  init_types();
69
80
  init_constants();
70
81
  init_rules();
82
+ init_tokens();
71
83
  }
72
84
  });
73
85
 
@@ -125,6 +137,149 @@ var init_config = __esm({
125
137
  }
126
138
  });
127
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
+
128
283
  // src/api.ts
129
284
  var ApiClient;
130
285
  var init_api = __esm({
@@ -163,8 +318,15 @@ var init_api = __esm({
163
318
  }
164
319
  return res.json();
165
320
  }
166
- async generateCommit(diff, changes, rules, model) {
167
- 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
+ };
168
330
  const data = await this.request(
169
331
  "/v1/commit",
170
332
  body
@@ -248,6 +410,37 @@ var init_api = __esm({
248
410
  }
249
411
  });
250
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
+
251
444
  // ../../node_modules/.pnpm/yaml@2.8.4/node_modules/yaml/dist/nodes/identity.js
252
445
  var require_identity = __commonJS({
253
446
  "../../node_modules/.pnpm/yaml@2.8.4/node_modules/yaml/dist/nodes/identity.js"(exports2) {
@@ -7564,7 +7757,7 @@ function validateRef(ref, name = "ref") {
7564
7757
  }
7565
7758
  function isGitRepo() {
7566
7759
  try {
7567
- (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"], {
7568
7761
  stdio: "pipe"
7569
7762
  });
7570
7763
  return true;
@@ -7574,7 +7767,7 @@ function isGitRepo() {
7574
7767
  }
7575
7768
  function getGitRoot() {
7576
7769
  try {
7577
- return (0, import_child_process.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
7770
+ return (0, import_child_process2.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
7578
7771
  encoding: "utf-8"
7579
7772
  }).trim();
7580
7773
  } catch {
@@ -7590,37 +7783,37 @@ function getStagedDiff(excludes = []) {
7590
7783
  args.push(`:(exclude)${pattern}`);
7591
7784
  }
7592
7785
  }
7593
- return (0, import_child_process.execFileSync)("git", args, {
7786
+ return (0, import_child_process2.execFileSync)("git", args, {
7594
7787
  encoding: "utf-8",
7595
7788
  maxBuffer: 10 * 1024 * 1024
7596
7789
  });
7597
7790
  }
7598
7791
  function getStagedFiles() {
7599
- return (0, import_child_process.execFileSync)("git", ["diff", "--cached", "--name-only"], {
7792
+ return (0, import_child_process2.execFileSync)("git", ["diff", "--cached", "--name-only"], {
7600
7793
  encoding: "utf-8"
7601
7794
  });
7602
7795
  }
7603
7796
  function hasStagedChanges() {
7604
- 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"], {
7605
7798
  encoding: "utf-8"
7606
7799
  });
7607
7800
  return output.trim().length > 0;
7608
7801
  }
7609
7802
  function getUnstagedFiles() {
7610
- const output = (0, import_child_process.execFileSync)("git", ["status", "--porcelain"], {
7803
+ const output = (0, import_child_process2.execFileSync)("git", ["status", "--porcelain"], {
7611
7804
  encoding: "utf-8"
7612
7805
  });
7613
7806
  return output.trim().split("\n").filter(Boolean).filter((line) => !line.startsWith("??"));
7614
7807
  }
7615
7808
  function stageAll() {
7616
- (0, import_child_process.execFileSync)("git", ["add", "-u"], { stdio: "pipe" });
7809
+ (0, import_child_process2.execFileSync)("git", ["add", "-u"], { stdio: "pipe" });
7617
7810
  }
7618
7811
  function gitCommit(message) {
7619
- 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-"));
7620
7813
  const tmpFile = (0, import_path2.join)(tmpDir, "commit.txt");
7621
7814
  (0, import_fs2.writeFileSync)(tmpFile, message, { mode: 384 });
7622
7815
  try {
7623
- (0, import_child_process.execFileSync)("git", ["commit", "-F", tmpFile], { stdio: "inherit" });
7816
+ (0, import_child_process2.execFileSync)("git", ["commit", "-F", tmpFile], { stdio: "inherit" });
7624
7817
  } finally {
7625
7818
  try {
7626
7819
  (0, import_fs2.unlinkSync)(tmpFile);
@@ -7630,11 +7823,11 @@ function gitCommit(message) {
7630
7823
  }
7631
7824
  }
7632
7825
  function gitPush() {
7633
- (0, import_child_process.execFileSync)("git", ["push"], { stdio: "inherit" });
7826
+ (0, import_child_process2.execFileSync)("git", ["push"], { stdio: "inherit" });
7634
7827
  }
7635
7828
  function getBranchCommits(base = "main") {
7636
7829
  validateRef(base, "base");
7637
- 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"], {
7638
7831
  encoding: "utf-8",
7639
7832
  maxBuffer: 10 * 1024 * 1024
7640
7833
  });
@@ -7642,19 +7835,19 @@ function getBranchCommits(base = "main") {
7642
7835
  }
7643
7836
  function getDiffStat(base = "main") {
7644
7837
  validateRef(base, "base");
7645
- return (0, import_child_process.execFileSync)("git", ["diff", `${base}..HEAD`, "--stat"], {
7838
+ return (0, import_child_process2.execFileSync)("git", ["diff", `${base}..HEAD`, "--stat"], {
7646
7839
  encoding: "utf-8",
7647
7840
  maxBuffer: 10 * 1024 * 1024
7648
7841
  });
7649
7842
  }
7650
7843
  function getCurrentBranch() {
7651
- 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"], {
7652
7845
  encoding: "utf-8"
7653
7846
  }).trim();
7654
7847
  }
7655
7848
  function getLatestTag() {
7656
7849
  try {
7657
- return (0, import_child_process.execFileSync)("git", ["describe", "--tags", "--abbrev=0"], {
7850
+ return (0, import_child_process2.execFileSync)("git", ["describe", "--tags", "--abbrev=0"], {
7658
7851
  encoding: "utf-8"
7659
7852
  }).trim();
7660
7853
  } catch {
@@ -7664,7 +7857,7 @@ function getLatestTag() {
7664
7857
  function getCommitsSince(ref, to = "HEAD") {
7665
7858
  validateRef(ref, "from ref");
7666
7859
  validateRef(to, "to ref");
7667
- const output = (0, import_child_process.execFileSync)(
7860
+ const output = (0, import_child_process2.execFileSync)(
7668
7861
  "git",
7669
7862
  ["log", `${ref}..${to}`, "--format=%H %s", "--max-count=1000"],
7670
7863
  { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 }
@@ -7676,7 +7869,7 @@ function getCommitsSince(ref, to = "HEAD") {
7676
7869
  }
7677
7870
  function getChangedFilesSince(base = "main") {
7678
7871
  validateRef(base, "base");
7679
- 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"], {
7680
7873
  encoding: "utf-8",
7681
7874
  maxBuffer: 10 * 1024 * 1024
7682
7875
  });
@@ -7684,7 +7877,7 @@ function getChangedFilesSince(base = "main") {
7684
7877
  }
7685
7878
  function getOnlineLog(base = "main") {
7686
7879
  validateRef(base, "base");
7687
- return (0, import_child_process.execFileSync)(
7880
+ return (0, import_child_process2.execFileSync)(
7688
7881
  "git",
7689
7882
  ["log", `${base}..HEAD`, "--oneline", "--max-count=200"],
7690
7883
  {
@@ -7695,19 +7888,65 @@ function getOnlineLog(base = "main") {
7695
7888
  }
7696
7889
  function getFullDiff(base = "main") {
7697
7890
  validateRef(base, "base");
7698
- return (0, import_child_process.execFileSync)("git", ["diff", `${base}..HEAD`], {
7891
+ return (0, import_child_process2.execFileSync)("git", ["diff", `${base}..HEAD`], {
7699
7892
  encoding: "utf-8",
7700
7893
  maxBuffer: 10 * 1024 * 1024
7701
7894
  });
7702
7895
  }
7703
- 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;
7704
7943
  var init_git = __esm({
7705
7944
  "src/git.ts"() {
7706
7945
  "use strict";
7707
- import_child_process = require("child_process");
7946
+ import_child_process2 = require("child_process");
7708
7947
  import_fs2 = require("fs");
7709
7948
  import_path2 = require("path");
7710
- import_os2 = require("os");
7949
+ import_os3 = require("os");
7711
7950
  SAFE_GIT_REF = /^[a-zA-Z0-9._\-/~:^@]+$/;
7712
7951
  }
7713
7952
  });
@@ -7774,7 +8013,7 @@ function mapRulesToCommitRules(rules) {
7774
8013
  }
7775
8014
  function tryNpxPrintConfig(root) {
7776
8015
  try {
7777
- 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"], {
7778
8017
  encoding: "utf-8",
7779
8018
  cwd: root,
7780
8019
  timeout: 1e4,
@@ -7791,7 +8030,7 @@ function tryNodeEval(configPath) {
7791
8030
  const fileUrl = (0, import_node_url.pathToFileURL)(configPath).href;
7792
8031
  const script = `import cfg from ${JSON.stringify(fileUrl)}; process.stdout.write(JSON.stringify(cfg.default ?? cfg));`;
7793
8032
  try {
7794
- const output = (0, import_child_process2.execFileSync)("node", ["--input-type=module"], {
8033
+ const output = (0, import_child_process3.execFileSync)("node", ["--input-type=module"], {
7795
8034
  input: script,
7796
8035
  encoding: "utf-8",
7797
8036
  timeout: 1e4,
@@ -7808,7 +8047,7 @@ function tryNodeEvalTs(configPath, root) {
7808
8047
  const fileUrl = (0, import_node_url.pathToFileURL)(configPath).href;
7809
8048
  const script = `import cfg from ${JSON.stringify(fileUrl)}; process.stdout.write(JSON.stringify(cfg.default ?? cfg));`;
7810
8049
  try {
7811
- 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"], {
7812
8051
  input: script,
7813
8052
  encoding: "utf-8",
7814
8053
  cwd: root,
@@ -7822,7 +8061,7 @@ function tryNodeEvalTs(configPath, root) {
7822
8061
  }
7823
8062
  try {
7824
8063
  const tsxScript = `import cfg from ${JSON.stringify(fileUrl)}; console.log(JSON.stringify(cfg.default ?? cfg));`;
7825
- 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], {
7826
8065
  encoding: "utf-8",
7827
8066
  cwd: root,
7828
8067
  timeout: 15e3,
@@ -7875,11 +8114,11 @@ async function detectCommitlintRules() {
7875
8114
  return void 0;
7876
8115
  }
7877
8116
  }
7878
- 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;
7879
8118
  var init_commitlint = __esm({
7880
8119
  "src/commitlint.ts"() {
7881
8120
  "use strict";
7882
- import_child_process2 = require("child_process");
8121
+ import_child_process3 = require("child_process");
7883
8122
  import_fs3 = require("fs");
7884
8123
  import_path3 = require("path");
7885
8124
  import_node_url = require("node:url");
@@ -7900,351 +8139,37 @@ var init_commitlint = __esm({
7900
8139
  }
7901
8140
  });
7902
8141
 
7903
- // src/monorepo.ts
7904
- var monorepo_exports = {};
7905
- __export(monorepo_exports, {
7906
- autoDetectScope: () => autoDetectScope,
7907
- detectWorkspace: () => detectWorkspace,
7908
- getPackageForFile: () => getPackageForFile
8142
+ // src/commands/pr.ts
8143
+ var pr_exports = {};
8144
+ __export(pr_exports, {
8145
+ pr: () => pr
7909
8146
  });
7910
- function findGitRoot(start) {
7911
- try {
7912
- return (0, import_child_process3.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
7913
- encoding: "utf-8",
7914
- cwd: start,
7915
- stdio: ["pipe", "pipe", "pipe"]
7916
- }).trim();
7917
- } catch {
7918
- return start;
7919
- }
7920
- }
7921
- function detectWorkspace(cwd = findGitRoot(process.cwd())) {
7922
- const pnpmWs = (0, import_path4.join)(cwd, "pnpm-workspace.yaml");
7923
- if ((0, import_fs4.existsSync)(pnpmWs)) {
7924
- const content = (0, import_fs4.readFileSync)(pnpmWs, "utf-8");
7925
- const match = content.match(/packages:\s*\n((?:\s+-\s+.+\n?)*)/);
7926
- if (match) {
7927
- const packages = match[1].split("\n").map((l) => l.replace(/^\s+-\s+/, "").replace(/["']/g, "").trim()).filter(Boolean);
7928
- return { type: "pnpm", packages, root: cwd };
7929
- }
7930
- }
7931
- const lerna = (0, import_path4.join)(cwd, "lerna.json");
7932
- 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) {
7933
8154
  try {
7934
- const config2 = JSON.parse((0, import_fs4.readFileSync)(lerna, "utf-8"));
7935
- return {
7936
- type: "lerna",
7937
- packages: config2.packages ?? ["packages/*"],
7938
- root: cwd
7939
- };
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
+ }
7940
8158
  } catch {
7941
8159
  }
7942
8160
  }
7943
- if ((0, import_fs4.existsSync)((0, import_path4.join)(cwd, "nx.json"))) {
7944
- return {
7945
- type: "nx",
7946
- packages: ["packages/*", "apps/*", "libs/*"],
7947
- root: cwd
7948
- };
7949
- }
7950
- if ((0, import_fs4.existsSync)((0, import_path4.join)(cwd, "turbo.json"))) {
7951
- const pkgPath2 = (0, import_path4.join)(cwd, "package.json");
7952
- if ((0, import_fs4.existsSync)(pkgPath2)) {
7953
- try {
7954
- const config2 = JSON.parse((0, import_fs4.readFileSync)(pkgPath2, "utf-8"));
7955
- if (config2.workspaces) {
7956
- const ws = Array.isArray(config2.workspaces) ? config2.workspaces : config2.workspaces.packages ?? [];
7957
- 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") };
7958
8169
  }
7959
- } catch {
7960
- }
7961
- }
7962
- }
7963
- const pkgPath = (0, import_path4.join)(cwd, "package.json");
7964
- if ((0, import_fs4.existsSync)(pkgPath)) {
7965
- try {
7966
- const config2 = JSON.parse((0, import_fs4.readFileSync)(pkgPath, "utf-8"));
7967
- if (config2.workspaces) {
7968
- const ws = Array.isArray(config2.workspaces) ? config2.workspaces : config2.workspaces.packages ?? [];
7969
- return { type: "npm", packages: ws, root: cwd };
7970
8170
  }
7971
- } catch {
7972
8171
  }
7973
- }
7974
- return null;
7975
- }
7976
- function matchGlobPattern(rel, pattern) {
7977
- const dir = pattern.replace(/\/?\*\*?$/, "").replace(/\/$/, "");
7978
- if (!dir || dir === "*" || dir === "**") {
7979
- const pkg = rel.split("/")[0];
7980
- return pkg || null;
7981
- }
7982
- const starIdx = dir.indexOf("*");
7983
- if (starIdx !== -1) {
7984
- const prefix2 = dir.slice(0, starIdx);
7985
- if (rel.startsWith(prefix2)) {
7986
- const rest = rel.slice(prefix2.length);
7987
- const pkg = rest.split("/")[0];
7988
- return pkg || null;
7989
- }
7990
- return null;
7991
- }
7992
- const prefix = dir + "/";
7993
- const hasGlob = /\*/.test(pattern);
7994
- if (hasGlob) {
7995
- if (rel.startsWith(prefix)) {
7996
- const rest = rel.slice(prefix.length);
7997
- const pkg = rest.split("/")[0];
7998
- return pkg || null;
7999
- }
8000
- } else {
8001
- if (rel === dir || rel.startsWith(prefix)) {
8002
- const segments = dir.split("/").filter(Boolean);
8003
- return segments[segments.length - 1] ?? null;
8004
- }
8005
- }
8006
- return null;
8007
- }
8008
- function getPackageForFile(filePath, workspace) {
8009
- const absPath = filePath.startsWith("/") ? filePath : (0, import_path4.join)(workspace.root, filePath);
8010
- const rel = (0, import_path4.relative)(workspace.root, absPath);
8011
- for (const pattern of workspace.packages) {
8012
- const packageName = matchGlobPattern(rel, pattern);
8013
- if (packageName) return packageName;
8014
- }
8015
- return null;
8016
- }
8017
- function autoDetectScope(stagedFiles, workspace) {
8018
- const packages = /* @__PURE__ */ new Set();
8019
- for (const file of stagedFiles) {
8020
- const filePath = file.startsWith("/") ? file : (0, import_path4.join)(workspace.root, file);
8021
- const pkg = getPackageForFile(filePath, workspace);
8022
- if (pkg) packages.add(pkg);
8023
- }
8024
- if (packages.size === 1) return [...packages][0];
8025
- if (packages.size > 1 && packages.size <= 3) return [...packages].join(",");
8026
- if (packages.size > 3) {
8027
- console.error(
8028
- `[qc] Changes span ${packages.size} packages; skipping auto-scope detection.`
8029
- );
8030
- }
8031
- return null;
8032
- }
8033
- var import_child_process3, import_fs4, import_path4;
8034
- var init_monorepo = __esm({
8035
- "src/monorepo.ts"() {
8036
- "use strict";
8037
- import_child_process3 = require("child_process");
8038
- import_fs4 = require("fs");
8039
- import_path4 = require("path");
8040
- }
8041
- });
8042
-
8043
- // src/commands/login.ts
8044
- var login_exports = {};
8045
- __export(login_exports, {
8046
- runLogin: () => runLogin
8047
- });
8048
- function openBrowser(url) {
8049
- try {
8050
- if ((0, import_os3.platform)() === "darwin") {
8051
- (0, import_child_process4.execFileSync)("open", [url], { stdio: "pipe" });
8052
- return true;
8053
- }
8054
- if ((0, import_os3.platform)() === "linux") {
8055
- (0, import_child_process4.execFileSync)("xdg-open", [url], { stdio: "pipe" });
8056
- return true;
8057
- }
8058
- if ((0, import_os3.platform)() === "win32") {
8059
- (0, import_child_process4.execFileSync)("cmd", ["/c", "start", "", url], { stdio: "pipe" });
8060
- return true;
8061
- }
8062
- } catch {
8063
- }
8064
- return false;
8065
- }
8066
- async function runLogin() {
8067
- const codeRes = await fetch(`${API_URL}/api/auth/device/code`, {
8068
- method: "POST",
8069
- headers: { "Content-Type": "application/json" },
8070
- body: JSON.stringify({ client_id: CLIENT_ID })
8071
- });
8072
- if (!codeRes.ok) {
8073
- const err = await codeRes.json().catch(() => ({ error: codeRes.statusText }));
8074
- throw new Error(err.error ?? "Failed to start device flow");
8075
- }
8076
- const codeData = await codeRes.json();
8077
- const { device_code, user_code, verification_uri_complete, interval = 5 } = codeData;
8078
- if (!device_code || !user_code) {
8079
- throw new Error("Server did not return device codes");
8080
- }
8081
- console.log("Opening browser to sign in...");
8082
- console.log("");
8083
- console.log(` Your code: ${user_code}`);
8084
- console.log("");
8085
- const authUrl = verification_uri_complete ?? `${DASHBOARD_URL}/device?user_code=${encodeURIComponent(user_code)}`;
8086
- const opened = openBrowser(authUrl);
8087
- if (!opened) {
8088
- console.log("Could not open browser. Please visit:");
8089
- console.log(authUrl);
8090
- console.log("");
8091
- }
8092
- let frame = 0;
8093
- const spinner = setInterval(() => {
8094
- const elapsed = Math.floor((Date.now() - startTime) / 1e3);
8095
- process.stderr.write(
8096
- `\r${SPINNER_FRAMES[frame++ % SPINNER_FRAMES.length]} Waiting for authorization... (${elapsed}s)`
8097
- );
8098
- }, 80);
8099
- let pollingInterval = interval * 1e3;
8100
- const startTime = Date.now();
8101
- try {
8102
- while (Date.now() - startTime < DEVICE_FLOW_TIMEOUT) {
8103
- await new Promise((r) => setTimeout(r, pollingInterval));
8104
- try {
8105
- const tokenRes = await fetch(`${API_URL}/api/auth/device/token`, {
8106
- method: "POST",
8107
- headers: { "Content-Type": "application/json" },
8108
- body: JSON.stringify({
8109
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
8110
- device_code,
8111
- client_id: CLIENT_ID
8112
- })
8113
- });
8114
- const tokenData = await tokenRes.json();
8115
- if (tokenData.access_token) {
8116
- saveApiKey(tokenData.access_token);
8117
- process.stderr.write("\r\x1B[2K");
8118
- console.log("Successfully logged in!");
8119
- return;
8120
- }
8121
- if (tokenData.error) {
8122
- switch (tokenData.error) {
8123
- case "authorization_pending":
8124
- break;
8125
- // continue polling
8126
- case "slow_down":
8127
- pollingInterval += 5e3;
8128
- break;
8129
- case "access_denied":
8130
- process.stderr.write("\r\x1B[2K");
8131
- console.error("Authorization was denied.");
8132
- process.exit(1);
8133
- break;
8134
- case "expired_token":
8135
- process.stderr.write("\r\x1B[2K");
8136
- console.error("Device code expired. Please try again.");
8137
- process.exit(1);
8138
- break;
8139
- default:
8140
- process.stderr.write("\r\x1B[2K");
8141
- console.error(`Error: ${tokenData.error_description ?? tokenData.error}`);
8142
- process.exit(1);
8143
- }
8144
- }
8145
- } catch {
8146
- }
8147
- }
8148
- process.stderr.write("\r\x1B[2K");
8149
- console.error("Login timed out. Please try again.");
8150
- process.exit(1);
8151
- } finally {
8152
- clearInterval(spinner);
8153
- }
8154
- }
8155
- var import_child_process4, import_os3, API_URL, DASHBOARD_URL, CLIENT_ID, SPINNER_FRAMES;
8156
- var init_login = __esm({
8157
- "src/commands/login.ts"() {
8158
- "use strict";
8159
- import_child_process4 = require("child_process");
8160
- import_os3 = require("os");
8161
- init_config();
8162
- init_dist();
8163
- API_URL = process.env.QC_API_URL ?? DEFAULT_API_URL;
8164
- DASHBOARD_URL = "https://app.quikcommit.dev";
8165
- CLIENT_ID = "qc-cli";
8166
- SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
8167
- }
8168
- });
8169
-
8170
- // src/commands/logout.ts
8171
- var logout_exports = {};
8172
- __export(logout_exports, {
8173
- runLogout: () => runLogout
8174
- });
8175
- function runLogout() {
8176
- clearApiKey();
8177
- console.log("Logged out. Credentials cleared.");
8178
- }
8179
- var init_logout = __esm({
8180
- "src/commands/logout.ts"() {
8181
- "use strict";
8182
- init_config();
8183
- }
8184
- });
8185
-
8186
- // src/commands/status.ts
8187
- var status_exports = {};
8188
- __export(status_exports, {
8189
- runStatus: () => runStatus
8190
- });
8191
- async function runStatus(apiKeyFlag) {
8192
- const apiKey = apiKeyFlag ?? getApiKey();
8193
- if (!apiKey) {
8194
- console.log("Not logged in. Run `qc login` to authenticate.");
8195
- return;
8196
- }
8197
- console.log("Logged in: yes");
8198
- console.log(` API key: ...${apiKey.slice(-4)}`);
8199
- const client = new ApiClient({ apiKey });
8200
- const usage = await client.getUsage();
8201
- if (usage) {
8202
- console.log(`Plan: ${usage.plan}`);
8203
- console.log(`Usage: ${usage.commit_count}/${usage.limit} commits this period`);
8204
- console.log(`Remaining: ${usage.remaining}`);
8205
- } else {
8206
- console.log("Usage: (unable to fetch)");
8207
- }
8208
- }
8209
- var init_status = __esm({
8210
- "src/commands/status.ts"() {
8211
- "use strict";
8212
- init_config();
8213
- init_api();
8214
- }
8215
- });
8216
-
8217
- // src/commands/pr.ts
8218
- var pr_exports = {};
8219
- __export(pr_exports, {
8220
- pr: () => pr
8221
- });
8222
- function findPullRequestTemplate(gitRoot) {
8223
- const fileCandidates = [
8224
- (0, import_path5.join)(gitRoot, ".github", "pull_request_template.md"),
8225
- (0, import_path5.join)(gitRoot, ".github", "PULL_REQUEST_TEMPLATE.md"),
8226
- (0, import_path5.join)(gitRoot, "pull_request_template.md")
8227
- ];
8228
- for (const p of fileCandidates) {
8229
- try {
8230
- if ((0, import_fs5.existsSync)(p) && (0, import_fs5.statSync)(p).isFile()) {
8231
- return { path: p, content: (0, import_fs5.readFileSync)(p, "utf-8") };
8232
- }
8233
- } catch {
8234
- }
8235
- }
8236
- const multiDir = (0, import_path5.join)(gitRoot, ".github", "PULL_REQUEST_TEMPLATE");
8237
- try {
8238
- if ((0, import_fs5.existsSync)(multiDir) && (0, import_fs5.statSync)(multiDir).isDirectory()) {
8239
- const names = (0, import_fs5.readdirSync)(multiDir).filter((f) => f.toLowerCase().endsWith(".md")).sort();
8240
- if (names.length > 0) {
8241
- const p = (0, import_path5.join)(multiDir, names[0]);
8242
- if ((0, import_fs5.statSync)(p).isFile()) {
8243
- return { path: p, content: (0, import_fs5.readFileSync)(p, "utf-8") };
8244
- }
8245
- }
8246
- }
8247
- } catch {
8172
+ } catch {
8248
8173
  }
8249
8174
  return void 0;
8250
8175
  }
@@ -8257,7 +8182,7 @@ async function pr(options) {
8257
8182
  let prTemplate;
8258
8183
  if (templateHit) {
8259
8184
  prTemplate = templateHit.content.substring(0, 16 * 1024);
8260
- console.error(`[qc] Using PR template from ${(0, import_path5.relative)(gitRoot, templateHit.path)}`);
8185
+ console.error(`[qc] Using PR template from ${(0, import_path4.relative)(gitRoot, templateHit.path)}`);
8261
8186
  }
8262
8187
  const currentBranch = getCurrentBranch().slice(0, MAX_PR_CURRENT_BRANCH_CHARS);
8263
8188
  if (commits.length === 0) {
@@ -8293,7 +8218,7 @@ Title: ${trimmedTitle}
8293
8218
  if (options.create) {
8294
8219
  try {
8295
8220
  const prTitle = trimmedTitle || result.message.split("\n").find((l) => l.trim()) || result.message.substring(0, 72).trim();
8296
- (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], {
8297
8222
  stdio: "inherit"
8298
8223
  });
8299
8224
  } catch {
@@ -8302,13 +8227,13 @@ Title: ${trimmedTitle}
8302
8227
  }
8303
8228
  }
8304
8229
  }
8305
- var import_child_process5, import_fs5, import_path5;
8230
+ var import_child_process4, import_fs4, import_path4;
8306
8231
  var init_pr = __esm({
8307
8232
  "src/commands/pr.ts"() {
8308
8233
  "use strict";
8309
- import_child_process5 = require("child_process");
8310
- import_fs5 = require("fs");
8311
- import_path5 = require("path");
8234
+ import_child_process4 = require("child_process");
8235
+ import_fs4 = require("fs");
8236
+ import_path4 = require("path");
8312
8237
  init_dist();
8313
8238
  init_config();
8314
8239
  init_api();
@@ -8369,21 +8294,21 @@ async function changelog(options) {
8369
8294
  `;
8370
8295
  const changelogEntry = header + result.message;
8371
8296
  if (options.write) {
8372
- const path = (0, import_path6.join)(getGitRoot(), "CHANGELOG.md");
8373
- 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") : "";
8374
8299
  const newContent = changelogEntry + (existing ? "\n\n" + existing : "");
8375
- (0, import_fs6.writeFileSync)(path, newContent);
8300
+ (0, import_fs5.writeFileSync)(path, newContent);
8376
8301
  console.error(`Wrote to ${path}`);
8377
8302
  } else {
8378
8303
  console.log(changelogEntry);
8379
8304
  }
8380
8305
  }
8381
- var import_fs6, import_path6, CONVENTIONAL_TYPE_RE;
8306
+ var import_fs5, import_path5, CONVENTIONAL_TYPE_RE;
8382
8307
  var init_changelog = __esm({
8383
8308
  "src/commands/changelog.ts"() {
8384
8309
  "use strict";
8385
- import_fs6 = require("fs");
8386
- import_path6 = require("path");
8310
+ import_fs5 = require("fs");
8311
+ import_path5 = require("path");
8387
8312
  init_config();
8388
8313
  init_api();
8389
8314
  init_git();
@@ -8391,6 +8316,146 @@ var init_changelog = __esm({
8391
8316
  }
8392
8317
  });
8393
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
+
8394
8459
  // src/commands/changeset.ts
8395
8460
  var changeset_exports = {};
8396
8461
  __export(changeset_exports, {
@@ -9010,23 +9075,411 @@ async function upgrade() {
9010
9075
  Opening ${BILLING_URL}
9011
9076
  `);
9012
9077
  try {
9013
- const { execFileSync: execFileSync7 } = await import("child_process");
9014
- if (process.platform === "darwin") {
9015
- execFileSync7("open", [BILLING_URL]);
9016
- } else if (process.platform === "linux") {
9017
- execFileSync7("xdg-open", [BILLING_URL]);
9018
- } else if (process.platform === "win32") {
9019
- 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" };
9020
9439
  }
9021
- } catch {
9022
- console.log(`Visit: ${BILLING_URL}`);
9440
+ return { action: "commit" };
9441
+ } finally {
9442
+ rl.close();
9023
9443
  }
9024
9444
  }
9025
- var BILLING_URL;
9026
- var init_upgrade = __esm({
9027
- "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"() {
9028
9481
  "use strict";
9029
- BILLING_URL = "https://app.quikcommit.dev/billing";
9482
+ import_promises = __toESM(require("node:readline/promises"));
9030
9483
  }
9031
9484
  });
9032
9485
 
@@ -9081,7 +9534,7 @@ function getLocalProviderConfig() {
9081
9534
  if (provider === "openrouter" && !apiKey) return null;
9082
9535
  return { provider, baseUrl, model, apiKey };
9083
9536
  }
9084
- function buildUserPrompt(changes, diff, rules) {
9537
+ function buildUserPrompt(changes, diff, rules, recentCommits, hints) {
9085
9538
  let prompt2 = `Generate a commit message for these changes:
9086
9539
 
9087
9540
  ## File changes:
@@ -9095,6 +9548,23 @@ ${diff}
9095
9548
  </diff>
9096
9549
 
9097
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
+ }
9098
9568
  if (rules && Object.keys(rules).length > 0) {
9099
9569
  prompt2 += `Rules: ${JSON.stringify(rules)}
9100
9570
 
@@ -9105,7 +9575,7 @@ ${diff}
9105
9575
  - Response should be the commit message only, no explanations`;
9106
9576
  return prompt2;
9107
9577
  }
9108
- function buildRequest(provider, baseUrl, userContent, diff, changes, model, apiKey, rules) {
9578
+ function buildRequest(provider, baseUrl, userContent, diff, changes, model, apiKey, rules, recentCommits, hints) {
9109
9579
  const headers = {
9110
9580
  "Content-Type": "application/json"
9111
9581
  };
@@ -9157,10 +9627,18 @@ function buildRequest(provider, baseUrl, userContent, diff, changes, model, apiK
9157
9627
  ]
9158
9628
  };
9159
9629
  return { url, body, headers };
9160
- case "cloudflare":
9630
+ case "cloudflare": {
9161
9631
  url = `${baseUrl.replace(/\/$/, "")}/commit`;
9162
- 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;
9163
9640
  return { url, body, headers: { "Content-Type": "application/json" } };
9641
+ }
9164
9642
  default:
9165
9643
  throw new Error(`Unknown provider: ${provider}`);
9166
9644
  }
@@ -9182,7 +9660,9 @@ function parseResponse(provider, data) {
9182
9660
  return "";
9183
9661
  }
9184
9662
  }
9185
- 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;
9186
9666
  if (!isGitRepo()) {
9187
9667
  throw new Error("Not a git repository.");
9188
9668
  }
@@ -9196,11 +9676,19 @@ async function runLocalCommit(messageOnly, push, modelFlag) {
9196
9676
  );
9197
9677
  }
9198
9678
  const config2 = getConfig();
9199
- const excludes = config2.excludes ?? [];
9200
- const diff = getStagedDiff(excludes);
9679
+ const excludes = [...config2.excludes ?? [], ...args.exclude];
9680
+ let diff = getStagedDiff(excludes);
9201
9681
  const changes = getStagedFiles();
9202
- const model = modelFlag ?? local.model;
9203
- 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 ?? {} };
9204
9692
  const workspace = detectWorkspace();
9205
9693
  if (workspace) {
9206
9694
  const stagedFiles = changes.trim().split("\n").filter(Boolean);
@@ -9210,7 +9698,20 @@ async function runLocalCommit(messageOnly, push, modelFlag) {
9210
9698
  rules = { ...rules, scopes };
9211
9699
  }
9212
9700
  }
9213
- 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
+ );
9214
9715
  const { url, body, headers } = buildRequest(
9215
9716
  local.provider,
9216
9717
  local.baseUrl,
@@ -9219,18 +9720,29 @@ async function runLocalCommit(messageOnly, push, modelFlag) {
9219
9720
  changes,
9220
9721
  model,
9221
9722
  local.apiKey,
9222
- rules
9723
+ rules,
9724
+ recentCommits,
9725
+ generationHints
9223
9726
  );
9224
9727
  if (!url || url.includes("YOUR-WORKER")) {
9225
9728
  throw new Error(
9226
9729
  "Cloudflare provider requires api_url. Run: qc config set api_url https://your-worker.workers.dev"
9227
9730
  );
9228
9731
  }
9229
- const res = await fetch(url, {
9230
- method: "POST",
9231
- headers,
9232
- body: JSON.stringify(body)
9233
- });
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;
9234
9746
  if (!res.ok) {
9235
9747
  const text = await res.text();
9236
9748
  throw new Error(`Provider error (${res.status}): ${text}`);
@@ -9241,12 +9753,37 @@ async function runLocalCommit(messageOnly, push, modelFlag) {
9241
9753
  if (!message) {
9242
9754
  throw new Error("Failed to generate commit message.");
9243
9755
  }
9244
- 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) {
9245
9770
  console.log(message);
9246
9771
  return;
9247
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
+ }
9248
9785
  gitCommit(message);
9249
- if (push) {
9786
+ if (args.push) {
9250
9787
  gitPush();
9251
9788
  }
9252
9789
  }
@@ -9261,6 +9798,10 @@ var init_local = __esm({
9261
9798
  init_dist();
9262
9799
  init_git();
9263
9800
  init_monorepo();
9801
+ init_commitlint();
9802
+ init_smart_diff();
9803
+ init_ui();
9804
+ init_commit_helpers();
9264
9805
  CONFIG_PATH2 = (0, import_path10.join)((0, import_os4.homedir)(), CONFIG_DIR);
9265
9806
  PROVIDER_URLS = {
9266
9807
  ollama: "http://localhost:11434",
@@ -9279,166 +9820,55 @@ var init_local = __esm({
9279
9820
  }
9280
9821
  });
9281
9822
 
9282
- // src/index.ts
9283
- init_config();
9284
- init_api();
9285
- init_commitlint();
9286
- init_git();
9287
- init_monorepo();
9288
- var HELP = `Quikcommit - AI-powered conventional commit messages
9289
-
9290
- Usage:
9291
- qc Generate commit message and commit (default)
9292
- qc --message-only Generate message only, print to stdout
9293
- qc --push Commit and push to origin
9294
- qc pr Generate PR description from branch commits
9295
- qc changelog Generate changelog from commits since last tag
9296
- qc changeset Automate pnpm changeset with AI
9297
- qc init Install prepare-commit-msg hook for auto-generation
9298
- qc login Sign in via browser
9299
- qc logout Clear local credentials
9300
- qc status Show auth, plan, usage
9301
- qc team Team management (info, rules, invite)
9302
-
9303
- Options:
9304
- -h, --help Show this help
9305
- -a, --all Stage all tracked changes before generating
9306
- -m, --message-only Generate message only
9307
- -p, --push Commit and push after generating
9308
- --api-key <key> Use this API key (overrides credentials file)
9309
- --base <branch> Base branch for qc pr, qc changeset (default: main)
9310
- --create Create PR with gh CLI after qc pr
9311
- --from <ref> Start ref for qc changelog (default: latest tag)
9312
- --to <ref> End ref for qc changelog (default: HEAD)
9313
- --write Prepend changelog to CHANGELOG.md
9314
- --version <ver> Version label for changelog header (default: derived from --to or "<from>-next")
9315
- --uninstall Remove Quikcommit hook (qc init --uninstall)
9316
- --model <id> Use specific model (e.g. qwen25-coder-32b, llama-3.3-70b)
9317
-
9318
- Commands:
9319
- qc config Show current config
9320
- qc config set <k> <v> Set config (model, api_url)
9321
- qc config reset Reset to defaults
9322
- qc upgrade Open billing page in browser
9323
- `;
9324
- function parseArgs(args) {
9325
- let command = "commit";
9326
- let all = false;
9327
- let messageOnly = false;
9328
- let push = false;
9329
- let apiKey;
9330
- let model;
9331
- let local = false;
9332
- let base;
9333
- let create = false;
9334
- let from;
9335
- let to;
9336
- let write = false;
9337
- let version;
9338
- let uninstall = false;
9339
- let hookMode = false;
9340
- for (let i = 0; i < args.length; i++) {
9341
- const arg = args[i];
9342
- if (arg === "-h" || arg === "--help") {
9343
- command = "help";
9344
- } else if (arg === "-a" || arg === "--all") {
9345
- all = true;
9346
- } else if (arg === "-m" || arg === "--message-only") {
9347
- messageOnly = true;
9348
- } else if (arg === "-p" || arg === "--push") {
9349
- push = true;
9350
- } else if (arg === "--api-key" && i + 1 < args.length) {
9351
- apiKey = args[++i];
9352
- } else if (arg === "--base" && i + 1 < args.length) {
9353
- base = args[++i];
9354
- } else if (arg === "--create") {
9355
- create = true;
9356
- } else if (arg === "--from" && i + 1 < args.length) {
9357
- from = args[++i];
9358
- } else if (arg === "--to" && i + 1 < args.length) {
9359
- to = args[++i];
9360
- } else if (arg === "--write") {
9361
- write = true;
9362
- } else if (arg === "--version" && i + 1 < args.length) {
9363
- version = args[++i];
9364
- } else if (arg === "--uninstall") {
9365
- uninstall = true;
9366
- } else if (arg === "--hook-mode") {
9367
- hookMode = true;
9368
- } else if (arg === "login") {
9369
- command = "login";
9370
- } else if (arg === "logout") {
9371
- command = "logout";
9372
- } else if (arg === "status") {
9373
- command = "status";
9374
- } else if (arg === "pr") {
9375
- command = "pr";
9376
- } else if (arg === "changelog") {
9377
- command = "changelog";
9378
- } else if (arg === "init") {
9379
- command = "init";
9380
- } else if (arg === "team") {
9381
- command = "team";
9382
- } else if (arg === "config") {
9383
- command = "config";
9384
- } else if (arg === "upgrade") {
9385
- command = "upgrade";
9386
- } else if (arg === "changeset") {
9387
- command = "changeset";
9388
- } else if (arg === "--model" && i + 1 < args.length) {
9389
- model = args[++i];
9390
- } else if (arg === "--local" || arg === "--use-ollama" || arg === "--use-lmstudio" || arg === "--use-openrouter" || arg === "--use-cloudflare") {
9391
- local = true;
9392
- if (arg === "--use-ollama") {
9393
- saveConfig({ ...getConfig(), provider: "ollama", apiUrl: "http://localhost:11434", model: "codellama" });
9394
- } else if (arg === "--use-lmstudio") {
9395
- saveConfig({ ...getConfig(), provider: "lmstudio", apiUrl: "http://localhost:1234/v1", model: "default" });
9396
- } else if (arg === "--use-openrouter") {
9397
- saveConfig({ ...getConfig(), provider: "openrouter", apiUrl: "https://openrouter.ai/api/v1", model: "google/gemini-flash-1.5-8b" });
9398
- } else if (arg === "--use-cloudflare") {
9399
- saveConfig({
9400
- ...getConfig(),
9401
- provider: "cloudflare",
9402
- apiUrl: "https://YOUR-WORKER.workers.dev",
9403
- model: "@cf/qwen/qwen2.5-coder-32b-instruct"
9404
- });
9405
- console.error(
9406
- "[qc] Cloudflare provider set. Run: qc config set api_url https://your-worker.workers.dev"
9407
- );
9408
- }
9409
- }
9410
- }
9411
- return { command, all, messageOnly, push, apiKey, base, create, from, to, write, version, uninstall, hookMode, model, local };
9412
- }
9413
- async function runCommit(messageOnly, push, apiKeyFlag, hookMode = false, modelFlag, stageAll_) {
9414
- const log = hookMode ? () => {
9415
- } : (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;
9416
9832
  if (!isGitRepo()) {
9417
- log("Error: Not a git repository.");
9833
+ log.error("Not a git repository.");
9418
9834
  process.exit(1);
9419
9835
  }
9420
9836
  const config2 = getConfig();
9421
- if (stageAll_ || config2.autoStage) {
9837
+ if (all || config2.autoStage) {
9422
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}`);
9423
9843
  }
9424
9844
  if (!hasStagedChanges()) {
9425
9845
  const unstaged = getUnstagedFiles();
9426
9846
  if (unstaged.length > 0) {
9427
- 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.");
9428
9848
  } else {
9429
- log("Error: No changes to commit.");
9849
+ log.error("No changes to commit.");
9430
9850
  }
9431
9851
  process.exit(1);
9432
9852
  }
9433
9853
  const apiKey = apiKeyFlag ?? getApiKey();
9434
9854
  if (!apiKey) {
9435
- log("Error: Not authenticated. Run `qc login` first.");
9855
+ log.error("Not authenticated. Run `qc login` first.");
9436
9856
  process.exit(1);
9437
9857
  }
9438
9858
  const model = modelFlag ?? config2.model;
9439
- const excludes = config2.excludes ?? [];
9859
+ const excludes = [...config2.excludes ?? [], ...args.exclude];
9440
9860
  const diff = getStagedDiff(excludes);
9441
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
+ }
9442
9872
  const commitlintRules = await detectCommitlintRules();
9443
9873
  let rules = { ...commitlintRules, ...config2.rules ?? {} };
9444
9874
  const workspace = detectWorkspace();
@@ -9455,7 +9885,7 @@ async function runCommit(messageOnly, push, apiKeyFlag, hookMode = false, modelF
9455
9885
  try {
9456
9886
  const teamRules = await client.getTeamRules();
9457
9887
  if (teamRules && Object.keys(teamRules).length > 0) {
9458
- log("[qc] Using team rules from org");
9888
+ log.step("using team rules from org");
9459
9889
  rules = { ...rules, ...teamRules };
9460
9890
  if (monorepoScopes && teamRules.scopes && teamRules.scopes.length > 0) {
9461
9891
  const allowed = new Set(teamRules.scopes);
@@ -9465,24 +9895,393 @@ async function runCommit(messageOnly, push, apiKeyFlag, hookMode = false, modelF
9465
9895
  }
9466
9896
  } catch {
9467
9897
  }
9468
- 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
+ }
9469
9935
  if (messageOnly) {
9470
9936
  console.log(message);
9471
9937
  return;
9472
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
+ }
9473
9949
  gitCommit(message);
9950
+ const hash = getCommitHash();
9951
+ const branch = getCurrentBranch();
9952
+ log.step(`[${branch} ${hash}] committed`);
9474
9953
  if (push) {
9954
+ log.step(`pushing to origin/${branch}...`);
9475
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)");
9476
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;
9477
10243
  }
9478
10244
  async function main() {
9479
10245
  const argv = process.argv.slice(2);
9480
- const values = parseArgs(argv);
9481
- 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;
9482
10254
  if (command === "help") {
9483
10255
  console.log(HELP);
9484
10256
  return;
9485
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
+ }
9486
10285
  if (command === "login") {
9487
10286
  const { runLogin: runLogin2 } = await Promise.resolve().then(() => (init_login(), login_exports));
9488
10287
  await runLogin2();
@@ -9533,14 +10332,12 @@ async function main() {
9533
10332
  }
9534
10333
  if (command === "team") {
9535
10334
  const { team: team2 } = await Promise.resolve().then(() => (init_team(), team_exports));
9536
- const positionals = argv.filter((a) => !a.startsWith("-") && a !== "team");
9537
- await team2(positionals[0], positionals.slice(1));
10335
+ await team2(values.positionals[0], values.positionals.slice(1));
9538
10336
  return;
9539
10337
  }
9540
10338
  if (command === "config") {
9541
10339
  const { config: config2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
9542
- const positionals = argv.filter((a) => !a.startsWith("-") && a !== "config");
9543
- config2(positionals);
10340
+ config2(values.positionals);
9544
10341
  return;
9545
10342
  }
9546
10343
  if (command === "upgrade") {
@@ -9550,7 +10347,7 @@ async function main() {
9550
10347
  }
9551
10348
  if (values.local) {
9552
10349
  const { runLocalCommit: runLocalCommit2 } = await Promise.resolve().then(() => (init_local(), local_exports));
9553
- await runLocalCommit2(messageOnly, push, values.model);
10350
+ await runLocalCommit2(values);
9554
10351
  return;
9555
10352
  }
9556
10353
  const apiKeyToUse = apiKey ?? getApiKey();
@@ -9558,11 +10355,12 @@ async function main() {
9558
10355
  const { getLocalProviderConfig: getLocalProviderConfig2 } = await Promise.resolve().then(() => (init_local(), local_exports));
9559
10356
  if (getLocalProviderConfig2()) {
9560
10357
  const { runLocalCommit: runLocalCommit2 } = await Promise.resolve().then(() => (init_local(), local_exports));
9561
- await runLocalCommit2(messageOnly, push, values.model);
10358
+ await runLocalCommit2(values);
9562
10359
  return;
9563
10360
  }
9564
10361
  }
9565
- 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);
9566
10364
  }
9567
10365
  main().catch((err) => {
9568
10366
  const args = process.argv.slice(2);
@@ -9572,3 +10370,7 @@ main().catch((err) => {
9572
10370
  }
9573
10371
  process.exit(1);
9574
10372
  });
10373
+ // Annotate the CommonJS export names for ESM import in node:
10374
+ 0 && (module.exports = {
10375
+ parseArgs
10376
+ });