contribute-now 0.8.0-dev.7eaa951 → 0.8.0-dev.a835394

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 (3) hide show
  1. package/README.md +35 -0
  2. package/dist/cli.js +1417 -455
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -5644,6 +5644,242 @@ var init_dist = __esm(() => {
5644
5644
  init_types2();
5645
5645
  });
5646
5646
 
5647
+ // src/utils/gh.ts
5648
+ var exports_gh = {};
5649
+ __export(exports_gh, {
5650
+ isRepoFork: () => isRepoFork,
5651
+ getRepoLabels: () => getRepoLabels,
5652
+ getPRForBranch: () => getPRForBranch,
5653
+ getPRContent: () => getPRContent,
5654
+ getMergedPRForBranch: () => getMergedPRForBranch,
5655
+ getIssueContent: () => getIssueContent,
5656
+ getCurrentRepoInfo: () => getCurrentRepoInfo,
5657
+ createPRFill: () => createPRFill,
5658
+ createPR: () => createPR,
5659
+ checkRepoPermissions: () => checkRepoPermissions,
5660
+ checkGhInstalled: () => checkGhInstalled,
5661
+ checkGhAuth: () => checkGhAuth,
5662
+ addLabelsToPR: () => addLabelsToPR,
5663
+ addLabelsToIssue: () => addLabelsToIssue
5664
+ });
5665
+ import { execFile as execFileCb2 } from "child_process";
5666
+ function run2(args) {
5667
+ return new Promise((resolve5) => {
5668
+ execFileCb2("gh", args, (error2, stdout2, stderr) => {
5669
+ resolve5({
5670
+ exitCode: error2 ? error2.code === "ENOENT" ? 127 : error2.status ?? 1 : 0,
5671
+ stdout: stdout2 ?? "",
5672
+ stderr: stderr ?? ""
5673
+ });
5674
+ });
5675
+ });
5676
+ }
5677
+ async function checkGhInstalled() {
5678
+ try {
5679
+ const { exitCode } = await run2(["--version"]);
5680
+ return exitCode === 0;
5681
+ } catch {
5682
+ return false;
5683
+ }
5684
+ }
5685
+ async function checkGhAuth() {
5686
+ try {
5687
+ const { exitCode } = await run2(["auth", "status"]);
5688
+ return exitCode === 0;
5689
+ } catch {
5690
+ return false;
5691
+ }
5692
+ }
5693
+ async function checkRepoPermissions(owner, repo) {
5694
+ if (!SAFE_SLUG.test(owner) || !SAFE_SLUG.test(repo))
5695
+ return null;
5696
+ const { exitCode, stdout: stdout2 } = await run2(["api", `repos/${owner}/${repo}`, "--jq", ".permissions"]);
5697
+ if (exitCode !== 0)
5698
+ return null;
5699
+ try {
5700
+ return JSON.parse(stdout2.trim());
5701
+ } catch {
5702
+ return null;
5703
+ }
5704
+ }
5705
+ async function isRepoFork() {
5706
+ const { exitCode, stdout: stdout2 } = await run2(["repo", "view", "--json", "isFork", "-q", ".isFork"]);
5707
+ if (exitCode !== 0)
5708
+ return null;
5709
+ const val = stdout2.trim();
5710
+ if (val === "true")
5711
+ return true;
5712
+ if (val === "false")
5713
+ return false;
5714
+ return null;
5715
+ }
5716
+ async function getCurrentRepoInfo() {
5717
+ const { exitCode, stdout: stdout2 } = await run2([
5718
+ "repo",
5719
+ "view",
5720
+ "--json",
5721
+ "nameWithOwner",
5722
+ "-q",
5723
+ ".nameWithOwner"
5724
+ ]);
5725
+ if (exitCode !== 0)
5726
+ return null;
5727
+ const nameWithOwner = stdout2.trim();
5728
+ if (!nameWithOwner)
5729
+ return null;
5730
+ const [owner, repo] = nameWithOwner.split("/");
5731
+ if (!owner || !repo)
5732
+ return null;
5733
+ return { owner, repo };
5734
+ }
5735
+ async function createPR(options) {
5736
+ const args = [
5737
+ "pr",
5738
+ "create",
5739
+ "--base",
5740
+ options.base,
5741
+ "--title",
5742
+ options.title,
5743
+ "--body",
5744
+ options.body
5745
+ ];
5746
+ if (options.draft)
5747
+ args.push("--draft");
5748
+ return run2(args);
5749
+ }
5750
+ async function createPRFill(base, draft) {
5751
+ const args = ["pr", "create", "--base", base, "--fill"];
5752
+ if (draft)
5753
+ args.push("--draft");
5754
+ return run2(args);
5755
+ }
5756
+ async function getPRForBranch(headBranch) {
5757
+ const { exitCode, stdout: stdout2 } = await run2([
5758
+ "pr",
5759
+ "list",
5760
+ "--head",
5761
+ headBranch,
5762
+ "--state",
5763
+ "open",
5764
+ "--json",
5765
+ "number,url,title,state",
5766
+ "--limit",
5767
+ "1"
5768
+ ]);
5769
+ if (exitCode !== 0)
5770
+ return null;
5771
+ try {
5772
+ const prs = JSON.parse(stdout2.trim());
5773
+ return prs.length > 0 ? prs[0] : null;
5774
+ } catch {
5775
+ return null;
5776
+ }
5777
+ }
5778
+ async function getMergedPRForBranch(headBranch) {
5779
+ const { exitCode, stdout: stdout2 } = await run2([
5780
+ "pr",
5781
+ "list",
5782
+ "--head",
5783
+ headBranch,
5784
+ "--state",
5785
+ "merged",
5786
+ "--json",
5787
+ "number,url,title,state",
5788
+ "--limit",
5789
+ "1"
5790
+ ]);
5791
+ if (exitCode !== 0)
5792
+ return null;
5793
+ try {
5794
+ const prs = JSON.parse(stdout2.trim());
5795
+ return prs.length > 0 ? prs[0] : null;
5796
+ } catch {
5797
+ return null;
5798
+ }
5799
+ }
5800
+ async function getRepoLabels() {
5801
+ const PAGE_SIZE = 100;
5802
+ const allLabels = [];
5803
+ let page = 1;
5804
+ while (true) {
5805
+ const { exitCode, stdout: stdout2 } = await run2([
5806
+ "label",
5807
+ "list",
5808
+ "--json",
5809
+ "name,description,color",
5810
+ "--limit",
5811
+ String(PAGE_SIZE),
5812
+ "--page",
5813
+ String(page)
5814
+ ]);
5815
+ if (exitCode !== 0)
5816
+ break;
5817
+ let batch = [];
5818
+ try {
5819
+ batch = JSON.parse(stdout2.trim());
5820
+ } catch {
5821
+ break;
5822
+ }
5823
+ if (!Array.isArray(batch) || batch.length === 0)
5824
+ break;
5825
+ for (const item of batch) {
5826
+ if (typeof item.name === "string" && item.name.trim().length > 0) {
5827
+ allLabels.push({
5828
+ name: item.name.trim(),
5829
+ description: typeof item.description === "string" ? item.description.trim() : "",
5830
+ color: typeof item.color === "string" ? item.color.trim().replace(/^#/, "") : ""
5831
+ });
5832
+ }
5833
+ }
5834
+ if (batch.length < PAGE_SIZE)
5835
+ break;
5836
+ page++;
5837
+ }
5838
+ return allLabels;
5839
+ }
5840
+ async function getIssueContent(issueNumber) {
5841
+ const { exitCode, stdout: stdout2 } = await run2([
5842
+ "issue",
5843
+ "view",
5844
+ String(issueNumber),
5845
+ "--json",
5846
+ "title,body"
5847
+ ]);
5848
+ if (exitCode !== 0)
5849
+ return null;
5850
+ try {
5851
+ const parsed = JSON.parse(stdout2.trim());
5852
+ const title = typeof parsed.title === "string" ? parsed.title : "";
5853
+ const body = typeof parsed.body === "string" ? parsed.body : "";
5854
+ return { title, body };
5855
+ } catch {
5856
+ return null;
5857
+ }
5858
+ }
5859
+ async function getPRContent(prNumber) {
5860
+ const { exitCode, stdout: stdout2 } = await run2(["pr", "view", String(prNumber), "--json", "title,body"]);
5861
+ if (exitCode !== 0)
5862
+ return null;
5863
+ try {
5864
+ const parsed = JSON.parse(stdout2.trim());
5865
+ const title = typeof parsed.title === "string" ? parsed.title : "";
5866
+ const body = typeof parsed.body === "string" ? parsed.body : "";
5867
+ return { title, body };
5868
+ } catch {
5869
+ return null;
5870
+ }
5871
+ }
5872
+ async function addLabelsToIssue(issueNumber, labels) {
5873
+ return run2(["issue", "edit", String(issueNumber), "--add-label", labels.join(",")]);
5874
+ }
5875
+ async function addLabelsToPR(prNumber, labels) {
5876
+ return run2(["pr", "edit", String(prNumber), "--add-label", labels.join(",")]);
5877
+ }
5878
+ var SAFE_SLUG;
5879
+ var init_gh = __esm(() => {
5880
+ SAFE_SLUG = /^[\w.-]+$/;
5881
+ });
5882
+
5647
5883
  // node_modules/consola/dist/core.mjs
5648
5884
  var LogLevels = {
5649
5885
  silent: Number.NEGATIVE_INFINITY,
@@ -7055,10 +7291,13 @@ import {
7055
7291
  statSync,
7056
7292
  writeFileSync
7057
7293
  } from "fs";
7294
+ import { homedir } from "os";
7058
7295
  import { dirname, join, resolve } from "path";
7059
7296
  var CONFIG_FILENAME = ".contributerc.json";
7060
7297
  var LOCAL_CONFIG_DIRNAME = "contribute-now";
7061
7298
  var LOCAL_CONFIG_FILENAME = "config.json";
7299
+ var GLOBAL_CONFIG_DIRNAME = ".contribute-now";
7300
+ var GLOBAL_CONFIG_FILENAME = "config.json";
7062
7301
  function findRepoRoot(cwd = process.cwd()) {
7063
7302
  let current = resolve(cwd);
7064
7303
  while (true) {
@@ -7148,13 +7387,44 @@ function parseConfigFile(path) {
7148
7387
  aiHost: _aiHost,
7149
7388
  ...config
7150
7389
  } = parsed;
7151
- return {
7390
+ const normalizedConfig = {
7152
7391
  ...config,
7153
- aiEnabled: parsed.aiEnabled !== false,
7154
7392
  aiProvider: parsed.aiProvider,
7155
7393
  aiModel: parsed.aiModel?.trim() || undefined,
7156
7394
  showTips: parsed.showTips !== false
7157
7395
  };
7396
+ if (typeof parsed.aiEnabled === "boolean") {
7397
+ normalizedConfig.aiEnabled = parsed.aiEnabled;
7398
+ }
7399
+ return normalizedConfig;
7400
+ } catch {
7401
+ return null;
7402
+ }
7403
+ }
7404
+ function parseGlobalConfigFile(path) {
7405
+ try {
7406
+ const raw = readFileSync(path, "utf-8");
7407
+ const parsed = JSON.parse(raw);
7408
+ if (typeof parsed !== "object" || parsed === null) {
7409
+ return null;
7410
+ }
7411
+ if (parsed.aiProvider !== undefined && (typeof parsed.aiProvider !== "string" || !VALID_AI_PROVIDERS.includes(parsed.aiProvider))) {
7412
+ console.error(`Invalid aiProvider "${String(parsed.aiProvider)}" in ${GLOBAL_CONFIG_FILENAME}. Valid: ${VALID_AI_PROVIDERS.join(", ")}`);
7413
+ return null;
7414
+ }
7415
+ if (parsed.aiModel !== undefined && (typeof parsed.aiModel !== "string" || !parsed.aiModel.trim())) {
7416
+ console.error(`Invalid ${GLOBAL_CONFIG_FILENAME}: aiModel must be a non-empty string.`);
7417
+ return null;
7418
+ }
7419
+ if (parsed.aiEnabled !== undefined && typeof parsed.aiEnabled !== "boolean") {
7420
+ console.error(`Invalid ${GLOBAL_CONFIG_FILENAME}: aiEnabled must be a boolean when set.`);
7421
+ return null;
7422
+ }
7423
+ return {
7424
+ aiEnabled: parsed.aiEnabled,
7425
+ aiProvider: parsed.aiProvider,
7426
+ aiModel: parsed.aiModel?.trim() || undefined
7427
+ };
7158
7428
  } catch {
7159
7429
  return null;
7160
7430
  }
@@ -7169,6 +7439,9 @@ function getConfigPath(cwd = process.cwd()) {
7169
7439
  function getLegacyConfigPath(cwd = process.cwd()) {
7170
7440
  return join(findRepoRoot(cwd) ?? cwd, CONFIG_FILENAME);
7171
7441
  }
7442
+ function getGlobalConfigPath(baseDir = homedir()) {
7443
+ return join(baseDir, GLOBAL_CONFIG_DIRNAME, GLOBAL_CONFIG_FILENAME);
7444
+ }
7172
7445
  function getLocalConfigPath(cwd = process.cwd()) {
7173
7446
  const gitDir = resolveGitDir(cwd);
7174
7447
  if (!gitDir) {
@@ -7203,12 +7476,25 @@ function getConfigLocationLabel(cwd = process.cwd()) {
7203
7476
  function configExists(cwd = process.cwd()) {
7204
7477
  return getConfigSource(cwd) !== null;
7205
7478
  }
7479
+ function globalConfigExists(baseDir = homedir()) {
7480
+ return existsSync(getGlobalConfigPath(baseDir));
7481
+ }
7206
7482
  var VALID_WORKFLOWS = ["clean-flow", "github-flow", "git-flow"];
7207
7483
  var VALID_ROLES = ["maintainer", "contributor"];
7208
7484
  var VALID_CONVENTIONS = ["conventional", "clean-commit", "none"];
7209
7485
  var VALID_AI_PROVIDERS = ["copilot", "ollama-cloud", "openrouter"];
7210
- function isAIEnabled(config, cliNoAI = false) {
7211
- return config.aiEnabled !== false && !cliNoAI;
7486
+ function isAIEnabled(config, cliNoAI = false, globalConfig) {
7487
+ if (cliNoAI) {
7488
+ return false;
7489
+ }
7490
+ if (typeof config.aiEnabled === "boolean") {
7491
+ return config.aiEnabled;
7492
+ }
7493
+ const resolvedGlobalConfig = globalConfig === undefined ? readGlobalConfig() : globalConfig;
7494
+ if (typeof resolvedGlobalConfig?.aiEnabled === "boolean") {
7495
+ return resolvedGlobalConfig.aiEnabled;
7496
+ }
7497
+ return true;
7212
7498
  }
7213
7499
  function shouldShowTips(config) {
7214
7500
  return config?.showTips !== false;
@@ -7222,6 +7508,13 @@ function readConfig(cwd = process.cwd()) {
7222
7508
  return null;
7223
7509
  return parseConfigFile(path);
7224
7510
  }
7511
+ function readGlobalConfig(baseDir = homedir()) {
7512
+ const path = getGlobalConfigPath(baseDir);
7513
+ if (!existsSync(path)) {
7514
+ return null;
7515
+ }
7516
+ return parseGlobalConfigFile(path);
7517
+ }
7225
7518
  function writeConfig(config, cwd = process.cwd()) {
7226
7519
  const path = getConfigPath(cwd);
7227
7520
  const { aiHost: _aiHost, ...storedConfig } = config;
@@ -7229,6 +7522,15 @@ function writeConfig(config, cwd = process.cwd()) {
7229
7522
  writeFileSync(path, `${JSON.stringify(storedConfig, null, 2)}
7230
7523
  `, "utf-8");
7231
7524
  }
7525
+ function writeGlobalConfig(config, baseDir = homedir()) {
7526
+ const path = getGlobalConfigPath(baseDir);
7527
+ mkdirSync(dirname(path), { recursive: true, mode: 448 });
7528
+ writeFileSync(path, `${JSON.stringify(config, null, 2)}
7529
+ `, {
7530
+ encoding: "utf-8",
7531
+ mode: 384
7532
+ });
7533
+ }
7232
7534
  function isGitignored(cwd = process.cwd()) {
7233
7535
  const gitignorePath = join(cwd, ".gitignore");
7234
7536
  if (!existsSync(gitignorePath))
@@ -9115,6 +9417,24 @@ var COMMAND_GUIDES = {
9115
9417
  description: "check config, remotes, and workflow resolution together"
9116
9418
  }
9117
9419
  ]
9420
+ },
9421
+ label: {
9422
+ summary: "Apply existing labels or get ranked suggestions for issues and pull requests.",
9423
+ examples: [
9424
+ { command: "cn label --help", description: "learn label add and suggest usage" },
9425
+ {
9426
+ command: "cn label add --issue 42 bug,enhancement",
9427
+ description: "apply labels to an issue"
9428
+ },
9429
+ {
9430
+ command: 'cn label add --pr 7 "good first issue"',
9431
+ description: "apply a label with spaces to a PR"
9432
+ },
9433
+ {
9434
+ command: "cn label suggest --issue 42",
9435
+ description: "get ranked label suggestions from content"
9436
+ }
9437
+ ]
9118
9438
  }
9119
9439
  };
9120
9440
  var LOADING_TIPS = [
@@ -10619,19 +10939,19 @@ init_dist();
10619
10939
 
10620
10940
  // src/utils/secrets.ts
10621
10941
  import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync4 } from "fs";
10622
- import { homedir } from "os";
10942
+ import { homedir as homedir2 } from "os";
10623
10943
  import { join as join5, resolve as resolve4 } from "path";
10624
10944
  var CONTRIBUTE_NOW_SECRETS_DIRNAME = ".contribute-now";
10625
10945
  var CONTRIBUTE_NOW_SECRETS_STORE_DIRNAME = "secrets";
10626
10946
  var OLLAMA_CLOUD_API_KEY = "ollama.cloud.apiKey";
10627
10947
  var OPENROUTER_API_KEY = "openrouter.apiKey";
10628
- function getSecretsStorePath(baseDir = homedir()) {
10948
+ function getSecretsStorePath(baseDir = homedir2()) {
10629
10949
  return resolve4(baseDir, CONTRIBUTE_NOW_SECRETS_DIRNAME, CONTRIBUTE_NOW_SECRETS_STORE_DIRNAME);
10630
10950
  }
10631
- function getSecretsFilePath(baseDir = homedir()) {
10951
+ function getSecretsFilePath(baseDir = homedir2()) {
10632
10952
  return join5(getSecretsStorePath(baseDir), "store.json");
10633
10953
  }
10634
- function readSecretsStore(baseDir = homedir()) {
10954
+ function readSecretsStore(baseDir = homedir2()) {
10635
10955
  const filePath = getSecretsFilePath(baseDir);
10636
10956
  if (!existsSync5(filePath)) {
10637
10957
  return null;
@@ -10643,7 +10963,7 @@ function readSecretsStore(baseDir = homedir()) {
10643
10963
  return null;
10644
10964
  }
10645
10965
  }
10646
- function writeSecretsStore(store, baseDir = homedir()) {
10966
+ function writeSecretsStore(store, baseDir = homedir2()) {
10647
10967
  const storePath = getSecretsStorePath(baseDir);
10648
10968
  const filePath = getSecretsFilePath(baseDir);
10649
10969
  mkdirSync4(storePath, { recursive: true, mode: 448 });
@@ -10657,23 +10977,23 @@ function writeSecretsStore(store, baseDir = homedir()) {
10657
10977
  chmodSync(filePath, 384);
10658
10978
  } catch {}
10659
10979
  }
10660
- function hasSecretsStore(baseDir = homedir()) {
10980
+ function hasSecretsStore(baseDir = homedir2()) {
10661
10981
  return existsSync5(getSecretsFilePath(baseDir));
10662
10982
  }
10663
- async function hasOllamaCloudApiKey(baseDir = homedir()) {
10983
+ async function hasOllamaCloudApiKey(baseDir = homedir2()) {
10664
10984
  return typeof readSecretsStore(baseDir)?.[OLLAMA_CLOUD_API_KEY] === "string";
10665
10985
  }
10666
- async function getOllamaCloudApiKey(baseDir = homedir()) {
10986
+ async function getOllamaCloudApiKey(baseDir = homedir2()) {
10667
10987
  return readSecretsStore(baseDir)?.[OLLAMA_CLOUD_API_KEY] ?? null;
10668
10988
  }
10669
- async function setOllamaCloudApiKey(value, baseDir = homedir()) {
10989
+ async function setOllamaCloudApiKey(value, baseDir = homedir2()) {
10670
10990
  const existingStore = readSecretsStore(baseDir) ?? {};
10671
10991
  writeSecretsStore({
10672
10992
  ...existingStore,
10673
10993
  [OLLAMA_CLOUD_API_KEY]: value
10674
10994
  }, baseDir);
10675
10995
  }
10676
- async function deleteOllamaCloudApiKey(baseDir = homedir()) {
10996
+ async function deleteOllamaCloudApiKey(baseDir = homedir2()) {
10677
10997
  const existingStore = readSecretsStore(baseDir);
10678
10998
  if (!existingStore || !(OLLAMA_CLOUD_API_KEY in existingStore)) {
10679
10999
  return false;
@@ -10690,20 +11010,20 @@ async function deleteOllamaCloudApiKey(baseDir = homedir()) {
10690
11010
  writeSecretsStore(nextStore, baseDir);
10691
11011
  return true;
10692
11012
  }
10693
- async function hasOpenRouterApiKey(baseDir = homedir()) {
11013
+ async function hasOpenRouterApiKey(baseDir = homedir2()) {
10694
11014
  return typeof readSecretsStore(baseDir)?.[OPENROUTER_API_KEY] === "string";
10695
11015
  }
10696
- async function getOpenRouterApiKey(baseDir = homedir()) {
11016
+ async function getOpenRouterApiKey(baseDir = homedir2()) {
10697
11017
  return readSecretsStore(baseDir)?.[OPENROUTER_API_KEY] ?? null;
10698
11018
  }
10699
- async function setOpenRouterApiKey(value, baseDir = homedir()) {
11019
+ async function setOpenRouterApiKey(value, baseDir = homedir2()) {
10700
11020
  const existingStore = readSecretsStore(baseDir) ?? {};
10701
11021
  writeSecretsStore({
10702
11022
  ...existingStore,
10703
11023
  [OPENROUTER_API_KEY]: value
10704
11024
  }, baseDir);
10705
11025
  }
10706
- async function deleteOpenRouterApiKey(baseDir = homedir()) {
11026
+ async function deleteOpenRouterApiKey(baseDir = homedir2()) {
10707
11027
  const existingStore = readSecretsStore(baseDir);
10708
11028
  if (!existingStore || !(OPENROUTER_API_KEY in existingStore)) {
10709
11029
  return false;
@@ -10789,6 +11109,31 @@ var DEFAULT_OLLAMA_CLOUD_MODEL = "gpt-oss:120b";
10789
11109
  var DEFAULT_OLLAMA_CLOUD_HOST = "https://ollama.com/v1";
10790
11110
  var DEFAULT_OPENROUTER_MODEL = "openai/gpt-4o-mini";
10791
11111
  var DEFAULT_OPENROUTER_HOST = "https://openrouter.ai/api/v1";
11112
+ function resolveAIConfigFromSources(repoConfig, globalConfig) {
11113
+ const provider = repoConfig?.aiProvider ?? globalConfig?.aiProvider ?? "copilot";
11114
+ const useGlobalModel = !repoConfig?.aiModel && globalConfig?.aiProvider === provider;
11115
+ const globalModel = useGlobalModel ? globalConfig?.aiModel?.trim() : undefined;
11116
+ if (provider === "ollama-cloud") {
11117
+ return {
11118
+ provider,
11119
+ providerLabel: "Ollama Cloud",
11120
+ model: repoConfig?.aiModel?.trim() || globalModel || DEFAULT_OLLAMA_CLOUD_MODEL,
11121
+ host: DEFAULT_OLLAMA_CLOUD_HOST
11122
+ };
11123
+ }
11124
+ if (provider === "openrouter") {
11125
+ return {
11126
+ provider,
11127
+ providerLabel: "OpenRouter",
11128
+ model: repoConfig?.aiModel?.trim() || globalModel || DEFAULT_OPENROUTER_MODEL,
11129
+ host: DEFAULT_OPENROUTER_HOST
11130
+ };
11131
+ }
11132
+ return {
11133
+ provider: "copilot",
11134
+ providerLabel: "GitHub Copilot"
11135
+ };
11136
+ }
10792
11137
  function prioritizeOllamaCloudModels(models, preferredModel = DEFAULT_OLLAMA_CLOUD_MODEL) {
10793
11138
  const uniqueModels = [...new Set(models.map((model) => model.trim()).filter(Boolean))];
10794
11139
  const sortedModels = [...uniqueModels].sort((left, right) => left.localeCompare(right));
@@ -10862,28 +11207,9 @@ function prioritizeOpenRouterModels(models, preferredModel = DEFAULT_OPENROUTER_
10862
11207
  return sortedModels.includes(preferredModel) ? [preferredModel, ...sortedModels.filter((model) => model !== preferredModel)] : sortedModels;
10863
11208
  }
10864
11209
  function resolveAIConfig(config) {
10865
- const resolvedConfig = config ?? readConfig();
10866
- const provider = resolvedConfig?.aiProvider ?? "copilot";
10867
- if (provider === "ollama-cloud") {
10868
- return {
10869
- provider,
10870
- providerLabel: "Ollama Cloud",
10871
- model: resolvedConfig?.aiModel?.trim() || DEFAULT_OLLAMA_CLOUD_MODEL,
10872
- host: DEFAULT_OLLAMA_CLOUD_HOST
10873
- };
10874
- }
10875
- if (provider === "openrouter") {
10876
- return {
10877
- provider,
10878
- providerLabel: "OpenRouter",
10879
- model: resolvedConfig?.aiModel?.trim() || DEFAULT_OPENROUTER_MODEL,
10880
- host: DEFAULT_OPENROUTER_HOST
10881
- };
10882
- }
10883
- return {
10884
- provider: "copilot",
10885
- providerLabel: "GitHub Copilot"
10886
- };
11210
+ const repoConfig = config ?? readConfig();
11211
+ const globalConfig = readGlobalConfig();
11212
+ return resolveAIConfigFromSources(repoConfig, globalConfig);
10887
11213
  }
10888
11214
  function suppressSubprocessWarnings() {
10889
11215
  process.env.NODE_NO_WARNINGS = "1";
@@ -11793,145 +12119,8 @@ async function promptForBranchName(options) {
11793
12119
  }
11794
12120
  }
11795
12121
 
11796
- // src/utils/gh.ts
11797
- import { execFile as execFileCb2 } from "child_process";
11798
- function run2(args) {
11799
- return new Promise((resolve5) => {
11800
- execFileCb2("gh", args, (error2, stdout2, stderr) => {
11801
- resolve5({
11802
- exitCode: error2 ? error2.code === "ENOENT" ? 127 : error2.status ?? 1 : 0,
11803
- stdout: stdout2 ?? "",
11804
- stderr: stderr ?? ""
11805
- });
11806
- });
11807
- });
11808
- }
11809
- async function checkGhInstalled() {
11810
- try {
11811
- const { exitCode } = await run2(["--version"]);
11812
- return exitCode === 0;
11813
- } catch {
11814
- return false;
11815
- }
11816
- }
11817
- async function checkGhAuth() {
11818
- try {
11819
- const { exitCode } = await run2(["auth", "status"]);
11820
- return exitCode === 0;
11821
- } catch {
11822
- return false;
11823
- }
11824
- }
11825
- var SAFE_SLUG = /^[\w.-]+$/;
11826
- async function checkRepoPermissions(owner, repo) {
11827
- if (!SAFE_SLUG.test(owner) || !SAFE_SLUG.test(repo))
11828
- return null;
11829
- const { exitCode, stdout: stdout2 } = await run2(["api", `repos/${owner}/${repo}`, "--jq", ".permissions"]);
11830
- if (exitCode !== 0)
11831
- return null;
11832
- try {
11833
- return JSON.parse(stdout2.trim());
11834
- } catch {
11835
- return null;
11836
- }
11837
- }
11838
- async function isRepoFork() {
11839
- const { exitCode, stdout: stdout2 } = await run2(["repo", "view", "--json", "isFork", "-q", ".isFork"]);
11840
- if (exitCode !== 0)
11841
- return null;
11842
- const val = stdout2.trim();
11843
- if (val === "true")
11844
- return true;
11845
- if (val === "false")
11846
- return false;
11847
- return null;
11848
- }
11849
- async function getCurrentRepoInfo() {
11850
- const { exitCode, stdout: stdout2 } = await run2([
11851
- "repo",
11852
- "view",
11853
- "--json",
11854
- "nameWithOwner",
11855
- "-q",
11856
- ".nameWithOwner"
11857
- ]);
11858
- if (exitCode !== 0)
11859
- return null;
11860
- const nameWithOwner = stdout2.trim();
11861
- if (!nameWithOwner)
11862
- return null;
11863
- const [owner, repo] = nameWithOwner.split("/");
11864
- if (!owner || !repo)
11865
- return null;
11866
- return { owner, repo };
11867
- }
11868
- async function createPR(options) {
11869
- const args = [
11870
- "pr",
11871
- "create",
11872
- "--base",
11873
- options.base,
11874
- "--title",
11875
- options.title,
11876
- "--body",
11877
- options.body
11878
- ];
11879
- if (options.draft)
11880
- args.push("--draft");
11881
- return run2(args);
11882
- }
11883
- async function createPRFill(base, draft) {
11884
- const args = ["pr", "create", "--base", base, "--fill"];
11885
- if (draft)
11886
- args.push("--draft");
11887
- return run2(args);
11888
- }
11889
- async function getPRForBranch(headBranch) {
11890
- const { exitCode, stdout: stdout2 } = await run2([
11891
- "pr",
11892
- "list",
11893
- "--head",
11894
- headBranch,
11895
- "--state",
11896
- "open",
11897
- "--json",
11898
- "number,url,title,state",
11899
- "--limit",
11900
- "1"
11901
- ]);
11902
- if (exitCode !== 0)
11903
- return null;
11904
- try {
11905
- const prs = JSON.parse(stdout2.trim());
11906
- return prs.length > 0 ? prs[0] : null;
11907
- } catch {
11908
- return null;
11909
- }
11910
- }
11911
- async function getMergedPRForBranch(headBranch) {
11912
- const { exitCode, stdout: stdout2 } = await run2([
11913
- "pr",
11914
- "list",
11915
- "--head",
11916
- headBranch,
11917
- "--state",
11918
- "merged",
11919
- "--json",
11920
- "number,url,title,state",
11921
- "--limit",
11922
- "1"
11923
- ]);
11924
- if (exitCode !== 0)
11925
- return null;
11926
- try {
11927
- const prs = JSON.parse(stdout2.trim());
11928
- return prs.length > 0 ? prs[0] : null;
11929
- } catch {
11930
- return null;
11931
- }
11932
- }
11933
-
11934
12122
  // src/commands/clean.ts
12123
+ init_gh();
11935
12124
  async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
11936
12125
  if (!config)
11937
12126
  return "skipped";
@@ -12723,6 +12912,62 @@ function buildConfigSnapshot(config, meta) {
12723
12912
  }
12724
12913
  };
12725
12914
  }
12915
+ function normalizeGlobalConfig(config) {
12916
+ const normalized = {
12917
+ aiEnabled: config.aiEnabled !== false
12918
+ };
12919
+ if (normalized.aiEnabled) {
12920
+ normalized.aiProvider = config.aiProvider ?? "copilot";
12921
+ if (normalized.aiProvider === "ollama-cloud") {
12922
+ normalized.aiModel = (config.aiModel?.trim() || DEFAULT_OLLAMA_CLOUD_MODEL).trim();
12923
+ } else if (normalized.aiProvider === "openrouter") {
12924
+ normalized.aiModel = (config.aiModel?.trim() || DEFAULT_OPENROUTER_MODEL).trim();
12925
+ }
12926
+ }
12927
+ return normalized;
12928
+ }
12929
+ function buildGlobalConfigSnapshot(config) {
12930
+ const normalized = normalizeGlobalConfig(config);
12931
+ const aiProvider = normalized.aiProvider ?? "copilot";
12932
+ const aiEnabled = normalized.aiEnabled !== false;
12933
+ const providerLabel = aiProvider === "ollama-cloud" ? "Ollama Cloud" : aiProvider === "openrouter" ? "OpenRouter" : "GitHub Copilot";
12934
+ return {
12935
+ location: getGlobalConfigPath(),
12936
+ exists: globalConfigExists(),
12937
+ ai: {
12938
+ enabled: aiEnabled,
12939
+ provider: aiProvider,
12940
+ providerLabel,
12941
+ model: aiEnabled ? normalized.aiModel ?? null : null
12942
+ }
12943
+ };
12944
+ }
12945
+ async function promptForGlobalConfigEdits(current) {
12946
+ const normalized = normalizeGlobalConfig(current);
12947
+ const aiEnabled = await selectBooleanValue("Global AI default", normalized.aiEnabled !== false, "Enabled", "Disabled");
12948
+ if (!aiEnabled) {
12949
+ return {
12950
+ config: {
12951
+ aiEnabled: false
12952
+ }
12953
+ };
12954
+ }
12955
+ const currentProvider = normalized.aiProvider ?? "copilot";
12956
+ const aiProvider = await selectCurrentValue("Global AI provider", AI_PROVIDER_OPTIONS, currentProvider);
12957
+ let aiModel;
12958
+ if (aiProvider === "ollama-cloud") {
12959
+ aiModel = await inputPrompt("Global Ollama Cloud model", normalized.aiModel ?? DEFAULT_OLLAMA_CLOUD_MODEL);
12960
+ } else if (aiProvider === "openrouter") {
12961
+ aiModel = await inputPrompt("Global OpenRouter model", normalized.aiModel ?? DEFAULT_OPENROUTER_MODEL);
12962
+ }
12963
+ return {
12964
+ config: {
12965
+ aiEnabled: true,
12966
+ aiProvider,
12967
+ aiModel: aiModel?.trim() || undefined
12968
+ }
12969
+ };
12970
+ }
12726
12971
  function defaultDevBranchForWorkflow(workflow) {
12727
12972
  return workflow === "git-flow" ? "develop" : "dev";
12728
12973
  }
@@ -13003,12 +13248,26 @@ function printConfigSummary(snapshot) {
13003
13248
  }
13004
13249
  }
13005
13250
  }
13251
+ function printGlobalConfigSummary(snapshot) {
13252
+ info(`Global config path: ${import_picocolors10.default.bold(snapshot.location)}`);
13253
+ info(`Global defaults file: ${import_picocolors10.default.bold(snapshot.exists ? "present" : "missing (using built-ins)")}`);
13254
+ info(`AI default: ${import_picocolors10.default.bold(snapshot.ai.enabled ? "enabled" : "disabled")}`);
13255
+ info(`AI provider: ${import_picocolors10.default.bold(snapshot.ai.providerLabel)}`);
13256
+ if (snapshot.ai.model) {
13257
+ info(`AI model: ${import_picocolors10.default.bold(snapshot.ai.model)}`);
13258
+ }
13259
+ }
13006
13260
  var config_default = defineCommand({
13007
13261
  meta: {
13008
13262
  name: "config",
13009
13263
  description: "Inspect or edit the repo config without rerunning setup"
13010
13264
  },
13011
13265
  args: {
13266
+ global: {
13267
+ type: "boolean",
13268
+ description: "Read or edit global defaults instead of repo config",
13269
+ default: false
13270
+ },
13012
13271
  json: {
13013
13272
  type: "boolean",
13014
13273
  description: "Print the active repo config as JSON with metadata",
@@ -13021,15 +13280,44 @@ var config_default = defineCommand({
13021
13280
  }
13022
13281
  },
13023
13282
  async run({ args }) {
13024
- if (!await isGitRepo()) {
13025
- error("Not inside a git repository.");
13026
- process.exit(1);
13027
- }
13028
13283
  if (args.json && args.edit) {
13029
13284
  error("Use either --json or --edit, not both at the same time.");
13030
13285
  process.exit(1);
13031
13286
  }
13032
13287
  await projectHeading("config", "\u2699\uFE0F");
13288
+ if (args.global) {
13289
+ const currentGlobal = readGlobalConfig() ?? {};
13290
+ if (args.edit) {
13291
+ try {
13292
+ const editResult = await promptForGlobalConfigEdits(currentGlobal);
13293
+ writeGlobalConfig(normalizeGlobalConfig(editResult.config));
13294
+ success("Updated global defaults.");
13295
+ const snapshot3 = buildGlobalConfigSnapshot(readGlobalConfig() ?? {});
13296
+ printGlobalConfigSummary(snapshot3);
13297
+ if (args.json) {
13298
+ console.log(JSON.stringify(snapshot3, null, 2));
13299
+ }
13300
+ return;
13301
+ } catch (err) {
13302
+ error(err instanceof Error ? err.message : String(err));
13303
+ process.exit(1);
13304
+ }
13305
+ }
13306
+ const snapshot2 = buildGlobalConfigSnapshot(currentGlobal);
13307
+ if (args.json) {
13308
+ console.log(JSON.stringify(snapshot2, null, 2));
13309
+ return;
13310
+ }
13311
+ printGlobalConfigSummary(snapshot2);
13312
+ console.log();
13313
+ console.log(` ${import_picocolors10.default.dim("Run `cn config --global --edit` to update global defaults.")}`);
13314
+ console.log();
13315
+ return;
13316
+ }
13317
+ if (!await isGitRepo()) {
13318
+ error("Not inside a git repository.");
13319
+ process.exit(1);
13320
+ }
13033
13321
  if (!configExists()) {
13034
13322
  error("No repo config found. Run `cn setup` first.");
13035
13323
  process.exit(1);
@@ -13195,7 +13483,7 @@ var import_picocolors12 = __toESM(require_picocolors(), 1);
13195
13483
  // package.json
13196
13484
  var package_default = {
13197
13485
  name: "contribute-now",
13198
- version: "0.8.0-dev.7eaa951",
13486
+ version: "0.8.0-dev.a835394",
13199
13487
  description: "Developer CLI that automates git workflows \u2014 branching, syncing, committing, and PRs \u2014 with multi-workflow and commit convention support.",
13200
13488
  type: "module",
13201
13489
  bin: {
@@ -13253,6 +13541,9 @@ var package_default = {
13253
13541
  }
13254
13542
  };
13255
13543
 
13544
+ // src/commands/doctor.ts
13545
+ init_gh();
13546
+
13256
13547
  // src/utils/remote.ts
13257
13548
  function parseRepoFromUrl(url) {
13258
13549
  const httpsMatch = url.match(/https?:\/\/github\.com\/([^/]+)\/([^/.]+?)(?:\.git)?$/);
@@ -13762,8 +14053,610 @@ async function uninstallHook() {
13762
14053
  success("commit-msg hook removed.");
13763
14054
  }
13764
14055
 
13765
- // src/commands/log.ts
14056
+ // src/commands/label.ts
14057
+ init_gh();
13766
14058
  var import_picocolors14 = __toESM(require_picocolors(), 1);
14059
+
14060
+ // src/utils/label.ts
14061
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync6, statSync as statSync4, writeFileSync as writeFileSync6 } from "fs";
14062
+ import { dirname as dirname5, join as join7, resolve as resolve5 } from "path";
14063
+
14064
+ // src/data/clean-labels.ts
14065
+ var CLEAN_LABELS = [
14066
+ {
14067
+ name: "bug",
14068
+ description: "[Type] Something isn't working [issues, PRs]",
14069
+ color: "d73a4a"
14070
+ },
14071
+ {
14072
+ name: "enhancement",
14073
+ description: "[Type] New feature or improvement to existing functionality [issues, PRs]",
14074
+ color: "1a7f37"
14075
+ },
14076
+ {
14077
+ name: "documentation",
14078
+ description: "[Type] Improvements or additions to docs, README, or guides [issues, PRs]",
14079
+ color: "0075ca"
14080
+ },
14081
+ {
14082
+ name: "refactor",
14083
+ description: "[Type] Code improvement without changing functionality [PRs]",
14084
+ color: "8957e5"
14085
+ },
14086
+ {
14087
+ name: "performance",
14088
+ description: "[Type] Optimization, speed, or resource usage improvements [issues, PRs]",
14089
+ color: "e3795c"
14090
+ },
14091
+ {
14092
+ name: "security",
14093
+ description: "[Type] Security vulnerability or hardening [issues, PRs]",
14094
+ color: "d4a72c"
14095
+ },
14096
+ {
14097
+ name: "blocked",
14098
+ description: "[Status] Waiting on another issue, decision, or external factor [issues]",
14099
+ color: "cf222e"
14100
+ },
14101
+ {
14102
+ name: "needs triage",
14103
+ description: "[Status] New issue \u2014 needs review and categorization [issues]",
14104
+ color: "e16f24"
14105
+ },
14106
+ {
14107
+ name: "awaiting response",
14108
+ description: "[Status] Waiting for more information from the reporter [issues]",
14109
+ color: "1a7ec7"
14110
+ },
14111
+ {
14112
+ name: "ready",
14113
+ description: "[Status] Triaged and ready to be picked up [issues]",
14114
+ color: "2da44e"
14115
+ },
14116
+ {
14117
+ name: "good first issue",
14118
+ description: "[Community] Good for newcomers \u2014 well-scoped and documented [issues]",
14119
+ color: "7057ff"
14120
+ },
14121
+ {
14122
+ name: "help wanted",
14123
+ description: "[Community] Open for community contribution [issues]",
14124
+ color: "0e8a16"
14125
+ },
14126
+ {
14127
+ name: "maintainer only",
14128
+ description: "[Community] Reserved for maintainers \u2014 not open for external contribution [issues, PRs]",
14129
+ color: "b60205"
14130
+ },
14131
+ {
14132
+ name: "duplicate",
14133
+ description: "[Resolution] This issue or pull request already exists [issues, PRs]",
14134
+ color: "cfd3d7"
14135
+ },
14136
+ {
14137
+ name: "invalid",
14138
+ description: "[Resolution] This doesn't seem right [issues, PRs]",
14139
+ color: "cfd3d7"
14140
+ },
14141
+ {
14142
+ name: "wontfix",
14143
+ description: "[Resolution] This will not be worked on [issues]",
14144
+ color: "cfd3d7"
14145
+ },
14146
+ {
14147
+ name: "core",
14148
+ description: "[Area] Core logic, business rules, and primary functionality [issues, PRs]",
14149
+ color: "0052cc"
14150
+ },
14151
+ {
14152
+ name: "interface",
14153
+ description: "[Area] User-facing layer \u2014 UI, CLI, API endpoints, or SDK surface [issues, PRs]",
14154
+ color: "5319e7"
14155
+ },
14156
+ {
14157
+ name: "data",
14158
+ description: "[Area] Database, storage, caching, or data models [issues, PRs]",
14159
+ color: "006b75"
14160
+ },
14161
+ {
14162
+ name: "infra",
14163
+ description: "[Area] Build system, CI/CD, deployment, config, and DevOps [issues, PRs]",
14164
+ color: "e16f24"
14165
+ },
14166
+ {
14167
+ name: "testing",
14168
+ description: "[Area] Unit tests, integration tests, E2E, and test tooling [issues, PRs]",
14169
+ color: "1a7f37"
14170
+ }
14171
+ ];
14172
+
14173
+ // src/utils/label.ts
14174
+ var LABEL_CACHE_DIRNAME = "contribute-now";
14175
+ var LABEL_CACHE_FILENAME = "labels.json";
14176
+ function findRepoRoot3(cwd = process.cwd()) {
14177
+ let current = resolve5(cwd);
14178
+ while (true) {
14179
+ if (existsSync7(join7(current, ".git"))) {
14180
+ return current;
14181
+ }
14182
+ const parent = dirname5(current);
14183
+ if (parent === current) {
14184
+ return null;
14185
+ }
14186
+ current = parent;
14187
+ }
14188
+ }
14189
+ function resolveGitDir3(cwd = process.cwd()) {
14190
+ const repoRoot = findRepoRoot3(cwd);
14191
+ if (!repoRoot) {
14192
+ return null;
14193
+ }
14194
+ const dotGitPath = join7(repoRoot, ".git");
14195
+ try {
14196
+ const stat = statSync4(dotGitPath);
14197
+ if (stat.isDirectory()) {
14198
+ return dotGitPath;
14199
+ }
14200
+ if (!stat.isFile()) {
14201
+ return null;
14202
+ }
14203
+ const content = readFileSync6(dotGitPath, "utf-8").trim();
14204
+ const match = /^gitdir:\s*(.+)$/i.exec(content);
14205
+ if (!match) {
14206
+ return null;
14207
+ }
14208
+ return resolve5(repoRoot, match[1].trim());
14209
+ } catch {
14210
+ return null;
14211
+ }
14212
+ }
14213
+ function getLabelCachePath(cwd = process.cwd()) {
14214
+ const gitDir = resolveGitDir3(cwd);
14215
+ if (!gitDir) {
14216
+ return null;
14217
+ }
14218
+ return join7(gitDir, LABEL_CACHE_DIRNAME, LABEL_CACHE_FILENAME);
14219
+ }
14220
+ function readLabelCache(cwd = process.cwd()) {
14221
+ const cachePath = getLabelCachePath(cwd);
14222
+ if (!cachePath || !existsSync7(cachePath)) {
14223
+ return null;
14224
+ }
14225
+ try {
14226
+ const raw = JSON.parse(readFileSync6(cachePath, "utf-8"));
14227
+ if (!Array.isArray(raw.labels) || typeof raw.fetchedAt !== "string" || raw.source !== "clean-labels" && raw.source !== "repo") {
14228
+ return null;
14229
+ }
14230
+ const validatedLabels = [];
14231
+ for (const entry of raw.labels) {
14232
+ if (typeof entry !== "object" || entry === null || typeof entry.name !== "string" || !entry.name) {
14233
+ continue;
14234
+ }
14235
+ const e3 = entry;
14236
+ validatedLabels.push({
14237
+ name: e3.name.trim(),
14238
+ description: typeof e3.description === "string" ? e3.description.trim() : "",
14239
+ color: typeof e3.color === "string" ? e3.color.trim() : ""
14240
+ });
14241
+ }
14242
+ if (validatedLabels.length === 0) {
14243
+ return null;
14244
+ }
14245
+ return {
14246
+ labels: validatedLabels,
14247
+ source: raw.source,
14248
+ fetchedAt: raw.fetchedAt
14249
+ };
14250
+ } catch {
14251
+ return null;
14252
+ }
14253
+ }
14254
+ function writeLabelCache(cache, cwd = process.cwd()) {
14255
+ const cachePath = getLabelCachePath(cwd);
14256
+ if (!cachePath) {
14257
+ return;
14258
+ }
14259
+ mkdirSync6(dirname5(cachePath), { recursive: true });
14260
+ writeFileSync6(cachePath, `${JSON.stringify(cache, null, 2)}
14261
+ `, "utf-8");
14262
+ }
14263
+ function normalizeLabelName(name) {
14264
+ return name.toLowerCase().trim();
14265
+ }
14266
+ function isCleanLabelsMatch(repoLabels) {
14267
+ const cleanNames = new Set(CLEAN_LABELS.map((l2) => normalizeLabelName(l2.name)));
14268
+ const repoNames = new Set(repoLabels.map((l2) => normalizeLabelName(l2.name)));
14269
+ if (cleanNames.size !== repoNames.size) {
14270
+ return false;
14271
+ }
14272
+ for (const name of cleanNames) {
14273
+ if (!repoNames.has(name)) {
14274
+ return false;
14275
+ }
14276
+ }
14277
+ return true;
14278
+ }
14279
+ function buildEffectiveLabelSource(repoLabels) {
14280
+ if (isCleanLabelsMatch(repoLabels)) {
14281
+ return {
14282
+ labels: CLEAN_LABELS.map((cl) => ({
14283
+ name: cl.name,
14284
+ description: cl.description,
14285
+ color: cl.color
14286
+ })),
14287
+ source: "clean-labels"
14288
+ };
14289
+ }
14290
+ return { labels: repoLabels, source: "repo" };
14291
+ }
14292
+ async function syncLabelCache(cwd = process.cwd()) {
14293
+ const { getRepoLabels: getRepoLabels2 } = await Promise.resolve().then(() => (init_gh(), exports_gh));
14294
+ const repoLabels = await getRepoLabels2();
14295
+ if (repoLabels.length === 0) {
14296
+ return null;
14297
+ }
14298
+ const { labels, source } = buildEffectiveLabelSource(repoLabels);
14299
+ const cache = {
14300
+ labels,
14301
+ source,
14302
+ fetchedAt: new Date().toISOString()
14303
+ };
14304
+ writeLabelCache(cache, cwd);
14305
+ return cache;
14306
+ }
14307
+ async function getActiveLabels(cwd = process.cwd(), force = false) {
14308
+ if (!force) {
14309
+ const cached = readLabelCache(cwd);
14310
+ if (cached) {
14311
+ return cached;
14312
+ }
14313
+ }
14314
+ return syncLabelCache(cwd);
14315
+ }
14316
+ function parseLabelsCsv(csv) {
14317
+ return csv.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
14318
+ }
14319
+ function validateLabels(requested, available) {
14320
+ const availableNormalized = new Map;
14321
+ for (const label of available) {
14322
+ availableNormalized.set(normalizeLabelName(label.name), label.name);
14323
+ }
14324
+ const valid = [];
14325
+ const invalid = [];
14326
+ for (const req of requested) {
14327
+ const normalized = normalizeLabelName(req);
14328
+ const canonical = availableNormalized.get(normalized);
14329
+ if (canonical !== undefined) {
14330
+ valid.push(canonical);
14331
+ } else {
14332
+ invalid.push(req);
14333
+ }
14334
+ }
14335
+ return { valid, invalid };
14336
+ }
14337
+ function findCloseMatches(input, available, maxResults = 3) {
14338
+ const needle = normalizeLabelName(input);
14339
+ const scored = available.map((label) => {
14340
+ const haystack = normalizeLabelName(label.name);
14341
+ let score = 0;
14342
+ if (haystack === needle) {
14343
+ score = 100;
14344
+ } else if (haystack.startsWith(needle) || needle.startsWith(haystack)) {
14345
+ score = 60;
14346
+ } else if (haystack.includes(needle) || needle.includes(haystack)) {
14347
+ score = 40;
14348
+ } else {
14349
+ const needleBigrams = toBigrams(needle);
14350
+ const haystackBigrams = toBigrams(haystack);
14351
+ const overlap = [...needleBigrams].filter((b2) => haystackBigrams.has(b2)).length;
14352
+ const union = new Set([...needleBigrams, ...haystackBigrams]).size;
14353
+ score = union > 0 ? Math.round(overlap / union * 30) : 0;
14354
+ }
14355
+ return { name: label.name, score };
14356
+ });
14357
+ return scored.filter((item) => item.score > 0).sort((a2, b2) => b2.score - a2.score).slice(0, maxResults).map((item) => item.name);
14358
+ }
14359
+ function scoreLabelsForContent(content, labels) {
14360
+ const contentTokens = tokenize(content.toLowerCase());
14361
+ const scored = labels.map((label) => {
14362
+ const nameTokens = tokenize(label.name.toLowerCase());
14363
+ const descTokens = [...tokenize(stripDescriptionMeta(label.description).toLowerCase())].filter((t2) => t2.length > 3 && !STOP_WORDS.has(t2));
14364
+ let score = 0;
14365
+ for (const token of nameTokens) {
14366
+ if (contentTokens.has(token))
14367
+ score += 3;
14368
+ }
14369
+ const normalizedName = normalizeLabelName(label.name);
14370
+ if (content.toLowerCase().includes(normalizedName)) {
14371
+ score += 5;
14372
+ }
14373
+ for (const token of descTokens) {
14374
+ if (contentTokens.has(token))
14375
+ score += 1;
14376
+ }
14377
+ return { label, score };
14378
+ });
14379
+ return scored.filter((item) => item.score > 0).sort((a2, b2) => b2.score - a2.score);
14380
+ }
14381
+ var STOP_WORDS = new Set([
14382
+ "the",
14383
+ "and",
14384
+ "for",
14385
+ "with",
14386
+ "this",
14387
+ "that",
14388
+ "from",
14389
+ "into",
14390
+ "over",
14391
+ "under",
14392
+ "have",
14393
+ "will",
14394
+ "been",
14395
+ "more",
14396
+ "than",
14397
+ "also",
14398
+ "when",
14399
+ "what",
14400
+ "which",
14401
+ "some",
14402
+ "such",
14403
+ "its",
14404
+ "not",
14405
+ "only",
14406
+ "any",
14407
+ "each",
14408
+ "both"
14409
+ ]);
14410
+ function stripDescriptionMeta(description) {
14411
+ return description.replace(/^\[[\w\s]+\]\s*/u, "").replace(/\s*\[[\w,\s]+\]$/u, "").trim();
14412
+ }
14413
+ function tokenize(text) {
14414
+ return new Set(text.split(/[\s\-_/,.:;!?()[\]{}"']+/).filter((t2) => t2.length > 0));
14415
+ }
14416
+ function toBigrams(text) {
14417
+ const bigrams = new Set;
14418
+ for (let i2 = 0;i2 < text.length - 1; i2++) {
14419
+ bigrams.add(text.slice(i2, i2 + 2));
14420
+ }
14421
+ return bigrams;
14422
+ }
14423
+
14424
+ // src/commands/label.ts
14425
+ async function requireGitRepository() {
14426
+ if (!await isGitRepo()) {
14427
+ error("Not inside a git repository.");
14428
+ process.exit(1);
14429
+ }
14430
+ }
14431
+ async function requireGhCli() {
14432
+ if (!await checkGhInstalled()) {
14433
+ error("GitHub CLI (gh) is required. Install it at https://cli.github.com");
14434
+ process.exit(1);
14435
+ }
14436
+ if (!await checkGhAuth()) {
14437
+ error("Not authenticated with GitHub CLI. Run `gh auth login` first.");
14438
+ process.exit(1);
14439
+ }
14440
+ }
14441
+ function extractLabelsCsv(rawArgs) {
14442
+ const knownFlagsWithValues = new Set(["--issue", "--pr", "-i", "-p"]);
14443
+ const parts = [];
14444
+ let skipNext = false;
14445
+ for (const arg of rawArgs) {
14446
+ if (skipNext) {
14447
+ skipNext = false;
14448
+ continue;
14449
+ }
14450
+ if (knownFlagsWithValues.has(arg)) {
14451
+ skipNext = true;
14452
+ continue;
14453
+ }
14454
+ if (arg.startsWith("-")) {
14455
+ continue;
14456
+ }
14457
+ parts.push(arg);
14458
+ }
14459
+ return parts.join(" ");
14460
+ }
14461
+ function formatSourceNote(source) {
14462
+ return source === "clean-labels" ? "(source: Clean Labels dataset)" : "(source: repo labels)";
14463
+ }
14464
+ var addCommand = defineCommand({
14465
+ meta: {
14466
+ name: "add",
14467
+ description: "Apply existing labels to an issue or pull request"
14468
+ },
14469
+ args: {
14470
+ issue: {
14471
+ type: "string",
14472
+ alias: "i",
14473
+ description: "Issue number to label"
14474
+ },
14475
+ pr: {
14476
+ type: "string",
14477
+ alias: "p",
14478
+ description: "Pull request number to label"
14479
+ },
14480
+ labels: {
14481
+ type: "positional",
14482
+ description: "Comma-separated label names (spaces are part of label names)",
14483
+ required: false
14484
+ }
14485
+ },
14486
+ async run({ args, rawArgs }) {
14487
+ await requireGitRepository();
14488
+ await requireGhCli();
14489
+ await projectHeading("label add", "\uD83C\uDFF7\uFE0F");
14490
+ const hasIssue = Boolean(args.issue);
14491
+ const hasPr = Boolean(args.pr);
14492
+ if (!hasIssue && !hasPr) {
14493
+ error("Provide a target: --issue <number> or --pr <number>");
14494
+ process.exit(1);
14495
+ }
14496
+ if (hasIssue && hasPr) {
14497
+ error("Use either --issue or --pr, not both.");
14498
+ process.exit(1);
14499
+ }
14500
+ const targetNumber = Number(hasIssue ? args.issue : args.pr);
14501
+ if (!Number.isInteger(targetNumber) || targetNumber <= 0) {
14502
+ error(`Invalid ${hasIssue ? "issue" : "PR"} number: ${String(hasIssue ? args.issue : args.pr)}`);
14503
+ process.exit(1);
14504
+ }
14505
+ const labelsCsv = extractLabelsCsv(rawArgs);
14506
+ if (!labelsCsv) {
14507
+ error("No labels provided. Pass a comma-separated list after the target flag.");
14508
+ info("Example: cn label add --issue 42 bug,enhancement", "");
14509
+ process.exit(1);
14510
+ }
14511
+ const requested = parseLabelsCsv(labelsCsv);
14512
+ if (requested.length === 0) {
14513
+ error("No valid label names found in input.");
14514
+ process.exit(1);
14515
+ }
14516
+ let cache = await getActiveLabels();
14517
+ if (!cache) {
14518
+ error("Could not load repository labels. Make sure you are authenticated with `gh auth login`.");
14519
+ process.exit(1);
14520
+ }
14521
+ let { valid, invalid } = validateLabels(requested, cache.labels);
14522
+ if (invalid.length > 0) {
14523
+ warn(`Unknown label(s) detected \u2014 resyncing label cache\u2026`);
14524
+ const freshCache = await syncLabelCache();
14525
+ if (freshCache) {
14526
+ cache = freshCache;
14527
+ const revalidated = validateLabels(requested, cache.labels);
14528
+ valid = revalidated.valid;
14529
+ invalid = revalidated.invalid;
14530
+ }
14531
+ }
14532
+ if (invalid.length > 0) {
14533
+ error(`Unknown label(s): ${invalid.map((l2) => import_picocolors14.default.bold(l2)).join(", ")}`);
14534
+ console.log();
14535
+ for (const label of invalid) {
14536
+ const suggestions = findCloseMatches(label, cache.labels);
14537
+ if (suggestions.length > 0) {
14538
+ info(` Did you mean for "${label}": ${suggestions.map((s2) => import_picocolors14.default.cyan(s2)).join(", ")}`, "");
14539
+ }
14540
+ }
14541
+ console.log();
14542
+ info(`Available labels: ${cache.labels.map((l2) => import_picocolors14.default.dim(l2.name)).join(", ")}`, "");
14543
+ process.exit(1);
14544
+ }
14545
+ const targetLabel = hasIssue ? `issue #${targetNumber}` : `PR #${targetNumber}`;
14546
+ info(`Applying ${valid.length} label(s) to ${import_picocolors14.default.bold(targetLabel)}\u2026`, "\uD83C\uDFF7\uFE0F");
14547
+ const result = hasIssue ? await addLabelsToIssue(targetNumber, valid) : await addLabelsToPR(targetNumber, valid);
14548
+ if (result.exitCode !== 0) {
14549
+ const stderr = result.stderr.trim();
14550
+ const isDrift = /not found|does not exist/i.test(stderr) || /not found|does not exist/i.test(result.stdout);
14551
+ if (isDrift) {
14552
+ warn("Label not found on remote \u2014 resyncing and retrying\u2026");
14553
+ const freshCache = await syncLabelCache();
14554
+ if (freshCache) {
14555
+ const revalidated = validateLabels(requested, freshCache.labels);
14556
+ if (revalidated.invalid.length === 0) {
14557
+ const retry = hasIssue ? await addLabelsToIssue(targetNumber, revalidated.valid) : await addLabelsToPR(targetNumber, revalidated.valid);
14558
+ if (retry.exitCode === 0) {
14559
+ success(`Applied to ${import_picocolors14.default.bold(targetLabel)}: ${revalidated.valid.map((l2) => import_picocolors14.default.cyan(l2)).join(", ")}`);
14560
+ return;
14561
+ }
14562
+ error(`Retry failed: ${retry.stderr.trim() || retry.stdout.trim()}`);
14563
+ process.exit(1);
14564
+ }
14565
+ }
14566
+ }
14567
+ error(`Failed to apply labels: ${stderr || result.stdout.trim()}`);
14568
+ info("Run `cn label add --help` for usage guidance.", "");
14569
+ process.exit(1);
14570
+ }
14571
+ success(`Applied to ${import_picocolors14.default.bold(targetLabel)}: ${valid.map((l2) => import_picocolors14.default.cyan(l2)).join(", ")}`);
14572
+ const sourceNote = formatSourceNote(cache.source);
14573
+ info(sourceNote, "");
14574
+ }
14575
+ });
14576
+ var suggestCommand = defineCommand({
14577
+ meta: {
14578
+ name: "suggest",
14579
+ description: "Suggest labels for an issue or pull request based on its content"
14580
+ },
14581
+ args: {
14582
+ issue: {
14583
+ type: "string",
14584
+ alias: "i",
14585
+ description: "Issue number to suggest labels for"
14586
+ },
14587
+ pr: {
14588
+ type: "string",
14589
+ alias: "p",
14590
+ description: "Pull request number to suggest labels for"
14591
+ }
14592
+ },
14593
+ async run({ args }) {
14594
+ await requireGitRepository();
14595
+ await requireGhCli();
14596
+ await projectHeading("label suggest", "\uD83C\uDFF7\uFE0F");
14597
+ const hasIssue = Boolean(args.issue);
14598
+ const hasPr = Boolean(args.pr);
14599
+ if (!hasIssue && !hasPr) {
14600
+ error("Provide a target: --issue <number> or --pr <number>");
14601
+ process.exit(1);
14602
+ }
14603
+ if (hasIssue && hasPr) {
14604
+ error("Use either --issue or --pr, not both.");
14605
+ process.exit(1);
14606
+ }
14607
+ const targetNumber = Number(hasIssue ? args.issue : args.pr);
14608
+ if (!Number.isInteger(targetNumber) || targetNumber <= 0) {
14609
+ error(`Invalid ${hasIssue ? "issue" : "PR"} number: ${String(hasIssue ? args.issue : args.pr)}`);
14610
+ process.exit(1);
14611
+ }
14612
+ const targetLabel = hasIssue ? `issue #${targetNumber}` : `PR #${targetNumber}`;
14613
+ info(`Fetching ${import_picocolors14.default.bold(targetLabel)} content\u2026`, "");
14614
+ const content = hasIssue ? await getIssueContent(targetNumber) : await getPRContent(targetNumber);
14615
+ if (!content) {
14616
+ error(`Could not fetch content for ${targetLabel}. Verify the number and your gh auth.`);
14617
+ process.exit(1);
14618
+ }
14619
+ const fullText = `${content.title}
14620
+
14621
+ ${content.body}`;
14622
+ const cache = await getActiveLabels();
14623
+ if (!cache) {
14624
+ error("Could not load repository labels. Run `cn label add --help` for setup guidance.");
14625
+ process.exit(1);
14626
+ }
14627
+ const ranked = scoreLabelsForContent(fullText, cache.labels);
14628
+ if (ranked.length === 0) {
14629
+ info(`No label suggestions found for ${import_picocolors14.default.bold(targetLabel)}.`);
14630
+ info(`Total labels available: ${cache.labels.length}`, "");
14631
+ return;
14632
+ }
14633
+ const sourceNote = formatSourceNote(cache.source);
14634
+ console.log();
14635
+ console.log(` ${import_picocolors14.default.bold(`Suggested labels for ${import_picocolors14.default.cyan(targetLabel)}:`)} ${import_picocolors14.default.dim(sourceNote)}`);
14636
+ console.log();
14637
+ const topN = ranked.slice(0, 5);
14638
+ for (const { label, score } of topN) {
14639
+ const descPart = label.description ? import_picocolors14.default.dim(` \u2014 ${label.description}`) : "";
14640
+ const scorePart = import_picocolors14.default.dim(` [score: ${score}]`);
14641
+ console.log(` ${import_picocolors14.default.cyan("\u2022")} ${import_picocolors14.default.bold(label.name)}${descPart}${scorePart}`);
14642
+ }
14643
+ console.log();
14644
+ info(`Apply a label: cn label add --${hasIssue ? "issue" : "pr"} ${targetNumber} <label>`, "");
14645
+ }
14646
+ });
14647
+ var label_default = defineCommand({
14648
+ meta: {
14649
+ name: "label",
14650
+ description: "Manage labels on issues and pull requests"
14651
+ },
14652
+ subCommands: {
14653
+ add: addCommand,
14654
+ suggest: suggestCommand
14655
+ }
14656
+ });
14657
+
14658
+ // src/commands/log.ts
14659
+ var import_picocolors15 = __toESM(require_picocolors(), 1);
13767
14660
  function getDefaultOverviewRemoteCommitCount(hasLocalUnpushedCommits) {
13768
14661
  return hasLocalUnpushedCommits ? 10 : 20;
13769
14662
  }
@@ -13861,9 +14754,9 @@ var log_default = defineCommand({
13861
14754
  } else if (mode === "local") {
13862
14755
  if (!compareRef) {
13863
14756
  console.log();
13864
- console.log(import_picocolors14.default.yellow(" \u26A0 Could not determine a comparison branch."));
13865
- console.log(import_picocolors14.default.dim(" No upstream tracking set and no remote base branch found."));
13866
- console.log(import_picocolors14.default.dim(` Use ${import_picocolors14.default.bold("cn log --full")} to see the full commit history instead.`));
14757
+ console.log(import_picocolors15.default.yellow(" \u26A0 Could not determine a comparison branch."));
14758
+ console.log(import_picocolors15.default.dim(" No upstream tracking set and no remote base branch found."));
14759
+ console.log(import_picocolors15.default.dim(` Use ${import_picocolors15.default.bold("cn log --full")} to see the full commit history instead.`));
13867
14760
  console.log();
13868
14761
  return;
13869
14762
  }
@@ -13882,8 +14775,8 @@ var log_default = defineCommand({
13882
14775
  const remoteBranch = compareRef ?? targetBranch;
13883
14776
  if (!remoteBranch) {
13884
14777
  console.log();
13885
- console.log(import_picocolors14.default.yellow(" \u26A0 Could not determine a remote branch to display."));
13886
- console.log(import_picocolors14.default.dim(" Set an upstream tracking branch or configure your base branch first."));
14778
+ console.log(import_picocolors15.default.yellow(" \u26A0 Could not determine a remote branch to display."));
14779
+ console.log(import_picocolors15.default.dim(" Set an upstream tracking branch or configure your base branch first."));
13887
14780
  console.log();
13888
14781
  return;
13889
14782
  }
@@ -13942,31 +14835,31 @@ async function getOverviewRemoteCommitCount(currentBranch, compareRef) {
13942
14835
  }
13943
14836
  function printModeHeader(mode, currentBranch, compareRef, usingFallback = false) {
13944
14837
  const branch = currentBranch ?? "HEAD";
13945
- const fallbackNote = usingFallback ? import_picocolors14.default.yellow(" (no upstream \u2014 comparing against base branch)") : "";
14838
+ const fallbackNote = usingFallback ? import_picocolors15.default.yellow(" (no upstream \u2014 comparing against base branch)") : "";
13946
14839
  switch (mode) {
13947
14840
  case "overview":
13948
- console.log(import_picocolors14.default.dim(` mode: ${import_picocolors14.default.bold("overview")} \u2014 local unpushed commits and remote branch history for ${import_picocolors14.default.bold(branch)}`) + fallbackNote);
14841
+ console.log(import_picocolors15.default.dim(` mode: ${import_picocolors15.default.bold("overview")} \u2014 local unpushed commits and remote branch history for ${import_picocolors15.default.bold(branch)}`) + fallbackNote);
13949
14842
  if (compareRef) {
13950
- console.log(import_picocolors14.default.dim(` remote source: ${import_picocolors14.default.bold(compareRef)}`));
14843
+ console.log(import_picocolors15.default.dim(` remote source: ${import_picocolors15.default.bold(compareRef)}`));
13951
14844
  }
13952
14845
  break;
13953
14846
  case "local":
13954
- console.log(import_picocolors14.default.dim(` mode: ${import_picocolors14.default.bold("local")} \u2014 unpushed commits on ${import_picocolors14.default.bold(branch)}`) + fallbackNote);
14847
+ console.log(import_picocolors15.default.dim(` mode: ${import_picocolors15.default.bold("local")} \u2014 unpushed commits on ${import_picocolors15.default.bold(branch)}`) + fallbackNote);
13955
14848
  if (compareRef) {
13956
- console.log(import_picocolors14.default.dim(` comparing: ${import_picocolors14.default.bold(compareRef)} \u279C ${import_picocolors14.default.bold("HEAD")}`));
14849
+ console.log(import_picocolors15.default.dim(` comparing: ${import_picocolors15.default.bold(compareRef)} \u279C ${import_picocolors15.default.bold("HEAD")}`));
13957
14850
  }
13958
14851
  break;
13959
14852
  case "remote":
13960
- console.log(import_picocolors14.default.dim(` mode: ${import_picocolors14.default.bold("remote")} \u2014 remote branch history relevant to ${import_picocolors14.default.bold(branch)}`) + fallbackNote);
14853
+ console.log(import_picocolors15.default.dim(` mode: ${import_picocolors15.default.bold("remote")} \u2014 remote branch history relevant to ${import_picocolors15.default.bold(branch)}`) + fallbackNote);
13961
14854
  if (compareRef) {
13962
- console.log(import_picocolors14.default.dim(` branch: ${import_picocolors14.default.bold(compareRef)}`));
14855
+ console.log(import_picocolors15.default.dim(` branch: ${import_picocolors15.default.bold(compareRef)}`));
13963
14856
  }
13964
14857
  break;
13965
14858
  case "full":
13966
- console.log(import_picocolors14.default.dim(` mode: ${import_picocolors14.default.bold("full")} \u2014 complete commit history for ${import_picocolors14.default.bold(branch)}`));
14859
+ console.log(import_picocolors15.default.dim(` mode: ${import_picocolors15.default.bold("full")} \u2014 complete commit history for ${import_picocolors15.default.bold(branch)}`));
13967
14860
  break;
13968
14861
  case "all":
13969
- console.log(import_picocolors14.default.dim(` mode: ${import_picocolors14.default.bold("all")} \u2014 commits across all branches`));
14862
+ console.log(import_picocolors15.default.dim(` mode: ${import_picocolors15.default.bold("all")} \u2014 commits across all branches`));
13970
14863
  break;
13971
14864
  }
13972
14865
  }
@@ -13990,7 +14883,7 @@ async function renderScopedLog(options) {
13990
14883
  }
13991
14884
  console.log();
13992
14885
  for (const entry of entries) {
13993
- const hashStr = import_picocolors14.default.yellow(entry.hash);
14886
+ const hashStr = import_picocolors15.default.yellow(entry.hash);
13994
14887
  const refsStr = entry.refs ? ` ${colorizeRefs(entry.refs, protectedBranches, currentBranch)}` : "";
13995
14888
  const subjectStr = colorizeSubject(entry.subject);
13996
14889
  console.log(` ${hashStr}${refsStr} ${subjectStr}`);
@@ -14009,10 +14902,10 @@ async function renderOverviewLog(options) {
14009
14902
  usingFallback
14010
14903
  } = options;
14011
14904
  console.log();
14012
- console.log(import_picocolors14.default.bold(import_picocolors14.default.cyan(" Local Unpushed Commits")));
14905
+ console.log(import_picocolors15.default.bold(import_picocolors15.default.cyan(" Local Unpushed Commits")));
14013
14906
  if (!compareRef) {
14014
- console.log(import_picocolors14.default.dim(" No comparison branch detected for local commit status."));
14015
- console.log(import_picocolors14.default.dim(" Set an upstream tracking branch or run cn log --full to inspect the current branch history."));
14907
+ console.log(import_picocolors15.default.dim(" No comparison branch detected for local commit status."));
14908
+ console.log(import_picocolors15.default.dim(" Set an upstream tracking branch or run cn log --full to inspect the current branch history."));
14016
14909
  } else {
14017
14910
  await renderScopedLog({
14018
14911
  mode: "local",
@@ -14024,11 +14917,11 @@ async function renderOverviewLog(options) {
14024
14917
  });
14025
14918
  }
14026
14919
  console.log();
14027
- console.log(import_picocolors14.default.bold(import_picocolors14.default.cyan(" Remote Branch History")));
14920
+ console.log(import_picocolors15.default.bold(import_picocolors15.default.cyan(" Remote Branch History")));
14028
14921
  if (!compareRef) {
14029
- console.log(import_picocolors14.default.dim(" No remote branch detected."));
14922
+ console.log(import_picocolors15.default.dim(" No remote branch detected."));
14030
14923
  if (usingFallback) {
14031
- console.log(import_picocolors14.default.dim(" Configure your base branch or upstream tracking to enable the split view."));
14924
+ console.log(import_picocolors15.default.dim(" Configure your base branch or upstream tracking to enable the split view."));
14032
14925
  }
14033
14926
  return;
14034
14927
  }
@@ -14041,15 +14934,15 @@ async function renderOverviewLog(options) {
14041
14934
  currentBranch
14042
14935
  });
14043
14936
  if (!hasRemoteHistory) {
14044
- console.log(import_picocolors14.default.dim(" No remote history found for the selected branch."));
14937
+ console.log(import_picocolors15.default.dim(" No remote history found for the selected branch."));
14045
14938
  }
14046
14939
  }
14047
14940
  function printEmptyState(mode) {
14048
14941
  console.log();
14049
14942
  if (mode === "local") {
14050
- console.log(import_picocolors14.default.dim(" No local unpushed commits \u2014 you're up to date with remote!"));
14943
+ console.log(import_picocolors15.default.dim(" No local unpushed commits \u2014 you're up to date with remote!"));
14051
14944
  } else {
14052
- console.log(import_picocolors14.default.dim(" No remote-only commits \u2014 your local branch is up to date!"));
14945
+ console.log(import_picocolors15.default.dim(" No remote-only commits \u2014 your local branch is up to date!"));
14053
14946
  }
14054
14947
  console.log();
14055
14948
  }
@@ -14058,7 +14951,7 @@ async function renderFullLog(options) {
14058
14951
  if (showGraph) {
14059
14952
  const lines = await getLogGraph({ count, all, branch: targetBranch });
14060
14953
  if (lines.length === 0) {
14061
- console.log(import_picocolors14.default.dim(" No commits found."));
14954
+ console.log(import_picocolors15.default.dim(" No commits found."));
14062
14955
  console.log();
14063
14956
  return false;
14064
14957
  }
@@ -14069,13 +14962,13 @@ async function renderFullLog(options) {
14069
14962
  } else {
14070
14963
  const entries = await getLogEntries({ count, all, branch: targetBranch });
14071
14964
  if (entries.length === 0) {
14072
- console.log(import_picocolors14.default.dim(" No commits found."));
14965
+ console.log(import_picocolors15.default.dim(" No commits found."));
14073
14966
  console.log();
14074
14967
  return false;
14075
14968
  }
14076
14969
  console.log();
14077
14970
  for (const entry of entries) {
14078
- const hashStr = import_picocolors14.default.yellow(entry.hash);
14971
+ const hashStr = import_picocolors15.default.yellow(entry.hash);
14079
14972
  const refsStr = entry.refs ? ` ${colorizeRefs(entry.refs, protectedBranches, currentBranch)}` : "";
14080
14973
  const subjectStr = colorizeSubject(entry.subject);
14081
14974
  console.log(` ${hashStr}${refsStr} ${subjectStr}`);
@@ -14087,33 +14980,33 @@ function printFooter(mode, count, overviewRemoteCount, targetBranch) {
14087
14980
  console.log();
14088
14981
  switch (mode) {
14089
14982
  case "overview":
14090
- console.log(import_picocolors14.default.dim(` Showing up to ${count} local commits and ${overviewRemoteCount} remote commits`));
14983
+ console.log(import_picocolors15.default.dim(` Showing up to ${count} local commits and ${overviewRemoteCount} remote commits`));
14091
14984
  break;
14092
14985
  case "local":
14093
- console.log(import_picocolors14.default.dim(` Showing up to ${count} unpushed commits`));
14986
+ console.log(import_picocolors15.default.dim(` Showing up to ${count} unpushed commits`));
14094
14987
  break;
14095
14988
  case "remote":
14096
- console.log(import_picocolors14.default.dim(` Showing ${count} most recent commits from the remote branch`));
14989
+ console.log(import_picocolors15.default.dim(` Showing ${count} most recent commits from the remote branch`));
14097
14990
  break;
14098
14991
  case "full":
14099
- console.log(import_picocolors14.default.dim(` Showing ${count} most recent commits${targetBranch ? ` (${targetBranch})` : ""}`));
14992
+ console.log(import_picocolors15.default.dim(` Showing ${count} most recent commits${targetBranch ? ` (${targetBranch})` : ""}`));
14100
14993
  break;
14101
14994
  case "all":
14102
- console.log(import_picocolors14.default.dim(` Showing ${count} most recent commits (all branches)`));
14995
+ console.log(import_picocolors15.default.dim(` Showing ${count} most recent commits (all branches)`));
14103
14996
  break;
14104
14997
  }
14105
14998
  }
14106
14999
  function colorizeGraphLine(line, protectedBranches, currentBranch) {
14107
15000
  const match = line.match(/^([|/\\*\s_.-]*)([a-f0-9]{7,12})(\s+\(([^)]+)\))?\s*(.*)/);
14108
15001
  if (!match) {
14109
- return import_picocolors14.default.cyan(line);
15002
+ return import_picocolors15.default.cyan(line);
14110
15003
  }
14111
15004
  const [, graphPart = "", hash, , refs, subject = ""] = match;
14112
15005
  const parts = [];
14113
15006
  if (graphPart) {
14114
15007
  parts.push(colorizeGraphChars(graphPart));
14115
15008
  }
14116
- parts.push(import_picocolors14.default.yellow(hash));
15009
+ parts.push(import_picocolors15.default.yellow(hash));
14117
15010
  if (refs) {
14118
15011
  parts.push(` (${colorizeRefs(refs, protectedBranches, currentBranch)})`);
14119
15012
  }
@@ -14124,15 +15017,15 @@ function colorizeGraphChars(graphPart) {
14124
15017
  return graphPart.split("").map((ch) => {
14125
15018
  switch (ch) {
14126
15019
  case "*":
14127
- return import_picocolors14.default.green(ch);
15020
+ return import_picocolors15.default.green(ch);
14128
15021
  case "|":
14129
- return import_picocolors14.default.cyan(ch);
15022
+ return import_picocolors15.default.cyan(ch);
14130
15023
  case "/":
14131
15024
  case "\\":
14132
- return import_picocolors14.default.cyan(ch);
15025
+ return import_picocolors15.default.cyan(ch);
14133
15026
  case "-":
14134
15027
  case "_":
14135
- return import_picocolors14.default.cyan(ch);
15028
+ return import_picocolors15.default.cyan(ch);
14136
15029
  default:
14137
15030
  return ch;
14138
15031
  }
@@ -14144,50 +15037,50 @@ function colorizeRefs(refs, protectedBranches, currentBranch) {
14144
15037
  if (trimmed.startsWith("HEAD ->") || trimmed === "HEAD") {
14145
15038
  const branchName = trimmed.replace("HEAD -> ", "");
14146
15039
  if (trimmed === "HEAD") {
14147
- return import_picocolors14.default.bold(import_picocolors14.default.cyan("HEAD"));
15040
+ return import_picocolors15.default.bold(import_picocolors15.default.cyan("HEAD"));
14148
15041
  }
14149
- return `${import_picocolors14.default.bold(import_picocolors14.default.cyan("HEAD"))} ${import_picocolors14.default.dim("->")} ${colorizeRefName(branchName, protectedBranches, currentBranch)}`;
15042
+ return `${import_picocolors15.default.bold(import_picocolors15.default.cyan("HEAD"))} ${import_picocolors15.default.dim("->")} ${colorizeRefName(branchName, protectedBranches, currentBranch)}`;
14150
15043
  }
14151
15044
  if (trimmed.startsWith("tag:")) {
14152
- return import_picocolors14.default.bold(import_picocolors14.default.magenta(trimmed));
15045
+ return import_picocolors15.default.bold(import_picocolors15.default.magenta(trimmed));
14153
15046
  }
14154
15047
  return colorizeRefName(trimmed, protectedBranches, currentBranch);
14155
- }).join(import_picocolors14.default.dim(", "));
15048
+ }).join(import_picocolors15.default.dim(", "));
14156
15049
  }
14157
15050
  function colorizeRefName(name, protectedBranches, currentBranch) {
14158
15051
  const isRemote = name.includes("/");
14159
15052
  const localName = isRemote ? name.split("/").slice(1).join("/") : name;
14160
15053
  if (protectedBranches.includes(localName)) {
14161
- return isRemote ? import_picocolors14.default.bold(import_picocolors14.default.red(name)) : import_picocolors14.default.bold(import_picocolors14.default.red(name));
15054
+ return isRemote ? import_picocolors15.default.bold(import_picocolors15.default.red(name)) : import_picocolors15.default.bold(import_picocolors15.default.red(name));
14162
15055
  }
14163
15056
  if (localName === currentBranch) {
14164
- return import_picocolors14.default.bold(import_picocolors14.default.green(name));
15057
+ return import_picocolors15.default.bold(import_picocolors15.default.green(name));
14165
15058
  }
14166
15059
  if (isRemote) {
14167
- return import_picocolors14.default.blue(name);
15060
+ return import_picocolors15.default.blue(name);
14168
15061
  }
14169
- return import_picocolors14.default.green(name);
15062
+ return import_picocolors15.default.green(name);
14170
15063
  }
14171
15064
  function colorizeSubject(subject) {
14172
15065
  const emojiMatch = subject.match(/^((?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F)+\s*)/u);
14173
15066
  if (emojiMatch) {
14174
15067
  const emoji = emojiMatch[1];
14175
15068
  const rest = subject.slice(emoji.length);
14176
- return `${emoji}${import_picocolors14.default.white(rest)}`;
15069
+ return `${emoji}${import_picocolors15.default.white(rest)}`;
14177
15070
  }
14178
15071
  if (subject.startsWith("Merge ")) {
14179
- return import_picocolors14.default.dim(subject);
15072
+ return import_picocolors15.default.dim(subject);
14180
15073
  }
14181
- return import_picocolors14.default.white(subject);
15074
+ return import_picocolors15.default.white(subject);
14182
15075
  }
14183
15076
 
14184
15077
  // src/commands/save.ts
14185
15078
  import { execFile as execFileCb4 } from "child_process";
14186
- var import_picocolors15 = __toESM(require_picocolors(), 1);
15079
+ var import_picocolors16 = __toESM(require_picocolors(), 1);
14187
15080
  function gitRun(args) {
14188
- return new Promise((resolve5) => {
15081
+ return new Promise((resolve6) => {
14189
15082
  execFileCb4("git", args, (err, stdout2, stderr) => {
14190
- resolve5({
15083
+ resolve6({
14191
15084
  exitCode: err ? err.code === "ENOENT" ? 127 : err.status ?? 1 : 0,
14192
15085
  stdout: stdout2 ?? "",
14193
15086
  stderr: stderr ?? ""
@@ -14257,8 +15150,8 @@ async function handleSave(message) {
14257
15150
  info("No uncommitted changes to save.");
14258
15151
  return;
14259
15152
  }
14260
- success(`Saved: ${import_picocolors15.default.dim(label)}`);
14261
- info(`Use ${import_picocolors15.default.bold("cn save --restore")} to bring them back.`, "");
15153
+ success(`Saved: ${import_picocolors16.default.dim(label)}`);
15154
+ info(`Use ${import_picocolors16.default.bold("cn save --restore")} to bring them back.`, "");
14262
15155
  }
14263
15156
  async function handleRestore() {
14264
15157
  await projectHeading("save --restore", "\uD83D\uDCBE");
@@ -14274,7 +15167,7 @@ async function handleRestore() {
14274
15167
  warn("You may have conflicts. Resolve them and run `git stash drop` when done.");
14275
15168
  process.exit(1);
14276
15169
  }
14277
- success(`Restored: ${import_picocolors15.default.dim(stashes[0].message)}`);
15170
+ success(`Restored: ${import_picocolors16.default.dim(stashes[0].message)}`);
14278
15171
  return;
14279
15172
  }
14280
15173
  const choices = stashes.map((s2) => `${s2.index} ${s2.message}`);
@@ -14287,7 +15180,7 @@ async function handleRestore() {
14287
15180
  process.exit(1);
14288
15181
  }
14289
15182
  const match = stashes.find((s2) => String(s2.index) === idx);
14290
- success(`Restored: ${import_picocolors15.default.dim(match?.message ?? "saved changes")}`);
15183
+ success(`Restored: ${import_picocolors16.default.dim(match?.message ?? "saved changes")}`);
14291
15184
  }
14292
15185
  async function handleList() {
14293
15186
  await projectHeading("save --list", "\uD83D\uDCBE");
@@ -14298,13 +15191,13 @@ async function handleList() {
14298
15191
  }
14299
15192
  console.log();
14300
15193
  for (const s2 of stashes) {
14301
- const idx = import_picocolors15.default.dim(`[${s2.index}]`);
15194
+ const idx = import_picocolors16.default.dim(`[${s2.index}]`);
14302
15195
  const msg = s2.message;
14303
15196
  console.log(` ${idx} ${msg}`);
14304
15197
  }
14305
15198
  console.log();
14306
- info(`Use ${import_picocolors15.default.bold("cn save --restore")} to bring changes back.`, "");
14307
- info(`Use ${import_picocolors15.default.bold("cn save --drop")} to discard saved changes.`, "");
15199
+ info(`Use ${import_picocolors16.default.bold("cn save --restore")} to bring changes back.`, "");
15200
+ info(`Use ${import_picocolors16.default.bold("cn save --drop")} to discard saved changes.`, "");
14308
15201
  }
14309
15202
  async function handleDrop() {
14310
15203
  await projectHeading("save --drop", "\uD83D\uDCBE");
@@ -14322,7 +15215,7 @@ async function handleDrop() {
14322
15215
  process.exit(1);
14323
15216
  }
14324
15217
  const match = stashes.find((s2) => String(s2.index) === idx);
14325
- success(`Dropped: ${import_picocolors15.default.dim(match?.message ?? "saved changes")}`);
15218
+ success(`Dropped: ${import_picocolors16.default.dim(match?.message ?? "saved changes")}`);
14326
15219
  }
14327
15220
  async function getStashList() {
14328
15221
  const result = await gitRun(["stash", "list"]);
@@ -14339,7 +15232,8 @@ async function getStashList() {
14339
15232
  }
14340
15233
 
14341
15234
  // src/commands/setup.ts
14342
- var import_picocolors16 = __toESM(require_picocolors(), 1);
15235
+ var import_picocolors17 = __toESM(require_picocolors(), 1);
15236
+ init_gh();
14343
15237
  async function shouldContinueSetupWithExistingConfig(options) {
14344
15238
  const { existingConfig, hasConfigFile, confirm, onInfo, onWarn, onSuccess, summary } = options;
14345
15239
  if (existingConfig) {
@@ -14362,6 +15256,43 @@ async function shouldContinueSetupWithExistingConfig(options) {
14362
15256
  }
14363
15257
  return true;
14364
15258
  }
15259
+ async function resolveApiKeyForSetup(options) {
15260
+ const { providerLabel, hasStoredKey, getStoredKey, select, promptSecret } = options;
15261
+ if (hasStoredKey) {
15262
+ const choice = await select(`${providerLabel} API key`, [
15263
+ "Keep existing stored key",
15264
+ "Replace stored key"
15265
+ ]);
15266
+ if (choice === "Keep existing stored key") {
15267
+ const existing = (await getStoredKey())?.trim() || "";
15268
+ if (existing) {
15269
+ return {
15270
+ apiKey: existing,
15271
+ shouldStore: false,
15272
+ reusedStoredKey: true
15273
+ };
15274
+ }
15275
+ }
15276
+ const replacement = (await promptSecret(`Enter your ${providerLabel} API key`)).trim();
15277
+ if (!replacement) {
15278
+ throw new Error(`${providerLabel} API key is required when ${providerLabel} is selected.`);
15279
+ }
15280
+ return {
15281
+ apiKey: replacement,
15282
+ shouldStore: true,
15283
+ reusedStoredKey: false
15284
+ };
15285
+ }
15286
+ const initial = (await promptSecret(`Enter your ${providerLabel} API key`)).trim();
15287
+ if (!initial) {
15288
+ throw new Error(`${providerLabel} API key is required when ${providerLabel} is selected.`);
15289
+ }
15290
+ return {
15291
+ apiKey: initial,
15292
+ shouldStore: true,
15293
+ reusedStoredKey: false
15294
+ };
15295
+ }
14365
15296
  async function promptForOllamaCloudModel(apiKey, host = DEFAULT_OLLAMA_CLOUD_HOST) {
14366
15297
  try {
14367
15298
  info("Fetching available Ollama Cloud models...");
@@ -14448,7 +15379,7 @@ var setup_default = defineCommand({
14448
15379
  workflow = "github-flow";
14449
15380
  else if (workflowChoice.startsWith("Git Flow"))
14450
15381
  workflow = "git-flow";
14451
- info(`Workflow: ${import_picocolors16.default.bold(WORKFLOW_DESCRIPTIONS[workflow])}`);
15382
+ info(`Workflow: ${import_picocolors17.default.bold(WORKFLOW_DESCRIPTIONS[workflow])}`);
14452
15383
  const conventionChoice = await selectPrompt("Which commit convention should this project use?", [
14453
15384
  `${CONVENTION_DESCRIPTIONS["clean-commit"]} (recommended)`,
14454
15385
  CONVENTION_DESCRIPTIONS.conventional,
@@ -14476,32 +15407,58 @@ var setup_default = defineCommand({
14476
15407
  aiProvider = "copilot";
14477
15408
  }
14478
15409
  if (aiProvider === "ollama-cloud") {
14479
- const apiKey = (await passwordPrompt("Enter your Ollama Cloud API key")).trim();
14480
- if (!apiKey) {
14481
- error("Ollama Cloud API key is required when Ollama Cloud is selected.");
15410
+ let resolvedKey;
15411
+ try {
15412
+ resolvedKey = await resolveApiKeyForSetup({
15413
+ providerLabel: "Ollama Cloud",
15414
+ hasStoredKey: await hasOllamaCloudApiKey(),
15415
+ getStoredKey: getOllamaCloudApiKey,
15416
+ select: selectPrompt,
15417
+ promptSecret: passwordPrompt
15418
+ });
15419
+ } catch (err) {
15420
+ const message = err instanceof Error ? err.message : String(err);
15421
+ error(message);
14482
15422
  process.exit(1);
14483
15423
  }
14484
- aiModel = await promptForOllamaCloudModel(apiKey);
15424
+ aiModel = await promptForOllamaCloudModel(resolvedKey.apiKey);
14485
15425
  try {
14486
- await setOllamaCloudApiKey(apiKey);
14487
- success("Stored Ollama Cloud API key in the local secrets store.");
14488
- info(`Secrets path: ${import_picocolors16.default.bold(getSecretsStorePath())}`);
15426
+ if (resolvedKey.shouldStore) {
15427
+ await setOllamaCloudApiKey(resolvedKey.apiKey);
15428
+ success("Stored Ollama Cloud API key in the local secrets store.");
15429
+ info(`Secrets path: ${import_picocolors17.default.bold(getSecretsStorePath())}`);
15430
+ } else {
15431
+ info("Using existing Ollama Cloud API key from the local secrets store.");
15432
+ }
14489
15433
  } catch (err) {
14490
15434
  const message = err instanceof Error ? err.message : String(err);
14491
15435
  error(`Failed to store Ollama Cloud API key: ${message}`);
14492
15436
  process.exit(1);
14493
15437
  }
14494
15438
  } else if (aiProvider === "openrouter") {
14495
- const apiKey = (await passwordPrompt("Enter your OpenRouter API key")).trim();
14496
- if (!apiKey) {
14497
- error("OpenRouter API key is required when OpenRouter is selected.");
15439
+ let resolvedKey;
15440
+ try {
15441
+ resolvedKey = await resolveApiKeyForSetup({
15442
+ providerLabel: "OpenRouter",
15443
+ hasStoredKey: await hasOpenRouterApiKey(),
15444
+ getStoredKey: getOpenRouterApiKey,
15445
+ select: selectPrompt,
15446
+ promptSecret: passwordPrompt
15447
+ });
15448
+ } catch (err) {
15449
+ const message = err instanceof Error ? err.message : String(err);
15450
+ error(message);
14498
15451
  process.exit(1);
14499
15452
  }
14500
- aiModel = await promptForOpenRouterModel(apiKey);
15453
+ aiModel = await promptForOpenRouterModel(resolvedKey.apiKey);
14501
15454
  try {
14502
- await setOpenRouterApiKey(apiKey);
14503
- success("Stored OpenRouter API key in the local secrets store.");
14504
- info(`Secrets path: ${import_picocolors16.default.bold(getSecretsStorePath())}`);
15455
+ if (resolvedKey.shouldStore) {
15456
+ await setOpenRouterApiKey(resolvedKey.apiKey);
15457
+ success("Stored OpenRouter API key in the local secrets store.");
15458
+ info(`Secrets path: ${import_picocolors17.default.bold(getSecretsStorePath())}`);
15459
+ } else {
15460
+ info("Using existing OpenRouter API key from the local secrets store.");
15461
+ }
14505
15462
  } catch (err) {
14506
15463
  const message = err instanceof Error ? err.message : String(err);
14507
15464
  error(`Failed to store OpenRouter API key: ${message}`);
@@ -14563,15 +15520,15 @@ var setup_default = defineCommand({
14563
15520
  detectedRole = roleChoice;
14564
15521
  detectionSource = "user selection";
14565
15522
  } else {
14566
- info(`Detected role: ${import_picocolors16.default.bold(detectedRole)} (via ${detectionSource})`);
14567
- const confirmed = await confirmPrompt(`Role detected as ${import_picocolors16.default.bold(detectedRole)}. Is this correct?`);
15523
+ info(`Detected role: ${import_picocolors17.default.bold(detectedRole)} (via ${detectionSource})`);
15524
+ const confirmed = await confirmPrompt(`Role detected as ${import_picocolors17.default.bold(detectedRole)}. Is this correct?`);
14568
15525
  if (!confirmed) {
14569
15526
  const roleChoice = await selectPrompt("Select your role:", ["maintainer", "contributor"]);
14570
15527
  detectedRole = roleChoice;
14571
15528
  }
14572
15529
  }
14573
15530
  const defaultConfig = getDefaultConfig();
14574
- info(import_picocolors16.default.dim("Tip: press Enter to keep the default branch name shown in each prompt."));
15531
+ info(import_picocolors17.default.dim("Tip: press Enter to keep the default branch name shown in each prompt."));
14575
15532
  const mainBranchDefault = defaultConfig.mainBranch;
14576
15533
  const mainBranch = await inputPrompt(`Main branch name (default: ${mainBranchDefault} \u2014 press Enter to keep)`, mainBranchDefault);
14577
15534
  let devBranch;
@@ -14597,7 +15554,7 @@ var setup_default = defineCommand({
14597
15554
  error("Setup cannot continue without the upstream remote for contributors.");
14598
15555
  process.exit(1);
14599
15556
  }
14600
- success(`Added remote ${import_picocolors16.default.bold(upstreamRemote)} \u2192 ${upstreamUrl}`);
15557
+ success(`Added remote ${import_picocolors17.default.bold(upstreamRemote)} \u2192 ${upstreamUrl}`);
14601
15558
  } else {
14602
15559
  error("An upstream remote URL is required for contributors.");
14603
15560
  info("Add it manually: git remote add upstream <url>", "");
@@ -14620,67 +15577,67 @@ var setup_default = defineCommand({
14620
15577
  showTips
14621
15578
  };
14622
15579
  writeConfig(config);
14623
- success(`Config written to ${import_picocolors16.default.bold(getConfigLocationLabel())}`);
15580
+ success(`Config written to ${import_picocolors17.default.bold(getConfigLocationLabel())}`);
14624
15581
  info("This setup is stored locally for this clone and does not modify tracked files.", "");
14625
15582
  const syncRemote = config.role === "contributor" ? config.upstream : config.origin;
14626
- info(`Fetching ${import_picocolors16.default.bold(syncRemote)} to verify branch configuration...`, "");
15583
+ info(`Fetching ${import_picocolors17.default.bold(syncRemote)} to verify branch configuration...`, "");
14627
15584
  await fetchRemote(syncRemote);
14628
15585
  const mainRef = `${syncRemote}/${config.mainBranch}`;
14629
15586
  if (!await refExists(mainRef)) {
14630
- warn(`Main branch ref ${import_picocolors16.default.bold(mainRef)} not found on remote.`);
15587
+ warn(`Main branch ref ${import_picocolors17.default.bold(mainRef)} not found on remote.`);
14631
15588
  warn("Config was saved \u2014 verify the branch name and re-run setup if needed.");
14632
15589
  }
14633
15590
  if (config.devBranch) {
14634
15591
  const devRef = `${syncRemote}/${config.devBranch}`;
14635
15592
  if (!await refExists(devRef)) {
14636
- warn(`Dev branch ref ${import_picocolors16.default.bold(devRef)} not found on remote.`);
15593
+ warn(`Dev branch ref ${import_picocolors17.default.bold(devRef)} not found on remote.`);
14637
15594
  warn("Config was saved \u2014 verify the branch name and re-run setup if needed.");
14638
15595
  }
14639
15596
  }
14640
15597
  console.log();
14641
15598
  const resolvedAIConfig = resolveAIConfig(config);
14642
- info(`Workflow: ${import_picocolors16.default.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
14643
- info(`Convention: ${import_picocolors16.default.bold(CONVENTION_DESCRIPTIONS[config.commitConvention])}`);
14644
- info(`AI: ${import_picocolors16.default.bold(isAIEnabled(config) ? "enabled" : "disabled")}`);
15599
+ info(`Workflow: ${import_picocolors17.default.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
15600
+ info(`Convention: ${import_picocolors17.default.bold(CONVENTION_DESCRIPTIONS[config.commitConvention])}`);
15601
+ info(`AI: ${import_picocolors17.default.bold(isAIEnabled(config) ? "enabled" : "disabled")}`);
14645
15602
  if (isAIEnabled(config)) {
14646
- info(`AI provider: ${import_picocolors16.default.bold(resolvedAIConfig.providerLabel)}`);
15603
+ info(`AI provider: ${import_picocolors17.default.bold(resolvedAIConfig.providerLabel)}`);
14647
15604
  if (resolvedAIConfig.model) {
14648
- info(`AI model: ${import_picocolors16.default.bold(resolvedAIConfig.model)}`);
15605
+ info(`AI model: ${import_picocolors17.default.bold(resolvedAIConfig.model)}`);
14649
15606
  }
14650
15607
  }
14651
- info(`Guides: ${import_picocolors16.default.bold(shouldShowTips(config) ? "shown" : "hidden")}`);
14652
- info(`Role: ${import_picocolors16.default.bold(config.role)}`);
15608
+ info(`Guides: ${import_picocolors17.default.bold(shouldShowTips(config) ? "shown" : "hidden")}`);
15609
+ info(`Role: ${import_picocolors17.default.bold(config.role)}`);
14653
15610
  if (config.devBranch) {
14654
- info(`Main: ${import_picocolors16.default.bold(config.mainBranch)} | Dev: ${import_picocolors16.default.bold(config.devBranch)}`);
15611
+ info(`Main: ${import_picocolors17.default.bold(config.mainBranch)} | Dev: ${import_picocolors17.default.bold(config.devBranch)}`);
14655
15612
  } else {
14656
- info(`Main: ${import_picocolors16.default.bold(config.mainBranch)}`);
15613
+ info(`Main: ${import_picocolors17.default.bold(config.mainBranch)}`);
14657
15614
  }
14658
- info(`Origin: ${import_picocolors16.default.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${import_picocolors16.default.bold(config.upstream)}` : ""}`);
15615
+ info(`Origin: ${import_picocolors17.default.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${import_picocolors17.default.bold(config.upstream)}` : ""}`);
14659
15616
  }
14660
15617
  });
14661
15618
  function logConfigSummary(config) {
14662
15619
  const aiConfig = resolveAIConfig(config);
14663
- info(`Workflow: ${import_picocolors16.default.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
14664
- info(`Convention: ${import_picocolors16.default.bold(CONVENTION_DESCRIPTIONS[config.commitConvention])}`);
14665
- info(`AI: ${import_picocolors16.default.bold(isAIEnabled(config) ? "enabled" : "disabled")}`);
15620
+ info(`Workflow: ${import_picocolors17.default.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
15621
+ info(`Convention: ${import_picocolors17.default.bold(CONVENTION_DESCRIPTIONS[config.commitConvention])}`);
15622
+ info(`AI: ${import_picocolors17.default.bold(isAIEnabled(config) ? "enabled" : "disabled")}`);
14666
15623
  if (isAIEnabled(config)) {
14667
- info(`AI provider: ${import_picocolors16.default.bold(aiConfig.providerLabel)}`);
15624
+ info(`AI provider: ${import_picocolors17.default.bold(aiConfig.providerLabel)}`);
14668
15625
  if (aiConfig.model) {
14669
- info(`AI model: ${import_picocolors16.default.bold(aiConfig.model)}`);
15626
+ info(`AI model: ${import_picocolors17.default.bold(aiConfig.model)}`);
14670
15627
  }
14671
15628
  }
14672
- info(`Guides: ${import_picocolors16.default.bold(shouldShowTips(config) ? "shown" : "hidden")}`);
14673
- info(`Role: ${import_picocolors16.default.bold(config.role)}`);
15629
+ info(`Guides: ${import_picocolors17.default.bold(shouldShowTips(config) ? "shown" : "hidden")}`);
15630
+ info(`Role: ${import_picocolors17.default.bold(config.role)}`);
14674
15631
  if (config.devBranch) {
14675
- info(`Main: ${import_picocolors16.default.bold(config.mainBranch)} | Dev: ${import_picocolors16.default.bold(config.devBranch)}`);
15632
+ info(`Main: ${import_picocolors17.default.bold(config.mainBranch)} | Dev: ${import_picocolors17.default.bold(config.devBranch)}`);
14676
15633
  } else {
14677
- info(`Main: ${import_picocolors16.default.bold(config.mainBranch)}`);
15634
+ info(`Main: ${import_picocolors17.default.bold(config.mainBranch)}`);
14678
15635
  }
14679
- info(`Origin: ${import_picocolors16.default.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${import_picocolors16.default.bold(config.upstream)}` : ""}`);
15636
+ info(`Origin: ${import_picocolors17.default.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${import_picocolors17.default.bold(config.upstream)}` : ""}`);
14680
15637
  }
14681
15638
 
14682
15639
  // src/commands/start.ts
14683
- var import_picocolors17 = __toESM(require_picocolors(), 1);
15640
+ var import_picocolors18 = __toESM(require_picocolors(), 1);
14684
15641
  var start_default = defineCommand({
14685
15642
  meta: {
14686
15643
  name: "start",
@@ -14732,16 +15689,16 @@ var start_default = defineCommand({
14732
15689
  warn("Start cancelled.");
14733
15690
  process.exit(0);
14734
15691
  }
14735
- info(`Creating branch: ${import_picocolors17.default.bold(branchName)}`);
15692
+ info(`Creating branch: ${import_picocolors18.default.bold(branchName)}`);
14736
15693
  await fetchRemote(syncSource.remote);
14737
15694
  if (!await refExists(syncSource.ref)) {
14738
- warn(`Remote ref ${import_picocolors17.default.bold(syncSource.ref)} not found. Creating branch from local ${import_picocolors17.default.bold(baseBranch)}.`);
15695
+ warn(`Remote ref ${import_picocolors18.default.bold(syncSource.ref)} not found. Creating branch from local ${import_picocolors18.default.bold(baseBranch)}.`);
14739
15696
  }
14740
15697
  const currentBranch = await getCurrentBranch();
14741
15698
  if (currentBranch === baseBranch && await refExists(syncSource.ref)) {
14742
15699
  const ahead = await countCommitsAhead(baseBranch, syncSource.ref);
14743
15700
  if (ahead > 0) {
14744
- warn(`You are on ${import_picocolors17.default.bold(baseBranch)} with ${import_picocolors17.default.bold(String(ahead))} local commit${ahead > 1 ? "s" : ""} not in ${import_picocolors17.default.bold(syncSource.ref)}.`);
15701
+ warn(`You are on ${import_picocolors18.default.bold(baseBranch)} with ${import_picocolors18.default.bold(String(ahead))} local commit${ahead > 1 ? "s" : ""} not in ${import_picocolors18.default.bold(syncSource.ref)}.`);
14745
15702
  info(" Syncing will discard those commits. Consider backing them up first (e.g. create a branch).");
14746
15703
  const proceed = await confirmPrompt("Discard local commits and sync to remote?");
14747
15704
  if (!proceed) {
@@ -14758,10 +15715,10 @@ var start_default = defineCommand({
14758
15715
  error(`Failed to create branch: ${result2.stderr}`);
14759
15716
  process.exit(1);
14760
15717
  }
14761
- success(`Created ${import_picocolors17.default.bold(branchName)} from ${import_picocolors17.default.bold(syncSource.ref)}`);
15718
+ success(`Created ${import_picocolors18.default.bold(branchName)} from ${import_picocolors18.default.bold(syncSource.ref)}`);
14762
15719
  return;
14763
15720
  }
14764
- error(`Failed to update ${import_picocolors17.default.bold(baseBranch)}: ${updateResult.stderr}`);
15721
+ error(`Failed to update ${import_picocolors18.default.bold(baseBranch)}: ${updateResult.stderr}`);
14765
15722
  info("Make sure your base branch exists locally or the remote ref is available.", "");
14766
15723
  process.exit(1);
14767
15724
  }
@@ -14770,12 +15727,13 @@ var start_default = defineCommand({
14770
15727
  error(`Failed to create branch: ${result.stderr}`);
14771
15728
  process.exit(1);
14772
15729
  }
14773
- success(`Created ${import_picocolors17.default.bold(branchName)} from latest ${import_picocolors17.default.bold(baseBranch)}`);
15730
+ success(`Created ${import_picocolors18.default.bold(branchName)} from latest ${import_picocolors18.default.bold(baseBranch)}`);
14774
15731
  }
14775
15732
  });
14776
15733
 
14777
15734
  // src/commands/status.ts
14778
- var import_picocolors18 = __toESM(require_picocolors(), 1);
15735
+ var import_picocolors19 = __toESM(require_picocolors(), 1);
15736
+ init_gh();
14779
15737
  var status_default = defineCommand({
14780
15738
  meta: {
14781
15739
  name: "status",
@@ -14792,8 +15750,8 @@ var status_default = defineCommand({
14792
15750
  process.exit(1);
14793
15751
  }
14794
15752
  await projectHeading("status", "\uD83D\uDCCA");
14795
- console.log(` ${import_picocolors18.default.dim("Workflow:")} ${import_picocolors18.default.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
14796
- console.log(` ${import_picocolors18.default.dim("Role:")} ${import_picocolors18.default.bold(config.role)}`);
15753
+ console.log(` ${import_picocolors19.default.dim("Workflow:")} ${import_picocolors19.default.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
15754
+ console.log(` ${import_picocolors19.default.dim("Role:")} ${import_picocolors19.default.bold(config.role)}`);
14797
15755
  console.log();
14798
15756
  await fetchAll();
14799
15757
  const currentBranch = await getCurrentBranch();
@@ -14802,7 +15760,7 @@ var status_default = defineCommand({
14802
15760
  const isContributor = config.role === "contributor";
14803
15761
  const [dirty, fileStatus] = await Promise.all([hasUncommittedChanges(), getFileStatus()]);
14804
15762
  if (dirty) {
14805
- console.log(` ${import_picocolors18.default.yellow("\u26A0")} ${import_picocolors18.default.yellow("Uncommitted changes in working tree")}`);
15763
+ console.log(` ${import_picocolors19.default.yellow("\u26A0")} ${import_picocolors19.default.yellow("Uncommitted changes in working tree")}`);
14806
15764
  console.log();
14807
15765
  }
14808
15766
  const mainRemote = `${origin}/${mainBranch}`;
@@ -14821,16 +15779,16 @@ var status_default = defineCommand({
14821
15779
  if (isFeatureBranch) {
14822
15780
  const branchDiv = await getDivergence(currentBranch, baseBranch);
14823
15781
  const branchLine = formatStatus(currentBranch, baseBranch, branchDiv.ahead, branchDiv.behind);
14824
- console.log(branchLine + import_picocolors18.default.dim(` (current ${import_picocolors18.default.green("*")})`));
15782
+ console.log(branchLine + import_picocolors19.default.dim(` (current ${import_picocolors19.default.green("*")})`));
14825
15783
  branchStatus = await detectBranchStatus(currentBranch, baseBranch);
14826
15784
  if (branchStatus.merged) {
14827
- console.log(` ${import_picocolors18.default.green("\u2713")} ${import_picocolors18.default.green("Branch merged")} \u2014 ${import_picocolors18.default.dim(branchStatus.mergedReason ?? "all commits reachable from base")}`);
15785
+ console.log(` ${import_picocolors19.default.green("\u2713")} ${import_picocolors19.default.green("Branch merged")} \u2014 ${import_picocolors19.default.dim(branchStatus.mergedReason ?? "all commits reachable from base")}`);
14828
15786
  }
14829
15787
  if (branchStatus.stale) {
14830
- console.log(` ${import_picocolors18.default.yellow("\u23F3")} ${import_picocolors18.default.yellow("Branch is stale")} \u2014 ${import_picocolors18.default.dim(`last commit ${branchStatus.staleDaysAgo} days ago`)}`);
15788
+ console.log(` ${import_picocolors19.default.yellow("\u23F3")} ${import_picocolors19.default.yellow("Branch is stale")} \u2014 ${import_picocolors19.default.dim(`last commit ${branchStatus.staleDaysAgo} days ago`)}`);
14831
15789
  }
14832
15790
  } else if (currentBranch) {
14833
- console.log(import_picocolors18.default.dim(` (on ${import_picocolors18.default.bold(currentBranch)} branch)`));
15791
+ console.log(import_picocolors19.default.dim(` (on ${import_picocolors19.default.bold(currentBranch)} branch)`));
14834
15792
  }
14835
15793
  let branchesAligned = true;
14836
15794
  {
@@ -14867,20 +15825,20 @@ var status_default = defineCommand({
14867
15825
  }
14868
15826
  branchesAligned = groups.size === 1;
14869
15827
  console.log();
14870
- console.log(` ${import_picocolors18.default.bold("\uD83D\uDD17 Branch Alignment")}`);
15828
+ console.log(` ${import_picocolors19.default.bold("\uD83D\uDD17 Branch Alignment")}`);
14871
15829
  for (const [hash, names] of groups) {
14872
15830
  const short = hash.slice(0, 7);
14873
- const nameStr = names.map((n2) => import_picocolors18.default.bold(n2)).join(import_picocolors18.default.dim(" \xB7 "));
14874
- console.log(` ${import_picocolors18.default.yellow(short)} ${import_picocolors18.default.dim("\u2500\u2500")} ${nameStr}`);
15831
+ const nameStr = names.map((n2) => import_picocolors19.default.bold(n2)).join(import_picocolors19.default.dim(" \xB7 "));
15832
+ console.log(` ${import_picocolors19.default.yellow(short)} ${import_picocolors19.default.dim("\u2500\u2500")} ${nameStr}`);
14875
15833
  const subject = await getCommitSubject(hash);
14876
15834
  if (subject) {
14877
- console.log(` ${import_picocolors18.default.dim(subject)}`);
15835
+ console.log(` ${import_picocolors19.default.dim(subject)}`);
14878
15836
  }
14879
15837
  }
14880
15838
  if (branchesAligned) {
14881
- console.log(` ${import_picocolors18.default.green("\u2713")} ${import_picocolors18.default.green("All branches aligned")} ${import_picocolors18.default.dim("\u2014 ready to start")}`);
15839
+ console.log(` ${import_picocolors19.default.green("\u2713")} ${import_picocolors19.default.green("All branches aligned")} ${import_picocolors19.default.dim("\u2014 ready to start")}`);
14882
15840
  } else {
14883
- console.log(` ${import_picocolors18.default.yellow("\u26A0")} ${import_picocolors18.default.yellow("Branches are not fully aligned")}`);
15841
+ console.log(` ${import_picocolors19.default.yellow("\u26A0")} ${import_picocolors19.default.yellow("Branches are not fully aligned")}`);
14884
15842
  }
14885
15843
  }
14886
15844
  }
@@ -14888,41 +15846,41 @@ var status_default = defineCommand({
14888
15846
  if (hasFiles) {
14889
15847
  console.log();
14890
15848
  if (fileStatus.staged.length > 0) {
14891
- console.log(` ${import_picocolors18.default.green("Staged for commit:")}`);
15849
+ console.log(` ${import_picocolors19.default.green("Staged for commit:")}`);
14892
15850
  for (const { file, status } of fileStatus.staged) {
14893
- console.log(` ${import_picocolors18.default.green("+")} ${import_picocolors18.default.dim(`${status}:`)} ${file}`);
15851
+ console.log(` ${import_picocolors19.default.green("+")} ${import_picocolors19.default.dim(`${status}:`)} ${file}`);
14894
15852
  }
14895
15853
  }
14896
15854
  if (fileStatus.modified.length > 0) {
14897
- console.log(` ${import_picocolors18.default.yellow("Unstaged changes:")}`);
15855
+ console.log(` ${import_picocolors19.default.yellow("Unstaged changes:")}`);
14898
15856
  for (const { file, status } of fileStatus.modified) {
14899
- console.log(` ${import_picocolors18.default.yellow("~")} ${import_picocolors18.default.dim(`${status}:`)} ${file}`);
15857
+ console.log(` ${import_picocolors19.default.yellow("~")} ${import_picocolors19.default.dim(`${status}:`)} ${file}`);
14900
15858
  }
14901
15859
  }
14902
15860
  if (fileStatus.untracked.length > 0) {
14903
- console.log(` ${import_picocolors18.default.red("Untracked files:")}`);
15861
+ console.log(` ${import_picocolors19.default.red("Untracked files:")}`);
14904
15862
  for (const file of fileStatus.untracked) {
14905
- console.log(` ${import_picocolors18.default.red("?")} ${file}`);
15863
+ console.log(` ${import_picocolors19.default.red("?")} ${file}`);
14906
15864
  }
14907
15865
  }
14908
15866
  } else if (!dirty) {
14909
- console.log(` ${import_picocolors18.default.green("\u2713")} ${import_picocolors18.default.dim("Working tree clean")}`);
15867
+ console.log(` ${import_picocolors19.default.green("\u2713")} ${import_picocolors19.default.dim("Working tree clean")}`);
14910
15868
  }
14911
15869
  console.log();
14912
15870
  }
14913
15871
  });
14914
15872
  function formatStatus(branch, base, ahead, behind) {
14915
- const label = import_picocolors18.default.bold(branch.padEnd(20));
15873
+ const label = import_picocolors19.default.bold(branch.padEnd(20));
14916
15874
  if (ahead === 0 && behind === 0) {
14917
- return ` ${import_picocolors18.default.green("\u2713")} ${label} ${import_picocolors18.default.dim(`in sync with ${base}`)}`;
15875
+ return ` ${import_picocolors19.default.green("\u2713")} ${label} ${import_picocolors19.default.dim(`in sync with ${base}`)}`;
14918
15876
  }
14919
15877
  if (ahead > 0 && behind === 0) {
14920
- return ` ${import_picocolors18.default.yellow("\u2191")} ${label} ${import_picocolors18.default.yellow(`${ahead} commit${ahead !== 1 ? "s" : ""} ahead of ${base}`)}`;
15878
+ return ` ${import_picocolors19.default.yellow("\u2191")} ${label} ${import_picocolors19.default.yellow(`${ahead} commit${ahead !== 1 ? "s" : ""} ahead of ${base}`)}`;
14921
15879
  }
14922
15880
  if (behind > 0 && ahead === 0) {
14923
- return ` ${import_picocolors18.default.red("\u2193")} ${label} ${import_picocolors18.default.red(`${behind} commit${behind !== 1 ? "s" : ""} behind ${base}`)}`;
15881
+ return ` ${import_picocolors19.default.red("\u2193")} ${label} ${import_picocolors19.default.red(`${behind} commit${behind !== 1 ? "s" : ""} behind ${base}`)}`;
14924
15882
  }
14925
- return ` ${import_picocolors18.default.red("\u26A1")} ${label} ${import_picocolors18.default.yellow(`${ahead} ahead`)}${import_picocolors18.default.dim(", ")}${import_picocolors18.default.red(`${behind} behind`)} ${import_picocolors18.default.dim(base)}`;
15883
+ return ` ${import_picocolors19.default.red("\u26A1")} ${label} ${import_picocolors19.default.yellow(`${ahead} ahead`)}${import_picocolors19.default.dim(", ")}${import_picocolors19.default.red(`${behind} behind`)} ${import_picocolors19.default.dim(base)}`;
14926
15884
  }
14927
15885
  var STALE_THRESHOLD_DAYS = 14;
14928
15886
  async function detectBranchStatus(branch, baseBranch) {
@@ -14973,15 +15931,16 @@ async function detectBranchStatus(branch, baseBranch) {
14973
15931
  }
14974
15932
 
14975
15933
  // src/commands/submit.ts
14976
- var import_picocolors19 = __toESM(require_picocolors(), 1);
15934
+ var import_picocolors20 = __toESM(require_picocolors(), 1);
15935
+ init_gh();
14977
15936
  async function performSquashMerge(origin, baseBranch, featureBranch, options) {
14978
- info(`Checking out ${import_picocolors19.default.bold(baseBranch)}...`);
15937
+ info(`Checking out ${import_picocolors20.default.bold(baseBranch)}...`);
14979
15938
  const coResult = await checkoutBranch(baseBranch);
14980
15939
  if (coResult.exitCode !== 0) {
14981
15940
  error(`Failed to checkout ${baseBranch}: ${coResult.stderr}`);
14982
15941
  process.exit(1);
14983
15942
  }
14984
- info(`Squash merging ${import_picocolors19.default.bold(featureBranch)} into ${import_picocolors19.default.bold(baseBranch)}...`);
15943
+ info(`Squash merging ${import_picocolors20.default.bold(featureBranch)} into ${import_picocolors20.default.bold(baseBranch)}...`);
14985
15944
  const mergeResult = await mergeSquash(featureBranch);
14986
15945
  if (mergeResult.exitCode !== 0) {
14987
15946
  error(`Squash merge failed: ${mergeResult.stderr}`);
@@ -15001,7 +15960,7 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
15001
15960
  message = aiMsg;
15002
15961
  spinner.success("AI commit message generated.");
15003
15962
  console.log(`
15004
- ${import_picocolors19.default.dim("AI suggestion:")} ${import_picocolors19.default.bold(import_picocolors19.default.cyan(message))}`);
15963
+ ${import_picocolors20.default.dim("AI suggestion:")} ${import_picocolors20.default.bold(import_picocolors20.default.cyan(message))}`);
15005
15964
  break;
15006
15965
  }
15007
15966
  spinner.fail("AI did not return a commit message.");
@@ -15041,7 +16000,7 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
15041
16000
  message = regen;
15042
16001
  spinner.success("Commit message regenerated.");
15043
16002
  console.log(`
15044
- ${import_picocolors19.default.dim("AI suggestion:")} ${import_picocolors19.default.bold(import_picocolors19.default.cyan(regen))}`);
16003
+ ${import_picocolors20.default.dim("AI suggestion:")} ${import_picocolors20.default.bold(import_picocolors20.default.cyan(regen))}`);
15045
16004
  } else {
15046
16005
  spinner.fail("Regeneration failed.");
15047
16006
  }
@@ -15057,13 +16016,13 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
15057
16016
  error(`Commit failed: ${commitResult.stderr}`);
15058
16017
  process.exit(1);
15059
16018
  }
15060
- info(`Pushing ${import_picocolors19.default.bold(baseBranch)} to ${origin}...`);
16019
+ info(`Pushing ${import_picocolors20.default.bold(baseBranch)} to ${origin}...`);
15061
16020
  const pushResult = await pushBranch(origin, baseBranch);
15062
16021
  if (pushResult.exitCode !== 0) {
15063
16022
  error(`Failed to push ${baseBranch}: ${pushResult.stderr}`);
15064
16023
  process.exit(1);
15065
16024
  }
15066
- info(`Deleting local branch ${import_picocolors19.default.bold(featureBranch)}...`);
16025
+ info(`Deleting local branch ${import_picocolors20.default.bold(featureBranch)}...`);
15067
16026
  const delLocal = await forceDeleteBranch(featureBranch);
15068
16027
  if (delLocal.exitCode !== 0) {
15069
16028
  warn(`Could not delete local branch: ${delLocal.stderr.trim()}`);
@@ -15071,14 +16030,14 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
15071
16030
  const remoteBranchRef = `${origin}/${featureBranch}`;
15072
16031
  const remoteExists = await branchExists(remoteBranchRef);
15073
16032
  if (remoteExists) {
15074
- info(`Deleting remote branch ${import_picocolors19.default.bold(featureBranch)}...`);
16033
+ info(`Deleting remote branch ${import_picocolors20.default.bold(featureBranch)}...`);
15075
16034
  const delRemote = await deleteRemoteBranch(origin, featureBranch);
15076
16035
  if (delRemote.exitCode !== 0) {
15077
16036
  warn(`Could not delete remote branch: ${delRemote.stderr.trim()}`);
15078
16037
  }
15079
16038
  }
15080
- success(`Squash merged ${import_picocolors19.default.bold(featureBranch)} into ${import_picocolors19.default.bold(baseBranch)} and pushed.`);
15081
- info(`Run ${import_picocolors19.default.bold("cn start")} to begin a new feature.`, "");
16039
+ success(`Squash merged ${import_picocolors20.default.bold(featureBranch)} into ${import_picocolors20.default.bold(baseBranch)} and pushed.`);
16040
+ info(`Run ${import_picocolors20.default.bold("cn start")} to begin a new feature.`, "");
15082
16041
  }
15083
16042
  var submit_default = defineCommand({
15084
16043
  meta: {
@@ -15135,7 +16094,7 @@ var submit_default = defineCommand({
15135
16094
  }
15136
16095
  if (protectedBranches.includes(currentBranch)) {
15137
16096
  await projectHeading("submit", "\uD83D\uDE80");
15138
- warn(`You're on ${import_picocolors19.default.bold(currentBranch)}, which is a protected branch. PRs should come from feature branches.`);
16097
+ warn(`You're on ${import_picocolors20.default.bold(currentBranch)}, which is a protected branch. PRs should come from feature branches.`);
15139
16098
  await fetchAll();
15140
16099
  const remoteRef = `${origin}/${currentBranch}`;
15141
16100
  const localWork = await hasLocalWork(origin, currentBranch);
@@ -15144,11 +16103,11 @@ var submit_default = defineCommand({
15144
16103
  const hasAnything = hasCommits || dirty;
15145
16104
  if (!hasAnything) {
15146
16105
  error("No local changes or commits to move. Switch to a feature branch first.");
15147
- info(` Run ${import_picocolors19.default.bold("cn start")} to create a new feature branch.`, "");
16106
+ info(` Run ${import_picocolors20.default.bold("cn start")} to create a new feature branch.`, "");
15148
16107
  process.exit(1);
15149
16108
  }
15150
16109
  if (hasCommits) {
15151
- info(`Found ${import_picocolors19.default.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${import_picocolors19.default.bold(currentBranch)}.`);
16110
+ info(`Found ${import_picocolors20.default.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${import_picocolors20.default.bold(currentBranch)}.`);
15152
16111
  }
15153
16112
  if (dirty) {
15154
16113
  info("You also have uncommitted changes in the working tree.");
@@ -15178,12 +16137,12 @@ var submit_default = defineCommand({
15178
16137
  error(`Failed to create branch: ${branchResult.stderr}`);
15179
16138
  process.exit(1);
15180
16139
  }
15181
- success(`Created ${import_picocolors19.default.bold(newBranchName)} with your changes.`);
16140
+ success(`Created ${import_picocolors20.default.bold(newBranchName)} with your changes.`);
15182
16141
  await updateLocalBranch(currentBranch, remoteRef);
15183
- info(`Reset ${import_picocolors19.default.bold(currentBranch)} back to ${import_picocolors19.default.bold(remoteRef)} \u2014 no damage done.`, "");
16142
+ info(`Reset ${import_picocolors20.default.bold(currentBranch)} back to ${import_picocolors20.default.bold(remoteRef)} \u2014 no damage done.`, "");
15184
16143
  console.log();
15185
- success(`You're now on ${import_picocolors19.default.bold(newBranchName)} with all your work intact.`);
15186
- info(`Run ${import_picocolors19.default.bold("cn submit")} again to push and create your PR.`, "");
16144
+ success(`You're now on ${import_picocolors20.default.bold(newBranchName)} with all your work intact.`);
16145
+ info(`Run ${import_picocolors20.default.bold("cn submit")} again to push and create your PR.`, "");
15187
16146
  return;
15188
16147
  }
15189
16148
  await projectHeading("submit", "\uD83D\uDE80");
@@ -15192,7 +16151,7 @@ var submit_default = defineCommand({
15192
16151
  if (ghInstalled && ghAuthed) {
15193
16152
  const mergedPR = await getMergedPRForBranch(currentBranch);
15194
16153
  if (mergedPR) {
15195
- warn(`PR #${mergedPR.number} (${import_picocolors19.default.bold(mergedPR.title)}) was already merged.`);
16154
+ warn(`PR #${mergedPR.number} (${import_picocolors20.default.bold(mergedPR.title)}) was already merged.`);
15196
16155
  const localWork = await hasLocalWork(origin, currentBranch);
15197
16156
  const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
15198
16157
  if (hasWork) {
@@ -15200,7 +16159,7 @@ var submit_default = defineCommand({
15200
16159
  warn("You have uncommitted changes in your working tree.");
15201
16160
  }
15202
16161
  if (localWork.unpushedCommits > 0) {
15203
- warn(`You have ${import_picocolors19.default.bold(String(localWork.unpushedCommits))} local commit${localWork.unpushedCommits !== 1 ? "s" : ""} not in the merged PR.`);
16162
+ warn(`You have ${import_picocolors20.default.bold(String(localWork.unpushedCommits))} local commit${localWork.unpushedCommits !== 1 ? "s" : ""} not in the merged PR.`);
15204
16163
  }
15205
16164
  const SAVE_NEW_BRANCH = "Save changes to a new branch";
15206
16165
  const DISCARD = "Discard all changes and clean up";
@@ -15227,10 +16186,10 @@ var submit_default = defineCommand({
15227
16186
  error(`Failed to rename branch: ${renameResult.stderr}`);
15228
16187
  process.exit(1);
15229
16188
  }
15230
- success(`Renamed ${import_picocolors19.default.bold(currentBranch)} \u2192 ${import_picocolors19.default.bold(newBranchName)}`);
16189
+ success(`Renamed ${import_picocolors20.default.bold(currentBranch)} \u2192 ${import_picocolors20.default.bold(newBranchName)}`);
15231
16190
  await unsetUpstream();
15232
16191
  const syncSource2 = getSyncSource(config);
15233
- info(`Syncing ${import_picocolors19.default.bold(newBranchName)} with latest ${import_picocolors19.default.bold(baseBranch)}...`);
16192
+ info(`Syncing ${import_picocolors20.default.bold(newBranchName)} with latest ${import_picocolors20.default.bold(baseBranch)}...`);
15234
16193
  await fetchRemote(syncSource2.remote);
15235
16194
  let rebaseResult;
15236
16195
  if (staleUpstreamHash) {
@@ -15241,17 +16200,17 @@ var submit_default = defineCommand({
15241
16200
  }
15242
16201
  if (rebaseResult.exitCode !== 0) {
15243
16202
  warn("Rebase encountered conflicts. Resolve them manually, then run:");
15244
- info(` ${import_picocolors19.default.bold("git rebase --continue")}`, "");
16203
+ info(` ${import_picocolors20.default.bold("git rebase --continue")}`, "");
15245
16204
  } else {
15246
- success(`Rebased ${import_picocolors19.default.bold(newBranchName)} onto ${import_picocolors19.default.bold(syncSource2.ref)}.`);
16205
+ success(`Rebased ${import_picocolors20.default.bold(newBranchName)} onto ${import_picocolors20.default.bold(syncSource2.ref)}.`);
15247
16206
  }
15248
- info(`All your changes are preserved. Run ${import_picocolors19.default.bold("cn submit")} when ready to create a new PR.`, "");
16207
+ info(`All your changes are preserved. Run ${import_picocolors20.default.bold("cn submit")} when ready to create a new PR.`, "");
15249
16208
  return;
15250
16209
  }
15251
16210
  warn("Discarding local changes...");
15252
16211
  }
15253
16212
  const syncSource = getSyncSource(config);
15254
- info(`Switching to ${import_picocolors19.default.bold(baseBranch)} and syncing...`);
16213
+ info(`Switching to ${import_picocolors20.default.bold(baseBranch)} and syncing...`);
15255
16214
  await fetchRemote(syncSource.remote);
15256
16215
  await resetHard("HEAD");
15257
16216
  const coResult = await checkoutBranch(baseBranch);
@@ -15260,23 +16219,23 @@ var submit_default = defineCommand({
15260
16219
  process.exit(1);
15261
16220
  }
15262
16221
  await updateLocalBranch(baseBranch, syncSource.ref);
15263
- success(`Synced ${import_picocolors19.default.bold(baseBranch)} with ${import_picocolors19.default.bold(syncSource.ref)}.`);
15264
- info(`Deleting stale branch ${import_picocolors19.default.bold(currentBranch)}...`);
16222
+ success(`Synced ${import_picocolors20.default.bold(baseBranch)} with ${import_picocolors20.default.bold(syncSource.ref)}.`);
16223
+ info(`Deleting stale branch ${import_picocolors20.default.bold(currentBranch)}...`);
15265
16224
  const delResult = await forceDeleteBranch(currentBranch);
15266
16225
  if (delResult.exitCode === 0) {
15267
- success(`Deleted ${import_picocolors19.default.bold(currentBranch)}.`);
16226
+ success(`Deleted ${import_picocolors20.default.bold(currentBranch)}.`);
15268
16227
  } else {
15269
16228
  warn(`Could not delete branch: ${delResult.stderr.trim()}`);
15270
16229
  }
15271
16230
  console.log();
15272
- info(`You're now on ${import_picocolors19.default.bold(baseBranch)}. Run ${import_picocolors19.default.bold("cn start")} to begin a new feature.`);
16231
+ info(`You're now on ${import_picocolors20.default.bold(baseBranch)}. Run ${import_picocolors20.default.bold("cn start")} to begin a new feature.`);
15273
16232
  return;
15274
16233
  }
15275
16234
  }
15276
16235
  if (ghInstalled && ghAuthed) {
15277
16236
  const existingPR = await getPRForBranch(currentBranch);
15278
16237
  if (existingPR) {
15279
- info(`Pushing ${import_picocolors19.default.bold(currentBranch)} to ${origin}...`);
16238
+ info(`Pushing ${import_picocolors20.default.bold(currentBranch)} to ${origin}...`);
15280
16239
  const pushResult2 = await pushSetUpstream(origin, currentBranch);
15281
16240
  if (pushResult2.exitCode !== 0) {
15282
16241
  error(`Failed to push: ${pushResult2.stderr}`);
@@ -15287,8 +16246,8 @@ var submit_default = defineCommand({
15287
16246
  }
15288
16247
  process.exit(1);
15289
16248
  }
15290
- success(`Pushed changes to existing PR #${existingPR.number}: ${import_picocolors19.default.bold(existingPR.title)}`);
15291
- console.log(` ${import_picocolors19.default.cyan(existingPR.url)}`);
16249
+ success(`Pushed changes to existing PR #${existingPR.number}: ${import_picocolors20.default.bold(existingPR.title)}`);
16250
+ console.log(` ${import_picocolors20.default.cyan(existingPR.url)}`);
15292
16251
  return;
15293
16252
  }
15294
16253
  }
@@ -15310,10 +16269,10 @@ var submit_default = defineCommand({
15310
16269
  prBody = result.body;
15311
16270
  spinner.success("PR description generated.");
15312
16271
  console.log(`
15313
- ${import_picocolors19.default.dim("AI title:")} ${import_picocolors19.default.bold(import_picocolors19.default.cyan(prTitle))}`);
16272
+ ${import_picocolors20.default.dim("AI title:")} ${import_picocolors20.default.bold(import_picocolors20.default.cyan(prTitle))}`);
15314
16273
  console.log(`
15315
- ${import_picocolors19.default.dim("AI body preview:")}`);
15316
- console.log(import_picocolors19.default.dim(prBody.slice(0, 300) + (prBody.length > 300 ? "..." : "")));
16274
+ ${import_picocolors20.default.dim("AI body preview:")}`);
16275
+ console.log(import_picocolors20.default.dim(prBody.slice(0, 300) + (prBody.length > 300 ? "..." : "")));
15317
16276
  } else {
15318
16277
  spinner.fail("AI did not return a PR description.");
15319
16278
  }
@@ -15424,7 +16383,7 @@ ${import_picocolors19.default.dim("AI body preview:")}`);
15424
16383
  warn("Submit cancelled.");
15425
16384
  return;
15426
16385
  }
15427
- info(`Pushing ${import_picocolors19.default.bold(currentBranch)} to ${origin}...`);
16386
+ info(`Pushing ${import_picocolors20.default.bold(currentBranch)} to ${origin}...`);
15428
16387
  const pushResult = await pushSetUpstream(origin, currentBranch);
15429
16388
  if (pushResult.exitCode !== 0) {
15430
16389
  error(`Failed to push: ${pushResult.stderr}`);
@@ -15443,7 +16402,7 @@ ${import_picocolors19.default.dim("AI body preview:")}`);
15443
16402
  const prUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}/compare/${baseBranch}...${currentBranch}?expand=1`;
15444
16403
  console.log();
15445
16404
  info("Create your PR manually:", "");
15446
- console.log(` ${import_picocolors19.default.cyan(prUrl)}`);
16405
+ console.log(` ${import_picocolors20.default.cyan(prUrl)}`);
15447
16406
  } else {
15448
16407
  info("gh CLI not available. Create your PR manually on GitHub.", "");
15449
16408
  }
@@ -15477,7 +16436,7 @@ ${import_picocolors19.default.dim("AI body preview:")}`);
15477
16436
  });
15478
16437
 
15479
16438
  // src/commands/switch.ts
15480
- var import_picocolors20 = __toESM(require_picocolors(), 1);
16439
+ var import_picocolors21 = __toESM(require_picocolors(), 1);
15481
16440
  var switch_default = defineCommand({
15482
16441
  meta: {
15483
16442
  name: "switch",
@@ -15509,11 +16468,11 @@ var switch_default = defineCommand({
15509
16468
  const choices = localBranches.filter((b2) => b2.name !== currentBranch).map((b2) => {
15510
16469
  const labels = [];
15511
16470
  if (protectedBranches.includes(b2.name))
15512
- labels.push(import_picocolors20.default.red("protected"));
16471
+ labels.push(import_picocolors21.default.red("protected"));
15513
16472
  if (b2.upstream)
15514
- labels.push(import_picocolors20.default.dim(`\u2192 ${b2.upstream}`));
16473
+ labels.push(import_picocolors21.default.dim(`\u2192 ${b2.upstream}`));
15515
16474
  if (b2.gone)
15516
- labels.push(import_picocolors20.default.red("remote gone"));
16475
+ labels.push(import_picocolors21.default.red("remote gone"));
15517
16476
  const suffix = labels.length > 0 ? ` ${labels.join(" \xB7 ")}` : "";
15518
16477
  return `${b2.name}${suffix}`;
15519
16478
  });
@@ -15525,7 +16484,7 @@ var switch_default = defineCommand({
15525
16484
  targetBranch = selected.split(/\s{2,}/)[0].trim();
15526
16485
  }
15527
16486
  if (targetBranch === currentBranch) {
15528
- info(`Already on ${import_picocolors20.default.bold(targetBranch)}.`);
16487
+ info(`Already on ${import_picocolors21.default.bold(targetBranch)}.`);
15529
16488
  return;
15530
16489
  }
15531
16490
  if (await hasUncommittedChanges()) {
@@ -15544,7 +16503,7 @@ var switch_default = defineCommand({
15544
16503
  const stashMsg = `contrib-save: auto-save from ${currentBranch}`;
15545
16504
  try {
15546
16505
  await exec("git", ["stash", "push", "-m", stashMsg]);
15547
- info(`Saved changes: ${import_picocolors20.default.dim(stashMsg)}`);
16506
+ info(`Saved changes: ${import_picocolors21.default.dim(stashMsg)}`);
15548
16507
  } catch {
15549
16508
  error("Failed to save changes. Please commit or save manually.");
15550
16509
  process.exit(1);
@@ -15560,9 +16519,9 @@ var switch_default = defineCommand({
15560
16519
  }
15561
16520
  process.exit(1);
15562
16521
  }
15563
- success(`Switched to ${import_picocolors20.default.bold(targetBranch)}`);
15564
- info(`Your changes from ${import_picocolors20.default.bold(currentBranch ?? "previous branch")} are saved.`, "");
15565
- info(`Use ${import_picocolors20.default.bold("cn save --restore")} to bring them back.`, "");
16522
+ success(`Switched to ${import_picocolors21.default.bold(targetBranch)}`);
16523
+ info(`Your changes from ${import_picocolors21.default.bold(currentBranch ?? "previous branch")} are saved.`, "");
16524
+ info(`Use ${import_picocolors21.default.bold("cn save --restore")} to bring them back.`, "");
15566
16525
  return;
15567
16526
  }
15568
16527
  const result = await checkoutBranch(targetBranch);
@@ -15570,12 +16529,12 @@ var switch_default = defineCommand({
15570
16529
  error(`Failed to switch to ${targetBranch}: ${result.stderr}`);
15571
16530
  process.exit(1);
15572
16531
  }
15573
- success(`Switched to ${import_picocolors20.default.bold(targetBranch)}`);
16532
+ success(`Switched to ${import_picocolors21.default.bold(targetBranch)}`);
15574
16533
  }
15575
16534
  });
15576
16535
 
15577
16536
  // src/commands/sync.ts
15578
- var import_picocolors21 = __toESM(require_picocolors(), 1);
16537
+ var import_picocolors22 = __toESM(require_picocolors(), 1);
15579
16538
  var sync_default = defineCommand({
15580
16539
  meta: {
15581
16540
  name: "sync",
@@ -15627,24 +16586,24 @@ var sync_default = defineCommand({
15627
16586
  await fetchRemote(origin);
15628
16587
  }
15629
16588
  if (!await refExists(syncSource.ref)) {
15630
- error(`Remote ref ${import_picocolors21.default.bold(syncSource.ref)} does not exist.`);
16589
+ error(`Remote ref ${import_picocolors22.default.bold(syncSource.ref)} does not exist.`);
15631
16590
  info("This can happen if the branch was renamed or deleted on the remote.", "");
15632
- info(`Check your config: the base branch may need updating via ${import_picocolors21.default.bold("cn setup")}.`, "");
16591
+ info(`Check your config: the base branch may need updating via ${import_picocolors22.default.bold("cn setup")}.`, "");
15633
16592
  process.exit(1);
15634
16593
  }
15635
16594
  let allowMergeCommit = false;
15636
16595
  const div = await getDivergence(baseBranch, syncSource.ref);
15637
16596
  if (div.ahead > 0 || div.behind > 0) {
15638
- info(`${import_picocolors21.default.bold(baseBranch)} is ${import_picocolors21.default.yellow(`${div.ahead} ahead`)} and ${import_picocolors21.default.red(`${div.behind} behind`)} ${syncSource.ref}`);
16597
+ info(`${import_picocolors22.default.bold(baseBranch)} is ${import_picocolors22.default.yellow(`${div.ahead} ahead`)} and ${import_picocolors22.default.red(`${div.behind} behind`)} ${syncSource.ref}`);
15639
16598
  } else {
15640
- info(`${import_picocolors21.default.bold(baseBranch)} is already in sync with ${syncSource.ref}`);
16599
+ info(`${import_picocolors22.default.bold(baseBranch)} is already in sync with ${syncSource.ref}`);
15641
16600
  }
15642
16601
  if (div.ahead > 0) {
15643
16602
  const currentBranch = await getCurrentBranch();
15644
16603
  const protectedBranches = getProtectedBranches(config);
15645
16604
  const isOnProtected = currentBranch && protectedBranches.includes(currentBranch);
15646
16605
  if (isOnProtected) {
15647
- warn(`You have ${import_picocolors21.default.bold(String(div.ahead))} local commit${div.ahead !== 1 ? "s" : ""} on ${import_picocolors21.default.bold(baseBranch)} that aren't on the remote.`);
16606
+ warn(`You have ${import_picocolors22.default.bold(String(div.ahead))} local commit${div.ahead !== 1 ? "s" : ""} on ${import_picocolors22.default.bold(baseBranch)} that aren't on the remote.`);
15648
16607
  info("Pulling now could create a merge commit, which breaks clean history.");
15649
16608
  console.log();
15650
16609
  const MOVE_BRANCH = "Move my commits to a new feature branch, then sync";
@@ -15674,7 +16633,7 @@ var sync_default = defineCommand({
15674
16633
  error(`Failed to create branch: ${branchResult.stderr}`);
15675
16634
  process.exit(1);
15676
16635
  }
15677
- success(`Created ${import_picocolors21.default.bold(newBranchName)} with your commits.`);
16636
+ success(`Created ${import_picocolors22.default.bold(newBranchName)} with your commits.`);
15678
16637
  const coResult2 = await checkoutBranch(baseBranch);
15679
16638
  if (coResult2.exitCode !== 0) {
15680
16639
  error(`Failed to checkout ${baseBranch}: ${coResult2.stderr}`);
@@ -15682,11 +16641,11 @@ var sync_default = defineCommand({
15682
16641
  }
15683
16642
  const remoteRef = syncSource.ref;
15684
16643
  await updateLocalBranch(baseBranch, remoteRef);
15685
- success(`Reset ${import_picocolors21.default.bold(baseBranch)} to ${import_picocolors21.default.bold(remoteRef)}.`);
15686
- success(`${import_picocolors21.default.bold(baseBranch)} is now in sync with ${syncSource.ref}`);
16644
+ success(`Reset ${import_picocolors22.default.bold(baseBranch)} to ${import_picocolors22.default.bold(remoteRef)}.`);
16645
+ success(`${import_picocolors22.default.bold(baseBranch)} is now in sync with ${syncSource.ref}`);
15687
16646
  console.log();
15688
- info(`Your commits are safe on ${import_picocolors21.default.bold(newBranchName)}.`, "");
15689
- info(`Run ${import_picocolors21.default.bold(`git checkout ${newBranchName}`)} then ${import_picocolors21.default.bold("cn update")} to rebase onto the synced ${import_picocolors21.default.bold(baseBranch)}.`, "");
16647
+ info(`Your commits are safe on ${import_picocolors22.default.bold(newBranchName)}.`, "");
16648
+ info(`Run ${import_picocolors22.default.bold(`git checkout ${newBranchName}`)} then ${import_picocolors22.default.bold("cn update")} to rebase onto the synced ${import_picocolors22.default.bold(baseBranch)}.`, "");
15690
16649
  return;
15691
16650
  }
15692
16651
  allowMergeCommit = true;
@@ -15694,7 +16653,7 @@ var sync_default = defineCommand({
15694
16653
  }
15695
16654
  }
15696
16655
  if (!args.yes) {
15697
- const ok = await confirmPrompt(`This will pull ${import_picocolors21.default.bold(syncSource.ref)} into local ${import_picocolors21.default.bold(baseBranch)}.`);
16656
+ const ok = await confirmPrompt(`This will pull ${import_picocolors22.default.bold(syncSource.ref)} into local ${import_picocolors22.default.bold(baseBranch)}.`);
15698
16657
  if (!ok)
15699
16658
  process.exit(0);
15700
16659
  }
@@ -15708,8 +16667,8 @@ var sync_default = defineCommand({
15708
16667
  if (allowMergeCommit) {
15709
16668
  error(`Pull failed: ${pullResult.stderr.trim()}`);
15710
16669
  } else {
15711
- error(`Fast-forward pull failed. Your local ${import_picocolors21.default.bold(baseBranch)} may have diverged.`);
15712
- info(`Use ${import_picocolors21.default.bold("cn sync")} again and choose "Move my commits to a new feature branch" to fix this.`, "");
16670
+ error(`Fast-forward pull failed. Your local ${import_picocolors22.default.bold(baseBranch)} may have diverged.`);
16671
+ info(`Use ${import_picocolors22.default.bold("cn sync")} again and choose "Move my commits to a new feature branch" to fix this.`, "");
15713
16672
  }
15714
16673
  process.exit(1);
15715
16674
  }
@@ -15717,7 +16676,7 @@ var sync_default = defineCommand({
15717
16676
  if (hasDevBranch(workflow) && role === "maintainer") {
15718
16677
  const mainDiv = await getDivergence(config.mainBranch, `${origin}/${config.mainBranch}`);
15719
16678
  if (mainDiv.behind > 0) {
15720
- info(`Also syncing ${import_picocolors21.default.bold(config.mainBranch)}...`);
16679
+ info(`Also syncing ${import_picocolors22.default.bold(config.mainBranch)}...`);
15721
16680
  const mainCoResult = await checkoutBranch(config.mainBranch);
15722
16681
  if (mainCoResult.exitCode === 0) {
15723
16682
  const mainPullResult = await pullFastForwardOnly(origin, config.mainBranch);
@@ -15758,20 +16717,20 @@ var sync_default = defineCommand({
15758
16717
  }
15759
16718
  }
15760
16719
  console.log();
15761
- console.log(` ${import_picocolors21.default.bold("\uD83D\uDD17 Branch Alignment")}`);
16720
+ console.log(` ${import_picocolors22.default.bold("\uD83D\uDD17 Branch Alignment")}`);
15762
16721
  for (const [hash, names] of groups) {
15763
16722
  const short = hash.slice(0, 7);
15764
- const nameStr = names.map((n2) => import_picocolors21.default.bold(n2)).join(import_picocolors21.default.dim(" \xB7 "));
15765
- console.log(` ${import_picocolors21.default.yellow(short)} ${import_picocolors21.default.dim("\u2500\u2500")} ${nameStr}`);
16723
+ const nameStr = names.map((n2) => import_picocolors22.default.bold(n2)).join(import_picocolors22.default.dim(" \xB7 "));
16724
+ console.log(` ${import_picocolors22.default.yellow(short)} ${import_picocolors22.default.dim("\u2500\u2500")} ${nameStr}`);
15766
16725
  const subject = await getCommitSubject(hash);
15767
16726
  if (subject) {
15768
- console.log(` ${import_picocolors21.default.dim(subject)}`);
16727
+ console.log(` ${import_picocolors22.default.dim(subject)}`);
15769
16728
  }
15770
16729
  }
15771
16730
  if (groups.size === 1) {
15772
- console.log(` ${import_picocolors21.default.green("\u2713")} ${import_picocolors21.default.green("All branches aligned")} ${import_picocolors21.default.dim("\u2014 ready to start")}`);
16731
+ console.log(` ${import_picocolors22.default.green("\u2713")} ${import_picocolors22.default.green("All branches aligned")} ${import_picocolors22.default.dim("\u2014 ready to start")}`);
15773
16732
  } else {
15774
- console.log(` ${import_picocolors21.default.yellow("\u26A0")} ${import_picocolors21.default.yellow("Branches are not fully aligned")}`);
16733
+ console.log(` ${import_picocolors22.default.yellow("\u26A0")} ${import_picocolors22.default.yellow("Branches are not fully aligned")}`);
15775
16734
  }
15776
16735
  }
15777
16736
  }
@@ -15779,8 +16738,9 @@ var sync_default = defineCommand({
15779
16738
  });
15780
16739
 
15781
16740
  // src/commands/update.ts
15782
- import { readFileSync as readFileSync6 } from "fs";
15783
- var import_picocolors22 = __toESM(require_picocolors(), 1);
16741
+ import { readFileSync as readFileSync7 } from "fs";
16742
+ var import_picocolors23 = __toESM(require_picocolors(), 1);
16743
+ init_gh();
15784
16744
  function hasStaleBranchWorkToPreserve(uniqueCommitsAheadOfBase, hasUncommittedChanges2) {
15785
16745
  return hasUncommittedChanges2 || uniqueCommitsAheadOfBase > 0;
15786
16746
  }
@@ -15821,7 +16781,7 @@ var update_default = defineCommand({
15821
16781
  }
15822
16782
  if (protectedBranches.includes(currentBranch)) {
15823
16783
  await projectHeading("update", "\uD83D\uDD03");
15824
- warn(`You're on ${import_picocolors22.default.bold(currentBranch)}, which is a protected branch. Updates (rebase) apply to feature branches.`);
16784
+ warn(`You're on ${import_picocolors23.default.bold(currentBranch)}, which is a protected branch. Updates (rebase) apply to feature branches.`);
15825
16785
  await fetchAll();
15826
16786
  const { origin } = config;
15827
16787
  const remoteRef = `${origin}/${currentBranch}`;
@@ -15830,12 +16790,12 @@ var update_default = defineCommand({
15830
16790
  const hasCommits = localWork.unpushedCommits > 0;
15831
16791
  const hasAnything = hasCommits || dirty;
15832
16792
  if (!hasAnything) {
15833
- info(`No local changes found on ${import_picocolors22.default.bold(currentBranch)}.`);
15834
- info(`Use ${import_picocolors22.default.bold("cn sync")} to sync protected branches, or ${import_picocolors22.default.bold("cn start")} to create a feature branch.`);
16793
+ info(`No local changes found on ${import_picocolors23.default.bold(currentBranch)}.`);
16794
+ info(`Use ${import_picocolors23.default.bold("cn sync")} to sync protected branches, or ${import_picocolors23.default.bold("cn start")} to create a feature branch.`);
15835
16795
  process.exit(1);
15836
16796
  }
15837
16797
  if (hasCommits) {
15838
- info(`Found ${import_picocolors22.default.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${import_picocolors22.default.bold(currentBranch)}.`);
16798
+ info(`Found ${import_picocolors23.default.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${import_picocolors23.default.bold(currentBranch)}.`);
15839
16799
  }
15840
16800
  if (dirty) {
15841
16801
  info("You also have uncommitted changes in the working tree.");
@@ -15865,12 +16825,12 @@ var update_default = defineCommand({
15865
16825
  error(`Failed to create branch: ${branchResult.stderr}`);
15866
16826
  process.exit(1);
15867
16827
  }
15868
- success(`Created ${import_picocolors22.default.bold(newBranchName)} with your changes.`);
16828
+ success(`Created ${import_picocolors23.default.bold(newBranchName)} with your changes.`);
15869
16829
  await updateLocalBranch(currentBranch, remoteRef);
15870
- info(`Reset ${import_picocolors22.default.bold(currentBranch)} back to ${import_picocolors22.default.bold(remoteRef)} \u2014 no damage done.`, "");
16830
+ info(`Reset ${import_picocolors23.default.bold(currentBranch)} back to ${import_picocolors23.default.bold(remoteRef)} \u2014 no damage done.`, "");
15871
16831
  console.log();
15872
- success(`You're now on ${import_picocolors22.default.bold(newBranchName)} with all your work intact.`);
15873
- info(`Run ${import_picocolors22.default.bold("cn update")} again to rebase onto latest ${import_picocolors22.default.bold(baseBranch)}.`, "");
16832
+ success(`You're now on ${import_picocolors23.default.bold(newBranchName)} with all your work intact.`);
16833
+ info(`Run ${import_picocolors23.default.bold("cn update")} again to rebase onto latest ${import_picocolors23.default.bold(baseBranch)}.`, "");
15874
16834
  return;
15875
16835
  }
15876
16836
  if (await hasUncommittedChanges()) {
@@ -15880,8 +16840,8 @@ var update_default = defineCommand({
15880
16840
  await projectHeading("update", "\uD83D\uDD03");
15881
16841
  const mergedPR = await getMergedPRForBranch(currentBranch);
15882
16842
  if (mergedPR) {
15883
- warn(`PR #${mergedPR.number} (${import_picocolors22.default.bold(mergedPR.title)}) has already been merged.`);
15884
- info(`Link: ${import_picocolors22.default.underline(mergedPR.url)}`, "");
16843
+ warn(`PR #${mergedPR.number} (${import_picocolors23.default.bold(mergedPR.title)}) has already been merged.`);
16844
+ info(`Link: ${import_picocolors23.default.underline(mergedPR.url)}`, "");
15885
16845
  const uniqueCommitsAheadOfBase = await countCommitsAhead(currentBranch, syncSource.ref);
15886
16846
  const dirty = await hasUncommittedChanges();
15887
16847
  const hasWork = hasStaleBranchWorkToPreserve(uniqueCommitsAheadOfBase, dirty);
@@ -15890,12 +16850,12 @@ var update_default = defineCommand({
15890
16850
  info("You have uncommitted local changes.");
15891
16851
  }
15892
16852
  if (uniqueCommitsAheadOfBase > 0) {
15893
- info(`You have ${uniqueCommitsAheadOfBase} local commit(s) not in ${import_picocolors22.default.bold(syncSource.ref)}.`);
16853
+ info(`You have ${uniqueCommitsAheadOfBase} local commit(s) not in ${import_picocolors23.default.bold(syncSource.ref)}.`);
15894
16854
  }
15895
16855
  const SAVE_NEW_BRANCH = "Save changes to a new branch";
15896
16856
  const DISCARD = "Discard all changes and clean up";
15897
16857
  const CANCEL = "Cancel";
15898
- const action = await selectPrompt(`${import_picocolors22.default.bold(currentBranch)} is stale but has local work. What would you like to do?`, [SAVE_NEW_BRANCH, DISCARD, CANCEL]);
16858
+ const action = await selectPrompt(`${import_picocolors23.default.bold(currentBranch)} is stale but has local work. What would you like to do?`, [SAVE_NEW_BRANCH, DISCARD, CANCEL]);
15899
16859
  if (action === CANCEL) {
15900
16860
  info("No changes made. You are still on your current branch.");
15901
16861
  return;
@@ -15917,7 +16877,7 @@ var update_default = defineCommand({
15917
16877
  error(`Failed to rename branch: ${renameResult.stderr}`);
15918
16878
  process.exit(1);
15919
16879
  }
15920
- success(`Renamed ${import_picocolors22.default.bold(currentBranch)} \u2192 ${import_picocolors22.default.bold(newBranchName)}`);
16880
+ success(`Renamed ${import_picocolors23.default.bold(currentBranch)} \u2192 ${import_picocolors23.default.bold(newBranchName)}`);
15921
16881
  await unsetUpstream();
15922
16882
  await fetchRemote(syncSource.remote);
15923
16883
  let rebaseResult2;
@@ -15929,11 +16889,11 @@ var update_default = defineCommand({
15929
16889
  }
15930
16890
  if (rebaseResult2.exitCode !== 0) {
15931
16891
  warn("Rebase encountered conflicts. Resolve them manually, then run:");
15932
- info(` ${import_picocolors22.default.bold("git rebase --continue")}`, "");
16892
+ info(` ${import_picocolors23.default.bold("git rebase --continue")}`, "");
15933
16893
  } else {
15934
- success(`Rebased ${import_picocolors22.default.bold(newBranchName)} onto ${import_picocolors22.default.bold(syncSource.ref)}.`);
16894
+ success(`Rebased ${import_picocolors23.default.bold(newBranchName)} onto ${import_picocolors23.default.bold(syncSource.ref)}.`);
15935
16895
  }
15936
- info(`All your changes are preserved. Run ${import_picocolors22.default.bold("cn submit")} when ready to create a new PR.`, "");
16896
+ info(`All your changes are preserved. Run ${import_picocolors23.default.bold("cn submit")} when ready to create a new PR.`, "");
15937
16897
  return;
15938
16898
  }
15939
16899
  warn("Discarding local changes...");
@@ -15952,24 +16912,24 @@ var update_default = defineCommand({
15952
16912
  process.exit(1);
15953
16913
  }
15954
16914
  await updateLocalBranch(baseBranch, syncSource.ref);
15955
- success(`Synced ${import_picocolors22.default.bold(baseBranch)} with ${import_picocolors22.default.bold(syncSource.ref)}.`);
15956
- info(`Deleting stale branch ${import_picocolors22.default.bold(currentBranch)}...`);
16915
+ success(`Synced ${import_picocolors23.default.bold(baseBranch)} with ${import_picocolors23.default.bold(syncSource.ref)}.`);
16916
+ info(`Deleting stale branch ${import_picocolors23.default.bold(currentBranch)}...`);
15957
16917
  await forceDeleteBranch(currentBranch);
15958
- success(`Deleted ${import_picocolors22.default.bold(currentBranch)}.`);
15959
- info(`Run ${import_picocolors22.default.bold("cn start")} to begin a new feature branch.`, "");
16918
+ success(`Deleted ${import_picocolors23.default.bold(currentBranch)}.`);
16919
+ info(`Run ${import_picocolors23.default.bold("cn start")} to begin a new feature branch.`, "");
15960
16920
  return;
15961
16921
  }
15962
- info(`Updating ${import_picocolors22.default.bold(currentBranch)} with latest ${import_picocolors22.default.bold(baseBranch)}...`);
16922
+ info(`Updating ${import_picocolors23.default.bold(currentBranch)} with latest ${import_picocolors23.default.bold(baseBranch)}...`);
15963
16923
  await fetchRemote(syncSource.remote);
15964
16924
  if (!await refExists(syncSource.ref)) {
15965
- error(`Remote ref ${import_picocolors22.default.bold(syncSource.ref)} does not exist.`);
16925
+ error(`Remote ref ${import_picocolors23.default.bold(syncSource.ref)} does not exist.`);
15966
16926
  error("Run `git fetch --all` and verify your remote configuration.");
15967
16927
  process.exit(1);
15968
16928
  }
15969
16929
  await updateLocalBranch(baseBranch, syncSource.ref);
15970
16930
  const rebaseStrategy = await determineRebaseStrategy(currentBranch, syncSource.ref);
15971
16931
  if (rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase) {
15972
- info(import_picocolors22.default.dim(`Using --onto rebase (branch was based on a different ref)`));
16932
+ info(import_picocolors23.default.dim(`Using --onto rebase (branch was based on a different ref)`));
15973
16933
  }
15974
16934
  const rebaseResult = rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase ? await rebaseOnto(syncSource.ref, rebaseStrategy.ontoOldBase) : await rebase(syncSource.ref);
15975
16935
  if (rebaseResult.exitCode !== 0) {
@@ -15983,7 +16943,7 @@ var update_default = defineCommand({
15983
16943
  let conflictDiff = "";
15984
16944
  for (const file of conflictFiles.slice(0, 3)) {
15985
16945
  try {
15986
- const content = readFileSync6(file, "utf-8");
16946
+ const content = readFileSync7(file, "utf-8");
15987
16947
  if (content.includes("<<<<<<<")) {
15988
16948
  conflictDiff += `
15989
16949
  --- ${file} ---
@@ -16000,10 +16960,10 @@ ${content.slice(0, 2000)}
16000
16960
  if (suggestion) {
16001
16961
  spinner.success("AI conflict guidance ready.");
16002
16962
  console.log(`
16003
- ${import_picocolors22.default.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
16004
- console.log(import_picocolors22.default.dim("\u2500".repeat(60)));
16963
+ ${import_picocolors23.default.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
16964
+ console.log(import_picocolors23.default.dim("\u2500".repeat(60)));
16005
16965
  console.log(suggestion);
16006
- console.log(import_picocolors22.default.dim("\u2500".repeat(60)));
16966
+ console.log(import_picocolors23.default.dim("\u2500".repeat(60)));
16007
16967
  console.log();
16008
16968
  } else {
16009
16969
  spinner.fail("AI could not analyze the conflicts.");
@@ -16011,21 +16971,21 @@ ${import_picocolors22.default.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance
16011
16971
  }
16012
16972
  }
16013
16973
  }
16014
- console.log(import_picocolors22.default.bold("To resolve:"));
16974
+ console.log(import_picocolors23.default.bold("To resolve:"));
16015
16975
  console.log(` 1. Fix conflicts in the affected files`);
16016
- console.log(` 2. ${import_picocolors22.default.cyan("git add <resolved-files>")}`);
16017
- console.log(` 3. ${import_picocolors22.default.cyan("git rebase --continue")}`);
16976
+ console.log(` 2. ${import_picocolors23.default.cyan("git add <resolved-files>")}`);
16977
+ console.log(` 3. ${import_picocolors23.default.cyan("git rebase --continue")}`);
16018
16978
  console.log();
16019
- console.log(` Or abort: ${import_picocolors22.default.cyan("git rebase --abort")}`);
16979
+ console.log(` Or abort: ${import_picocolors23.default.cyan("git rebase --abort")}`);
16020
16980
  process.exit(1);
16021
16981
  }
16022
- success(`${import_picocolors22.default.bold(currentBranch)} has been rebased onto latest ${import_picocolors22.default.bold(baseBranch)}`);
16982
+ success(`${import_picocolors23.default.bold(currentBranch)} has been rebased onto latest ${import_picocolors23.default.bold(baseBranch)}`);
16023
16983
  }
16024
16984
  });
16025
16985
 
16026
16986
  // src/commands/validate.ts
16027
- import { readFileSync as readFileSync7 } from "fs";
16028
- var import_picocolors23 = __toESM(require_picocolors(), 1);
16987
+ import { readFileSync as readFileSync8 } from "fs";
16988
+ var import_picocolors24 = __toESM(require_picocolors(), 1);
16029
16989
  var validate_default = defineCommand({
16030
16990
  meta: {
16031
16991
  name: "validate",
@@ -16054,7 +17014,7 @@ var validate_default = defineCommand({
16054
17014
  info('Commit convention is set to "none". All messages are accepted.');
16055
17015
  process.exit(0);
16056
17016
  }
16057
- const message = args.file ? readFileSync7(args.file, "utf-8").split(/\r?\n/, 1)[0] ?? "" : args.message;
17017
+ const message = args.file ? readFileSync8(args.file, "utf-8").split(/\r?\n/, 1)[0] ?? "" : args.message;
16058
17018
  if (!message) {
16059
17019
  error("No commit message provided. Pass a message or use --file <path>.");
16060
17020
  process.exit(1);
@@ -16065,14 +17025,14 @@ var validate_default = defineCommand({
16065
17025
  }
16066
17026
  const errors = getValidationError(convention);
16067
17027
  for (const line of errors) {
16068
- console.error(import_picocolors23.default.red(` \u2717 ${line}`));
17028
+ console.error(import_picocolors24.default.red(` \u2717 ${line}`));
16069
17029
  }
16070
17030
  process.exit(1);
16071
17031
  }
16072
17032
  });
16073
17033
 
16074
17034
  // src/ui/banner.ts
16075
- var import_picocolors24 = __toESM(require_picocolors(), 1);
17035
+ var import_picocolors25 = __toESM(require_picocolors(), 1);
16076
17036
 
16077
17037
  // src/data/announcements.json
16078
17038
  var announcements_default = [
@@ -16133,9 +17093,9 @@ function getAuthor() {
16133
17093
  return typeof package_default.author === "string" ? package_default.author : "unknown";
16134
17094
  }
16135
17095
  function showBanner(variant = "small") {
16136
- console.log(import_picocolors24.default.cyan(`
17096
+ console.log(import_picocolors25.default.cyan(`
16137
17097
  ${LOGO}`));
16138
- console.log(` ${import_picocolors24.default.dim(`v${getVersion()}`)} ${import_picocolors24.default.dim("\u2014")} ${import_picocolors24.default.dim(`Built by ${getAuthor()}`)}`);
17098
+ console.log(` ${import_picocolors25.default.dim(`v${getVersion()}`)} ${import_picocolors25.default.dim("\u2014")} ${import_picocolors25.default.dim(`Built by ${getAuthor()}`)}`);
16139
17099
  const announcements = getActiveAnnouncements();
16140
17100
  if (announcements.length > 0) {
16141
17101
  console.log();
@@ -16144,27 +17104,27 @@ ${LOGO}`));
16144
17104
  if (variant === "big") {
16145
17105
  const panelLines = [
16146
17106
  {
16147
- label: import_picocolors24.default.bold(import_picocolors24.default.cyan("Getting Started")),
17107
+ label: import_picocolors25.default.bold(import_picocolors25.default.cyan("Getting Started")),
16148
17108
  rawLabel: "Getting Started",
16149
17109
  value: "",
16150
17110
  rawValue: ""
16151
17111
  },
16152
17112
  {
16153
- label: import_picocolors24.default.cyan("cn setup"),
17113
+ label: import_picocolors25.default.cyan("cn setup"),
16154
17114
  rawLabel: "cn setup",
16155
- value: import_picocolors24.default.dim("configure workflow, remotes, and defaults"),
17115
+ value: import_picocolors25.default.dim("configure workflow, remotes, and defaults"),
16156
17116
  rawValue: "configure workflow, remotes, and defaults"
16157
17117
  },
16158
17118
  {
16159
- label: import_picocolors24.default.cyan("cn doctor"),
17119
+ label: import_picocolors25.default.cyan("cn doctor"),
16160
17120
  rawLabel: "cn doctor",
16161
- value: import_picocolors24.default.dim("verify your environment before doing any work"),
17121
+ value: import_picocolors25.default.dim("verify your environment before doing any work"),
16162
17122
  rawValue: "verify your environment before doing any work"
16163
17123
  },
16164
17124
  {
16165
- label: import_picocolors24.default.cyan("cn start"),
17125
+ label: import_picocolors25.default.cyan("cn start"),
16166
17126
  rawLabel: "cn start",
16167
- value: import_picocolors24.default.dim("create a branch and begin the next task"),
17127
+ value: import_picocolors25.default.dim("create a branch and begin the next task"),
16168
17128
  rawValue: "create a branch and begin the next task"
16169
17129
  },
16170
17130
  {
@@ -16174,13 +17134,13 @@ ${LOGO}`));
16174
17134
  rawValue: ""
16175
17135
  },
16176
17136
  {
16177
- label: import_picocolors24.default.bold(import_picocolors24.default.cyan("Workflow")),
17137
+ label: import_picocolors25.default.bold(import_picocolors25.default.cyan("Workflow")),
16178
17138
  rawLabel: "Workflow",
16179
17139
  value: "",
16180
17140
  rawValue: ""
16181
17141
  },
16182
17142
  {
16183
- label: import_picocolors24.default.dim("cn setup \u2192 cn commit \u2192 cn update \u2192 cn submit"),
17143
+ label: import_picocolors25.default.dim("cn setup \u2192 cn commit \u2192 cn update \u2192 cn submit"),
16184
17144
  rawLabel: "cn setup \u2192 cn commit \u2192 cn update \u2192 cn submit",
16185
17145
  value: "",
16186
17146
  rawValue: ""
@@ -16205,22 +17165,22 @@ ${LOGO}`));
16205
17165
  return Math.max(max, lineLength);
16206
17166
  }, 0));
16207
17167
  console.log();
16208
- console.log(` ${import_picocolors24.default.dim(`\u250C${"\u2500".repeat(contentWidth + 2)}\u2510`)}`);
17168
+ console.log(` ${import_picocolors25.default.dim(`\u250C${"\u2500".repeat(contentWidth + 2)}\u2510`)}`);
16209
17169
  for (const line of rows) {
16210
17170
  if (!line.rawLabel && !line.rawValue) {
16211
- console.log(` ${import_picocolors24.default.dim("\u2502")} ${" ".repeat(contentWidth)} ${import_picocolors24.default.dim("\u2502")}`);
17171
+ console.log(` ${import_picocolors25.default.dim("\u2502")} ${" ".repeat(contentWidth)} ${import_picocolors25.default.dim("\u2502")}`);
16212
17172
  continue;
16213
17173
  }
16214
17174
  const left = line.rawValue ? `${line.label}${" ".repeat(Math.max(0, labelWidth - line.rawLabel.length + 2))}` : line.label;
16215
- const value = line.rawValue ? import_picocolors24.default.dim(line.rawValue) : "";
17175
+ const value = line.rawValue ? import_picocolors25.default.dim(line.rawValue) : "";
16216
17176
  const rawLength = line.rawValue ? labelWidth + 2 + line.rawValue.length : line.rawLabel.length;
16217
17177
  const trailing = " ".repeat(Math.max(0, contentWidth - rawLength));
16218
- console.log(` ${import_picocolors24.default.dim("\u2502")} ${left}${value}${trailing} ${import_picocolors24.default.dim("\u2502")}`);
17178
+ console.log(` ${import_picocolors25.default.dim("\u2502")} ${left}${value}${trailing} ${import_picocolors25.default.dim("\u2502")}`);
16219
17179
  }
16220
- console.log(` ${import_picocolors24.default.dim(`\u2514${"\u2500".repeat(contentWidth + 2)}\u2518`)}`);
17180
+ console.log(` ${import_picocolors25.default.dim(`\u2514${"\u2500".repeat(contentWidth + 2)}\u2518`)}`);
16221
17181
  console.log();
16222
- console.log(` ${import_picocolors24.default.dim("Star or contribute:")} ${import_picocolors24.default.dim(linkify("gh.waren.build/contribute-now", "https://gh.waren.build/contribute-now"))}`);
16223
- console.log(` ${import_picocolors24.default.dim("Sponsor:")} ${import_picocolors24.default.dim(linkify("warengonzaga.com/sponsor", "https://warengonzaga.com/sponsor"))}`);
17182
+ console.log(` ${import_picocolors25.default.dim("Star or contribute:")} ${import_picocolors25.default.dim(linkify("gh.waren.build/contribute-now", "https://gh.waren.build/contribute-now"))}`);
17183
+ console.log(` ${import_picocolors25.default.dim("Sponsor:")} ${import_picocolors25.default.dim(linkify("warengonzaga.com/sponsor", "https://warengonzaga.com/sponsor"))}`);
16224
17184
  }
16225
17185
  console.log();
16226
17186
  }
@@ -16252,7 +17212,7 @@ function renderAnnouncementBanner(announcement) {
16252
17212
  console.log(` ${tone.border(`\u250C${"\u2500".repeat(rawWidth + 2)}\u2510`)}`);
16253
17213
  for (const line of lines) {
16254
17214
  const trailing = " ".repeat(Math.max(0, rawWidth - line.length));
16255
- const content = line === title ? tone.title(line) : import_picocolors24.default.dim(line);
17215
+ const content = line === title ? tone.title(line) : import_picocolors25.default.dim(line);
16256
17216
  console.log(` ${tone.border("\u2502")} ${content}${trailing} ${tone.border("\u2502")}`);
16257
17217
  }
16258
17218
  console.log(` ${tone.border(`\u2514${"\u2500".repeat(rawWidth + 2)}\u2518`)}`);
@@ -16288,20 +17248,20 @@ function getAnnouncementTone(kind) {
16288
17248
  case "info":
16289
17249
  return {
16290
17250
  emoji: "\u2139",
16291
- border: import_picocolors24.default.blue,
16292
- title: (value) => import_picocolors24.default.bold(import_picocolors24.default.blue(value))
17251
+ border: import_picocolors25.default.blue,
17252
+ title: (value) => import_picocolors25.default.bold(import_picocolors25.default.blue(value))
16293
17253
  };
16294
17254
  case "warning":
16295
17255
  return {
16296
17256
  emoji: "\uD83D\uDEA8",
16297
- border: import_picocolors24.default.red,
16298
- title: (value) => import_picocolors24.default.bold(import_picocolors24.default.red(value))
17257
+ border: import_picocolors25.default.red,
17258
+ title: (value) => import_picocolors25.default.bold(import_picocolors25.default.red(value))
16299
17259
  };
16300
17260
  default:
16301
17261
  return {
16302
17262
  emoji: "\u26A0",
16303
- border: import_picocolors24.default.yellow,
16304
- title: (value) => import_picocolors24.default.bold(import_picocolors24.default.yellow(value))
17263
+ border: import_picocolors25.default.yellow,
17264
+ title: (value) => import_picocolors25.default.bold(import_picocolors25.default.yellow(value))
16305
17265
  };
16306
17266
  }
16307
17267
  }
@@ -16340,7 +17300,8 @@ if (!isVersion) {
16340
17300
  "branch",
16341
17301
  "hook",
16342
17302
  "validate",
16343
- "doctor"
17303
+ "doctor",
17304
+ "label"
16344
17305
  ];
16345
17306
  const isHelp = process.argv.includes("--help") || process.argv.includes("-h");
16346
17307
  const hasSubCommand = subCommands.some((cmd) => process.argv.includes(cmd));
@@ -16377,7 +17338,8 @@ var main = defineCommand({
16377
17338
  log: log_default,
16378
17339
  hook: hook_default,
16379
17340
  validate: validate_default,
16380
- doctor: doctor_default
17341
+ doctor: doctor_default,
17342
+ label: label_default
16381
17343
  },
16382
17344
  run({ args }) {
16383
17345
  if (args.version) {