contribute-now 0.8.0-dev.7db6dea → 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.
- package/README.md +51 -5
- package/dist/cli.js +2070 -792
- 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
|
-
|
|
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
|
-
var VALID_AI_PROVIDERS = ["copilot", "ollama-cloud"];
|
|
7210
|
-
function isAIEnabled(config, cliNoAI = false) {
|
|
7211
|
-
|
|
7485
|
+
var VALID_AI_PROVIDERS = ["copilot", "ollama-cloud", "openrouter"];
|
|
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,18 +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";
|
|
10948
|
+
function getSecretsStorePath(baseDir = homedir2()) {
|
|
10628
10949
|
return resolve4(baseDir, CONTRIBUTE_NOW_SECRETS_DIRNAME, CONTRIBUTE_NOW_SECRETS_STORE_DIRNAME);
|
|
10629
10950
|
}
|
|
10630
|
-
function getSecretsFilePath(baseDir =
|
|
10951
|
+
function getSecretsFilePath(baseDir = homedir2()) {
|
|
10631
10952
|
return join5(getSecretsStorePath(baseDir), "store.json");
|
|
10632
10953
|
}
|
|
10633
|
-
function readSecretsStore(baseDir =
|
|
10954
|
+
function readSecretsStore(baseDir = homedir2()) {
|
|
10634
10955
|
const filePath = getSecretsFilePath(baseDir);
|
|
10635
10956
|
if (!existsSync5(filePath)) {
|
|
10636
10957
|
return null;
|
|
@@ -10642,7 +10963,7 @@ function readSecretsStore(baseDir = homedir()) {
|
|
|
10642
10963
|
return null;
|
|
10643
10964
|
}
|
|
10644
10965
|
}
|
|
10645
|
-
function writeSecretsStore(store, baseDir =
|
|
10966
|
+
function writeSecretsStore(store, baseDir = homedir2()) {
|
|
10646
10967
|
const storePath = getSecretsStorePath(baseDir);
|
|
10647
10968
|
const filePath = getSecretsFilePath(baseDir);
|
|
10648
10969
|
mkdirSync4(storePath, { recursive: true, mode: 448 });
|
|
@@ -10656,23 +10977,23 @@ function writeSecretsStore(store, baseDir = homedir()) {
|
|
|
10656
10977
|
chmodSync(filePath, 384);
|
|
10657
10978
|
} catch {}
|
|
10658
10979
|
}
|
|
10659
|
-
function hasSecretsStore(baseDir =
|
|
10980
|
+
function hasSecretsStore(baseDir = homedir2()) {
|
|
10660
10981
|
return existsSync5(getSecretsFilePath(baseDir));
|
|
10661
10982
|
}
|
|
10662
|
-
async function hasOllamaCloudApiKey(baseDir =
|
|
10983
|
+
async function hasOllamaCloudApiKey(baseDir = homedir2()) {
|
|
10663
10984
|
return typeof readSecretsStore(baseDir)?.[OLLAMA_CLOUD_API_KEY] === "string";
|
|
10664
10985
|
}
|
|
10665
|
-
async function getOllamaCloudApiKey(baseDir =
|
|
10986
|
+
async function getOllamaCloudApiKey(baseDir = homedir2()) {
|
|
10666
10987
|
return readSecretsStore(baseDir)?.[OLLAMA_CLOUD_API_KEY] ?? null;
|
|
10667
10988
|
}
|
|
10668
|
-
async function setOllamaCloudApiKey(value, baseDir =
|
|
10989
|
+
async function setOllamaCloudApiKey(value, baseDir = homedir2()) {
|
|
10669
10990
|
const existingStore = readSecretsStore(baseDir) ?? {};
|
|
10670
10991
|
writeSecretsStore({
|
|
10671
10992
|
...existingStore,
|
|
10672
10993
|
[OLLAMA_CLOUD_API_KEY]: value
|
|
10673
10994
|
}, baseDir);
|
|
10674
10995
|
}
|
|
10675
|
-
async function deleteOllamaCloudApiKey(baseDir =
|
|
10996
|
+
async function deleteOllamaCloudApiKey(baseDir = homedir2()) {
|
|
10676
10997
|
const existingStore = readSecretsStore(baseDir);
|
|
10677
10998
|
if (!existingStore || !(OLLAMA_CLOUD_API_KEY in existingStore)) {
|
|
10678
10999
|
return false;
|
|
@@ -10689,6 +11010,36 @@ async function deleteOllamaCloudApiKey(baseDir = homedir()) {
|
|
|
10689
11010
|
writeSecretsStore(nextStore, baseDir);
|
|
10690
11011
|
return true;
|
|
10691
11012
|
}
|
|
11013
|
+
async function hasOpenRouterApiKey(baseDir = homedir2()) {
|
|
11014
|
+
return typeof readSecretsStore(baseDir)?.[OPENROUTER_API_KEY] === "string";
|
|
11015
|
+
}
|
|
11016
|
+
async function getOpenRouterApiKey(baseDir = homedir2()) {
|
|
11017
|
+
return readSecretsStore(baseDir)?.[OPENROUTER_API_KEY] ?? null;
|
|
11018
|
+
}
|
|
11019
|
+
async function setOpenRouterApiKey(value, baseDir = homedir2()) {
|
|
11020
|
+
const existingStore = readSecretsStore(baseDir) ?? {};
|
|
11021
|
+
writeSecretsStore({
|
|
11022
|
+
...existingStore,
|
|
11023
|
+
[OPENROUTER_API_KEY]: value
|
|
11024
|
+
}, baseDir);
|
|
11025
|
+
}
|
|
11026
|
+
async function deleteOpenRouterApiKey(baseDir = homedir2()) {
|
|
11027
|
+
const existingStore = readSecretsStore(baseDir);
|
|
11028
|
+
if (!existingStore || !(OPENROUTER_API_KEY in existingStore)) {
|
|
11029
|
+
return false;
|
|
11030
|
+
}
|
|
11031
|
+
const nextStore = { ...existingStore };
|
|
11032
|
+
delete nextStore[OPENROUTER_API_KEY];
|
|
11033
|
+
if (Object.keys(nextStore).length === 0) {
|
|
11034
|
+
try {
|
|
11035
|
+
rmSync(getSecretsFilePath(baseDir), { force: true });
|
|
11036
|
+
rmSync(getSecretsStorePath(baseDir), { recursive: true, force: true });
|
|
11037
|
+
} catch {}
|
|
11038
|
+
return true;
|
|
11039
|
+
}
|
|
11040
|
+
writeSecretsStore(nextStore, baseDir);
|
|
11041
|
+
return true;
|
|
11042
|
+
}
|
|
10692
11043
|
|
|
10693
11044
|
// src/utils/copilot.ts
|
|
10694
11045
|
var CONVENTIONAL_COMMIT_SYSTEM_PROMPT = `Git commit message generator. Format: <type>[!][(<scope>)]: <description>
|
|
@@ -10756,21 +11107,50 @@ Rules: title concise present tense, describes the PR theme not individual commit
|
|
|
10756
11107
|
var CONFLICT_RESOLUTION_SYSTEM_PROMPT = `Git merge conflict advisor. Explain each side, suggest resolution strategy. Never auto-resolve \u2014 guidance only. Be concise and actionable.`;
|
|
10757
11108
|
var DEFAULT_OLLAMA_CLOUD_MODEL = "gpt-oss:120b";
|
|
10758
11109
|
var DEFAULT_OLLAMA_CLOUD_HOST = "https://ollama.com/v1";
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
|
|
10773
|
-
|
|
11110
|
+
var DEFAULT_OPENROUTER_MODEL = "openai/gpt-4o-mini";
|
|
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
|
+
}
|
|
11137
|
+
function prioritizeOllamaCloudModels(models, preferredModel = DEFAULT_OLLAMA_CLOUD_MODEL) {
|
|
11138
|
+
const uniqueModels = [...new Set(models.map((model) => model.trim()).filter(Boolean))];
|
|
11139
|
+
const sortedModels = [...uniqueModels].sort((left, right) => left.localeCompare(right));
|
|
11140
|
+
return sortedModels.includes(preferredModel) ? [preferredModel, ...sortedModels.filter((model) => model !== preferredModel)] : sortedModels;
|
|
11141
|
+
}
|
|
11142
|
+
function extractOllamaCloudModelIds(payload) {
|
|
11143
|
+
const records = typeof payload === "object" && payload !== null ? Array.isArray(payload.data) ? payload.data : Array.isArray(payload.models) ? payload.models : [] : [];
|
|
11144
|
+
return [
|
|
11145
|
+
...new Set(records.map(getOllamaCloudModelId).filter((id) => id !== null))
|
|
11146
|
+
].sort((left, right) => left.localeCompare(right));
|
|
11147
|
+
}
|
|
11148
|
+
function getOllamaCloudModelId(record) {
|
|
11149
|
+
if (typeof record !== "object" || record === null) {
|
|
11150
|
+
return null;
|
|
11151
|
+
}
|
|
11152
|
+
const candidate = typeof record.id === "string" ? record.id : typeof record.name === "string" ? record.name : null;
|
|
11153
|
+
const normalized = candidate?.trim();
|
|
10774
11154
|
return normalized ? normalized : null;
|
|
10775
11155
|
}
|
|
10776
11156
|
async function fetchOllamaCloudModels(apiKey, host) {
|
|
@@ -10792,21 +11172,44 @@ function normalizeOllamaCloudHost(host) {
|
|
|
10792
11172
|
const trimmed = (host?.trim() || DEFAULT_OLLAMA_CLOUD_HOST).replace(/\/+$/, "");
|
|
10793
11173
|
return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
|
|
10794
11174
|
}
|
|
10795
|
-
function
|
|
10796
|
-
const
|
|
10797
|
-
|
|
10798
|
-
|
|
10799
|
-
|
|
10800
|
-
|
|
10801
|
-
|
|
10802
|
-
|
|
10803
|
-
|
|
10804
|
-
};
|
|
11175
|
+
function extractOpenRouterModelIds(payload) {
|
|
11176
|
+
const records = typeof payload === "object" && payload !== null ? Array.isArray(payload.data) ? payload.data : [] : [];
|
|
11177
|
+
return [
|
|
11178
|
+
...new Set(records.map(getOpenRouterModelId).filter((id) => id !== null))
|
|
11179
|
+
].sort((left, right) => left.localeCompare(right));
|
|
11180
|
+
}
|
|
11181
|
+
function getOpenRouterModelId(record) {
|
|
11182
|
+
if (typeof record !== "object" || record === null) {
|
|
11183
|
+
return null;
|
|
10805
11184
|
}
|
|
10806
|
-
|
|
10807
|
-
|
|
10808
|
-
|
|
10809
|
-
|
|
11185
|
+
const candidate = typeof record.id === "string" ? record.id : null;
|
|
11186
|
+
const normalized = candidate?.trim();
|
|
11187
|
+
return normalized ? normalized : null;
|
|
11188
|
+
}
|
|
11189
|
+
async function fetchOpenRouterModels(apiKey) {
|
|
11190
|
+
const response = await fetch(`${DEFAULT_OPENROUTER_HOST}/models`, {
|
|
11191
|
+
headers: {
|
|
11192
|
+
Accept: "application/json",
|
|
11193
|
+
Authorization: `Bearer ${apiKey}`
|
|
11194
|
+
}
|
|
11195
|
+
});
|
|
11196
|
+
if (!response.ok) {
|
|
11197
|
+
if (response.status === 401 || response.status === 403) {
|
|
11198
|
+
throw new Error("OpenRouter authentication failed");
|
|
11199
|
+
}
|
|
11200
|
+
throw new Error(`OpenRouter model lookup failed (${response.status} ${response.statusText})`);
|
|
11201
|
+
}
|
|
11202
|
+
return extractOpenRouterModelIds(await response.json());
|
|
11203
|
+
}
|
|
11204
|
+
function prioritizeOpenRouterModels(models, preferredModel = DEFAULT_OPENROUTER_MODEL) {
|
|
11205
|
+
const uniqueModels = [...new Set(models.map((model) => model.trim()).filter(Boolean))];
|
|
11206
|
+
const sortedModels = [...uniqueModels].sort((left, right) => left.localeCompare(right));
|
|
11207
|
+
return sortedModels.includes(preferredModel) ? [preferredModel, ...sortedModels.filter((model) => model !== preferredModel)] : sortedModels;
|
|
11208
|
+
}
|
|
11209
|
+
function resolveAIConfig(config) {
|
|
11210
|
+
const repoConfig = config ?? readConfig();
|
|
11211
|
+
const globalConfig = readGlobalConfig();
|
|
11212
|
+
return resolveAIConfigFromSources(repoConfig, globalConfig);
|
|
10810
11213
|
}
|
|
10811
11214
|
function suppressSubprocessWarnings() {
|
|
10812
11215
|
process.env.NODE_NO_WARNINGS = "1";
|
|
@@ -10996,6 +11399,28 @@ async function checkCopilotAvailable2() {
|
|
|
10996
11399
|
return `Could not reach Ollama Cloud API: ${msg}`;
|
|
10997
11400
|
}
|
|
10998
11401
|
}
|
|
11402
|
+
if (aiConfig.provider === "openrouter") {
|
|
11403
|
+
if (!await hasOpenRouterApiKey()) {
|
|
11404
|
+
return "OpenRouter API key not found. Run `cn setup` to save it.";
|
|
11405
|
+
}
|
|
11406
|
+
try {
|
|
11407
|
+
const apiKey = await getOpenRouterApiKey();
|
|
11408
|
+
if (!apiKey) {
|
|
11409
|
+
return "OpenRouter API key not found. Run `cn setup` to save it.";
|
|
11410
|
+
}
|
|
11411
|
+
await fetchOpenRouterModels(apiKey);
|
|
11412
|
+
return null;
|
|
11413
|
+
} catch (err) {
|
|
11414
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11415
|
+
if (msg === "OpenRouter authentication failed") {
|
|
11416
|
+
return "OpenRouter authentication failed. Update your saved API key with `cn setup`.";
|
|
11417
|
+
}
|
|
11418
|
+
if (msg.startsWith("OpenRouter model lookup failed")) {
|
|
11419
|
+
return msg.replace("model lookup", "health check");
|
|
11420
|
+
}
|
|
11421
|
+
return `Could not reach OpenRouter API: ${msg}`;
|
|
11422
|
+
}
|
|
11423
|
+
}
|
|
10999
11424
|
try {
|
|
11000
11425
|
const client = await getManagedClient();
|
|
11001
11426
|
try {
|
|
@@ -11097,11 +11522,54 @@ async function callOllamaCloud(systemMessage, userMessage, model, timeoutMs = CO
|
|
|
11097
11522
|
clearTimeout(timer);
|
|
11098
11523
|
}
|
|
11099
11524
|
}
|
|
11525
|
+
async function callOpenRouter(systemMessage, userMessage, model, timeoutMs = COPILOT_TIMEOUT_MS) {
|
|
11526
|
+
const aiConfig = resolveAIConfig();
|
|
11527
|
+
const apiKey = await getOpenRouterApiKey();
|
|
11528
|
+
if (!apiKey) {
|
|
11529
|
+
throw new Error("OpenRouter API key is not configured");
|
|
11530
|
+
}
|
|
11531
|
+
const controller = new AbortController;
|
|
11532
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
11533
|
+
try {
|
|
11534
|
+
const response = await fetch(`${DEFAULT_OPENROUTER_HOST}/chat/completions`, {
|
|
11535
|
+
method: "POST",
|
|
11536
|
+
headers: {
|
|
11537
|
+
Authorization: `Bearer ${apiKey}`,
|
|
11538
|
+
"Content-Type": "application/json",
|
|
11539
|
+
"HTTP-Referer": "https://github.com/warengonzaga/contribute-now",
|
|
11540
|
+
"X-Title": "contribute-now"
|
|
11541
|
+
},
|
|
11542
|
+
body: JSON.stringify({
|
|
11543
|
+
model: model?.trim() || aiConfig.model || DEFAULT_OPENROUTER_MODEL,
|
|
11544
|
+
messages: [
|
|
11545
|
+
{ role: "system", content: systemMessage },
|
|
11546
|
+
{ role: "user", content: userMessage }
|
|
11547
|
+
],
|
|
11548
|
+
stream: false
|
|
11549
|
+
}),
|
|
11550
|
+
signal: controller.signal
|
|
11551
|
+
});
|
|
11552
|
+
if (!response.ok) {
|
|
11553
|
+
const body = await response.text();
|
|
11554
|
+
if (response.status === 401 || response.status === 403) {
|
|
11555
|
+
throw new Error("OpenRouter authentication failed");
|
|
11556
|
+
}
|
|
11557
|
+
throw new Error(`OpenRouter request failed (${response.status} ${response.statusText}): ${body.slice(0, 200)}`);
|
|
11558
|
+
}
|
|
11559
|
+
const data = await response.json();
|
|
11560
|
+
return data.choices?.[0]?.message?.content?.trim() || null;
|
|
11561
|
+
} finally {
|
|
11562
|
+
clearTimeout(timer);
|
|
11563
|
+
}
|
|
11564
|
+
}
|
|
11100
11565
|
async function callAI(systemMessage, userMessage, model, timeoutMs = COPILOT_TIMEOUT_MS) {
|
|
11101
11566
|
const aiConfig = resolveAIConfig();
|
|
11102
11567
|
if (aiConfig.provider === "ollama-cloud") {
|
|
11103
11568
|
return callOllamaCloud(systemMessage, userMessage, model, timeoutMs);
|
|
11104
11569
|
}
|
|
11570
|
+
if (aiConfig.provider === "openrouter") {
|
|
11571
|
+
return callOpenRouter(systemMessage, userMessage, model, timeoutMs);
|
|
11572
|
+
}
|
|
11105
11573
|
return callCopilot(systemMessage, userMessage, model, timeoutMs);
|
|
11106
11574
|
}
|
|
11107
11575
|
function getCommitSystemPrompt(convention) {
|
|
@@ -11651,145 +12119,8 @@ async function promptForBranchName(options) {
|
|
|
11651
12119
|
}
|
|
11652
12120
|
}
|
|
11653
12121
|
|
|
11654
|
-
// src/utils/gh.ts
|
|
11655
|
-
import { execFile as execFileCb2 } from "child_process";
|
|
11656
|
-
function run2(args) {
|
|
11657
|
-
return new Promise((resolve5) => {
|
|
11658
|
-
execFileCb2("gh", args, (error2, stdout2, stderr) => {
|
|
11659
|
-
resolve5({
|
|
11660
|
-
exitCode: error2 ? error2.code === "ENOENT" ? 127 : error2.status ?? 1 : 0,
|
|
11661
|
-
stdout: stdout2 ?? "",
|
|
11662
|
-
stderr: stderr ?? ""
|
|
11663
|
-
});
|
|
11664
|
-
});
|
|
11665
|
-
});
|
|
11666
|
-
}
|
|
11667
|
-
async function checkGhInstalled() {
|
|
11668
|
-
try {
|
|
11669
|
-
const { exitCode } = await run2(["--version"]);
|
|
11670
|
-
return exitCode === 0;
|
|
11671
|
-
} catch {
|
|
11672
|
-
return false;
|
|
11673
|
-
}
|
|
11674
|
-
}
|
|
11675
|
-
async function checkGhAuth() {
|
|
11676
|
-
try {
|
|
11677
|
-
const { exitCode } = await run2(["auth", "status"]);
|
|
11678
|
-
return exitCode === 0;
|
|
11679
|
-
} catch {
|
|
11680
|
-
return false;
|
|
11681
|
-
}
|
|
11682
|
-
}
|
|
11683
|
-
var SAFE_SLUG = /^[\w.-]+$/;
|
|
11684
|
-
async function checkRepoPermissions(owner, repo) {
|
|
11685
|
-
if (!SAFE_SLUG.test(owner) || !SAFE_SLUG.test(repo))
|
|
11686
|
-
return null;
|
|
11687
|
-
const { exitCode, stdout: stdout2 } = await run2(["api", `repos/${owner}/${repo}`, "--jq", ".permissions"]);
|
|
11688
|
-
if (exitCode !== 0)
|
|
11689
|
-
return null;
|
|
11690
|
-
try {
|
|
11691
|
-
return JSON.parse(stdout2.trim());
|
|
11692
|
-
} catch {
|
|
11693
|
-
return null;
|
|
11694
|
-
}
|
|
11695
|
-
}
|
|
11696
|
-
async function isRepoFork() {
|
|
11697
|
-
const { exitCode, stdout: stdout2 } = await run2(["repo", "view", "--json", "isFork", "-q", ".isFork"]);
|
|
11698
|
-
if (exitCode !== 0)
|
|
11699
|
-
return null;
|
|
11700
|
-
const val = stdout2.trim();
|
|
11701
|
-
if (val === "true")
|
|
11702
|
-
return true;
|
|
11703
|
-
if (val === "false")
|
|
11704
|
-
return false;
|
|
11705
|
-
return null;
|
|
11706
|
-
}
|
|
11707
|
-
async function getCurrentRepoInfo() {
|
|
11708
|
-
const { exitCode, stdout: stdout2 } = await run2([
|
|
11709
|
-
"repo",
|
|
11710
|
-
"view",
|
|
11711
|
-
"--json",
|
|
11712
|
-
"nameWithOwner",
|
|
11713
|
-
"-q",
|
|
11714
|
-
".nameWithOwner"
|
|
11715
|
-
]);
|
|
11716
|
-
if (exitCode !== 0)
|
|
11717
|
-
return null;
|
|
11718
|
-
const nameWithOwner = stdout2.trim();
|
|
11719
|
-
if (!nameWithOwner)
|
|
11720
|
-
return null;
|
|
11721
|
-
const [owner, repo] = nameWithOwner.split("/");
|
|
11722
|
-
if (!owner || !repo)
|
|
11723
|
-
return null;
|
|
11724
|
-
return { owner, repo };
|
|
11725
|
-
}
|
|
11726
|
-
async function createPR(options) {
|
|
11727
|
-
const args = [
|
|
11728
|
-
"pr",
|
|
11729
|
-
"create",
|
|
11730
|
-
"--base",
|
|
11731
|
-
options.base,
|
|
11732
|
-
"--title",
|
|
11733
|
-
options.title,
|
|
11734
|
-
"--body",
|
|
11735
|
-
options.body
|
|
11736
|
-
];
|
|
11737
|
-
if (options.draft)
|
|
11738
|
-
args.push("--draft");
|
|
11739
|
-
return run2(args);
|
|
11740
|
-
}
|
|
11741
|
-
async function createPRFill(base, draft) {
|
|
11742
|
-
const args = ["pr", "create", "--base", base, "--fill"];
|
|
11743
|
-
if (draft)
|
|
11744
|
-
args.push("--draft");
|
|
11745
|
-
return run2(args);
|
|
11746
|
-
}
|
|
11747
|
-
async function getPRForBranch(headBranch) {
|
|
11748
|
-
const { exitCode, stdout: stdout2 } = await run2([
|
|
11749
|
-
"pr",
|
|
11750
|
-
"list",
|
|
11751
|
-
"--head",
|
|
11752
|
-
headBranch,
|
|
11753
|
-
"--state",
|
|
11754
|
-
"open",
|
|
11755
|
-
"--json",
|
|
11756
|
-
"number,url,title,state",
|
|
11757
|
-
"--limit",
|
|
11758
|
-
"1"
|
|
11759
|
-
]);
|
|
11760
|
-
if (exitCode !== 0)
|
|
11761
|
-
return null;
|
|
11762
|
-
try {
|
|
11763
|
-
const prs = JSON.parse(stdout2.trim());
|
|
11764
|
-
return prs.length > 0 ? prs[0] : null;
|
|
11765
|
-
} catch {
|
|
11766
|
-
return null;
|
|
11767
|
-
}
|
|
11768
|
-
}
|
|
11769
|
-
async function getMergedPRForBranch(headBranch) {
|
|
11770
|
-
const { exitCode, stdout: stdout2 } = await run2([
|
|
11771
|
-
"pr",
|
|
11772
|
-
"list",
|
|
11773
|
-
"--head",
|
|
11774
|
-
headBranch,
|
|
11775
|
-
"--state",
|
|
11776
|
-
"merged",
|
|
11777
|
-
"--json",
|
|
11778
|
-
"number,url,title,state",
|
|
11779
|
-
"--limit",
|
|
11780
|
-
"1"
|
|
11781
|
-
]);
|
|
11782
|
-
if (exitCode !== 0)
|
|
11783
|
-
return null;
|
|
11784
|
-
try {
|
|
11785
|
-
const prs = JSON.parse(stdout2.trim());
|
|
11786
|
-
return prs.length > 0 ? prs[0] : null;
|
|
11787
|
-
} catch {
|
|
11788
|
-
return null;
|
|
11789
|
-
}
|
|
11790
|
-
}
|
|
11791
|
-
|
|
11792
12122
|
// src/commands/clean.ts
|
|
12123
|
+
init_gh();
|
|
11793
12124
|
async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
11794
12125
|
if (!config)
|
|
11795
12126
|
return "skipped";
|
|
@@ -11992,116 +12323,8 @@ ${import_picocolors8.default.bold("Stale branches (remote deleted, likely squash
|
|
|
11992
12323
|
}
|
|
11993
12324
|
});
|
|
11994
12325
|
|
|
11995
|
-
// src/commands/
|
|
12326
|
+
// src/commands/commit.ts
|
|
11996
12327
|
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
11997
|
-
var discard_default = defineCommand({
|
|
11998
|
-
meta: {
|
|
11999
|
-
name: "discard",
|
|
12000
|
-
description: "Discard the current feature branch and return to the base branch"
|
|
12001
|
-
},
|
|
12002
|
-
args: {
|
|
12003
|
-
force: {
|
|
12004
|
-
type: "boolean",
|
|
12005
|
-
alias: "f",
|
|
12006
|
-
description: "Skip confirmation and discard immediately",
|
|
12007
|
-
default: false
|
|
12008
|
-
}
|
|
12009
|
-
},
|
|
12010
|
-
async run({ args }) {
|
|
12011
|
-
if (!await isGitRepo()) {
|
|
12012
|
-
error("Not inside a git repository.");
|
|
12013
|
-
process.exit(1);
|
|
12014
|
-
}
|
|
12015
|
-
await assertCleanGitState("discarding a branch");
|
|
12016
|
-
const config = readConfig();
|
|
12017
|
-
if (!config) {
|
|
12018
|
-
error("No repo config found. Run `cn setup` first.");
|
|
12019
|
-
process.exit(1);
|
|
12020
|
-
}
|
|
12021
|
-
const currentBranch = await getCurrentBranch();
|
|
12022
|
-
const baseBranch = getBaseBranch(config);
|
|
12023
|
-
await projectHeading("discard", "\uD83D\uDDD1\uFE0F");
|
|
12024
|
-
if (isBranchProtected(currentBranch, config)) {
|
|
12025
|
-
error(`${import_picocolors9.default.bold(currentBranch)} is a protected branch and cannot be discarded.`);
|
|
12026
|
-
info(`Switch to a feature branch first, then run ${import_picocolors9.default.bold("cn discard")}.`);
|
|
12027
|
-
process.exit(1);
|
|
12028
|
-
}
|
|
12029
|
-
if (currentBranch === baseBranch) {
|
|
12030
|
-
info(`You are already on ${import_picocolors9.default.bold(baseBranch)}.`);
|
|
12031
|
-
process.exit(0);
|
|
12032
|
-
}
|
|
12033
|
-
const { origin } = config;
|
|
12034
|
-
const localWork = await hasLocalWork(origin, currentBranch);
|
|
12035
|
-
const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
|
|
12036
|
-
if (hasWork) {
|
|
12037
|
-
if (localWork.uncommitted) {
|
|
12038
|
-
warn("You have uncommitted changes in your working tree.");
|
|
12039
|
-
}
|
|
12040
|
-
if (localWork.unpushedCommits > 0) {
|
|
12041
|
-
warn(`You have ${import_picocolors9.default.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on this branch.`);
|
|
12042
|
-
}
|
|
12043
|
-
warn("Discarding this branch will permanently lose that work.");
|
|
12044
|
-
const SAVE_FIRST = "Save my changes first (cn save), then discard";
|
|
12045
|
-
const DISCARD_ANYWAY = "Discard anyway \u2014 I do not need this work";
|
|
12046
|
-
const CANCEL = "Keep the branch, take me back";
|
|
12047
|
-
const action = await selectPrompt("This branch has unsaved work. What would you like to do?", [SAVE_FIRST, DISCARD_ANYWAY, CANCEL]);
|
|
12048
|
-
if (action === CANCEL) {
|
|
12049
|
-
info("Discard cancelled. Your branch is untouched.");
|
|
12050
|
-
process.exit(0);
|
|
12051
|
-
}
|
|
12052
|
-
if (action === SAVE_FIRST) {
|
|
12053
|
-
if (!localWork.uncommitted) {
|
|
12054
|
-
info("No uncommitted changes to stash \u2014 unpushed commits will still be lost.");
|
|
12055
|
-
const confirm = await confirmPrompt("Continue discarding the branch?");
|
|
12056
|
-
if (!confirm) {
|
|
12057
|
-
info("Discard cancelled.");
|
|
12058
|
-
process.exit(0);
|
|
12059
|
-
}
|
|
12060
|
-
} else {
|
|
12061
|
-
const stashResult = await stashChanges(`work-in-progress on ${currentBranch}`);
|
|
12062
|
-
if (stashResult.exitCode !== 0) {
|
|
12063
|
-
error(`Failed to save changes: ${stashResult.stderr}`);
|
|
12064
|
-
process.exit(1);
|
|
12065
|
-
}
|
|
12066
|
-
success(`Changes saved. Use ${import_picocolors9.default.bold("cn save --restore")} to bring them back.`);
|
|
12067
|
-
}
|
|
12068
|
-
}
|
|
12069
|
-
} else if (!args.force) {
|
|
12070
|
-
const confirmed = await confirmPrompt(`Discard ${import_picocolors9.default.bold(currentBranch)} and return to ${import_picocolors9.default.bold(baseBranch)}?`);
|
|
12071
|
-
if (!confirmed) {
|
|
12072
|
-
info("Discard cancelled.");
|
|
12073
|
-
process.exit(0);
|
|
12074
|
-
}
|
|
12075
|
-
}
|
|
12076
|
-
const upstreamRef = await getUpstreamRef();
|
|
12077
|
-
let deleteRemote = false;
|
|
12078
|
-
if (upstreamRef) {
|
|
12079
|
-
deleteRemote = await confirmPrompt(`Also delete the remote branch ${import_picocolors9.default.bold(upstreamRef)}?`);
|
|
12080
|
-
}
|
|
12081
|
-
const checkoutResult = await checkoutBranch(baseBranch);
|
|
12082
|
-
if (checkoutResult.exitCode !== 0) {
|
|
12083
|
-
error(`Failed to switch to ${import_picocolors9.default.bold(baseBranch)}: ${checkoutResult.stderr}`);
|
|
12084
|
-
process.exit(1);
|
|
12085
|
-
}
|
|
12086
|
-
const deleteResult = await forceDeleteBranch(currentBranch);
|
|
12087
|
-
if (deleteResult.exitCode !== 0) {
|
|
12088
|
-
error(`Failed to delete branch ${import_picocolors9.default.bold(currentBranch)}: ${deleteResult.stderr}`);
|
|
12089
|
-
process.exit(1);
|
|
12090
|
-
}
|
|
12091
|
-
success(`Discarded ${import_picocolors9.default.bold(currentBranch)} and switched back to ${import_picocolors9.default.bold(baseBranch)}`);
|
|
12092
|
-
if (deleteRemote) {
|
|
12093
|
-
const remoteDeleteResult = await deleteRemoteBranch(origin, currentBranch);
|
|
12094
|
-
if (remoteDeleteResult.exitCode !== 0) {
|
|
12095
|
-
warn(`Could not delete remote branch: ${remoteDeleteResult.stderr.trim()}`);
|
|
12096
|
-
} else {
|
|
12097
|
-
success(`Deleted remote branch ${import_picocolors9.default.bold(`${origin}/${currentBranch}`)}`);
|
|
12098
|
-
}
|
|
12099
|
-
}
|
|
12100
|
-
}
|
|
12101
|
-
});
|
|
12102
|
-
|
|
12103
|
-
// src/commands/commit.ts
|
|
12104
|
-
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
12105
12328
|
|
|
12106
12329
|
// src/utils/convention.ts
|
|
12107
12330
|
var CLEAN_COMMIT_PATTERN = /^(\uD83D\uDCE6|\uD83D\uDD27|\uD83D\uDDD1\uFE0F?|\uD83D\uDD12|\u2699\uFE0F?|\u2615|\uD83E\uDDEA|\uD83D\uDCD6|\uD83D\uDE80) (new|update|remove|security|setup|chore|test|docs|release)(!?)( \([a-zA-Z0-9][a-zA-Z0-9-]*\))?: .{1,72}$/u;
|
|
@@ -12208,9 +12431,9 @@ var commit_default = defineCommand({
|
|
|
12208
12431
|
process.exit(1);
|
|
12209
12432
|
}
|
|
12210
12433
|
console.log(`
|
|
12211
|
-
${
|
|
12434
|
+
${import_picocolors9.default.bold("Changed files:")}`);
|
|
12212
12435
|
for (const f3 of changedFiles) {
|
|
12213
|
-
console.log(` ${
|
|
12436
|
+
console.log(` ${import_picocolors9.default.dim("\u2022")} ${f3}`);
|
|
12214
12437
|
}
|
|
12215
12438
|
const stageAction = await selectPrompt("No staged changes. How would you like to stage?", [
|
|
12216
12439
|
"Stage all changes",
|
|
@@ -12252,8 +12475,8 @@ ${import_picocolors10.default.bold("Changed files:")}`);
|
|
|
12252
12475
|
const dirs = new Set(stagedFiles.map((f3) => f3.split("/")[0]));
|
|
12253
12476
|
if (dirs.size > 1) {
|
|
12254
12477
|
console.log();
|
|
12255
|
-
warn(`You're staging ${
|
|
12256
|
-
info(
|
|
12478
|
+
warn(`You're staging ${import_picocolors9.default.bold(String(stagedFiles.length))} files across ${import_picocolors9.default.bold(String(dirs.size))} directories in a single commit.`);
|
|
12479
|
+
info(import_picocolors9.default.dim("Large commits mixing different topics make history harder to read and bisect. " + "For cleaner history, consider splitting into atomic commits."));
|
|
12257
12480
|
const choice = await selectPrompt("How would you like to proceed?", [
|
|
12258
12481
|
"Continue as single commit",
|
|
12259
12482
|
"Switch to group mode (AI splits into atomic commits)",
|
|
@@ -12284,7 +12507,7 @@ ${import_picocolors10.default.bold("Changed files:")}`);
|
|
|
12284
12507
|
if (commitMessage) {
|
|
12285
12508
|
spinner.success("AI commit message generated.");
|
|
12286
12509
|
console.log(`
|
|
12287
|
-
${
|
|
12510
|
+
${import_picocolors9.default.dim("AI suggestion:")} ${import_picocolors9.default.bold(import_picocolors9.default.cyan(commitMessage))}`);
|
|
12288
12511
|
} else {
|
|
12289
12512
|
spinner.fail("AI did not return a commit message.");
|
|
12290
12513
|
warn("Falling back to manual entry.");
|
|
@@ -12312,7 +12535,7 @@ ${import_picocolors10.default.bold("Changed files:")}`);
|
|
|
12312
12535
|
if (regen) {
|
|
12313
12536
|
spinner.success("Commit message regenerated.");
|
|
12314
12537
|
console.log(`
|
|
12315
|
-
${
|
|
12538
|
+
${import_picocolors9.default.dim("AI suggestion:")} ${import_picocolors9.default.bold(import_picocolors9.default.cyan(regen))}`);
|
|
12316
12539
|
const ok = await confirmPrompt("Use this message?");
|
|
12317
12540
|
finalMessage = ok ? regen : await inputPrompt("Enter commit message manually");
|
|
12318
12541
|
} else {
|
|
@@ -12327,7 +12550,7 @@ ${import_picocolors10.default.bold("Changed files:")}`);
|
|
|
12327
12550
|
if (convention2 !== "none") {
|
|
12328
12551
|
console.log();
|
|
12329
12552
|
for (const hint of CONVENTION_FORMAT_HINTS[convention2]) {
|
|
12330
|
-
console.log(
|
|
12553
|
+
console.log(import_picocolors9.default.dim(hint));
|
|
12331
12554
|
}
|
|
12332
12555
|
console.log();
|
|
12333
12556
|
}
|
|
@@ -12351,7 +12574,7 @@ ${import_picocolors10.default.bold("Changed files:")}`);
|
|
|
12351
12574
|
error(`Failed to commit: ${result.stderr}`);
|
|
12352
12575
|
process.exit(1);
|
|
12353
12576
|
}
|
|
12354
|
-
success(`Committed: ${
|
|
12577
|
+
success(`Committed: ${import_picocolors9.default.bold(finalMessage)}`);
|
|
12355
12578
|
}
|
|
12356
12579
|
});
|
|
12357
12580
|
async function runGroupCommit(model, config) {
|
|
@@ -12368,9 +12591,9 @@ async function runGroupCommit(model, config) {
|
|
|
12368
12591
|
process.exit(1);
|
|
12369
12592
|
}
|
|
12370
12593
|
console.log(`
|
|
12371
|
-
${
|
|
12594
|
+
${import_picocolors9.default.bold("Changed files:")}`);
|
|
12372
12595
|
for (const f3 of changedFiles) {
|
|
12373
|
-
console.log(` ${
|
|
12596
|
+
console.log(` ${import_picocolors9.default.dim("\u2022")} ${f3}`);
|
|
12374
12597
|
}
|
|
12375
12598
|
const spinner = createSpinner(changedFiles.length >= BATCH_CONFIG.LARGE_CHANGESET_THRESHOLD ? `Asking AI to group ${changedFiles.length} file(s) into logical commits (using optimized batching)...` : `Asking AI to group ${changedFiles.length} file(s) into logical commits...`, {
|
|
12376
12599
|
tips: LOADING_TIPS
|
|
@@ -12416,13 +12639,13 @@ ${import_picocolors10.default.bold("Changed files:")}`);
|
|
|
12416
12639
|
let commitAll = false;
|
|
12417
12640
|
while (!proceedToCommit) {
|
|
12418
12641
|
console.log(`
|
|
12419
|
-
${
|
|
12642
|
+
${import_picocolors9.default.bold(`AI suggested ${validGroups.length} commit group(s):`)}
|
|
12420
12643
|
`);
|
|
12421
12644
|
for (let i2 = 0;i2 < validGroups.length; i2++) {
|
|
12422
12645
|
const g3 = validGroups[i2];
|
|
12423
|
-
console.log(` ${
|
|
12646
|
+
console.log(` ${import_picocolors9.default.cyan(`Group ${i2 + 1}:`)} ${import_picocolors9.default.bold(g3.message)}`);
|
|
12424
12647
|
for (const f3 of g3.files) {
|
|
12425
|
-
console.log(` ${
|
|
12648
|
+
console.log(` ${import_picocolors9.default.dim("\u2022")} ${f3}`);
|
|
12426
12649
|
}
|
|
12427
12650
|
console.log();
|
|
12428
12651
|
}
|
|
@@ -12488,16 +12711,16 @@ ${import_picocolors10.default.bold(`AI suggested ${validGroups.length} commit gr
|
|
|
12488
12711
|
continue;
|
|
12489
12712
|
}
|
|
12490
12713
|
committed++;
|
|
12491
|
-
success(`Committed group ${i2 + 1}: ${
|
|
12714
|
+
success(`Committed group ${i2 + 1}: ${import_picocolors9.default.bold(group.message)}`);
|
|
12492
12715
|
}
|
|
12493
12716
|
} else {
|
|
12494
12717
|
for (let i2 = 0;i2 < validGroups.length; i2++) {
|
|
12495
12718
|
const group = validGroups[i2];
|
|
12496
|
-
console.log(
|
|
12719
|
+
console.log(import_picocolors9.default.bold(`
|
|
12497
12720
|
\u2500\u2500 Group ${i2 + 1}/${validGroups.length} \u2500\u2500`));
|
|
12498
|
-
console.log(` ${
|
|
12721
|
+
console.log(` ${import_picocolors9.default.cyan(group.message)}`);
|
|
12499
12722
|
for (const f3 of group.files) {
|
|
12500
|
-
console.log(` ${
|
|
12723
|
+
console.log(` ${import_picocolors9.default.dim("\u2022")} ${f3}`);
|
|
12501
12724
|
}
|
|
12502
12725
|
let message = group.message;
|
|
12503
12726
|
let actionDone = false;
|
|
@@ -12521,7 +12744,7 @@ ${import_picocolors10.default.bold(`AI suggested ${validGroups.length} commit gr
|
|
|
12521
12744
|
if (newMsg) {
|
|
12522
12745
|
message = newMsg;
|
|
12523
12746
|
group.message = newMsg;
|
|
12524
|
-
regenSpinner.success(`New message: ${
|
|
12747
|
+
regenSpinner.success(`New message: ${import_picocolors9.default.bold(message)}`);
|
|
12525
12748
|
} else {
|
|
12526
12749
|
regenSpinner.fail("AI could not generate a new message. Keeping current one.");
|
|
12527
12750
|
}
|
|
@@ -12584,7 +12807,7 @@ ${import_picocolors10.default.bold(`AI suggested ${validGroups.length} commit gr
|
|
|
12584
12807
|
continue;
|
|
12585
12808
|
}
|
|
12586
12809
|
committed++;
|
|
12587
|
-
success(`Committed group ${i2 + 1}: ${
|
|
12810
|
+
success(`Committed group ${i2 + 1}: ${import_picocolors9.default.bold(message)}`);
|
|
12588
12811
|
actionDone = true;
|
|
12589
12812
|
}
|
|
12590
12813
|
}
|
|
@@ -12599,7 +12822,7 @@ ${import_picocolors10.default.bold(`AI suggested ${validGroups.length} commit gr
|
|
|
12599
12822
|
}
|
|
12600
12823
|
|
|
12601
12824
|
// src/commands/config.ts
|
|
12602
|
-
var
|
|
12825
|
+
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
12603
12826
|
var WORKFLOW_OPTIONS = [
|
|
12604
12827
|
{ value: "clean-flow", label: WORKFLOW_DESCRIPTIONS["clean-flow"] },
|
|
12605
12828
|
{ value: "github-flow", label: WORKFLOW_DESCRIPTIONS["github-flow"] },
|
|
@@ -12616,7 +12839,8 @@ var CONVENTION_OPTIONS = [
|
|
|
12616
12839
|
];
|
|
12617
12840
|
var AI_PROVIDER_OPTIONS = [
|
|
12618
12841
|
{ value: "copilot", label: "GitHub Copilot" },
|
|
12619
|
-
{ value: "ollama-cloud", label: "Ollama Cloud" }
|
|
12842
|
+
{ value: "ollama-cloud", label: "Ollama Cloud" },
|
|
12843
|
+
{ value: "openrouter", label: "OpenRouter" }
|
|
12620
12844
|
];
|
|
12621
12845
|
function parseBranchPrefixesInput(input, fallback) {
|
|
12622
12846
|
const values = input.split(",").map((value) => value.trim()).filter(Boolean);
|
|
@@ -12650,6 +12874,10 @@ function finalizeEditedConfig(current, draft) {
|
|
|
12650
12874
|
next.aiModel = (draft.aiModel?.trim() || DEFAULT_OLLAMA_CLOUD_MODEL).trim();
|
|
12651
12875
|
return next;
|
|
12652
12876
|
}
|
|
12877
|
+
if (next.aiProvider === "openrouter") {
|
|
12878
|
+
next.aiModel = (draft.aiModel?.trim() || DEFAULT_OPENROUTER_MODEL).trim();
|
|
12879
|
+
return next;
|
|
12880
|
+
}
|
|
12653
12881
|
delete next.aiModel;
|
|
12654
12882
|
return next;
|
|
12655
12883
|
}
|
|
@@ -12657,6 +12885,8 @@ function buildConfigSnapshot(config, meta) {
|
|
|
12657
12885
|
const aiConfig = resolveAIConfig(config);
|
|
12658
12886
|
const aiEnabled = isAIEnabled(config);
|
|
12659
12887
|
const usingOllamaCloud = aiEnabled && aiConfig.provider === "ollama-cloud";
|
|
12888
|
+
const usingOpenRouter = aiEnabled && aiConfig.provider === "openrouter";
|
|
12889
|
+
const needsSecretInfo = usingOllamaCloud || usingOpenRouter;
|
|
12660
12890
|
return {
|
|
12661
12891
|
source: meta.source,
|
|
12662
12892
|
location: meta.location,
|
|
@@ -12677,7 +12907,64 @@ function buildConfigSnapshot(config, meta) {
|
|
|
12677
12907
|
providerLabel: aiEnabled ? aiConfig.providerLabel : null,
|
|
12678
12908
|
model: aiEnabled ? aiConfig.model ?? null : null,
|
|
12679
12909
|
ollamaCloudApiKeyPresent: usingOllamaCloud ? meta.hasOllamaCloudApiKey : null,
|
|
12680
|
-
|
|
12910
|
+
openrouterApiKeyPresent: usingOpenRouter ? meta.hasOpenRouterApiKey : null,
|
|
12911
|
+
secretsPath: needsSecretInfo ? meta.secretsPath : null
|
|
12912
|
+
}
|
|
12913
|
+
};
|
|
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
|
|
12681
12968
|
}
|
|
12682
12969
|
};
|
|
12683
12970
|
}
|
|
@@ -12714,6 +13001,36 @@ async function promptForOllamaCloudModelSelection(apiKey, fallbackModel) {
|
|
|
12714
13001
|
}
|
|
12715
13002
|
return inputPrompt("Ollama Cloud model", fallbackModel);
|
|
12716
13003
|
}
|
|
13004
|
+
async function promptForOpenRouterModelSelection(apiKey, fallbackModel) {
|
|
13005
|
+
if (apiKey) {
|
|
13006
|
+
try {
|
|
13007
|
+
info("Fetching available OpenRouter models...");
|
|
13008
|
+
const models = prioritizeOpenRouterModels(await fetchOpenRouterModels(apiKey));
|
|
13009
|
+
if (models.length > 0) {
|
|
13010
|
+
const manualChoice = "Enter model manually";
|
|
13011
|
+
const choices = models.map((model) => ({
|
|
13012
|
+
value: model,
|
|
13013
|
+
label: model === DEFAULT_OPENROUTER_MODEL ? `${model} (default)` : model
|
|
13014
|
+
}));
|
|
13015
|
+
const selected = await selectPrompt("OpenRouter model", [
|
|
13016
|
+
...choices.map((choice) => choice.label),
|
|
13017
|
+
manualChoice
|
|
13018
|
+
]);
|
|
13019
|
+
if (selected !== manualChoice) {
|
|
13020
|
+
return choices.find((choice) => choice.label === selected)?.value ?? fallbackModel;
|
|
13021
|
+
}
|
|
13022
|
+
} else {
|
|
13023
|
+
warn("OpenRouter returned no available models. Enter the model name manually.");
|
|
13024
|
+
}
|
|
13025
|
+
} catch (err) {
|
|
13026
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
13027
|
+
warn(`Could not fetch OpenRouter models: ${message}`);
|
|
13028
|
+
}
|
|
13029
|
+
} else {
|
|
13030
|
+
warn("No OpenRouter API key is available yet, so the model list cannot be fetched.");
|
|
13031
|
+
}
|
|
13032
|
+
return inputPrompt("OpenRouter model", fallbackModel);
|
|
13033
|
+
}
|
|
12717
13034
|
async function selectCurrentValue(message, options, current) {
|
|
12718
13035
|
const choices = options.map((option) => ({
|
|
12719
13036
|
value: option.value,
|
|
@@ -12728,7 +13045,7 @@ async function selectBooleanValue(message, current, trueLabel, falseLabel) {
|
|
|
12728
13045
|
{ value: "false", label: falseLabel }
|
|
12729
13046
|
], current ? "true" : "false").then((value) => value === "true");
|
|
12730
13047
|
}
|
|
12731
|
-
async function promptForConfigEdits(current, hasExistingOllamaApiKey) {
|
|
13048
|
+
async function promptForConfigEdits(current, hasExistingOllamaApiKey, hasExistingOpenRouterApiKey) {
|
|
12732
13049
|
const workflow = await selectCurrentValue("Workflow mode", WORKFLOW_OPTIONS, current.workflow);
|
|
12733
13050
|
const role = await selectCurrentValue("Your role in this clone", ROLE_OPTIONS, current.role);
|
|
12734
13051
|
const mainBranch = await inputPrompt("Main branch name", current.mainBranch);
|
|
@@ -12749,6 +13066,8 @@ async function promptForConfigEdits(current, hasExistingOllamaApiKey) {
|
|
|
12749
13066
|
let aiModel;
|
|
12750
13067
|
let ollamaApiKeyAction = "keep";
|
|
12751
13068
|
let ollamaApiKey;
|
|
13069
|
+
let openrouterApiKeyAction = "keep";
|
|
13070
|
+
let openrouterApiKey;
|
|
12752
13071
|
if (aiEnabled) {
|
|
12753
13072
|
const currentProvider = current.aiProvider ?? "copilot";
|
|
12754
13073
|
aiProvider = await selectCurrentValue("AI provider", AI_PROVIDER_OPTIONS, currentProvider);
|
|
@@ -12780,16 +13099,72 @@ async function promptForConfigEdits(current, hasExistingOllamaApiKey) {
|
|
|
12780
13099
|
}
|
|
12781
13100
|
const modelLookupApiKey = ollamaApiKeyAction === "set" ? ollamaApiKey ?? null : ollamaApiKeyAction === "keep" ? await getOllamaCloudApiKey() : null;
|
|
12782
13101
|
aiModel = await promptForOllamaCloudModelSelection(modelLookupApiKey, current.aiProvider === "ollama-cloud" ? current.aiModel ?? DEFAULT_OLLAMA_CLOUD_MODEL : DEFAULT_OLLAMA_CLOUD_MODEL);
|
|
12783
|
-
|
|
12784
|
-
|
|
13102
|
+
if (hasExistingOpenRouterApiKey) {
|
|
13103
|
+
const shouldDeleteOpenRouterKey = await confirmPrompt("Delete the stored OpenRouter API key from the local secrets store?");
|
|
13104
|
+
if (shouldDeleteOpenRouterKey) {
|
|
13105
|
+
openrouterApiKeyAction = "delete";
|
|
13106
|
+
}
|
|
13107
|
+
}
|
|
13108
|
+
} else if (aiProvider === "openrouter") {
|
|
13109
|
+
if (hasExistingOpenRouterApiKey) {
|
|
13110
|
+
const apiKeyChoice = await selectPrompt("OpenRouter API key", [
|
|
13111
|
+
"Keep existing stored key",
|
|
13112
|
+
"Replace stored key",
|
|
13113
|
+
"Delete stored key"
|
|
13114
|
+
]);
|
|
13115
|
+
if (apiKeyChoice === "Replace stored key") {
|
|
13116
|
+
openrouterApiKey = (await passwordPrompt("Enter the new OpenRouter API key")).trim();
|
|
13117
|
+
if (!openrouterApiKey) {
|
|
13118
|
+
throw new Error("OpenRouter API key cannot be empty when replacing the stored key.");
|
|
13119
|
+
}
|
|
13120
|
+
openrouterApiKeyAction = "set";
|
|
13121
|
+
} else if (apiKeyChoice === "Delete stored key") {
|
|
13122
|
+
openrouterApiKeyAction = "delete";
|
|
13123
|
+
}
|
|
13124
|
+
} else {
|
|
13125
|
+
const addApiKey = await confirmPrompt("No OpenRouter API key is stored. Add one now?");
|
|
13126
|
+
if (addApiKey) {
|
|
13127
|
+
openrouterApiKey = (await passwordPrompt("Enter your OpenRouter API key")).trim();
|
|
13128
|
+
if (!openrouterApiKey) {
|
|
13129
|
+
throw new Error("OpenRouter API key cannot be empty when enabling OpenRouter.");
|
|
13130
|
+
}
|
|
13131
|
+
openrouterApiKeyAction = "set";
|
|
13132
|
+
}
|
|
13133
|
+
}
|
|
13134
|
+
const modelLookupApiKey = openrouterApiKeyAction === "set" ? openrouterApiKey ?? null : openrouterApiKeyAction === "keep" ? await getOpenRouterApiKey() : null;
|
|
13135
|
+
aiModel = await promptForOpenRouterModelSelection(modelLookupApiKey, current.aiProvider === "openrouter" ? current.aiModel ?? DEFAULT_OPENROUTER_MODEL : DEFAULT_OPENROUTER_MODEL);
|
|
13136
|
+
if (hasExistingOllamaApiKey) {
|
|
13137
|
+
const shouldDeleteStoredKey = await confirmPrompt("Delete the stored Ollama Cloud API key from the local secrets store?");
|
|
13138
|
+
if (shouldDeleteStoredKey) {
|
|
13139
|
+
ollamaApiKeyAction = "delete";
|
|
13140
|
+
}
|
|
13141
|
+
}
|
|
13142
|
+
} else {
|
|
13143
|
+
if (hasExistingOllamaApiKey) {
|
|
13144
|
+
const shouldDeleteStoredKey = await confirmPrompt("Delete the stored Ollama Cloud API key from the local secrets store?");
|
|
13145
|
+
if (shouldDeleteStoredKey) {
|
|
13146
|
+
ollamaApiKeyAction = "delete";
|
|
13147
|
+
}
|
|
13148
|
+
}
|
|
13149
|
+
if (hasExistingOpenRouterApiKey) {
|
|
13150
|
+
const shouldDeleteOpenRouterKey = await confirmPrompt("Delete the stored OpenRouter API key from the local secrets store?");
|
|
13151
|
+
if (shouldDeleteOpenRouterKey) {
|
|
13152
|
+
openrouterApiKeyAction = "delete";
|
|
13153
|
+
}
|
|
13154
|
+
}
|
|
13155
|
+
}
|
|
13156
|
+
} else {
|
|
13157
|
+
if (hasExistingOllamaApiKey) {
|
|
13158
|
+
const shouldDeleteStoredKey = await confirmPrompt("AI is disabled. Delete the stored Ollama Cloud API key from the local secrets store?");
|
|
12785
13159
|
if (shouldDeleteStoredKey) {
|
|
12786
13160
|
ollamaApiKeyAction = "delete";
|
|
12787
13161
|
}
|
|
12788
13162
|
}
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
|
|
12792
|
-
|
|
13163
|
+
if (hasExistingOpenRouterApiKey) {
|
|
13164
|
+
const shouldDeleteOpenRouterKey = await confirmPrompt("AI is disabled. Delete the stored OpenRouter API key from the local secrets store?");
|
|
13165
|
+
if (shouldDeleteOpenRouterKey) {
|
|
13166
|
+
openrouterApiKeyAction = "delete";
|
|
13167
|
+
}
|
|
12793
13168
|
}
|
|
12794
13169
|
}
|
|
12795
13170
|
return {
|
|
@@ -12808,17 +13183,17 @@ async function promptForConfigEdits(current, hasExistingOllamaApiKey) {
|
|
|
12808
13183
|
showTips
|
|
12809
13184
|
}),
|
|
12810
13185
|
ollamaApiKeyAction,
|
|
12811
|
-
ollamaApiKey
|
|
13186
|
+
ollamaApiKey,
|
|
13187
|
+
openrouterApiKeyAction,
|
|
13188
|
+
openrouterApiKey
|
|
12812
13189
|
};
|
|
12813
13190
|
}
|
|
12814
|
-
async function
|
|
13191
|
+
async function applyApiKeyEdits(result) {
|
|
12815
13192
|
if (result.ollamaApiKeyAction === "set" && result.ollamaApiKey) {
|
|
12816
13193
|
await setOllamaCloudApiKey(result.ollamaApiKey);
|
|
12817
13194
|
success("Stored Ollama Cloud API key in the local secrets store.");
|
|
12818
|
-
info(`Secrets path: ${
|
|
12819
|
-
|
|
12820
|
-
}
|
|
12821
|
-
if (result.ollamaApiKeyAction === "delete") {
|
|
13195
|
+
info(`Secrets path: ${import_picocolors10.default.bold(getSecretsStorePath())}`);
|
|
13196
|
+
} else if (result.ollamaApiKeyAction === "delete") {
|
|
12822
13197
|
const deleted = await deleteOllamaCloudApiKey();
|
|
12823
13198
|
if (deleted) {
|
|
12824
13199
|
success("Deleted stored Ollama Cloud API key.");
|
|
@@ -12826,33 +13201,60 @@ async function applyOllamaApiKeyEdit(result) {
|
|
|
12826
13201
|
info("No stored Ollama Cloud API key was found to delete.");
|
|
12827
13202
|
}
|
|
12828
13203
|
}
|
|
13204
|
+
if (result.openrouterApiKeyAction === "set" && result.openrouterApiKey) {
|
|
13205
|
+
await setOpenRouterApiKey(result.openrouterApiKey);
|
|
13206
|
+
success("Stored OpenRouter API key in the local secrets store.");
|
|
13207
|
+
info(`Secrets path: ${import_picocolors10.default.bold(getSecretsStorePath())}`);
|
|
13208
|
+
} else if (result.openrouterApiKeyAction === "delete") {
|
|
13209
|
+
const deleted = await deleteOpenRouterApiKey();
|
|
13210
|
+
if (deleted) {
|
|
13211
|
+
success("Deleted stored OpenRouter API key.");
|
|
13212
|
+
} else {
|
|
13213
|
+
info("No stored OpenRouter API key was found to delete.");
|
|
13214
|
+
}
|
|
13215
|
+
}
|
|
12829
13216
|
}
|
|
12830
13217
|
function printConfigSummary(snapshot) {
|
|
12831
|
-
info(`Config source: ${
|
|
12832
|
-
info(`Config path: ${
|
|
12833
|
-
info(`Workflow: ${
|
|
12834
|
-
info(`Convention: ${
|
|
12835
|
-
info(`Role: ${
|
|
13218
|
+
info(`Config source: ${import_picocolors10.default.bold(snapshot.source)}`);
|
|
13219
|
+
info(`Config path: ${import_picocolors10.default.bold(snapshot.location)}`);
|
|
13220
|
+
info(`Workflow: ${import_picocolors10.default.bold(snapshot.workflowLabel)}`);
|
|
13221
|
+
info(`Convention: ${import_picocolors10.default.bold(snapshot.commitConventionLabel)}`);
|
|
13222
|
+
info(`Role: ${import_picocolors10.default.bold(snapshot.role)}`);
|
|
12836
13223
|
if (snapshot.devBranch) {
|
|
12837
|
-
info(`Main: ${
|
|
13224
|
+
info(`Main: ${import_picocolors10.default.bold(snapshot.mainBranch)} | Dev: ${import_picocolors10.default.bold(snapshot.devBranch)}`);
|
|
12838
13225
|
} else {
|
|
12839
|
-
info(`Main: ${
|
|
13226
|
+
info(`Main: ${import_picocolors10.default.bold(snapshot.mainBranch)}`);
|
|
12840
13227
|
}
|
|
12841
|
-
info(`Origin: ${
|
|
12842
|
-
info(`Branch prefixes: ${
|
|
12843
|
-
info(`Guides: ${
|
|
12844
|
-
info(`AI: ${
|
|
13228
|
+
info(`Origin: ${import_picocolors10.default.bold(snapshot.origin)} | Upstream: ${import_picocolors10.default.bold(snapshot.upstream)}`);
|
|
13229
|
+
info(`Branch prefixes: ${import_picocolors10.default.bold(snapshot.branchPrefixes.join(", "))}`);
|
|
13230
|
+
info(`Guides: ${import_picocolors10.default.bold(snapshot.showTips ? "shown" : "hidden")}`);
|
|
13231
|
+
info(`AI: ${import_picocolors10.default.bold(snapshot.ai.enabled ? "enabled" : "disabled")}`);
|
|
12845
13232
|
if (snapshot.ai.enabled && snapshot.ai.providerLabel) {
|
|
12846
|
-
info(`AI provider: ${
|
|
13233
|
+
info(`AI provider: ${import_picocolors10.default.bold(snapshot.ai.providerLabel)}`);
|
|
12847
13234
|
if (snapshot.ai.model) {
|
|
12848
|
-
info(`AI model: ${
|
|
13235
|
+
info(`AI model: ${import_picocolors10.default.bold(snapshot.ai.model)}`);
|
|
12849
13236
|
}
|
|
12850
13237
|
if (snapshot.ai.provider === "ollama-cloud") {
|
|
12851
|
-
info(`Ollama Cloud API key: ${
|
|
13238
|
+
info(`Ollama Cloud API key: ${import_picocolors10.default.bold(snapshot.ai.ollamaCloudApiKeyPresent ? "stored" : "missing")}`);
|
|
12852
13239
|
if (snapshot.ai.secretsPath) {
|
|
12853
|
-
info(`Secrets path: ${
|
|
13240
|
+
info(`Secrets path: ${import_picocolors10.default.bold(snapshot.ai.secretsPath)}`);
|
|
12854
13241
|
}
|
|
12855
13242
|
}
|
|
13243
|
+
if (snapshot.ai.provider === "openrouter") {
|
|
13244
|
+
info(`OpenRouter API key: ${import_picocolors10.default.bold(snapshot.ai.openrouterApiKeyPresent ? "stored" : "missing")}`);
|
|
13245
|
+
if (snapshot.ai.secretsPath) {
|
|
13246
|
+
info(`Secrets path: ${import_picocolors10.default.bold(snapshot.ai.secretsPath)}`);
|
|
13247
|
+
}
|
|
13248
|
+
}
|
|
13249
|
+
}
|
|
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)}`);
|
|
12856
13258
|
}
|
|
12857
13259
|
}
|
|
12858
13260
|
var config_default = defineCommand({
|
|
@@ -12861,6 +13263,11 @@ var config_default = defineCommand({
|
|
|
12861
13263
|
description: "Inspect or edit the repo config without rerunning setup"
|
|
12862
13264
|
},
|
|
12863
13265
|
args: {
|
|
13266
|
+
global: {
|
|
13267
|
+
type: "boolean",
|
|
13268
|
+
description: "Read or edit global defaults instead of repo config",
|
|
13269
|
+
default: false
|
|
13270
|
+
},
|
|
12864
13271
|
json: {
|
|
12865
13272
|
type: "boolean",
|
|
12866
13273
|
description: "Print the active repo config as JSON with metadata",
|
|
@@ -12873,15 +13280,44 @@ var config_default = defineCommand({
|
|
|
12873
13280
|
}
|
|
12874
13281
|
},
|
|
12875
13282
|
async run({ args }) {
|
|
12876
|
-
if (!await isGitRepo()) {
|
|
12877
|
-
error("Not inside a git repository.");
|
|
12878
|
-
process.exit(1);
|
|
12879
|
-
}
|
|
12880
13283
|
if (args.json && args.edit) {
|
|
12881
13284
|
error("Use either --json or --edit, not both at the same time.");
|
|
12882
13285
|
process.exit(1);
|
|
12883
13286
|
}
|
|
12884
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
|
+
}
|
|
12885
13321
|
if (!configExists()) {
|
|
12886
13322
|
error("No repo config found. Run `cn setup` first.");
|
|
12887
13323
|
process.exit(1);
|
|
@@ -12898,14 +13334,15 @@ var config_default = defineCommand({
|
|
|
12898
13334
|
}
|
|
12899
13335
|
if (args.edit) {
|
|
12900
13336
|
try {
|
|
12901
|
-
const editResult = await promptForConfigEdits(config, await hasOllamaCloudApiKey());
|
|
13337
|
+
const editResult = await promptForConfigEdits(config, await hasOllamaCloudApiKey(), await hasOpenRouterApiKey());
|
|
12902
13338
|
writeConfig(editResult.config);
|
|
12903
|
-
await
|
|
13339
|
+
await applyApiKeyEdits(editResult);
|
|
12904
13340
|
success("Updated repo config.");
|
|
12905
13341
|
printConfigSummary(buildConfigSnapshot(editResult.config, {
|
|
12906
13342
|
source,
|
|
12907
13343
|
location: getConfigLocationLabel(),
|
|
12908
13344
|
hasOllamaCloudApiKey: await hasOllamaCloudApiKey(),
|
|
13345
|
+
hasOpenRouterApiKey: await hasOpenRouterApiKey(),
|
|
12909
13346
|
secretsPath: getSecretsStorePath()
|
|
12910
13347
|
}));
|
|
12911
13348
|
return;
|
|
@@ -12918,6 +13355,7 @@ var config_default = defineCommand({
|
|
|
12918
13355
|
source,
|
|
12919
13356
|
location: getConfigLocationLabel(),
|
|
12920
13357
|
hasOllamaCloudApiKey: await hasOllamaCloudApiKey(),
|
|
13358
|
+
hasOpenRouterApiKey: await hasOpenRouterApiKey(),
|
|
12921
13359
|
secretsPath: getSecretsStorePath()
|
|
12922
13360
|
});
|
|
12923
13361
|
if (args.json) {
|
|
@@ -12926,60 +13364,168 @@ var config_default = defineCommand({
|
|
|
12926
13364
|
}
|
|
12927
13365
|
printConfigSummary(snapshot);
|
|
12928
13366
|
console.log();
|
|
12929
|
-
console.log(` ${
|
|
13367
|
+
console.log(` ${import_picocolors10.default.dim("Run `cn config --edit` to update these settings.")}`);
|
|
12930
13368
|
console.log();
|
|
12931
13369
|
}
|
|
12932
13370
|
});
|
|
12933
13371
|
|
|
12934
|
-
// src/commands/
|
|
12935
|
-
|
|
12936
|
-
var
|
|
12937
|
-
|
|
12938
|
-
|
|
12939
|
-
|
|
12940
|
-
version: "0.8.0-dev.7db6dea",
|
|
12941
|
-
description: "Developer CLI that automates git workflows \u2014 branching, syncing, committing, and PRs \u2014 with multi-workflow and commit convention support.",
|
|
12942
|
-
type: "module",
|
|
12943
|
-
bin: {
|
|
12944
|
-
contrib: "dist/cli.js",
|
|
12945
|
-
contribute: "dist/cli.js",
|
|
12946
|
-
cn: "dist/cli.js"
|
|
12947
|
-
},
|
|
12948
|
-
files: [
|
|
12949
|
-
"dist"
|
|
12950
|
-
],
|
|
12951
|
-
scripts: {
|
|
12952
|
-
build: "bun build src/cli.ts --outfile dist/cli.js --target bun && bun run scripts/add-shebang.mjs",
|
|
12953
|
-
cli: "bun run src/cli.ts --",
|
|
12954
|
-
dev: "bun src/cli.ts",
|
|
12955
|
-
test: "bun test",
|
|
12956
|
-
lint: "biome check .",
|
|
12957
|
-
"lint:fix": "biome check --write .",
|
|
12958
|
-
format: "biome format --write .",
|
|
12959
|
-
"landing:install": "bun install --cwd landing",
|
|
12960
|
-
"landing:dev": "bun run --cwd landing dev",
|
|
12961
|
-
"landing:build": "bun run --cwd landing build",
|
|
12962
|
-
"landing:preview": "bun run --cwd landing preview"
|
|
13372
|
+
// src/commands/discard.ts
|
|
13373
|
+
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
13374
|
+
var discard_default = defineCommand({
|
|
13375
|
+
meta: {
|
|
13376
|
+
name: "discard",
|
|
13377
|
+
description: "Discard the current feature branch and return to the base branch"
|
|
12963
13378
|
},
|
|
12964
|
-
|
|
12965
|
-
|
|
13379
|
+
args: {
|
|
13380
|
+
force: {
|
|
13381
|
+
type: "boolean",
|
|
13382
|
+
alias: "f",
|
|
13383
|
+
description: "Skip confirmation and discard immediately",
|
|
13384
|
+
default: false
|
|
13385
|
+
}
|
|
12966
13386
|
},
|
|
12967
|
-
|
|
12968
|
-
|
|
12969
|
-
|
|
12970
|
-
|
|
12971
|
-
|
|
12972
|
-
"
|
|
12973
|
-
|
|
12974
|
-
|
|
12975
|
-
|
|
12976
|
-
|
|
12977
|
-
|
|
12978
|
-
|
|
12979
|
-
|
|
12980
|
-
|
|
12981
|
-
|
|
12982
|
-
|
|
13387
|
+
async run({ args }) {
|
|
13388
|
+
if (!await isGitRepo()) {
|
|
13389
|
+
error("Not inside a git repository.");
|
|
13390
|
+
process.exit(1);
|
|
13391
|
+
}
|
|
13392
|
+
await assertCleanGitState("discarding a branch");
|
|
13393
|
+
const config = readConfig();
|
|
13394
|
+
if (!config) {
|
|
13395
|
+
error("No repo config found. Run `cn setup` first.");
|
|
13396
|
+
process.exit(1);
|
|
13397
|
+
}
|
|
13398
|
+
const currentBranch = await getCurrentBranch();
|
|
13399
|
+
const baseBranch = getBaseBranch(config);
|
|
13400
|
+
await projectHeading("discard", "\uD83D\uDDD1\uFE0F");
|
|
13401
|
+
if (isBranchProtected(currentBranch, config)) {
|
|
13402
|
+
error(`${import_picocolors11.default.bold(currentBranch)} is a protected branch and cannot be discarded.`);
|
|
13403
|
+
info(`Switch to a feature branch first, then run ${import_picocolors11.default.bold("cn discard")}.`);
|
|
13404
|
+
process.exit(1);
|
|
13405
|
+
}
|
|
13406
|
+
if (currentBranch === baseBranch) {
|
|
13407
|
+
info(`You are already on ${import_picocolors11.default.bold(baseBranch)}.`);
|
|
13408
|
+
process.exit(0);
|
|
13409
|
+
}
|
|
13410
|
+
const { origin } = config;
|
|
13411
|
+
const localWork = await hasLocalWork(origin, currentBranch);
|
|
13412
|
+
const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
|
|
13413
|
+
if (hasWork) {
|
|
13414
|
+
if (localWork.uncommitted) {
|
|
13415
|
+
warn("You have uncommitted changes in your working tree.");
|
|
13416
|
+
}
|
|
13417
|
+
if (localWork.unpushedCommits > 0) {
|
|
13418
|
+
warn(`You have ${import_picocolors11.default.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on this branch.`);
|
|
13419
|
+
}
|
|
13420
|
+
warn("Discarding this branch will permanently lose that work.");
|
|
13421
|
+
const SAVE_FIRST = "Save my changes first (cn save), then discard";
|
|
13422
|
+
const DISCARD_ANYWAY = "Discard anyway \u2014 I do not need this work";
|
|
13423
|
+
const CANCEL = "Keep the branch, take me back";
|
|
13424
|
+
const action = await selectPrompt("This branch has unsaved work. What would you like to do?", [SAVE_FIRST, DISCARD_ANYWAY, CANCEL]);
|
|
13425
|
+
if (action === CANCEL) {
|
|
13426
|
+
info("Discard cancelled. Your branch is untouched.");
|
|
13427
|
+
process.exit(0);
|
|
13428
|
+
}
|
|
13429
|
+
if (action === SAVE_FIRST) {
|
|
13430
|
+
if (!localWork.uncommitted) {
|
|
13431
|
+
info("No uncommitted changes to stash \u2014 unpushed commits will still be lost.");
|
|
13432
|
+
const confirm = await confirmPrompt("Continue discarding the branch?");
|
|
13433
|
+
if (!confirm) {
|
|
13434
|
+
info("Discard cancelled.");
|
|
13435
|
+
process.exit(0);
|
|
13436
|
+
}
|
|
13437
|
+
} else {
|
|
13438
|
+
const stashResult = await stashChanges(`work-in-progress on ${currentBranch}`);
|
|
13439
|
+
if (stashResult.exitCode !== 0) {
|
|
13440
|
+
error(`Failed to save changes: ${stashResult.stderr}`);
|
|
13441
|
+
process.exit(1);
|
|
13442
|
+
}
|
|
13443
|
+
success(`Changes saved. Use ${import_picocolors11.default.bold("cn save --restore")} to bring them back.`);
|
|
13444
|
+
}
|
|
13445
|
+
}
|
|
13446
|
+
} else if (!args.force) {
|
|
13447
|
+
const confirmed = await confirmPrompt(`Discard ${import_picocolors11.default.bold(currentBranch)} and return to ${import_picocolors11.default.bold(baseBranch)}?`);
|
|
13448
|
+
if (!confirmed) {
|
|
13449
|
+
info("Discard cancelled.");
|
|
13450
|
+
process.exit(0);
|
|
13451
|
+
}
|
|
13452
|
+
}
|
|
13453
|
+
const upstreamRef = await getUpstreamRef();
|
|
13454
|
+
let deleteRemote = false;
|
|
13455
|
+
if (upstreamRef) {
|
|
13456
|
+
deleteRemote = await confirmPrompt(`Also delete the remote branch ${import_picocolors11.default.bold(upstreamRef)}?`);
|
|
13457
|
+
}
|
|
13458
|
+
const checkoutResult = await checkoutBranch(baseBranch);
|
|
13459
|
+
if (checkoutResult.exitCode !== 0) {
|
|
13460
|
+
error(`Failed to switch to ${import_picocolors11.default.bold(baseBranch)}: ${checkoutResult.stderr}`);
|
|
13461
|
+
process.exit(1);
|
|
13462
|
+
}
|
|
13463
|
+
const deleteResult = await forceDeleteBranch(currentBranch);
|
|
13464
|
+
if (deleteResult.exitCode !== 0) {
|
|
13465
|
+
error(`Failed to delete branch ${import_picocolors11.default.bold(currentBranch)}: ${deleteResult.stderr}`);
|
|
13466
|
+
process.exit(1);
|
|
13467
|
+
}
|
|
13468
|
+
success(`Discarded ${import_picocolors11.default.bold(currentBranch)} and switched back to ${import_picocolors11.default.bold(baseBranch)}`);
|
|
13469
|
+
if (deleteRemote) {
|
|
13470
|
+
const remoteDeleteResult = await deleteRemoteBranch(origin, currentBranch);
|
|
13471
|
+
if (remoteDeleteResult.exitCode !== 0) {
|
|
13472
|
+
warn(`Could not delete remote branch: ${remoteDeleteResult.stderr.trim()}`);
|
|
13473
|
+
} else {
|
|
13474
|
+
success(`Deleted remote branch ${import_picocolors11.default.bold(`${origin}/${currentBranch}`)}`);
|
|
13475
|
+
}
|
|
13476
|
+
}
|
|
13477
|
+
}
|
|
13478
|
+
});
|
|
13479
|
+
|
|
13480
|
+
// src/commands/doctor.ts
|
|
13481
|
+
import { execFile as execFileCb3 } from "child_process";
|
|
13482
|
+
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
13483
|
+
// package.json
|
|
13484
|
+
var package_default = {
|
|
13485
|
+
name: "contribute-now",
|
|
13486
|
+
version: "0.8.0-dev.a835394",
|
|
13487
|
+
description: "Developer CLI that automates git workflows \u2014 branching, syncing, committing, and PRs \u2014 with multi-workflow and commit convention support.",
|
|
13488
|
+
type: "module",
|
|
13489
|
+
bin: {
|
|
13490
|
+
contrib: "dist/cli.js",
|
|
13491
|
+
contribute: "dist/cli.js",
|
|
13492
|
+
cn: "dist/cli.js"
|
|
13493
|
+
},
|
|
13494
|
+
files: [
|
|
13495
|
+
"dist"
|
|
13496
|
+
],
|
|
13497
|
+
scripts: {
|
|
13498
|
+
build: "bun build src/cli.ts --outfile dist/cli.js --target bun && bun run scripts/add-shebang.mjs",
|
|
13499
|
+
cli: "bun run src/cli.ts --",
|
|
13500
|
+
dev: "bun src/cli.ts",
|
|
13501
|
+
test: "bun test",
|
|
13502
|
+
lint: "biome check .",
|
|
13503
|
+
"lint:fix": "biome check --write .",
|
|
13504
|
+
format: "biome format --write .",
|
|
13505
|
+
"landing:install": "bun install --cwd landing",
|
|
13506
|
+
"landing:dev": "bun run --cwd landing dev",
|
|
13507
|
+
"landing:build": "bun run --cwd landing build",
|
|
13508
|
+
"landing:preview": "bun run --cwd landing preview"
|
|
13509
|
+
},
|
|
13510
|
+
engines: {
|
|
13511
|
+
bun: ">=1.0"
|
|
13512
|
+
},
|
|
13513
|
+
keywords: [
|
|
13514
|
+
"git",
|
|
13515
|
+
"workflow",
|
|
13516
|
+
"squash-merge",
|
|
13517
|
+
"sync",
|
|
13518
|
+
"cli",
|
|
13519
|
+
"contribute",
|
|
13520
|
+
"fork",
|
|
13521
|
+
"dev-branch",
|
|
13522
|
+
"clean-commit"
|
|
13523
|
+
],
|
|
13524
|
+
author: "Waren Gonzaga",
|
|
13525
|
+
license: "GPL-3.0",
|
|
13526
|
+
repository: {
|
|
13527
|
+
type: "git",
|
|
13528
|
+
url: "git+https://github.com/warengonzaga/contribute-now.git"
|
|
12983
13529
|
},
|
|
12984
13530
|
dependencies: {
|
|
12985
13531
|
"@clack/prompts": "^1.0.1",
|
|
@@ -12995,6 +13541,9 @@ var package_default = {
|
|
|
12995
13541
|
}
|
|
12996
13542
|
};
|
|
12997
13543
|
|
|
13544
|
+
// src/commands/doctor.ts
|
|
13545
|
+
init_gh();
|
|
13546
|
+
|
|
12998
13547
|
// src/utils/remote.ts
|
|
12999
13548
|
function parseRepoFromUrl(url) {
|
|
13000
13549
|
const httpsMatch = url.match(/https?:\/\/github\.com\/([^/]+)\/([^/.]+?)(?:\.git)?$/);
|
|
@@ -13190,6 +13739,15 @@ async function configSection() {
|
|
|
13190
13739
|
detail: hasSecretsStore() ? "stored in the local secrets store" : "run `cn setup` to save it"
|
|
13191
13740
|
});
|
|
13192
13741
|
}
|
|
13742
|
+
if (aiConfig.provider === "openrouter") {
|
|
13743
|
+
const hasApiKey = await hasOpenRouterApiKey();
|
|
13744
|
+
checks.push({
|
|
13745
|
+
label: hasApiKey ? "OpenRouter API key present" : "OpenRouter API key missing",
|
|
13746
|
+
ok: true,
|
|
13747
|
+
warning: !hasApiKey,
|
|
13748
|
+
detail: hasSecretsStore() ? "stored in the local secrets store" : "run `cn setup` to save it"
|
|
13749
|
+
});
|
|
13750
|
+
}
|
|
13193
13751
|
}
|
|
13194
13752
|
if (hasDevBranch(config.workflow)) {
|
|
13195
13753
|
checks.push({
|
|
@@ -13342,161 +13900,763 @@ var doctor_default = defineCommand({
|
|
|
13342
13900
|
default: false
|
|
13343
13901
|
}
|
|
13344
13902
|
},
|
|
13345
|
-
async run({ args }) {
|
|
13346
|
-
const isJson = args.json;
|
|
13347
|
-
const [tool, deps, config, git, fork, workflow] = await Promise.all([
|
|
13348
|
-
toolSection(),
|
|
13349
|
-
depsSection(),
|
|
13350
|
-
configSection(),
|
|
13351
|
-
gitSection(),
|
|
13352
|
-
forkSection(),
|
|
13353
|
-
workflowSection()
|
|
13354
|
-
]);
|
|
13355
|
-
const env2 = envSection();
|
|
13356
|
-
const report = {
|
|
13357
|
-
sections: [tool, deps, config, git, fork, workflow, env2]
|
|
13358
|
-
};
|
|
13359
|
-
if (isJson) {
|
|
13360
|
-
console.log(toJson(report));
|
|
13361
|
-
return;
|
|
13903
|
+
async run({ args }) {
|
|
13904
|
+
const isJson = args.json;
|
|
13905
|
+
const [tool, deps, config, git, fork, workflow] = await Promise.all([
|
|
13906
|
+
toolSection(),
|
|
13907
|
+
depsSection(),
|
|
13908
|
+
configSection(),
|
|
13909
|
+
gitSection(),
|
|
13910
|
+
forkSection(),
|
|
13911
|
+
workflowSection()
|
|
13912
|
+
]);
|
|
13913
|
+
const env2 = envSection();
|
|
13914
|
+
const report = {
|
|
13915
|
+
sections: [tool, deps, config, git, fork, workflow, env2]
|
|
13916
|
+
};
|
|
13917
|
+
if (isJson) {
|
|
13918
|
+
console.log(toJson(report));
|
|
13919
|
+
return;
|
|
13920
|
+
}
|
|
13921
|
+
await projectHeading("doctor", "\uD83E\uDE7A");
|
|
13922
|
+
printReport(report);
|
|
13923
|
+
const total = report.sections.flatMap((s2) => s2.checks);
|
|
13924
|
+
const failures = total.filter((c3) => !c3.ok);
|
|
13925
|
+
const warnings = total.filter((c3) => c3.ok && c3.warning);
|
|
13926
|
+
if (failures.length === 0 && warnings.length === 0) {
|
|
13927
|
+
console.log(` ${import_picocolors12.default.green("All checks passed!")} No issues detected.
|
|
13928
|
+
`);
|
|
13929
|
+
} else {
|
|
13930
|
+
if (failures.length > 0) {
|
|
13931
|
+
console.log(` ${import_picocolors12.default.red(`${failures.length} issue${failures.length !== 1 ? "s" : ""} found.`)}`);
|
|
13932
|
+
}
|
|
13933
|
+
if (warnings.length > 0) {
|
|
13934
|
+
console.log(` ${import_picocolors12.default.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}.`)}`);
|
|
13935
|
+
}
|
|
13936
|
+
console.log();
|
|
13937
|
+
}
|
|
13938
|
+
}
|
|
13939
|
+
});
|
|
13940
|
+
|
|
13941
|
+
// src/commands/hook.ts
|
|
13942
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
13943
|
+
import { join as join6 } from "path";
|
|
13944
|
+
var import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
13945
|
+
var HOOK_MARKER = "# managed by contribute-now";
|
|
13946
|
+
function getHooksDir(cwd = process.cwd()) {
|
|
13947
|
+
return join6(cwd, ".git", "hooks");
|
|
13948
|
+
}
|
|
13949
|
+
function getHookPath(cwd = process.cwd()) {
|
|
13950
|
+
return join6(getHooksDir(cwd), "commit-msg");
|
|
13951
|
+
}
|
|
13952
|
+
function generateHookScript() {
|
|
13953
|
+
return `#!/bin/sh
|
|
13954
|
+
${HOOK_MARKER}
|
|
13955
|
+
# Validates commit messages against your configured convention.
|
|
13956
|
+
# Install: cn hook install
|
|
13957
|
+
# Uninstall: cn hook uninstall
|
|
13958
|
+
|
|
13959
|
+
commit_msg_file="$1"
|
|
13960
|
+
commit_msg=$(head -1 "$commit_msg_file")
|
|
13961
|
+
|
|
13962
|
+
# Skip merge commits and fixup/squash commits
|
|
13963
|
+
case "$commit_msg" in
|
|
13964
|
+
Merge\\ *|fixup!*|squash!*|amend!*) exit 0 ;;
|
|
13965
|
+
esac
|
|
13966
|
+
|
|
13967
|
+
# Detect available package runner
|
|
13968
|
+
if command -v cn >/dev/null 2>&1; then
|
|
13969
|
+
cn validate --file "$commit_msg_file"
|
|
13970
|
+
elif command -v bunx >/dev/null 2>&1; then
|
|
13971
|
+
bunx cn validate --file "$commit_msg_file"
|
|
13972
|
+
else
|
|
13973
|
+
echo "Warning: Neither cn nor bunx is available. Skipping commit message validation."
|
|
13974
|
+
exit 0
|
|
13975
|
+
fi
|
|
13976
|
+
`;
|
|
13977
|
+
}
|
|
13978
|
+
var hook_default = defineCommand({
|
|
13979
|
+
meta: {
|
|
13980
|
+
name: "hook",
|
|
13981
|
+
description: "Install or uninstall the commit-msg git hook"
|
|
13982
|
+
},
|
|
13983
|
+
args: {
|
|
13984
|
+
action: {
|
|
13985
|
+
type: "positional",
|
|
13986
|
+
description: "Action to perform: install or uninstall",
|
|
13987
|
+
required: true
|
|
13988
|
+
}
|
|
13989
|
+
},
|
|
13990
|
+
async run({ args }) {
|
|
13991
|
+
if (!await isGitRepo()) {
|
|
13992
|
+
error("Not inside a git repository.");
|
|
13993
|
+
process.exit(1);
|
|
13994
|
+
}
|
|
13995
|
+
const action = args.action;
|
|
13996
|
+
if (action !== "install" && action !== "uninstall") {
|
|
13997
|
+
error(`Unknown action "${action}". Use "install" or "uninstall".`);
|
|
13998
|
+
process.exit(1);
|
|
13999
|
+
}
|
|
14000
|
+
if (action === "install") {
|
|
14001
|
+
await installHook();
|
|
14002
|
+
} else {
|
|
14003
|
+
await uninstallHook();
|
|
14004
|
+
}
|
|
14005
|
+
}
|
|
14006
|
+
});
|
|
14007
|
+
async function installHook() {
|
|
14008
|
+
await projectHeading("hook install", "\uD83E\uDE9D");
|
|
14009
|
+
const config = readConfig();
|
|
14010
|
+
if (!config) {
|
|
14011
|
+
error("No repo config found. Run `cn setup` first.");
|
|
14012
|
+
process.exit(1);
|
|
14013
|
+
}
|
|
14014
|
+
if (config.commitConvention === "none") {
|
|
14015
|
+
warn('Commit convention is set to "none". No hook to install.');
|
|
14016
|
+
info("Change your convention with `cn setup` first.", "");
|
|
14017
|
+
process.exit(0);
|
|
14018
|
+
}
|
|
14019
|
+
const hookPath = getHookPath();
|
|
14020
|
+
const hooksDir = getHooksDir();
|
|
14021
|
+
if (existsSync6(hookPath)) {
|
|
14022
|
+
const existing = readFileSync5(hookPath, "utf-8");
|
|
14023
|
+
if (!existing.includes(HOOK_MARKER)) {
|
|
14024
|
+
error("A commit-msg hook already exists and was not installed by contribute-now.");
|
|
14025
|
+
warn(`Path: ${hookPath}`);
|
|
14026
|
+
warn("Remove it manually or back it up before installing.");
|
|
14027
|
+
process.exit(1);
|
|
14028
|
+
}
|
|
14029
|
+
info("Updating existing contribute-now hook...");
|
|
14030
|
+
}
|
|
14031
|
+
if (!existsSync6(hooksDir)) {
|
|
14032
|
+
mkdirSync5(hooksDir, { recursive: true });
|
|
14033
|
+
}
|
|
14034
|
+
writeFileSync5(hookPath, generateHookScript(), { mode: 493 });
|
|
14035
|
+
success(`commit-msg hook installed.`);
|
|
14036
|
+
info(`Convention: ${import_picocolors13.default.bold(CONVENTION_LABELS[config.commitConvention])}`, "");
|
|
14037
|
+
info(`Path: ${import_picocolors13.default.dim(hookPath)}`, "");
|
|
14038
|
+
warn("Note: hooks can be bypassed with `git commit --no-verify`.");
|
|
14039
|
+
}
|
|
14040
|
+
async function uninstallHook() {
|
|
14041
|
+
await projectHeading("hook uninstall", "\uD83E\uDE9D");
|
|
14042
|
+
const hookPath = getHookPath();
|
|
14043
|
+
if (!existsSync6(hookPath)) {
|
|
14044
|
+
info("No commit-msg hook found. Nothing to uninstall.");
|
|
14045
|
+
return;
|
|
14046
|
+
}
|
|
14047
|
+
const content = readFileSync5(hookPath, "utf-8");
|
|
14048
|
+
if (!content.includes(HOOK_MARKER)) {
|
|
14049
|
+
error("The commit-msg hook was not installed by contribute-now. Leaving it untouched.");
|
|
14050
|
+
process.exit(1);
|
|
14051
|
+
}
|
|
14052
|
+
rmSync2(hookPath);
|
|
14053
|
+
success("commit-msg hook removed.");
|
|
14054
|
+
}
|
|
14055
|
+
|
|
14056
|
+
// src/commands/label.ts
|
|
14057
|
+
init_gh();
|
|
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);
|
|
13362
14495
|
}
|
|
13363
|
-
|
|
13364
|
-
|
|
13365
|
-
|
|
13366
|
-
|
|
13367
|
-
const
|
|
13368
|
-
if (
|
|
13369
|
-
|
|
13370
|
-
|
|
13371
|
-
}
|
|
13372
|
-
|
|
13373
|
-
|
|
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;
|
|
13374
14530
|
}
|
|
13375
|
-
|
|
13376
|
-
|
|
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
|
+
}
|
|
13377
14540
|
}
|
|
13378
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);
|
|
13379
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, "");
|
|
13380
14574
|
}
|
|
13381
14575
|
});
|
|
13382
|
-
|
|
13383
|
-
// src/commands/hook.ts
|
|
13384
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
13385
|
-
import { join as join6 } from "path";
|
|
13386
|
-
var import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
13387
|
-
var HOOK_MARKER = "# managed by contribute-now";
|
|
13388
|
-
function getHooksDir(cwd = process.cwd()) {
|
|
13389
|
-
return join6(cwd, ".git", "hooks");
|
|
13390
|
-
}
|
|
13391
|
-
function getHookPath(cwd = process.cwd()) {
|
|
13392
|
-
return join6(getHooksDir(cwd), "commit-msg");
|
|
13393
|
-
}
|
|
13394
|
-
function generateHookScript() {
|
|
13395
|
-
return `#!/bin/sh
|
|
13396
|
-
${HOOK_MARKER}
|
|
13397
|
-
# Validates commit messages against your configured convention.
|
|
13398
|
-
# Install: cn hook install
|
|
13399
|
-
# Uninstall: cn hook uninstall
|
|
13400
|
-
|
|
13401
|
-
commit_msg_file="$1"
|
|
13402
|
-
commit_msg=$(head -1 "$commit_msg_file")
|
|
13403
|
-
|
|
13404
|
-
# Skip merge commits and fixup/squash commits
|
|
13405
|
-
case "$commit_msg" in
|
|
13406
|
-
Merge\\ *|fixup!*|squash!*|amend!*) exit 0 ;;
|
|
13407
|
-
esac
|
|
13408
|
-
|
|
13409
|
-
# Detect available package runner
|
|
13410
|
-
if command -v cn >/dev/null 2>&1; then
|
|
13411
|
-
cn validate --file "$commit_msg_file"
|
|
13412
|
-
elif command -v bunx >/dev/null 2>&1; then
|
|
13413
|
-
bunx cn validate --file "$commit_msg_file"
|
|
13414
|
-
else
|
|
13415
|
-
echo "Warning: Neither cn nor bunx is available. Skipping commit message validation."
|
|
13416
|
-
exit 0
|
|
13417
|
-
fi
|
|
13418
|
-
`;
|
|
13419
|
-
}
|
|
13420
|
-
var hook_default = defineCommand({
|
|
14576
|
+
var suggestCommand = defineCommand({
|
|
13421
14577
|
meta: {
|
|
13422
|
-
name: "
|
|
13423
|
-
description: "
|
|
14578
|
+
name: "suggest",
|
|
14579
|
+
description: "Suggest labels for an issue or pull request based on its content"
|
|
13424
14580
|
},
|
|
13425
14581
|
args: {
|
|
13426
|
-
|
|
13427
|
-
type: "
|
|
13428
|
-
|
|
13429
|
-
|
|
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"
|
|
13430
14591
|
}
|
|
13431
14592
|
},
|
|
13432
14593
|
async run({ args }) {
|
|
13433
|
-
|
|
13434
|
-
|
|
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>");
|
|
13435
14601
|
process.exit(1);
|
|
13436
14602
|
}
|
|
13437
|
-
|
|
13438
|
-
|
|
13439
|
-
error(`Unknown action "${action}". Use "install" or "uninstall".`);
|
|
14603
|
+
if (hasIssue && hasPr) {
|
|
14604
|
+
error("Use either --issue or --pr, not both.");
|
|
13440
14605
|
process.exit(1);
|
|
13441
14606
|
}
|
|
13442
|
-
|
|
13443
|
-
|
|
13444
|
-
|
|
13445
|
-
|
|
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);
|
|
13446
14611
|
}
|
|
13447
|
-
|
|
13448
|
-
});
|
|
13449
|
-
|
|
13450
|
-
|
|
13451
|
-
|
|
13452
|
-
if (!config) {
|
|
13453
|
-
error("No repo config found. Run `cn setup` first.");
|
|
13454
|
-
process.exit(1);
|
|
13455
|
-
}
|
|
13456
|
-
if (config.commitConvention === "none") {
|
|
13457
|
-
warn('Commit convention is set to "none". No hook to install.');
|
|
13458
|
-
info("Change your convention with `cn setup` first.", "");
|
|
13459
|
-
process.exit(0);
|
|
13460
|
-
}
|
|
13461
|
-
const hookPath = getHookPath();
|
|
13462
|
-
const hooksDir = getHooksDir();
|
|
13463
|
-
if (existsSync6(hookPath)) {
|
|
13464
|
-
const existing = readFileSync5(hookPath, "utf-8");
|
|
13465
|
-
if (!existing.includes(HOOK_MARKER)) {
|
|
13466
|
-
error("A commit-msg hook already exists and was not installed by contribute-now.");
|
|
13467
|
-
warn(`Path: ${hookPath}`);
|
|
13468
|
-
warn("Remove it manually or back it up before installing.");
|
|
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.`);
|
|
13469
14617
|
process.exit(1);
|
|
13470
14618
|
}
|
|
13471
|
-
|
|
13472
|
-
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
|
|
13479
|
-
|
|
13480
|
-
|
|
13481
|
-
}
|
|
13482
|
-
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
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>`, "");
|
|
13488
14645
|
}
|
|
13489
|
-
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
|
|
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
|
|
13493
14655
|
}
|
|
13494
|
-
|
|
13495
|
-
success("commit-msg hook removed.");
|
|
13496
|
-
}
|
|
14656
|
+
});
|
|
13497
14657
|
|
|
13498
14658
|
// src/commands/log.ts
|
|
13499
|
-
var
|
|
14659
|
+
var import_picocolors15 = __toESM(require_picocolors(), 1);
|
|
13500
14660
|
function getDefaultOverviewRemoteCommitCount(hasLocalUnpushedCommits) {
|
|
13501
14661
|
return hasLocalUnpushedCommits ? 10 : 20;
|
|
13502
14662
|
}
|
|
@@ -13594,9 +14754,9 @@ var log_default = defineCommand({
|
|
|
13594
14754
|
} else if (mode === "local") {
|
|
13595
14755
|
if (!compareRef) {
|
|
13596
14756
|
console.log();
|
|
13597
|
-
console.log(
|
|
13598
|
-
console.log(
|
|
13599
|
-
console.log(
|
|
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.`));
|
|
13600
14760
|
console.log();
|
|
13601
14761
|
return;
|
|
13602
14762
|
}
|
|
@@ -13615,8 +14775,8 @@ var log_default = defineCommand({
|
|
|
13615
14775
|
const remoteBranch = compareRef ?? targetBranch;
|
|
13616
14776
|
if (!remoteBranch) {
|
|
13617
14777
|
console.log();
|
|
13618
|
-
console.log(
|
|
13619
|
-
console.log(
|
|
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."));
|
|
13620
14780
|
console.log();
|
|
13621
14781
|
return;
|
|
13622
14782
|
}
|
|
@@ -13675,31 +14835,31 @@ async function getOverviewRemoteCommitCount(currentBranch, compareRef) {
|
|
|
13675
14835
|
}
|
|
13676
14836
|
function printModeHeader(mode, currentBranch, compareRef, usingFallback = false) {
|
|
13677
14837
|
const branch = currentBranch ?? "HEAD";
|
|
13678
|
-
const fallbackNote = usingFallback ?
|
|
14838
|
+
const fallbackNote = usingFallback ? import_picocolors15.default.yellow(" (no upstream \u2014 comparing against base branch)") : "";
|
|
13679
14839
|
switch (mode) {
|
|
13680
14840
|
case "overview":
|
|
13681
|
-
console.log(
|
|
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);
|
|
13682
14842
|
if (compareRef) {
|
|
13683
|
-
console.log(
|
|
14843
|
+
console.log(import_picocolors15.default.dim(` remote source: ${import_picocolors15.default.bold(compareRef)}`));
|
|
13684
14844
|
}
|
|
13685
14845
|
break;
|
|
13686
14846
|
case "local":
|
|
13687
|
-
console.log(
|
|
14847
|
+
console.log(import_picocolors15.default.dim(` mode: ${import_picocolors15.default.bold("local")} \u2014 unpushed commits on ${import_picocolors15.default.bold(branch)}`) + fallbackNote);
|
|
13688
14848
|
if (compareRef) {
|
|
13689
|
-
console.log(
|
|
14849
|
+
console.log(import_picocolors15.default.dim(` comparing: ${import_picocolors15.default.bold(compareRef)} \u279C ${import_picocolors15.default.bold("HEAD")}`));
|
|
13690
14850
|
}
|
|
13691
14851
|
break;
|
|
13692
14852
|
case "remote":
|
|
13693
|
-
console.log(
|
|
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);
|
|
13694
14854
|
if (compareRef) {
|
|
13695
|
-
console.log(
|
|
14855
|
+
console.log(import_picocolors15.default.dim(` branch: ${import_picocolors15.default.bold(compareRef)}`));
|
|
13696
14856
|
}
|
|
13697
14857
|
break;
|
|
13698
14858
|
case "full":
|
|
13699
|
-
console.log(
|
|
14859
|
+
console.log(import_picocolors15.default.dim(` mode: ${import_picocolors15.default.bold("full")} \u2014 complete commit history for ${import_picocolors15.default.bold(branch)}`));
|
|
13700
14860
|
break;
|
|
13701
14861
|
case "all":
|
|
13702
|
-
console.log(
|
|
14862
|
+
console.log(import_picocolors15.default.dim(` mode: ${import_picocolors15.default.bold("all")} \u2014 commits across all branches`));
|
|
13703
14863
|
break;
|
|
13704
14864
|
}
|
|
13705
14865
|
}
|
|
@@ -13723,7 +14883,7 @@ async function renderScopedLog(options) {
|
|
|
13723
14883
|
}
|
|
13724
14884
|
console.log();
|
|
13725
14885
|
for (const entry of entries) {
|
|
13726
|
-
const hashStr =
|
|
14886
|
+
const hashStr = import_picocolors15.default.yellow(entry.hash);
|
|
13727
14887
|
const refsStr = entry.refs ? ` ${colorizeRefs(entry.refs, protectedBranches, currentBranch)}` : "";
|
|
13728
14888
|
const subjectStr = colorizeSubject(entry.subject);
|
|
13729
14889
|
console.log(` ${hashStr}${refsStr} ${subjectStr}`);
|
|
@@ -13742,10 +14902,10 @@ async function renderOverviewLog(options) {
|
|
|
13742
14902
|
usingFallback
|
|
13743
14903
|
} = options;
|
|
13744
14904
|
console.log();
|
|
13745
|
-
console.log(
|
|
14905
|
+
console.log(import_picocolors15.default.bold(import_picocolors15.default.cyan(" Local Unpushed Commits")));
|
|
13746
14906
|
if (!compareRef) {
|
|
13747
|
-
console.log(
|
|
13748
|
-
console.log(
|
|
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."));
|
|
13749
14909
|
} else {
|
|
13750
14910
|
await renderScopedLog({
|
|
13751
14911
|
mode: "local",
|
|
@@ -13757,11 +14917,11 @@ async function renderOverviewLog(options) {
|
|
|
13757
14917
|
});
|
|
13758
14918
|
}
|
|
13759
14919
|
console.log();
|
|
13760
|
-
console.log(
|
|
14920
|
+
console.log(import_picocolors15.default.bold(import_picocolors15.default.cyan(" Remote Branch History")));
|
|
13761
14921
|
if (!compareRef) {
|
|
13762
|
-
console.log(
|
|
14922
|
+
console.log(import_picocolors15.default.dim(" No remote branch detected."));
|
|
13763
14923
|
if (usingFallback) {
|
|
13764
|
-
console.log(
|
|
14924
|
+
console.log(import_picocolors15.default.dim(" Configure your base branch or upstream tracking to enable the split view."));
|
|
13765
14925
|
}
|
|
13766
14926
|
return;
|
|
13767
14927
|
}
|
|
@@ -13774,15 +14934,15 @@ async function renderOverviewLog(options) {
|
|
|
13774
14934
|
currentBranch
|
|
13775
14935
|
});
|
|
13776
14936
|
if (!hasRemoteHistory) {
|
|
13777
|
-
console.log(
|
|
14937
|
+
console.log(import_picocolors15.default.dim(" No remote history found for the selected branch."));
|
|
13778
14938
|
}
|
|
13779
14939
|
}
|
|
13780
14940
|
function printEmptyState(mode) {
|
|
13781
14941
|
console.log();
|
|
13782
14942
|
if (mode === "local") {
|
|
13783
|
-
console.log(
|
|
14943
|
+
console.log(import_picocolors15.default.dim(" No local unpushed commits \u2014 you're up to date with remote!"));
|
|
13784
14944
|
} else {
|
|
13785
|
-
console.log(
|
|
14945
|
+
console.log(import_picocolors15.default.dim(" No remote-only commits \u2014 your local branch is up to date!"));
|
|
13786
14946
|
}
|
|
13787
14947
|
console.log();
|
|
13788
14948
|
}
|
|
@@ -13791,7 +14951,7 @@ async function renderFullLog(options) {
|
|
|
13791
14951
|
if (showGraph) {
|
|
13792
14952
|
const lines = await getLogGraph({ count, all, branch: targetBranch });
|
|
13793
14953
|
if (lines.length === 0) {
|
|
13794
|
-
console.log(
|
|
14954
|
+
console.log(import_picocolors15.default.dim(" No commits found."));
|
|
13795
14955
|
console.log();
|
|
13796
14956
|
return false;
|
|
13797
14957
|
}
|
|
@@ -13802,13 +14962,13 @@ async function renderFullLog(options) {
|
|
|
13802
14962
|
} else {
|
|
13803
14963
|
const entries = await getLogEntries({ count, all, branch: targetBranch });
|
|
13804
14964
|
if (entries.length === 0) {
|
|
13805
|
-
console.log(
|
|
14965
|
+
console.log(import_picocolors15.default.dim(" No commits found."));
|
|
13806
14966
|
console.log();
|
|
13807
14967
|
return false;
|
|
13808
14968
|
}
|
|
13809
14969
|
console.log();
|
|
13810
14970
|
for (const entry of entries) {
|
|
13811
|
-
const hashStr =
|
|
14971
|
+
const hashStr = import_picocolors15.default.yellow(entry.hash);
|
|
13812
14972
|
const refsStr = entry.refs ? ` ${colorizeRefs(entry.refs, protectedBranches, currentBranch)}` : "";
|
|
13813
14973
|
const subjectStr = colorizeSubject(entry.subject);
|
|
13814
14974
|
console.log(` ${hashStr}${refsStr} ${subjectStr}`);
|
|
@@ -13820,33 +14980,33 @@ function printFooter(mode, count, overviewRemoteCount, targetBranch) {
|
|
|
13820
14980
|
console.log();
|
|
13821
14981
|
switch (mode) {
|
|
13822
14982
|
case "overview":
|
|
13823
|
-
console.log(
|
|
14983
|
+
console.log(import_picocolors15.default.dim(` Showing up to ${count} local commits and ${overviewRemoteCount} remote commits`));
|
|
13824
14984
|
break;
|
|
13825
14985
|
case "local":
|
|
13826
|
-
console.log(
|
|
14986
|
+
console.log(import_picocolors15.default.dim(` Showing up to ${count} unpushed commits`));
|
|
13827
14987
|
break;
|
|
13828
14988
|
case "remote":
|
|
13829
|
-
console.log(
|
|
14989
|
+
console.log(import_picocolors15.default.dim(` Showing ${count} most recent commits from the remote branch`));
|
|
13830
14990
|
break;
|
|
13831
14991
|
case "full":
|
|
13832
|
-
console.log(
|
|
14992
|
+
console.log(import_picocolors15.default.dim(` Showing ${count} most recent commits${targetBranch ? ` (${targetBranch})` : ""}`));
|
|
13833
14993
|
break;
|
|
13834
14994
|
case "all":
|
|
13835
|
-
console.log(
|
|
14995
|
+
console.log(import_picocolors15.default.dim(` Showing ${count} most recent commits (all branches)`));
|
|
13836
14996
|
break;
|
|
13837
14997
|
}
|
|
13838
14998
|
}
|
|
13839
14999
|
function colorizeGraphLine(line, protectedBranches, currentBranch) {
|
|
13840
15000
|
const match = line.match(/^([|/\\*\s_.-]*)([a-f0-9]{7,12})(\s+\(([^)]+)\))?\s*(.*)/);
|
|
13841
15001
|
if (!match) {
|
|
13842
|
-
return
|
|
15002
|
+
return import_picocolors15.default.cyan(line);
|
|
13843
15003
|
}
|
|
13844
15004
|
const [, graphPart = "", hash, , refs, subject = ""] = match;
|
|
13845
15005
|
const parts = [];
|
|
13846
15006
|
if (graphPart) {
|
|
13847
15007
|
parts.push(colorizeGraphChars(graphPart));
|
|
13848
15008
|
}
|
|
13849
|
-
parts.push(
|
|
15009
|
+
parts.push(import_picocolors15.default.yellow(hash));
|
|
13850
15010
|
if (refs) {
|
|
13851
15011
|
parts.push(` (${colorizeRefs(refs, protectedBranches, currentBranch)})`);
|
|
13852
15012
|
}
|
|
@@ -13857,15 +15017,15 @@ function colorizeGraphChars(graphPart) {
|
|
|
13857
15017
|
return graphPart.split("").map((ch) => {
|
|
13858
15018
|
switch (ch) {
|
|
13859
15019
|
case "*":
|
|
13860
|
-
return
|
|
15020
|
+
return import_picocolors15.default.green(ch);
|
|
13861
15021
|
case "|":
|
|
13862
|
-
return
|
|
15022
|
+
return import_picocolors15.default.cyan(ch);
|
|
13863
15023
|
case "/":
|
|
13864
15024
|
case "\\":
|
|
13865
|
-
return
|
|
15025
|
+
return import_picocolors15.default.cyan(ch);
|
|
13866
15026
|
case "-":
|
|
13867
15027
|
case "_":
|
|
13868
|
-
return
|
|
15028
|
+
return import_picocolors15.default.cyan(ch);
|
|
13869
15029
|
default:
|
|
13870
15030
|
return ch;
|
|
13871
15031
|
}
|
|
@@ -13877,50 +15037,50 @@ function colorizeRefs(refs, protectedBranches, currentBranch) {
|
|
|
13877
15037
|
if (trimmed.startsWith("HEAD ->") || trimmed === "HEAD") {
|
|
13878
15038
|
const branchName = trimmed.replace("HEAD -> ", "");
|
|
13879
15039
|
if (trimmed === "HEAD") {
|
|
13880
|
-
return
|
|
15040
|
+
return import_picocolors15.default.bold(import_picocolors15.default.cyan("HEAD"));
|
|
13881
15041
|
}
|
|
13882
|
-
return `${
|
|
15042
|
+
return `${import_picocolors15.default.bold(import_picocolors15.default.cyan("HEAD"))} ${import_picocolors15.default.dim("->")} ${colorizeRefName(branchName, protectedBranches, currentBranch)}`;
|
|
13883
15043
|
}
|
|
13884
15044
|
if (trimmed.startsWith("tag:")) {
|
|
13885
|
-
return
|
|
15045
|
+
return import_picocolors15.default.bold(import_picocolors15.default.magenta(trimmed));
|
|
13886
15046
|
}
|
|
13887
15047
|
return colorizeRefName(trimmed, protectedBranches, currentBranch);
|
|
13888
|
-
}).join(
|
|
15048
|
+
}).join(import_picocolors15.default.dim(", "));
|
|
13889
15049
|
}
|
|
13890
15050
|
function colorizeRefName(name, protectedBranches, currentBranch) {
|
|
13891
15051
|
const isRemote = name.includes("/");
|
|
13892
15052
|
const localName = isRemote ? name.split("/").slice(1).join("/") : name;
|
|
13893
15053
|
if (protectedBranches.includes(localName)) {
|
|
13894
|
-
return isRemote ?
|
|
15054
|
+
return isRemote ? import_picocolors15.default.bold(import_picocolors15.default.red(name)) : import_picocolors15.default.bold(import_picocolors15.default.red(name));
|
|
13895
15055
|
}
|
|
13896
15056
|
if (localName === currentBranch) {
|
|
13897
|
-
return
|
|
15057
|
+
return import_picocolors15.default.bold(import_picocolors15.default.green(name));
|
|
13898
15058
|
}
|
|
13899
15059
|
if (isRemote) {
|
|
13900
|
-
return
|
|
15060
|
+
return import_picocolors15.default.blue(name);
|
|
13901
15061
|
}
|
|
13902
|
-
return
|
|
15062
|
+
return import_picocolors15.default.green(name);
|
|
13903
15063
|
}
|
|
13904
15064
|
function colorizeSubject(subject) {
|
|
13905
15065
|
const emojiMatch = subject.match(/^((?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F)+\s*)/u);
|
|
13906
15066
|
if (emojiMatch) {
|
|
13907
15067
|
const emoji = emojiMatch[1];
|
|
13908
15068
|
const rest = subject.slice(emoji.length);
|
|
13909
|
-
return `${emoji}${
|
|
15069
|
+
return `${emoji}${import_picocolors15.default.white(rest)}`;
|
|
13910
15070
|
}
|
|
13911
15071
|
if (subject.startsWith("Merge ")) {
|
|
13912
|
-
return
|
|
15072
|
+
return import_picocolors15.default.dim(subject);
|
|
13913
15073
|
}
|
|
13914
|
-
return
|
|
15074
|
+
return import_picocolors15.default.white(subject);
|
|
13915
15075
|
}
|
|
13916
15076
|
|
|
13917
15077
|
// src/commands/save.ts
|
|
13918
15078
|
import { execFile as execFileCb4 } from "child_process";
|
|
13919
|
-
var
|
|
15079
|
+
var import_picocolors16 = __toESM(require_picocolors(), 1);
|
|
13920
15080
|
function gitRun(args) {
|
|
13921
|
-
return new Promise((
|
|
15081
|
+
return new Promise((resolve6) => {
|
|
13922
15082
|
execFileCb4("git", args, (err, stdout2, stderr) => {
|
|
13923
|
-
|
|
15083
|
+
resolve6({
|
|
13924
15084
|
exitCode: err ? err.code === "ENOENT" ? 127 : err.status ?? 1 : 0,
|
|
13925
15085
|
stdout: stdout2 ?? "",
|
|
13926
15086
|
stderr: stderr ?? ""
|
|
@@ -13990,8 +15150,8 @@ async function handleSave(message) {
|
|
|
13990
15150
|
info("No uncommitted changes to save.");
|
|
13991
15151
|
return;
|
|
13992
15152
|
}
|
|
13993
|
-
success(`Saved: ${
|
|
13994
|
-
info(`Use ${
|
|
15153
|
+
success(`Saved: ${import_picocolors16.default.dim(label)}`);
|
|
15154
|
+
info(`Use ${import_picocolors16.default.bold("cn save --restore")} to bring them back.`, "");
|
|
13995
15155
|
}
|
|
13996
15156
|
async function handleRestore() {
|
|
13997
15157
|
await projectHeading("save --restore", "\uD83D\uDCBE");
|
|
@@ -14007,7 +15167,7 @@ async function handleRestore() {
|
|
|
14007
15167
|
warn("You may have conflicts. Resolve them and run `git stash drop` when done.");
|
|
14008
15168
|
process.exit(1);
|
|
14009
15169
|
}
|
|
14010
|
-
success(`Restored: ${
|
|
15170
|
+
success(`Restored: ${import_picocolors16.default.dim(stashes[0].message)}`);
|
|
14011
15171
|
return;
|
|
14012
15172
|
}
|
|
14013
15173
|
const choices = stashes.map((s2) => `${s2.index} ${s2.message}`);
|
|
@@ -14020,7 +15180,7 @@ async function handleRestore() {
|
|
|
14020
15180
|
process.exit(1);
|
|
14021
15181
|
}
|
|
14022
15182
|
const match = stashes.find((s2) => String(s2.index) === idx);
|
|
14023
|
-
success(`Restored: ${
|
|
15183
|
+
success(`Restored: ${import_picocolors16.default.dim(match?.message ?? "saved changes")}`);
|
|
14024
15184
|
}
|
|
14025
15185
|
async function handleList() {
|
|
14026
15186
|
await projectHeading("save --list", "\uD83D\uDCBE");
|
|
@@ -14031,13 +15191,13 @@ async function handleList() {
|
|
|
14031
15191
|
}
|
|
14032
15192
|
console.log();
|
|
14033
15193
|
for (const s2 of stashes) {
|
|
14034
|
-
const idx =
|
|
15194
|
+
const idx = import_picocolors16.default.dim(`[${s2.index}]`);
|
|
14035
15195
|
const msg = s2.message;
|
|
14036
15196
|
console.log(` ${idx} ${msg}`);
|
|
14037
15197
|
}
|
|
14038
15198
|
console.log();
|
|
14039
|
-
info(`Use ${
|
|
14040
|
-
info(`Use ${
|
|
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.`, "");
|
|
14041
15201
|
}
|
|
14042
15202
|
async function handleDrop() {
|
|
14043
15203
|
await projectHeading("save --drop", "\uD83D\uDCBE");
|
|
@@ -14055,7 +15215,7 @@ async function handleDrop() {
|
|
|
14055
15215
|
process.exit(1);
|
|
14056
15216
|
}
|
|
14057
15217
|
const match = stashes.find((s2) => String(s2.index) === idx);
|
|
14058
|
-
success(`Dropped: ${
|
|
15218
|
+
success(`Dropped: ${import_picocolors16.default.dim(match?.message ?? "saved changes")}`);
|
|
14059
15219
|
}
|
|
14060
15220
|
async function getStashList() {
|
|
14061
15221
|
const result = await gitRun(["stash", "list"]);
|
|
@@ -14072,7 +15232,8 @@ async function getStashList() {
|
|
|
14072
15232
|
}
|
|
14073
15233
|
|
|
14074
15234
|
// src/commands/setup.ts
|
|
14075
|
-
var
|
|
15235
|
+
var import_picocolors17 = __toESM(require_picocolors(), 1);
|
|
15236
|
+
init_gh();
|
|
14076
15237
|
async function shouldContinueSetupWithExistingConfig(options) {
|
|
14077
15238
|
const { existingConfig, hasConfigFile, confirm, onInfo, onWarn, onSuccess, summary } = options;
|
|
14078
15239
|
if (existingConfig) {
|
|
@@ -14095,6 +15256,43 @@ async function shouldContinueSetupWithExistingConfig(options) {
|
|
|
14095
15256
|
}
|
|
14096
15257
|
return true;
|
|
14097
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
|
+
}
|
|
14098
15296
|
async function promptForOllamaCloudModel(apiKey, host = DEFAULT_OLLAMA_CLOUD_HOST) {
|
|
14099
15297
|
try {
|
|
14100
15298
|
info("Fetching available Ollama Cloud models...");
|
|
@@ -14121,6 +15319,32 @@ async function promptForOllamaCloudModel(apiKey, host = DEFAULT_OLLAMA_CLOUD_HOS
|
|
|
14121
15319
|
}
|
|
14122
15320
|
return inputPrompt(`Ollama Cloud model (default: ${DEFAULT_OLLAMA_CLOUD_MODEL} \u2014 press Enter to keep)`, DEFAULT_OLLAMA_CLOUD_MODEL);
|
|
14123
15321
|
}
|
|
15322
|
+
async function promptForOpenRouterModel(apiKey) {
|
|
15323
|
+
try {
|
|
15324
|
+
info("Fetching available OpenRouter models...");
|
|
15325
|
+
const models = prioritizeOpenRouterModels(await fetchOpenRouterModels(apiKey));
|
|
15326
|
+
if (models.length > 0) {
|
|
15327
|
+
const manualChoice = "Enter model manually";
|
|
15328
|
+
const choices = models.map((model) => ({
|
|
15329
|
+
value: model,
|
|
15330
|
+
label: model === DEFAULT_OPENROUTER_MODEL ? `${model} (default)` : model
|
|
15331
|
+
}));
|
|
15332
|
+
const selected = await selectPrompt("Which OpenRouter model should this clone use?", [
|
|
15333
|
+
...choices.map((choice) => choice.label),
|
|
15334
|
+
manualChoice
|
|
15335
|
+
]);
|
|
15336
|
+
if (selected !== manualChoice) {
|
|
15337
|
+
return choices.find((choice) => choice.label === selected)?.value ?? DEFAULT_OPENROUTER_MODEL;
|
|
15338
|
+
}
|
|
15339
|
+
} else {
|
|
15340
|
+
warn("OpenRouter returned no available models. Enter the model name manually.");
|
|
15341
|
+
}
|
|
15342
|
+
} catch (err) {
|
|
15343
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
15344
|
+
warn(`Could not fetch OpenRouter models: ${message}`);
|
|
15345
|
+
}
|
|
15346
|
+
return inputPrompt(`OpenRouter model (default: ${DEFAULT_OPENROUTER_MODEL} \u2014 press Enter to keep)`, DEFAULT_OPENROUTER_MODEL);
|
|
15347
|
+
}
|
|
14124
15348
|
var setup_default = defineCommand({
|
|
14125
15349
|
meta: {
|
|
14126
15350
|
name: "setup",
|
|
@@ -14155,7 +15379,7 @@ var setup_default = defineCommand({
|
|
|
14155
15379
|
workflow = "github-flow";
|
|
14156
15380
|
else if (workflowChoice.startsWith("Git Flow"))
|
|
14157
15381
|
workflow = "git-flow";
|
|
14158
|
-
info(`Workflow: ${
|
|
15382
|
+
info(`Workflow: ${import_picocolors17.default.bold(WORKFLOW_DESCRIPTIONS[workflow])}`);
|
|
14159
15383
|
const conventionChoice = await selectPrompt("Which commit convention should this project use?", [
|
|
14160
15384
|
`${CONVENTION_DESCRIPTIONS["clean-commit"]} (recommended)`,
|
|
14161
15385
|
CONVENTION_DESCRIPTIONS.conventional,
|
|
@@ -14172,25 +15396,74 @@ var setup_default = defineCommand({
|
|
|
14172
15396
|
if (enableAI) {
|
|
14173
15397
|
const providerChoice = await selectPrompt("Which AI provider should this clone use?", [
|
|
14174
15398
|
"GitHub Copilot \u2014 use your existing GitHub/Copilot auth",
|
|
14175
|
-
"Ollama Cloud \u2014 use an API key stored in the local secrets store"
|
|
15399
|
+
"Ollama Cloud \u2014 use an API key stored in the local secrets store",
|
|
15400
|
+
"OpenRouter \u2014 use an API key stored in the local secrets store"
|
|
14176
15401
|
]);
|
|
14177
|
-
|
|
15402
|
+
if (providerChoice.startsWith("Ollama Cloud")) {
|
|
15403
|
+
aiProvider = "ollama-cloud";
|
|
15404
|
+
} else if (providerChoice.startsWith("OpenRouter")) {
|
|
15405
|
+
aiProvider = "openrouter";
|
|
15406
|
+
} else {
|
|
15407
|
+
aiProvider = "copilot";
|
|
15408
|
+
}
|
|
14178
15409
|
if (aiProvider === "ollama-cloud") {
|
|
14179
|
-
|
|
14180
|
-
|
|
14181
|
-
|
|
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);
|
|
14182
15422
|
process.exit(1);
|
|
14183
15423
|
}
|
|
14184
|
-
aiModel = await promptForOllamaCloudModel(apiKey);
|
|
15424
|
+
aiModel = await promptForOllamaCloudModel(resolvedKey.apiKey);
|
|
14185
15425
|
try {
|
|
14186
|
-
|
|
14187
|
-
|
|
14188
|
-
|
|
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
|
+
}
|
|
14189
15433
|
} catch (err) {
|
|
14190
15434
|
const message = err instanceof Error ? err.message : String(err);
|
|
14191
15435
|
error(`Failed to store Ollama Cloud API key: ${message}`);
|
|
14192
15436
|
process.exit(1);
|
|
14193
15437
|
}
|
|
15438
|
+
} else if (aiProvider === "openrouter") {
|
|
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);
|
|
15451
|
+
process.exit(1);
|
|
15452
|
+
}
|
|
15453
|
+
aiModel = await promptForOpenRouterModel(resolvedKey.apiKey);
|
|
15454
|
+
try {
|
|
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
|
+
}
|
|
15462
|
+
} catch (err) {
|
|
15463
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
15464
|
+
error(`Failed to store OpenRouter API key: ${message}`);
|
|
15465
|
+
process.exit(1);
|
|
15466
|
+
}
|
|
14194
15467
|
}
|
|
14195
15468
|
}
|
|
14196
15469
|
const showTips = await confirmPrompt("Show beginner quick guides and loading tips in command output?");
|
|
@@ -14247,15 +15520,15 @@ var setup_default = defineCommand({
|
|
|
14247
15520
|
detectedRole = roleChoice;
|
|
14248
15521
|
detectionSource = "user selection";
|
|
14249
15522
|
} else {
|
|
14250
|
-
info(`Detected role: ${
|
|
14251
|
-
const confirmed = await confirmPrompt(`Role detected as ${
|
|
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?`);
|
|
14252
15525
|
if (!confirmed) {
|
|
14253
15526
|
const roleChoice = await selectPrompt("Select your role:", ["maintainer", "contributor"]);
|
|
14254
15527
|
detectedRole = roleChoice;
|
|
14255
15528
|
}
|
|
14256
15529
|
}
|
|
14257
15530
|
const defaultConfig = getDefaultConfig();
|
|
14258
|
-
info(
|
|
15531
|
+
info(import_picocolors17.default.dim("Tip: press Enter to keep the default branch name shown in each prompt."));
|
|
14259
15532
|
const mainBranchDefault = defaultConfig.mainBranch;
|
|
14260
15533
|
const mainBranch = await inputPrompt(`Main branch name (default: ${mainBranchDefault} \u2014 press Enter to keep)`, mainBranchDefault);
|
|
14261
15534
|
let devBranch;
|
|
@@ -14281,7 +15554,7 @@ var setup_default = defineCommand({
|
|
|
14281
15554
|
error("Setup cannot continue without the upstream remote for contributors.");
|
|
14282
15555
|
process.exit(1);
|
|
14283
15556
|
}
|
|
14284
|
-
success(`Added remote ${
|
|
15557
|
+
success(`Added remote ${import_picocolors17.default.bold(upstreamRemote)} \u2192 ${upstreamUrl}`);
|
|
14285
15558
|
} else {
|
|
14286
15559
|
error("An upstream remote URL is required for contributors.");
|
|
14287
15560
|
info("Add it manually: git remote add upstream <url>", "");
|
|
@@ -14304,67 +15577,67 @@ var setup_default = defineCommand({
|
|
|
14304
15577
|
showTips
|
|
14305
15578
|
};
|
|
14306
15579
|
writeConfig(config);
|
|
14307
|
-
success(`Config written to ${
|
|
15580
|
+
success(`Config written to ${import_picocolors17.default.bold(getConfigLocationLabel())}`);
|
|
14308
15581
|
info("This setup is stored locally for this clone and does not modify tracked files.", "");
|
|
14309
15582
|
const syncRemote = config.role === "contributor" ? config.upstream : config.origin;
|
|
14310
|
-
info(`Fetching ${
|
|
15583
|
+
info(`Fetching ${import_picocolors17.default.bold(syncRemote)} to verify branch configuration...`, "");
|
|
14311
15584
|
await fetchRemote(syncRemote);
|
|
14312
15585
|
const mainRef = `${syncRemote}/${config.mainBranch}`;
|
|
14313
15586
|
if (!await refExists(mainRef)) {
|
|
14314
|
-
warn(`Main branch ref ${
|
|
15587
|
+
warn(`Main branch ref ${import_picocolors17.default.bold(mainRef)} not found on remote.`);
|
|
14315
15588
|
warn("Config was saved \u2014 verify the branch name and re-run setup if needed.");
|
|
14316
15589
|
}
|
|
14317
15590
|
if (config.devBranch) {
|
|
14318
15591
|
const devRef = `${syncRemote}/${config.devBranch}`;
|
|
14319
15592
|
if (!await refExists(devRef)) {
|
|
14320
|
-
warn(`Dev branch ref ${
|
|
15593
|
+
warn(`Dev branch ref ${import_picocolors17.default.bold(devRef)} not found on remote.`);
|
|
14321
15594
|
warn("Config was saved \u2014 verify the branch name and re-run setup if needed.");
|
|
14322
15595
|
}
|
|
14323
15596
|
}
|
|
14324
15597
|
console.log();
|
|
14325
15598
|
const resolvedAIConfig = resolveAIConfig(config);
|
|
14326
|
-
info(`Workflow: ${
|
|
14327
|
-
info(`Convention: ${
|
|
14328
|
-
info(`AI: ${
|
|
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")}`);
|
|
14329
15602
|
if (isAIEnabled(config)) {
|
|
14330
|
-
info(`AI provider: ${
|
|
15603
|
+
info(`AI provider: ${import_picocolors17.default.bold(resolvedAIConfig.providerLabel)}`);
|
|
14331
15604
|
if (resolvedAIConfig.model) {
|
|
14332
|
-
info(`AI model: ${
|
|
15605
|
+
info(`AI model: ${import_picocolors17.default.bold(resolvedAIConfig.model)}`);
|
|
14333
15606
|
}
|
|
14334
15607
|
}
|
|
14335
|
-
info(`Guides: ${
|
|
14336
|
-
info(`Role: ${
|
|
15608
|
+
info(`Guides: ${import_picocolors17.default.bold(shouldShowTips(config) ? "shown" : "hidden")}`);
|
|
15609
|
+
info(`Role: ${import_picocolors17.default.bold(config.role)}`);
|
|
14337
15610
|
if (config.devBranch) {
|
|
14338
|
-
info(`Main: ${
|
|
15611
|
+
info(`Main: ${import_picocolors17.default.bold(config.mainBranch)} | Dev: ${import_picocolors17.default.bold(config.devBranch)}`);
|
|
14339
15612
|
} else {
|
|
14340
|
-
info(`Main: ${
|
|
15613
|
+
info(`Main: ${import_picocolors17.default.bold(config.mainBranch)}`);
|
|
14341
15614
|
}
|
|
14342
|
-
info(`Origin: ${
|
|
15615
|
+
info(`Origin: ${import_picocolors17.default.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${import_picocolors17.default.bold(config.upstream)}` : ""}`);
|
|
14343
15616
|
}
|
|
14344
15617
|
});
|
|
14345
15618
|
function logConfigSummary(config) {
|
|
14346
15619
|
const aiConfig = resolveAIConfig(config);
|
|
14347
|
-
info(`Workflow: ${
|
|
14348
|
-
info(`Convention: ${
|
|
14349
|
-
info(`AI: ${
|
|
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")}`);
|
|
14350
15623
|
if (isAIEnabled(config)) {
|
|
14351
|
-
info(`AI provider: ${
|
|
15624
|
+
info(`AI provider: ${import_picocolors17.default.bold(aiConfig.providerLabel)}`);
|
|
14352
15625
|
if (aiConfig.model) {
|
|
14353
|
-
info(`AI model: ${
|
|
15626
|
+
info(`AI model: ${import_picocolors17.default.bold(aiConfig.model)}`);
|
|
14354
15627
|
}
|
|
14355
15628
|
}
|
|
14356
|
-
info(`Guides: ${
|
|
14357
|
-
info(`Role: ${
|
|
15629
|
+
info(`Guides: ${import_picocolors17.default.bold(shouldShowTips(config) ? "shown" : "hidden")}`);
|
|
15630
|
+
info(`Role: ${import_picocolors17.default.bold(config.role)}`);
|
|
14358
15631
|
if (config.devBranch) {
|
|
14359
|
-
info(`Main: ${
|
|
15632
|
+
info(`Main: ${import_picocolors17.default.bold(config.mainBranch)} | Dev: ${import_picocolors17.default.bold(config.devBranch)}`);
|
|
14360
15633
|
} else {
|
|
14361
|
-
info(`Main: ${
|
|
15634
|
+
info(`Main: ${import_picocolors17.default.bold(config.mainBranch)}`);
|
|
14362
15635
|
}
|
|
14363
|
-
info(`Origin: ${
|
|
15636
|
+
info(`Origin: ${import_picocolors17.default.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${import_picocolors17.default.bold(config.upstream)}` : ""}`);
|
|
14364
15637
|
}
|
|
14365
15638
|
|
|
14366
15639
|
// src/commands/start.ts
|
|
14367
|
-
var
|
|
15640
|
+
var import_picocolors18 = __toESM(require_picocolors(), 1);
|
|
14368
15641
|
var start_default = defineCommand({
|
|
14369
15642
|
meta: {
|
|
14370
15643
|
name: "start",
|
|
@@ -14416,16 +15689,16 @@ var start_default = defineCommand({
|
|
|
14416
15689
|
warn("Start cancelled.");
|
|
14417
15690
|
process.exit(0);
|
|
14418
15691
|
}
|
|
14419
|
-
info(`Creating branch: ${
|
|
15692
|
+
info(`Creating branch: ${import_picocolors18.default.bold(branchName)}`);
|
|
14420
15693
|
await fetchRemote(syncSource.remote);
|
|
14421
15694
|
if (!await refExists(syncSource.ref)) {
|
|
14422
|
-
warn(`Remote ref ${
|
|
15695
|
+
warn(`Remote ref ${import_picocolors18.default.bold(syncSource.ref)} not found. Creating branch from local ${import_picocolors18.default.bold(baseBranch)}.`);
|
|
14423
15696
|
}
|
|
14424
15697
|
const currentBranch = await getCurrentBranch();
|
|
14425
15698
|
if (currentBranch === baseBranch && await refExists(syncSource.ref)) {
|
|
14426
15699
|
const ahead = await countCommitsAhead(baseBranch, syncSource.ref);
|
|
14427
15700
|
if (ahead > 0) {
|
|
14428
|
-
warn(`You are on ${
|
|
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)}.`);
|
|
14429
15702
|
info(" Syncing will discard those commits. Consider backing them up first (e.g. create a branch).");
|
|
14430
15703
|
const proceed = await confirmPrompt("Discard local commits and sync to remote?");
|
|
14431
15704
|
if (!proceed) {
|
|
@@ -14442,10 +15715,10 @@ var start_default = defineCommand({
|
|
|
14442
15715
|
error(`Failed to create branch: ${result2.stderr}`);
|
|
14443
15716
|
process.exit(1);
|
|
14444
15717
|
}
|
|
14445
|
-
success(`Created ${
|
|
15718
|
+
success(`Created ${import_picocolors18.default.bold(branchName)} from ${import_picocolors18.default.bold(syncSource.ref)}`);
|
|
14446
15719
|
return;
|
|
14447
15720
|
}
|
|
14448
|
-
error(`Failed to update ${
|
|
15721
|
+
error(`Failed to update ${import_picocolors18.default.bold(baseBranch)}: ${updateResult.stderr}`);
|
|
14449
15722
|
info("Make sure your base branch exists locally or the remote ref is available.", "");
|
|
14450
15723
|
process.exit(1);
|
|
14451
15724
|
}
|
|
@@ -14454,12 +15727,13 @@ var start_default = defineCommand({
|
|
|
14454
15727
|
error(`Failed to create branch: ${result.stderr}`);
|
|
14455
15728
|
process.exit(1);
|
|
14456
15729
|
}
|
|
14457
|
-
success(`Created ${
|
|
15730
|
+
success(`Created ${import_picocolors18.default.bold(branchName)} from latest ${import_picocolors18.default.bold(baseBranch)}`);
|
|
14458
15731
|
}
|
|
14459
15732
|
});
|
|
14460
15733
|
|
|
14461
15734
|
// src/commands/status.ts
|
|
14462
|
-
var
|
|
15735
|
+
var import_picocolors19 = __toESM(require_picocolors(), 1);
|
|
15736
|
+
init_gh();
|
|
14463
15737
|
var status_default = defineCommand({
|
|
14464
15738
|
meta: {
|
|
14465
15739
|
name: "status",
|
|
@@ -14476,8 +15750,8 @@ var status_default = defineCommand({
|
|
|
14476
15750
|
process.exit(1);
|
|
14477
15751
|
}
|
|
14478
15752
|
await projectHeading("status", "\uD83D\uDCCA");
|
|
14479
|
-
console.log(` ${
|
|
14480
|
-
console.log(` ${
|
|
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)}`);
|
|
14481
15755
|
console.log();
|
|
14482
15756
|
await fetchAll();
|
|
14483
15757
|
const currentBranch = await getCurrentBranch();
|
|
@@ -14486,7 +15760,7 @@ var status_default = defineCommand({
|
|
|
14486
15760
|
const isContributor = config.role === "contributor";
|
|
14487
15761
|
const [dirty, fileStatus] = await Promise.all([hasUncommittedChanges(), getFileStatus()]);
|
|
14488
15762
|
if (dirty) {
|
|
14489
|
-
console.log(` ${
|
|
15763
|
+
console.log(` ${import_picocolors19.default.yellow("\u26A0")} ${import_picocolors19.default.yellow("Uncommitted changes in working tree")}`);
|
|
14490
15764
|
console.log();
|
|
14491
15765
|
}
|
|
14492
15766
|
const mainRemote = `${origin}/${mainBranch}`;
|
|
@@ -14505,16 +15779,16 @@ var status_default = defineCommand({
|
|
|
14505
15779
|
if (isFeatureBranch) {
|
|
14506
15780
|
const branchDiv = await getDivergence(currentBranch, baseBranch);
|
|
14507
15781
|
const branchLine = formatStatus(currentBranch, baseBranch, branchDiv.ahead, branchDiv.behind);
|
|
14508
|
-
console.log(branchLine +
|
|
15782
|
+
console.log(branchLine + import_picocolors19.default.dim(` (current ${import_picocolors19.default.green("*")})`));
|
|
14509
15783
|
branchStatus = await detectBranchStatus(currentBranch, baseBranch);
|
|
14510
15784
|
if (branchStatus.merged) {
|
|
14511
|
-
console.log(` ${
|
|
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")}`);
|
|
14512
15786
|
}
|
|
14513
15787
|
if (branchStatus.stale) {
|
|
14514
|
-
console.log(` ${
|
|
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`)}`);
|
|
14515
15789
|
}
|
|
14516
15790
|
} else if (currentBranch) {
|
|
14517
|
-
console.log(
|
|
15791
|
+
console.log(import_picocolors19.default.dim(` (on ${import_picocolors19.default.bold(currentBranch)} branch)`));
|
|
14518
15792
|
}
|
|
14519
15793
|
let branchesAligned = true;
|
|
14520
15794
|
{
|
|
@@ -14551,20 +15825,20 @@ var status_default = defineCommand({
|
|
|
14551
15825
|
}
|
|
14552
15826
|
branchesAligned = groups.size === 1;
|
|
14553
15827
|
console.log();
|
|
14554
|
-
console.log(` ${
|
|
15828
|
+
console.log(` ${import_picocolors19.default.bold("\uD83D\uDD17 Branch Alignment")}`);
|
|
14555
15829
|
for (const [hash, names] of groups) {
|
|
14556
15830
|
const short = hash.slice(0, 7);
|
|
14557
|
-
const nameStr = names.map((n2) =>
|
|
14558
|
-
console.log(` ${
|
|
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}`);
|
|
14559
15833
|
const subject = await getCommitSubject(hash);
|
|
14560
15834
|
if (subject) {
|
|
14561
|
-
console.log(` ${
|
|
15835
|
+
console.log(` ${import_picocolors19.default.dim(subject)}`);
|
|
14562
15836
|
}
|
|
14563
15837
|
}
|
|
14564
15838
|
if (branchesAligned) {
|
|
14565
|
-
console.log(` ${
|
|
15839
|
+
console.log(` ${import_picocolors19.default.green("\u2713")} ${import_picocolors19.default.green("All branches aligned")} ${import_picocolors19.default.dim("\u2014 ready to start")}`);
|
|
14566
15840
|
} else {
|
|
14567
|
-
console.log(` ${
|
|
15841
|
+
console.log(` ${import_picocolors19.default.yellow("\u26A0")} ${import_picocolors19.default.yellow("Branches are not fully aligned")}`);
|
|
14568
15842
|
}
|
|
14569
15843
|
}
|
|
14570
15844
|
}
|
|
@@ -14572,41 +15846,41 @@ var status_default = defineCommand({
|
|
|
14572
15846
|
if (hasFiles) {
|
|
14573
15847
|
console.log();
|
|
14574
15848
|
if (fileStatus.staged.length > 0) {
|
|
14575
|
-
console.log(` ${
|
|
15849
|
+
console.log(` ${import_picocolors19.default.green("Staged for commit:")}`);
|
|
14576
15850
|
for (const { file, status } of fileStatus.staged) {
|
|
14577
|
-
console.log(` ${
|
|
15851
|
+
console.log(` ${import_picocolors19.default.green("+")} ${import_picocolors19.default.dim(`${status}:`)} ${file}`);
|
|
14578
15852
|
}
|
|
14579
15853
|
}
|
|
14580
15854
|
if (fileStatus.modified.length > 0) {
|
|
14581
|
-
console.log(` ${
|
|
15855
|
+
console.log(` ${import_picocolors19.default.yellow("Unstaged changes:")}`);
|
|
14582
15856
|
for (const { file, status } of fileStatus.modified) {
|
|
14583
|
-
console.log(` ${
|
|
15857
|
+
console.log(` ${import_picocolors19.default.yellow("~")} ${import_picocolors19.default.dim(`${status}:`)} ${file}`);
|
|
14584
15858
|
}
|
|
14585
15859
|
}
|
|
14586
15860
|
if (fileStatus.untracked.length > 0) {
|
|
14587
|
-
console.log(` ${
|
|
15861
|
+
console.log(` ${import_picocolors19.default.red("Untracked files:")}`);
|
|
14588
15862
|
for (const file of fileStatus.untracked) {
|
|
14589
|
-
console.log(` ${
|
|
15863
|
+
console.log(` ${import_picocolors19.default.red("?")} ${file}`);
|
|
14590
15864
|
}
|
|
14591
15865
|
}
|
|
14592
15866
|
} else if (!dirty) {
|
|
14593
|
-
console.log(` ${
|
|
15867
|
+
console.log(` ${import_picocolors19.default.green("\u2713")} ${import_picocolors19.default.dim("Working tree clean")}`);
|
|
14594
15868
|
}
|
|
14595
15869
|
console.log();
|
|
14596
15870
|
}
|
|
14597
15871
|
});
|
|
14598
15872
|
function formatStatus(branch, base, ahead, behind) {
|
|
14599
|
-
const label =
|
|
15873
|
+
const label = import_picocolors19.default.bold(branch.padEnd(20));
|
|
14600
15874
|
if (ahead === 0 && behind === 0) {
|
|
14601
|
-
return ` ${
|
|
15875
|
+
return ` ${import_picocolors19.default.green("\u2713")} ${label} ${import_picocolors19.default.dim(`in sync with ${base}`)}`;
|
|
14602
15876
|
}
|
|
14603
15877
|
if (ahead > 0 && behind === 0) {
|
|
14604
|
-
return ` ${
|
|
15878
|
+
return ` ${import_picocolors19.default.yellow("\u2191")} ${label} ${import_picocolors19.default.yellow(`${ahead} commit${ahead !== 1 ? "s" : ""} ahead of ${base}`)}`;
|
|
14605
15879
|
}
|
|
14606
15880
|
if (behind > 0 && ahead === 0) {
|
|
14607
|
-
return ` ${
|
|
15881
|
+
return ` ${import_picocolors19.default.red("\u2193")} ${label} ${import_picocolors19.default.red(`${behind} commit${behind !== 1 ? "s" : ""} behind ${base}`)}`;
|
|
14608
15882
|
}
|
|
14609
|
-
return ` ${
|
|
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)}`;
|
|
14610
15884
|
}
|
|
14611
15885
|
var STALE_THRESHOLD_DAYS = 14;
|
|
14612
15886
|
async function detectBranchStatus(branch, baseBranch) {
|
|
@@ -14657,15 +15931,16 @@ async function detectBranchStatus(branch, baseBranch) {
|
|
|
14657
15931
|
}
|
|
14658
15932
|
|
|
14659
15933
|
// src/commands/submit.ts
|
|
14660
|
-
var
|
|
15934
|
+
var import_picocolors20 = __toESM(require_picocolors(), 1);
|
|
15935
|
+
init_gh();
|
|
14661
15936
|
async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
14662
|
-
info(`Checking out ${
|
|
15937
|
+
info(`Checking out ${import_picocolors20.default.bold(baseBranch)}...`);
|
|
14663
15938
|
const coResult = await checkoutBranch(baseBranch);
|
|
14664
15939
|
if (coResult.exitCode !== 0) {
|
|
14665
15940
|
error(`Failed to checkout ${baseBranch}: ${coResult.stderr}`);
|
|
14666
15941
|
process.exit(1);
|
|
14667
15942
|
}
|
|
14668
|
-
info(`Squash merging ${
|
|
15943
|
+
info(`Squash merging ${import_picocolors20.default.bold(featureBranch)} into ${import_picocolors20.default.bold(baseBranch)}...`);
|
|
14669
15944
|
const mergeResult = await mergeSquash(featureBranch);
|
|
14670
15945
|
if (mergeResult.exitCode !== 0) {
|
|
14671
15946
|
error(`Squash merge failed: ${mergeResult.stderr}`);
|
|
@@ -14685,7 +15960,7 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
14685
15960
|
message = aiMsg;
|
|
14686
15961
|
spinner.success("AI commit message generated.");
|
|
14687
15962
|
console.log(`
|
|
14688
|
-
${
|
|
15963
|
+
${import_picocolors20.default.dim("AI suggestion:")} ${import_picocolors20.default.bold(import_picocolors20.default.cyan(message))}`);
|
|
14689
15964
|
break;
|
|
14690
15965
|
}
|
|
14691
15966
|
spinner.fail("AI did not return a commit message.");
|
|
@@ -14725,7 +16000,7 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
14725
16000
|
message = regen;
|
|
14726
16001
|
spinner.success("Commit message regenerated.");
|
|
14727
16002
|
console.log(`
|
|
14728
|
-
${
|
|
16003
|
+
${import_picocolors20.default.dim("AI suggestion:")} ${import_picocolors20.default.bold(import_picocolors20.default.cyan(regen))}`);
|
|
14729
16004
|
} else {
|
|
14730
16005
|
spinner.fail("Regeneration failed.");
|
|
14731
16006
|
}
|
|
@@ -14741,13 +16016,13 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
14741
16016
|
error(`Commit failed: ${commitResult.stderr}`);
|
|
14742
16017
|
process.exit(1);
|
|
14743
16018
|
}
|
|
14744
|
-
info(`Pushing ${
|
|
16019
|
+
info(`Pushing ${import_picocolors20.default.bold(baseBranch)} to ${origin}...`);
|
|
14745
16020
|
const pushResult = await pushBranch(origin, baseBranch);
|
|
14746
16021
|
if (pushResult.exitCode !== 0) {
|
|
14747
16022
|
error(`Failed to push ${baseBranch}: ${pushResult.stderr}`);
|
|
14748
16023
|
process.exit(1);
|
|
14749
16024
|
}
|
|
14750
|
-
info(`Deleting local branch ${
|
|
16025
|
+
info(`Deleting local branch ${import_picocolors20.default.bold(featureBranch)}...`);
|
|
14751
16026
|
const delLocal = await forceDeleteBranch(featureBranch);
|
|
14752
16027
|
if (delLocal.exitCode !== 0) {
|
|
14753
16028
|
warn(`Could not delete local branch: ${delLocal.stderr.trim()}`);
|
|
@@ -14755,14 +16030,14 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
14755
16030
|
const remoteBranchRef = `${origin}/${featureBranch}`;
|
|
14756
16031
|
const remoteExists = await branchExists(remoteBranchRef);
|
|
14757
16032
|
if (remoteExists) {
|
|
14758
|
-
info(`Deleting remote branch ${
|
|
16033
|
+
info(`Deleting remote branch ${import_picocolors20.default.bold(featureBranch)}...`);
|
|
14759
16034
|
const delRemote = await deleteRemoteBranch(origin, featureBranch);
|
|
14760
16035
|
if (delRemote.exitCode !== 0) {
|
|
14761
16036
|
warn(`Could not delete remote branch: ${delRemote.stderr.trim()}`);
|
|
14762
16037
|
}
|
|
14763
16038
|
}
|
|
14764
|
-
success(`Squash merged ${
|
|
14765
|
-
info(`Run ${
|
|
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.`, "");
|
|
14766
16041
|
}
|
|
14767
16042
|
var submit_default = defineCommand({
|
|
14768
16043
|
meta: {
|
|
@@ -14819,7 +16094,7 @@ var submit_default = defineCommand({
|
|
|
14819
16094
|
}
|
|
14820
16095
|
if (protectedBranches.includes(currentBranch)) {
|
|
14821
16096
|
await projectHeading("submit", "\uD83D\uDE80");
|
|
14822
|
-
warn(`You're on ${
|
|
16097
|
+
warn(`You're on ${import_picocolors20.default.bold(currentBranch)}, which is a protected branch. PRs should come from feature branches.`);
|
|
14823
16098
|
await fetchAll();
|
|
14824
16099
|
const remoteRef = `${origin}/${currentBranch}`;
|
|
14825
16100
|
const localWork = await hasLocalWork(origin, currentBranch);
|
|
@@ -14828,11 +16103,11 @@ var submit_default = defineCommand({
|
|
|
14828
16103
|
const hasAnything = hasCommits || dirty;
|
|
14829
16104
|
if (!hasAnything) {
|
|
14830
16105
|
error("No local changes or commits to move. Switch to a feature branch first.");
|
|
14831
|
-
info(` Run ${
|
|
16106
|
+
info(` Run ${import_picocolors20.default.bold("cn start")} to create a new feature branch.`, "");
|
|
14832
16107
|
process.exit(1);
|
|
14833
16108
|
}
|
|
14834
16109
|
if (hasCommits) {
|
|
14835
|
-
info(`Found ${
|
|
16110
|
+
info(`Found ${import_picocolors20.default.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${import_picocolors20.default.bold(currentBranch)}.`);
|
|
14836
16111
|
}
|
|
14837
16112
|
if (dirty) {
|
|
14838
16113
|
info("You also have uncommitted changes in the working tree.");
|
|
@@ -14862,12 +16137,12 @@ var submit_default = defineCommand({
|
|
|
14862
16137
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
14863
16138
|
process.exit(1);
|
|
14864
16139
|
}
|
|
14865
|
-
success(`Created ${
|
|
16140
|
+
success(`Created ${import_picocolors20.default.bold(newBranchName)} with your changes.`);
|
|
14866
16141
|
await updateLocalBranch(currentBranch, remoteRef);
|
|
14867
|
-
info(`Reset ${
|
|
16142
|
+
info(`Reset ${import_picocolors20.default.bold(currentBranch)} back to ${import_picocolors20.default.bold(remoteRef)} \u2014 no damage done.`, "");
|
|
14868
16143
|
console.log();
|
|
14869
|
-
success(`You're now on ${
|
|
14870
|
-
info(`Run ${
|
|
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.`, "");
|
|
14871
16146
|
return;
|
|
14872
16147
|
}
|
|
14873
16148
|
await projectHeading("submit", "\uD83D\uDE80");
|
|
@@ -14876,7 +16151,7 @@ var submit_default = defineCommand({
|
|
|
14876
16151
|
if (ghInstalled && ghAuthed) {
|
|
14877
16152
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
14878
16153
|
if (mergedPR) {
|
|
14879
|
-
warn(`PR #${mergedPR.number} (${
|
|
16154
|
+
warn(`PR #${mergedPR.number} (${import_picocolors20.default.bold(mergedPR.title)}) was already merged.`);
|
|
14880
16155
|
const localWork = await hasLocalWork(origin, currentBranch);
|
|
14881
16156
|
const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
|
|
14882
16157
|
if (hasWork) {
|
|
@@ -14884,7 +16159,7 @@ var submit_default = defineCommand({
|
|
|
14884
16159
|
warn("You have uncommitted changes in your working tree.");
|
|
14885
16160
|
}
|
|
14886
16161
|
if (localWork.unpushedCommits > 0) {
|
|
14887
|
-
warn(`You have ${
|
|
16162
|
+
warn(`You have ${import_picocolors20.default.bold(String(localWork.unpushedCommits))} local commit${localWork.unpushedCommits !== 1 ? "s" : ""} not in the merged PR.`);
|
|
14888
16163
|
}
|
|
14889
16164
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
14890
16165
|
const DISCARD = "Discard all changes and clean up";
|
|
@@ -14911,10 +16186,10 @@ var submit_default = defineCommand({
|
|
|
14911
16186
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
14912
16187
|
process.exit(1);
|
|
14913
16188
|
}
|
|
14914
|
-
success(`Renamed ${
|
|
16189
|
+
success(`Renamed ${import_picocolors20.default.bold(currentBranch)} \u2192 ${import_picocolors20.default.bold(newBranchName)}`);
|
|
14915
16190
|
await unsetUpstream();
|
|
14916
16191
|
const syncSource2 = getSyncSource(config);
|
|
14917
|
-
info(`Syncing ${
|
|
16192
|
+
info(`Syncing ${import_picocolors20.default.bold(newBranchName)} with latest ${import_picocolors20.default.bold(baseBranch)}...`);
|
|
14918
16193
|
await fetchRemote(syncSource2.remote);
|
|
14919
16194
|
let rebaseResult;
|
|
14920
16195
|
if (staleUpstreamHash) {
|
|
@@ -14925,17 +16200,17 @@ var submit_default = defineCommand({
|
|
|
14925
16200
|
}
|
|
14926
16201
|
if (rebaseResult.exitCode !== 0) {
|
|
14927
16202
|
warn("Rebase encountered conflicts. Resolve them manually, then run:");
|
|
14928
|
-
info(` ${
|
|
16203
|
+
info(` ${import_picocolors20.default.bold("git rebase --continue")}`, "");
|
|
14929
16204
|
} else {
|
|
14930
|
-
success(`Rebased ${
|
|
16205
|
+
success(`Rebased ${import_picocolors20.default.bold(newBranchName)} onto ${import_picocolors20.default.bold(syncSource2.ref)}.`);
|
|
14931
16206
|
}
|
|
14932
|
-
info(`All your changes are preserved. Run ${
|
|
16207
|
+
info(`All your changes are preserved. Run ${import_picocolors20.default.bold("cn submit")} when ready to create a new PR.`, "");
|
|
14933
16208
|
return;
|
|
14934
16209
|
}
|
|
14935
16210
|
warn("Discarding local changes...");
|
|
14936
16211
|
}
|
|
14937
16212
|
const syncSource = getSyncSource(config);
|
|
14938
|
-
info(`Switching to ${
|
|
16213
|
+
info(`Switching to ${import_picocolors20.default.bold(baseBranch)} and syncing...`);
|
|
14939
16214
|
await fetchRemote(syncSource.remote);
|
|
14940
16215
|
await resetHard("HEAD");
|
|
14941
16216
|
const coResult = await checkoutBranch(baseBranch);
|
|
@@ -14944,23 +16219,23 @@ var submit_default = defineCommand({
|
|
|
14944
16219
|
process.exit(1);
|
|
14945
16220
|
}
|
|
14946
16221
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
14947
|
-
success(`Synced ${
|
|
14948
|
-
info(`Deleting stale branch ${
|
|
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)}...`);
|
|
14949
16224
|
const delResult = await forceDeleteBranch(currentBranch);
|
|
14950
16225
|
if (delResult.exitCode === 0) {
|
|
14951
|
-
success(`Deleted ${
|
|
16226
|
+
success(`Deleted ${import_picocolors20.default.bold(currentBranch)}.`);
|
|
14952
16227
|
} else {
|
|
14953
16228
|
warn(`Could not delete branch: ${delResult.stderr.trim()}`);
|
|
14954
16229
|
}
|
|
14955
16230
|
console.log();
|
|
14956
|
-
info(`You're now on ${
|
|
16231
|
+
info(`You're now on ${import_picocolors20.default.bold(baseBranch)}. Run ${import_picocolors20.default.bold("cn start")} to begin a new feature.`);
|
|
14957
16232
|
return;
|
|
14958
16233
|
}
|
|
14959
16234
|
}
|
|
14960
16235
|
if (ghInstalled && ghAuthed) {
|
|
14961
16236
|
const existingPR = await getPRForBranch(currentBranch);
|
|
14962
16237
|
if (existingPR) {
|
|
14963
|
-
info(`Pushing ${
|
|
16238
|
+
info(`Pushing ${import_picocolors20.default.bold(currentBranch)} to ${origin}...`);
|
|
14964
16239
|
const pushResult2 = await pushSetUpstream(origin, currentBranch);
|
|
14965
16240
|
if (pushResult2.exitCode !== 0) {
|
|
14966
16241
|
error(`Failed to push: ${pushResult2.stderr}`);
|
|
@@ -14971,8 +16246,8 @@ var submit_default = defineCommand({
|
|
|
14971
16246
|
}
|
|
14972
16247
|
process.exit(1);
|
|
14973
16248
|
}
|
|
14974
|
-
success(`Pushed changes to existing PR #${existingPR.number}: ${
|
|
14975
|
-
console.log(` ${
|
|
16249
|
+
success(`Pushed changes to existing PR #${existingPR.number}: ${import_picocolors20.default.bold(existingPR.title)}`);
|
|
16250
|
+
console.log(` ${import_picocolors20.default.cyan(existingPR.url)}`);
|
|
14976
16251
|
return;
|
|
14977
16252
|
}
|
|
14978
16253
|
}
|
|
@@ -14994,10 +16269,10 @@ var submit_default = defineCommand({
|
|
|
14994
16269
|
prBody = result.body;
|
|
14995
16270
|
spinner.success("PR description generated.");
|
|
14996
16271
|
console.log(`
|
|
14997
|
-
${
|
|
16272
|
+
${import_picocolors20.default.dim("AI title:")} ${import_picocolors20.default.bold(import_picocolors20.default.cyan(prTitle))}`);
|
|
14998
16273
|
console.log(`
|
|
14999
|
-
${
|
|
15000
|
-
console.log(
|
|
16274
|
+
${import_picocolors20.default.dim("AI body preview:")}`);
|
|
16275
|
+
console.log(import_picocolors20.default.dim(prBody.slice(0, 300) + (prBody.length > 300 ? "..." : "")));
|
|
15001
16276
|
} else {
|
|
15002
16277
|
spinner.fail("AI did not return a PR description.");
|
|
15003
16278
|
}
|
|
@@ -15108,7 +16383,7 @@ ${import_picocolors19.default.dim("AI body preview:")}`);
|
|
|
15108
16383
|
warn("Submit cancelled.");
|
|
15109
16384
|
return;
|
|
15110
16385
|
}
|
|
15111
|
-
info(`Pushing ${
|
|
16386
|
+
info(`Pushing ${import_picocolors20.default.bold(currentBranch)} to ${origin}...`);
|
|
15112
16387
|
const pushResult = await pushSetUpstream(origin, currentBranch);
|
|
15113
16388
|
if (pushResult.exitCode !== 0) {
|
|
15114
16389
|
error(`Failed to push: ${pushResult.stderr}`);
|
|
@@ -15127,7 +16402,7 @@ ${import_picocolors19.default.dim("AI body preview:")}`);
|
|
|
15127
16402
|
const prUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}/compare/${baseBranch}...${currentBranch}?expand=1`;
|
|
15128
16403
|
console.log();
|
|
15129
16404
|
info("Create your PR manually:", "");
|
|
15130
|
-
console.log(` ${
|
|
16405
|
+
console.log(` ${import_picocolors20.default.cyan(prUrl)}`);
|
|
15131
16406
|
} else {
|
|
15132
16407
|
info("gh CLI not available. Create your PR manually on GitHub.", "");
|
|
15133
16408
|
}
|
|
@@ -15161,7 +16436,7 @@ ${import_picocolors19.default.dim("AI body preview:")}`);
|
|
|
15161
16436
|
});
|
|
15162
16437
|
|
|
15163
16438
|
// src/commands/switch.ts
|
|
15164
|
-
var
|
|
16439
|
+
var import_picocolors21 = __toESM(require_picocolors(), 1);
|
|
15165
16440
|
var switch_default = defineCommand({
|
|
15166
16441
|
meta: {
|
|
15167
16442
|
name: "switch",
|
|
@@ -15193,11 +16468,11 @@ var switch_default = defineCommand({
|
|
|
15193
16468
|
const choices = localBranches.filter((b2) => b2.name !== currentBranch).map((b2) => {
|
|
15194
16469
|
const labels = [];
|
|
15195
16470
|
if (protectedBranches.includes(b2.name))
|
|
15196
|
-
labels.push(
|
|
16471
|
+
labels.push(import_picocolors21.default.red("protected"));
|
|
15197
16472
|
if (b2.upstream)
|
|
15198
|
-
labels.push(
|
|
16473
|
+
labels.push(import_picocolors21.default.dim(`\u2192 ${b2.upstream}`));
|
|
15199
16474
|
if (b2.gone)
|
|
15200
|
-
labels.push(
|
|
16475
|
+
labels.push(import_picocolors21.default.red("remote gone"));
|
|
15201
16476
|
const suffix = labels.length > 0 ? ` ${labels.join(" \xB7 ")}` : "";
|
|
15202
16477
|
return `${b2.name}${suffix}`;
|
|
15203
16478
|
});
|
|
@@ -15209,7 +16484,7 @@ var switch_default = defineCommand({
|
|
|
15209
16484
|
targetBranch = selected.split(/\s{2,}/)[0].trim();
|
|
15210
16485
|
}
|
|
15211
16486
|
if (targetBranch === currentBranch) {
|
|
15212
|
-
info(`Already on ${
|
|
16487
|
+
info(`Already on ${import_picocolors21.default.bold(targetBranch)}.`);
|
|
15213
16488
|
return;
|
|
15214
16489
|
}
|
|
15215
16490
|
if (await hasUncommittedChanges()) {
|
|
@@ -15228,7 +16503,7 @@ var switch_default = defineCommand({
|
|
|
15228
16503
|
const stashMsg = `contrib-save: auto-save from ${currentBranch}`;
|
|
15229
16504
|
try {
|
|
15230
16505
|
await exec("git", ["stash", "push", "-m", stashMsg]);
|
|
15231
|
-
info(`Saved changes: ${
|
|
16506
|
+
info(`Saved changes: ${import_picocolors21.default.dim(stashMsg)}`);
|
|
15232
16507
|
} catch {
|
|
15233
16508
|
error("Failed to save changes. Please commit or save manually.");
|
|
15234
16509
|
process.exit(1);
|
|
@@ -15244,9 +16519,9 @@ var switch_default = defineCommand({
|
|
|
15244
16519
|
}
|
|
15245
16520
|
process.exit(1);
|
|
15246
16521
|
}
|
|
15247
|
-
success(`Switched to ${
|
|
15248
|
-
info(`Your changes from ${
|
|
15249
|
-
info(`Use ${
|
|
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.`, "");
|
|
15250
16525
|
return;
|
|
15251
16526
|
}
|
|
15252
16527
|
const result = await checkoutBranch(targetBranch);
|
|
@@ -15254,12 +16529,12 @@ var switch_default = defineCommand({
|
|
|
15254
16529
|
error(`Failed to switch to ${targetBranch}: ${result.stderr}`);
|
|
15255
16530
|
process.exit(1);
|
|
15256
16531
|
}
|
|
15257
|
-
success(`Switched to ${
|
|
16532
|
+
success(`Switched to ${import_picocolors21.default.bold(targetBranch)}`);
|
|
15258
16533
|
}
|
|
15259
16534
|
});
|
|
15260
16535
|
|
|
15261
16536
|
// src/commands/sync.ts
|
|
15262
|
-
var
|
|
16537
|
+
var import_picocolors22 = __toESM(require_picocolors(), 1);
|
|
15263
16538
|
var sync_default = defineCommand({
|
|
15264
16539
|
meta: {
|
|
15265
16540
|
name: "sync",
|
|
@@ -15311,24 +16586,24 @@ var sync_default = defineCommand({
|
|
|
15311
16586
|
await fetchRemote(origin);
|
|
15312
16587
|
}
|
|
15313
16588
|
if (!await refExists(syncSource.ref)) {
|
|
15314
|
-
error(`Remote ref ${
|
|
16589
|
+
error(`Remote ref ${import_picocolors22.default.bold(syncSource.ref)} does not exist.`);
|
|
15315
16590
|
info("This can happen if the branch was renamed or deleted on the remote.", "");
|
|
15316
|
-
info(`Check your config: the base branch may need updating via ${
|
|
16591
|
+
info(`Check your config: the base branch may need updating via ${import_picocolors22.default.bold("cn setup")}.`, "");
|
|
15317
16592
|
process.exit(1);
|
|
15318
16593
|
}
|
|
15319
16594
|
let allowMergeCommit = false;
|
|
15320
16595
|
const div = await getDivergence(baseBranch, syncSource.ref);
|
|
15321
16596
|
if (div.ahead > 0 || div.behind > 0) {
|
|
15322
|
-
info(`${
|
|
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}`);
|
|
15323
16598
|
} else {
|
|
15324
|
-
info(`${
|
|
16599
|
+
info(`${import_picocolors22.default.bold(baseBranch)} is already in sync with ${syncSource.ref}`);
|
|
15325
16600
|
}
|
|
15326
16601
|
if (div.ahead > 0) {
|
|
15327
16602
|
const currentBranch = await getCurrentBranch();
|
|
15328
16603
|
const protectedBranches = getProtectedBranches(config);
|
|
15329
16604
|
const isOnProtected = currentBranch && protectedBranches.includes(currentBranch);
|
|
15330
16605
|
if (isOnProtected) {
|
|
15331
|
-
warn(`You have ${
|
|
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.`);
|
|
15332
16607
|
info("Pulling now could create a merge commit, which breaks clean history.");
|
|
15333
16608
|
console.log();
|
|
15334
16609
|
const MOVE_BRANCH = "Move my commits to a new feature branch, then sync";
|
|
@@ -15358,7 +16633,7 @@ var sync_default = defineCommand({
|
|
|
15358
16633
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
15359
16634
|
process.exit(1);
|
|
15360
16635
|
}
|
|
15361
|
-
success(`Created ${
|
|
16636
|
+
success(`Created ${import_picocolors22.default.bold(newBranchName)} with your commits.`);
|
|
15362
16637
|
const coResult2 = await checkoutBranch(baseBranch);
|
|
15363
16638
|
if (coResult2.exitCode !== 0) {
|
|
15364
16639
|
error(`Failed to checkout ${baseBranch}: ${coResult2.stderr}`);
|
|
@@ -15366,11 +16641,11 @@ var sync_default = defineCommand({
|
|
|
15366
16641
|
}
|
|
15367
16642
|
const remoteRef = syncSource.ref;
|
|
15368
16643
|
await updateLocalBranch(baseBranch, remoteRef);
|
|
15369
|
-
success(`Reset ${
|
|
15370
|
-
success(`${
|
|
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}`);
|
|
15371
16646
|
console.log();
|
|
15372
|
-
info(`Your commits are safe on ${
|
|
15373
|
-
info(`Run ${
|
|
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)}.`, "");
|
|
15374
16649
|
return;
|
|
15375
16650
|
}
|
|
15376
16651
|
allowMergeCommit = true;
|
|
@@ -15378,7 +16653,7 @@ var sync_default = defineCommand({
|
|
|
15378
16653
|
}
|
|
15379
16654
|
}
|
|
15380
16655
|
if (!args.yes) {
|
|
15381
|
-
const ok = await confirmPrompt(`This will pull ${
|
|
16656
|
+
const ok = await confirmPrompt(`This will pull ${import_picocolors22.default.bold(syncSource.ref)} into local ${import_picocolors22.default.bold(baseBranch)}.`);
|
|
15382
16657
|
if (!ok)
|
|
15383
16658
|
process.exit(0);
|
|
15384
16659
|
}
|
|
@@ -15392,8 +16667,8 @@ var sync_default = defineCommand({
|
|
|
15392
16667
|
if (allowMergeCommit) {
|
|
15393
16668
|
error(`Pull failed: ${pullResult.stderr.trim()}`);
|
|
15394
16669
|
} else {
|
|
15395
|
-
error(`Fast-forward pull failed. Your local ${
|
|
15396
|
-
info(`Use ${
|
|
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.`, "");
|
|
15397
16672
|
}
|
|
15398
16673
|
process.exit(1);
|
|
15399
16674
|
}
|
|
@@ -15401,7 +16676,7 @@ var sync_default = defineCommand({
|
|
|
15401
16676
|
if (hasDevBranch(workflow) && role === "maintainer") {
|
|
15402
16677
|
const mainDiv = await getDivergence(config.mainBranch, `${origin}/${config.mainBranch}`);
|
|
15403
16678
|
if (mainDiv.behind > 0) {
|
|
15404
|
-
info(`Also syncing ${
|
|
16679
|
+
info(`Also syncing ${import_picocolors22.default.bold(config.mainBranch)}...`);
|
|
15405
16680
|
const mainCoResult = await checkoutBranch(config.mainBranch);
|
|
15406
16681
|
if (mainCoResult.exitCode === 0) {
|
|
15407
16682
|
const mainPullResult = await pullFastForwardOnly(origin, config.mainBranch);
|
|
@@ -15442,20 +16717,20 @@ var sync_default = defineCommand({
|
|
|
15442
16717
|
}
|
|
15443
16718
|
}
|
|
15444
16719
|
console.log();
|
|
15445
|
-
console.log(` ${
|
|
16720
|
+
console.log(` ${import_picocolors22.default.bold("\uD83D\uDD17 Branch Alignment")}`);
|
|
15446
16721
|
for (const [hash, names] of groups) {
|
|
15447
16722
|
const short = hash.slice(0, 7);
|
|
15448
|
-
const nameStr = names.map((n2) =>
|
|
15449
|
-
console.log(` ${
|
|
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}`);
|
|
15450
16725
|
const subject = await getCommitSubject(hash);
|
|
15451
16726
|
if (subject) {
|
|
15452
|
-
console.log(` ${
|
|
16727
|
+
console.log(` ${import_picocolors22.default.dim(subject)}`);
|
|
15453
16728
|
}
|
|
15454
16729
|
}
|
|
15455
16730
|
if (groups.size === 1) {
|
|
15456
|
-
console.log(` ${
|
|
16731
|
+
console.log(` ${import_picocolors22.default.green("\u2713")} ${import_picocolors22.default.green("All branches aligned")} ${import_picocolors22.default.dim("\u2014 ready to start")}`);
|
|
15457
16732
|
} else {
|
|
15458
|
-
console.log(` ${
|
|
16733
|
+
console.log(` ${import_picocolors22.default.yellow("\u26A0")} ${import_picocolors22.default.yellow("Branches are not fully aligned")}`);
|
|
15459
16734
|
}
|
|
15460
16735
|
}
|
|
15461
16736
|
}
|
|
@@ -15463,8 +16738,9 @@ var sync_default = defineCommand({
|
|
|
15463
16738
|
});
|
|
15464
16739
|
|
|
15465
16740
|
// src/commands/update.ts
|
|
15466
|
-
import { readFileSync as
|
|
15467
|
-
var
|
|
16741
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
16742
|
+
var import_picocolors23 = __toESM(require_picocolors(), 1);
|
|
16743
|
+
init_gh();
|
|
15468
16744
|
function hasStaleBranchWorkToPreserve(uniqueCommitsAheadOfBase, hasUncommittedChanges2) {
|
|
15469
16745
|
return hasUncommittedChanges2 || uniqueCommitsAheadOfBase > 0;
|
|
15470
16746
|
}
|
|
@@ -15505,7 +16781,7 @@ var update_default = defineCommand({
|
|
|
15505
16781
|
}
|
|
15506
16782
|
if (protectedBranches.includes(currentBranch)) {
|
|
15507
16783
|
await projectHeading("update", "\uD83D\uDD03");
|
|
15508
|
-
warn(`You're on ${
|
|
16784
|
+
warn(`You're on ${import_picocolors23.default.bold(currentBranch)}, which is a protected branch. Updates (rebase) apply to feature branches.`);
|
|
15509
16785
|
await fetchAll();
|
|
15510
16786
|
const { origin } = config;
|
|
15511
16787
|
const remoteRef = `${origin}/${currentBranch}`;
|
|
@@ -15514,12 +16790,12 @@ var update_default = defineCommand({
|
|
|
15514
16790
|
const hasCommits = localWork.unpushedCommits > 0;
|
|
15515
16791
|
const hasAnything = hasCommits || dirty;
|
|
15516
16792
|
if (!hasAnything) {
|
|
15517
|
-
info(`No local changes found on ${
|
|
15518
|
-
info(`Use ${
|
|
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.`);
|
|
15519
16795
|
process.exit(1);
|
|
15520
16796
|
}
|
|
15521
16797
|
if (hasCommits) {
|
|
15522
|
-
info(`Found ${
|
|
16798
|
+
info(`Found ${import_picocolors23.default.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${import_picocolors23.default.bold(currentBranch)}.`);
|
|
15523
16799
|
}
|
|
15524
16800
|
if (dirty) {
|
|
15525
16801
|
info("You also have uncommitted changes in the working tree.");
|
|
@@ -15549,12 +16825,12 @@ var update_default = defineCommand({
|
|
|
15549
16825
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
15550
16826
|
process.exit(1);
|
|
15551
16827
|
}
|
|
15552
|
-
success(`Created ${
|
|
16828
|
+
success(`Created ${import_picocolors23.default.bold(newBranchName)} with your changes.`);
|
|
15553
16829
|
await updateLocalBranch(currentBranch, remoteRef);
|
|
15554
|
-
info(`Reset ${
|
|
16830
|
+
info(`Reset ${import_picocolors23.default.bold(currentBranch)} back to ${import_picocolors23.default.bold(remoteRef)} \u2014 no damage done.`, "");
|
|
15555
16831
|
console.log();
|
|
15556
|
-
success(`You're now on ${
|
|
15557
|
-
info(`Run ${
|
|
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)}.`, "");
|
|
15558
16834
|
return;
|
|
15559
16835
|
}
|
|
15560
16836
|
if (await hasUncommittedChanges()) {
|
|
@@ -15564,8 +16840,8 @@ var update_default = defineCommand({
|
|
|
15564
16840
|
await projectHeading("update", "\uD83D\uDD03");
|
|
15565
16841
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
15566
16842
|
if (mergedPR) {
|
|
15567
|
-
warn(`PR #${mergedPR.number} (${
|
|
15568
|
-
info(`Link: ${
|
|
16843
|
+
warn(`PR #${mergedPR.number} (${import_picocolors23.default.bold(mergedPR.title)}) has already been merged.`);
|
|
16844
|
+
info(`Link: ${import_picocolors23.default.underline(mergedPR.url)}`, "");
|
|
15569
16845
|
const uniqueCommitsAheadOfBase = await countCommitsAhead(currentBranch, syncSource.ref);
|
|
15570
16846
|
const dirty = await hasUncommittedChanges();
|
|
15571
16847
|
const hasWork = hasStaleBranchWorkToPreserve(uniqueCommitsAheadOfBase, dirty);
|
|
@@ -15574,12 +16850,12 @@ var update_default = defineCommand({
|
|
|
15574
16850
|
info("You have uncommitted local changes.");
|
|
15575
16851
|
}
|
|
15576
16852
|
if (uniqueCommitsAheadOfBase > 0) {
|
|
15577
|
-
info(`You have ${uniqueCommitsAheadOfBase} local commit(s) not in ${
|
|
16853
|
+
info(`You have ${uniqueCommitsAheadOfBase} local commit(s) not in ${import_picocolors23.default.bold(syncSource.ref)}.`);
|
|
15578
16854
|
}
|
|
15579
16855
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
15580
16856
|
const DISCARD = "Discard all changes and clean up";
|
|
15581
16857
|
const CANCEL = "Cancel";
|
|
15582
|
-
const action = await selectPrompt(`${
|
|
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]);
|
|
15583
16859
|
if (action === CANCEL) {
|
|
15584
16860
|
info("No changes made. You are still on your current branch.");
|
|
15585
16861
|
return;
|
|
@@ -15601,7 +16877,7 @@ var update_default = defineCommand({
|
|
|
15601
16877
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
15602
16878
|
process.exit(1);
|
|
15603
16879
|
}
|
|
15604
|
-
success(`Renamed ${
|
|
16880
|
+
success(`Renamed ${import_picocolors23.default.bold(currentBranch)} \u2192 ${import_picocolors23.default.bold(newBranchName)}`);
|
|
15605
16881
|
await unsetUpstream();
|
|
15606
16882
|
await fetchRemote(syncSource.remote);
|
|
15607
16883
|
let rebaseResult2;
|
|
@@ -15613,11 +16889,11 @@ var update_default = defineCommand({
|
|
|
15613
16889
|
}
|
|
15614
16890
|
if (rebaseResult2.exitCode !== 0) {
|
|
15615
16891
|
warn("Rebase encountered conflicts. Resolve them manually, then run:");
|
|
15616
|
-
info(` ${
|
|
16892
|
+
info(` ${import_picocolors23.default.bold("git rebase --continue")}`, "");
|
|
15617
16893
|
} else {
|
|
15618
|
-
success(`Rebased ${
|
|
16894
|
+
success(`Rebased ${import_picocolors23.default.bold(newBranchName)} onto ${import_picocolors23.default.bold(syncSource.ref)}.`);
|
|
15619
16895
|
}
|
|
15620
|
-
info(`All your changes are preserved. Run ${
|
|
16896
|
+
info(`All your changes are preserved. Run ${import_picocolors23.default.bold("cn submit")} when ready to create a new PR.`, "");
|
|
15621
16897
|
return;
|
|
15622
16898
|
}
|
|
15623
16899
|
warn("Discarding local changes...");
|
|
@@ -15636,24 +16912,24 @@ var update_default = defineCommand({
|
|
|
15636
16912
|
process.exit(1);
|
|
15637
16913
|
}
|
|
15638
16914
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
15639
|
-
success(`Synced ${
|
|
15640
|
-
info(`Deleting stale branch ${
|
|
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)}...`);
|
|
15641
16917
|
await forceDeleteBranch(currentBranch);
|
|
15642
|
-
success(`Deleted ${
|
|
15643
|
-
info(`Run ${
|
|
16918
|
+
success(`Deleted ${import_picocolors23.default.bold(currentBranch)}.`);
|
|
16919
|
+
info(`Run ${import_picocolors23.default.bold("cn start")} to begin a new feature branch.`, "");
|
|
15644
16920
|
return;
|
|
15645
16921
|
}
|
|
15646
|
-
info(`Updating ${
|
|
16922
|
+
info(`Updating ${import_picocolors23.default.bold(currentBranch)} with latest ${import_picocolors23.default.bold(baseBranch)}...`);
|
|
15647
16923
|
await fetchRemote(syncSource.remote);
|
|
15648
16924
|
if (!await refExists(syncSource.ref)) {
|
|
15649
|
-
error(`Remote ref ${
|
|
16925
|
+
error(`Remote ref ${import_picocolors23.default.bold(syncSource.ref)} does not exist.`);
|
|
15650
16926
|
error("Run `git fetch --all` and verify your remote configuration.");
|
|
15651
16927
|
process.exit(1);
|
|
15652
16928
|
}
|
|
15653
16929
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
15654
16930
|
const rebaseStrategy = await determineRebaseStrategy(currentBranch, syncSource.ref);
|
|
15655
16931
|
if (rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase) {
|
|
15656
|
-
info(
|
|
16932
|
+
info(import_picocolors23.default.dim(`Using --onto rebase (branch was based on a different ref)`));
|
|
15657
16933
|
}
|
|
15658
16934
|
const rebaseResult = rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase ? await rebaseOnto(syncSource.ref, rebaseStrategy.ontoOldBase) : await rebase(syncSource.ref);
|
|
15659
16935
|
if (rebaseResult.exitCode !== 0) {
|
|
@@ -15667,7 +16943,7 @@ var update_default = defineCommand({
|
|
|
15667
16943
|
let conflictDiff = "";
|
|
15668
16944
|
for (const file of conflictFiles.slice(0, 3)) {
|
|
15669
16945
|
try {
|
|
15670
|
-
const content =
|
|
16946
|
+
const content = readFileSync7(file, "utf-8");
|
|
15671
16947
|
if (content.includes("<<<<<<<")) {
|
|
15672
16948
|
conflictDiff += `
|
|
15673
16949
|
--- ${file} ---
|
|
@@ -15684,10 +16960,10 @@ ${content.slice(0, 2000)}
|
|
|
15684
16960
|
if (suggestion) {
|
|
15685
16961
|
spinner.success("AI conflict guidance ready.");
|
|
15686
16962
|
console.log(`
|
|
15687
|
-
${
|
|
15688
|
-
console.log(
|
|
16963
|
+
${import_picocolors23.default.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
|
|
16964
|
+
console.log(import_picocolors23.default.dim("\u2500".repeat(60)));
|
|
15689
16965
|
console.log(suggestion);
|
|
15690
|
-
console.log(
|
|
16966
|
+
console.log(import_picocolors23.default.dim("\u2500".repeat(60)));
|
|
15691
16967
|
console.log();
|
|
15692
16968
|
} else {
|
|
15693
16969
|
spinner.fail("AI could not analyze the conflicts.");
|
|
@@ -15695,21 +16971,21 @@ ${import_picocolors22.default.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance
|
|
|
15695
16971
|
}
|
|
15696
16972
|
}
|
|
15697
16973
|
}
|
|
15698
|
-
console.log(
|
|
16974
|
+
console.log(import_picocolors23.default.bold("To resolve:"));
|
|
15699
16975
|
console.log(` 1. Fix conflicts in the affected files`);
|
|
15700
|
-
console.log(` 2. ${
|
|
15701
|
-
console.log(` 3. ${
|
|
16976
|
+
console.log(` 2. ${import_picocolors23.default.cyan("git add <resolved-files>")}`);
|
|
16977
|
+
console.log(` 3. ${import_picocolors23.default.cyan("git rebase --continue")}`);
|
|
15702
16978
|
console.log();
|
|
15703
|
-
console.log(` Or abort: ${
|
|
16979
|
+
console.log(` Or abort: ${import_picocolors23.default.cyan("git rebase --abort")}`);
|
|
15704
16980
|
process.exit(1);
|
|
15705
16981
|
}
|
|
15706
|
-
success(`${
|
|
16982
|
+
success(`${import_picocolors23.default.bold(currentBranch)} has been rebased onto latest ${import_picocolors23.default.bold(baseBranch)}`);
|
|
15707
16983
|
}
|
|
15708
16984
|
});
|
|
15709
16985
|
|
|
15710
16986
|
// src/commands/validate.ts
|
|
15711
|
-
import { readFileSync as
|
|
15712
|
-
var
|
|
16987
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
16988
|
+
var import_picocolors24 = __toESM(require_picocolors(), 1);
|
|
15713
16989
|
var validate_default = defineCommand({
|
|
15714
16990
|
meta: {
|
|
15715
16991
|
name: "validate",
|
|
@@ -15738,7 +17014,7 @@ var validate_default = defineCommand({
|
|
|
15738
17014
|
info('Commit convention is set to "none". All messages are accepted.');
|
|
15739
17015
|
process.exit(0);
|
|
15740
17016
|
}
|
|
15741
|
-
const message = args.file ?
|
|
17017
|
+
const message = args.file ? readFileSync8(args.file, "utf-8").split(/\r?\n/, 1)[0] ?? "" : args.message;
|
|
15742
17018
|
if (!message) {
|
|
15743
17019
|
error("No commit message provided. Pass a message or use --file <path>.");
|
|
15744
17020
|
process.exit(1);
|
|
@@ -15749,14 +17025,14 @@ var validate_default = defineCommand({
|
|
|
15749
17025
|
}
|
|
15750
17026
|
const errors = getValidationError(convention);
|
|
15751
17027
|
for (const line of errors) {
|
|
15752
|
-
console.error(
|
|
17028
|
+
console.error(import_picocolors24.default.red(` \u2717 ${line}`));
|
|
15753
17029
|
}
|
|
15754
17030
|
process.exit(1);
|
|
15755
17031
|
}
|
|
15756
17032
|
});
|
|
15757
17033
|
|
|
15758
17034
|
// src/ui/banner.ts
|
|
15759
|
-
var
|
|
17035
|
+
var import_picocolors25 = __toESM(require_picocolors(), 1);
|
|
15760
17036
|
|
|
15761
17037
|
// src/data/announcements.json
|
|
15762
17038
|
var announcements_default = [
|
|
@@ -15817,9 +17093,9 @@ function getAuthor() {
|
|
|
15817
17093
|
return typeof package_default.author === "string" ? package_default.author : "unknown";
|
|
15818
17094
|
}
|
|
15819
17095
|
function showBanner(variant = "small") {
|
|
15820
|
-
console.log(
|
|
17096
|
+
console.log(import_picocolors25.default.cyan(`
|
|
15821
17097
|
${LOGO}`));
|
|
15822
|
-
console.log(` ${
|
|
17098
|
+
console.log(` ${import_picocolors25.default.dim(`v${getVersion()}`)} ${import_picocolors25.default.dim("\u2014")} ${import_picocolors25.default.dim(`Built by ${getAuthor()}`)}`);
|
|
15823
17099
|
const announcements = getActiveAnnouncements();
|
|
15824
17100
|
if (announcements.length > 0) {
|
|
15825
17101
|
console.log();
|
|
@@ -15828,27 +17104,27 @@ ${LOGO}`));
|
|
|
15828
17104
|
if (variant === "big") {
|
|
15829
17105
|
const panelLines = [
|
|
15830
17106
|
{
|
|
15831
|
-
label:
|
|
17107
|
+
label: import_picocolors25.default.bold(import_picocolors25.default.cyan("Getting Started")),
|
|
15832
17108
|
rawLabel: "Getting Started",
|
|
15833
17109
|
value: "",
|
|
15834
17110
|
rawValue: ""
|
|
15835
17111
|
},
|
|
15836
17112
|
{
|
|
15837
|
-
label:
|
|
17113
|
+
label: import_picocolors25.default.cyan("cn setup"),
|
|
15838
17114
|
rawLabel: "cn setup",
|
|
15839
|
-
value:
|
|
17115
|
+
value: import_picocolors25.default.dim("configure workflow, remotes, and defaults"),
|
|
15840
17116
|
rawValue: "configure workflow, remotes, and defaults"
|
|
15841
17117
|
},
|
|
15842
17118
|
{
|
|
15843
|
-
label:
|
|
17119
|
+
label: import_picocolors25.default.cyan("cn doctor"),
|
|
15844
17120
|
rawLabel: "cn doctor",
|
|
15845
|
-
value:
|
|
17121
|
+
value: import_picocolors25.default.dim("verify your environment before doing any work"),
|
|
15846
17122
|
rawValue: "verify your environment before doing any work"
|
|
15847
17123
|
},
|
|
15848
17124
|
{
|
|
15849
|
-
label:
|
|
17125
|
+
label: import_picocolors25.default.cyan("cn start"),
|
|
15850
17126
|
rawLabel: "cn start",
|
|
15851
|
-
value:
|
|
17127
|
+
value: import_picocolors25.default.dim("create a branch and begin the next task"),
|
|
15852
17128
|
rawValue: "create a branch and begin the next task"
|
|
15853
17129
|
},
|
|
15854
17130
|
{
|
|
@@ -15858,13 +17134,13 @@ ${LOGO}`));
|
|
|
15858
17134
|
rawValue: ""
|
|
15859
17135
|
},
|
|
15860
17136
|
{
|
|
15861
|
-
label:
|
|
17137
|
+
label: import_picocolors25.default.bold(import_picocolors25.default.cyan("Workflow")),
|
|
15862
17138
|
rawLabel: "Workflow",
|
|
15863
17139
|
value: "",
|
|
15864
17140
|
rawValue: ""
|
|
15865
17141
|
},
|
|
15866
17142
|
{
|
|
15867
|
-
label:
|
|
17143
|
+
label: import_picocolors25.default.dim("cn setup \u2192 cn commit \u2192 cn update \u2192 cn submit"),
|
|
15868
17144
|
rawLabel: "cn setup \u2192 cn commit \u2192 cn update \u2192 cn submit",
|
|
15869
17145
|
value: "",
|
|
15870
17146
|
rawValue: ""
|
|
@@ -15889,22 +17165,22 @@ ${LOGO}`));
|
|
|
15889
17165
|
return Math.max(max, lineLength);
|
|
15890
17166
|
}, 0));
|
|
15891
17167
|
console.log();
|
|
15892
|
-
console.log(` ${
|
|
17168
|
+
console.log(` ${import_picocolors25.default.dim(`\u250C${"\u2500".repeat(contentWidth + 2)}\u2510`)}`);
|
|
15893
17169
|
for (const line of rows) {
|
|
15894
17170
|
if (!line.rawLabel && !line.rawValue) {
|
|
15895
|
-
console.log(` ${
|
|
17171
|
+
console.log(` ${import_picocolors25.default.dim("\u2502")} ${" ".repeat(contentWidth)} ${import_picocolors25.default.dim("\u2502")}`);
|
|
15896
17172
|
continue;
|
|
15897
17173
|
}
|
|
15898
17174
|
const left = line.rawValue ? `${line.label}${" ".repeat(Math.max(0, labelWidth - line.rawLabel.length + 2))}` : line.label;
|
|
15899
|
-
const value = line.rawValue ?
|
|
17175
|
+
const value = line.rawValue ? import_picocolors25.default.dim(line.rawValue) : "";
|
|
15900
17176
|
const rawLength = line.rawValue ? labelWidth + 2 + line.rawValue.length : line.rawLabel.length;
|
|
15901
17177
|
const trailing = " ".repeat(Math.max(0, contentWidth - rawLength));
|
|
15902
|
-
console.log(` ${
|
|
17178
|
+
console.log(` ${import_picocolors25.default.dim("\u2502")} ${left}${value}${trailing} ${import_picocolors25.default.dim("\u2502")}`);
|
|
15903
17179
|
}
|
|
15904
|
-
console.log(` ${
|
|
17180
|
+
console.log(` ${import_picocolors25.default.dim(`\u2514${"\u2500".repeat(contentWidth + 2)}\u2518`)}`);
|
|
15905
17181
|
console.log();
|
|
15906
|
-
console.log(` ${
|
|
15907
|
-
console.log(` ${
|
|
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"))}`);
|
|
15908
17184
|
}
|
|
15909
17185
|
console.log();
|
|
15910
17186
|
}
|
|
@@ -15936,7 +17212,7 @@ function renderAnnouncementBanner(announcement) {
|
|
|
15936
17212
|
console.log(` ${tone.border(`\u250C${"\u2500".repeat(rawWidth + 2)}\u2510`)}`);
|
|
15937
17213
|
for (const line of lines) {
|
|
15938
17214
|
const trailing = " ".repeat(Math.max(0, rawWidth - line.length));
|
|
15939
|
-
const content = line === title ? tone.title(line) :
|
|
17215
|
+
const content = line === title ? tone.title(line) : import_picocolors25.default.dim(line);
|
|
15940
17216
|
console.log(` ${tone.border("\u2502")} ${content}${trailing} ${tone.border("\u2502")}`);
|
|
15941
17217
|
}
|
|
15942
17218
|
console.log(` ${tone.border(`\u2514${"\u2500".repeat(rawWidth + 2)}\u2518`)}`);
|
|
@@ -15972,20 +17248,20 @@ function getAnnouncementTone(kind) {
|
|
|
15972
17248
|
case "info":
|
|
15973
17249
|
return {
|
|
15974
17250
|
emoji: "\u2139",
|
|
15975
|
-
border:
|
|
15976
|
-
title: (value) =>
|
|
17251
|
+
border: import_picocolors25.default.blue,
|
|
17252
|
+
title: (value) => import_picocolors25.default.bold(import_picocolors25.default.blue(value))
|
|
15977
17253
|
};
|
|
15978
17254
|
case "warning":
|
|
15979
17255
|
return {
|
|
15980
17256
|
emoji: "\uD83D\uDEA8",
|
|
15981
|
-
border:
|
|
15982
|
-
title: (value) =>
|
|
17257
|
+
border: import_picocolors25.default.red,
|
|
17258
|
+
title: (value) => import_picocolors25.default.bold(import_picocolors25.default.red(value))
|
|
15983
17259
|
};
|
|
15984
17260
|
default:
|
|
15985
17261
|
return {
|
|
15986
17262
|
emoji: "\u26A0",
|
|
15987
|
-
border:
|
|
15988
|
-
title: (value) =>
|
|
17263
|
+
border: import_picocolors25.default.yellow,
|
|
17264
|
+
title: (value) => import_picocolors25.default.bold(import_picocolors25.default.yellow(value))
|
|
15989
17265
|
};
|
|
15990
17266
|
}
|
|
15991
17267
|
}
|
|
@@ -16024,7 +17300,8 @@ if (!isVersion) {
|
|
|
16024
17300
|
"branch",
|
|
16025
17301
|
"hook",
|
|
16026
17302
|
"validate",
|
|
16027
|
-
"doctor"
|
|
17303
|
+
"doctor",
|
|
17304
|
+
"label"
|
|
16028
17305
|
];
|
|
16029
17306
|
const isHelp = process.argv.includes("--help") || process.argv.includes("-h");
|
|
16030
17307
|
const hasSubCommand = subCommands.some((cmd) => process.argv.includes(cmd));
|
|
@@ -16061,7 +17338,8 @@ var main = defineCommand({
|
|
|
16061
17338
|
log: log_default,
|
|
16062
17339
|
hook: hook_default,
|
|
16063
17340
|
validate: validate_default,
|
|
16064
|
-
doctor: doctor_default
|
|
17341
|
+
doctor: doctor_default,
|
|
17342
|
+
label: label_default
|
|
16065
17343
|
},
|
|
16066
17344
|
run({ args }) {
|
|
16067
17345
|
if (args.version) {
|