contribute-now 0.7.5 → 0.8.0-dev.1cf48ff
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 +55 -8
- package/dist/cli.js +1853 -809
- 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,
|
|
@@ -7206,7 +7442,7 @@ function configExists(cwd = process.cwd()) {
|
|
|
7206
7442
|
var VALID_WORKFLOWS = ["clean-flow", "github-flow", "git-flow"];
|
|
7207
7443
|
var VALID_ROLES = ["maintainer", "contributor"];
|
|
7208
7444
|
var VALID_CONVENTIONS = ["conventional", "clean-commit", "none"];
|
|
7209
|
-
var VALID_AI_PROVIDERS = ["copilot", "ollama-cloud"];
|
|
7445
|
+
var VALID_AI_PROVIDERS = ["copilot", "ollama-cloud", "openrouter"];
|
|
7210
7446
|
function isAIEnabled(config, cliNoAI = false) {
|
|
7211
7447
|
return config.aiEnabled !== false && !cliNoAI;
|
|
7212
7448
|
}
|
|
@@ -9115,6 +9351,24 @@ var COMMAND_GUIDES = {
|
|
|
9115
9351
|
description: "check config, remotes, and workflow resolution together"
|
|
9116
9352
|
}
|
|
9117
9353
|
]
|
|
9354
|
+
},
|
|
9355
|
+
label: {
|
|
9356
|
+
summary: "Apply existing labels or get ranked suggestions for issues and pull requests.",
|
|
9357
|
+
examples: [
|
|
9358
|
+
{ command: "cn label --help", description: "learn label add and suggest usage" },
|
|
9359
|
+
{
|
|
9360
|
+
command: "cn label add --issue 42 bug,enhancement",
|
|
9361
|
+
description: "apply labels to an issue"
|
|
9362
|
+
},
|
|
9363
|
+
{
|
|
9364
|
+
command: 'cn label add --pr 7 "good first issue"',
|
|
9365
|
+
description: "apply a label with spaces to a PR"
|
|
9366
|
+
},
|
|
9367
|
+
{
|
|
9368
|
+
command: "cn label suggest --issue 42",
|
|
9369
|
+
description: "get ranked label suggestions from content"
|
|
9370
|
+
}
|
|
9371
|
+
]
|
|
9118
9372
|
}
|
|
9119
9373
|
};
|
|
9120
9374
|
var LOADING_TIPS = [
|
|
@@ -10624,6 +10878,7 @@ import { join as join5, resolve as resolve4 } from "path";
|
|
|
10624
10878
|
var CONTRIBUTE_NOW_SECRETS_DIRNAME = ".contribute-now";
|
|
10625
10879
|
var CONTRIBUTE_NOW_SECRETS_STORE_DIRNAME = "secrets";
|
|
10626
10880
|
var OLLAMA_CLOUD_API_KEY = "ollama.cloud.apiKey";
|
|
10881
|
+
var OPENROUTER_API_KEY = "openrouter.apiKey";
|
|
10627
10882
|
function getSecretsStorePath(baseDir = homedir()) {
|
|
10628
10883
|
return resolve4(baseDir, CONTRIBUTE_NOW_SECRETS_DIRNAME, CONTRIBUTE_NOW_SECRETS_STORE_DIRNAME);
|
|
10629
10884
|
}
|
|
@@ -10689,6 +10944,36 @@ async function deleteOllamaCloudApiKey(baseDir = homedir()) {
|
|
|
10689
10944
|
writeSecretsStore(nextStore, baseDir);
|
|
10690
10945
|
return true;
|
|
10691
10946
|
}
|
|
10947
|
+
async function hasOpenRouterApiKey(baseDir = homedir()) {
|
|
10948
|
+
return typeof readSecretsStore(baseDir)?.[OPENROUTER_API_KEY] === "string";
|
|
10949
|
+
}
|
|
10950
|
+
async function getOpenRouterApiKey(baseDir = homedir()) {
|
|
10951
|
+
return readSecretsStore(baseDir)?.[OPENROUTER_API_KEY] ?? null;
|
|
10952
|
+
}
|
|
10953
|
+
async function setOpenRouterApiKey(value, baseDir = homedir()) {
|
|
10954
|
+
const existingStore = readSecretsStore(baseDir) ?? {};
|
|
10955
|
+
writeSecretsStore({
|
|
10956
|
+
...existingStore,
|
|
10957
|
+
[OPENROUTER_API_KEY]: value
|
|
10958
|
+
}, baseDir);
|
|
10959
|
+
}
|
|
10960
|
+
async function deleteOpenRouterApiKey(baseDir = homedir()) {
|
|
10961
|
+
const existingStore = readSecretsStore(baseDir);
|
|
10962
|
+
if (!existingStore || !(OPENROUTER_API_KEY in existingStore)) {
|
|
10963
|
+
return false;
|
|
10964
|
+
}
|
|
10965
|
+
const nextStore = { ...existingStore };
|
|
10966
|
+
delete nextStore[OPENROUTER_API_KEY];
|
|
10967
|
+
if (Object.keys(nextStore).length === 0) {
|
|
10968
|
+
try {
|
|
10969
|
+
rmSync(getSecretsFilePath(baseDir), { force: true });
|
|
10970
|
+
rmSync(getSecretsStorePath(baseDir), { recursive: true, force: true });
|
|
10971
|
+
} catch {}
|
|
10972
|
+
return true;
|
|
10973
|
+
}
|
|
10974
|
+
writeSecretsStore(nextStore, baseDir);
|
|
10975
|
+
return true;
|
|
10976
|
+
}
|
|
10692
10977
|
|
|
10693
10978
|
// src/utils/copilot.ts
|
|
10694
10979
|
var CONVENTIONAL_COMMIT_SYSTEM_PROMPT = `Git commit message generator. Format: <type>[!][(<scope>)]: <description>
|
|
@@ -10756,6 +11041,8 @@ Rules: title concise present tense, describes the PR theme not individual commit
|
|
|
10756
11041
|
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
11042
|
var DEFAULT_OLLAMA_CLOUD_MODEL = "gpt-oss:120b";
|
|
10758
11043
|
var DEFAULT_OLLAMA_CLOUD_HOST = "https://ollama.com/v1";
|
|
11044
|
+
var DEFAULT_OPENROUTER_MODEL = "openai/gpt-4o-mini";
|
|
11045
|
+
var DEFAULT_OPENROUTER_HOST = "https://openrouter.ai/api/v1";
|
|
10759
11046
|
function prioritizeOllamaCloudModels(models, preferredModel = DEFAULT_OLLAMA_CLOUD_MODEL) {
|
|
10760
11047
|
const uniqueModels = [...new Set(models.map((model) => model.trim()).filter(Boolean))];
|
|
10761
11048
|
const sortedModels = [...uniqueModels].sort((left, right) => left.localeCompare(right));
|
|
@@ -10763,7 +11050,9 @@ function prioritizeOllamaCloudModels(models, preferredModel = DEFAULT_OLLAMA_CLO
|
|
|
10763
11050
|
}
|
|
10764
11051
|
function extractOllamaCloudModelIds(payload) {
|
|
10765
11052
|
const records = typeof payload === "object" && payload !== null ? Array.isArray(payload.data) ? payload.data : Array.isArray(payload.models) ? payload.models : [] : [];
|
|
10766
|
-
return [
|
|
11053
|
+
return [
|
|
11054
|
+
...new Set(records.map(getOllamaCloudModelId).filter((id) => id !== null))
|
|
11055
|
+
].sort((left, right) => left.localeCompare(right));
|
|
10767
11056
|
}
|
|
10768
11057
|
function getOllamaCloudModelId(record) {
|
|
10769
11058
|
if (typeof record !== "object" || record === null) {
|
|
@@ -10792,6 +11081,40 @@ function normalizeOllamaCloudHost(host) {
|
|
|
10792
11081
|
const trimmed = (host?.trim() || DEFAULT_OLLAMA_CLOUD_HOST).replace(/\/+$/, "");
|
|
10793
11082
|
return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
|
|
10794
11083
|
}
|
|
11084
|
+
function extractOpenRouterModelIds(payload) {
|
|
11085
|
+
const records = typeof payload === "object" && payload !== null ? Array.isArray(payload.data) ? payload.data : [] : [];
|
|
11086
|
+
return [
|
|
11087
|
+
...new Set(records.map(getOpenRouterModelId).filter((id) => id !== null))
|
|
11088
|
+
].sort((left, right) => left.localeCompare(right));
|
|
11089
|
+
}
|
|
11090
|
+
function getOpenRouterModelId(record) {
|
|
11091
|
+
if (typeof record !== "object" || record === null) {
|
|
11092
|
+
return null;
|
|
11093
|
+
}
|
|
11094
|
+
const candidate = typeof record.id === "string" ? record.id : null;
|
|
11095
|
+
const normalized = candidate?.trim();
|
|
11096
|
+
return normalized ? normalized : null;
|
|
11097
|
+
}
|
|
11098
|
+
async function fetchOpenRouterModels(apiKey) {
|
|
11099
|
+
const response = await fetch(`${DEFAULT_OPENROUTER_HOST}/models`, {
|
|
11100
|
+
headers: {
|
|
11101
|
+
Accept: "application/json",
|
|
11102
|
+
Authorization: `Bearer ${apiKey}`
|
|
11103
|
+
}
|
|
11104
|
+
});
|
|
11105
|
+
if (!response.ok) {
|
|
11106
|
+
if (response.status === 401 || response.status === 403) {
|
|
11107
|
+
throw new Error("OpenRouter authentication failed");
|
|
11108
|
+
}
|
|
11109
|
+
throw new Error(`OpenRouter model lookup failed (${response.status} ${response.statusText})`);
|
|
11110
|
+
}
|
|
11111
|
+
return extractOpenRouterModelIds(await response.json());
|
|
11112
|
+
}
|
|
11113
|
+
function prioritizeOpenRouterModels(models, preferredModel = DEFAULT_OPENROUTER_MODEL) {
|
|
11114
|
+
const uniqueModels = [...new Set(models.map((model) => model.trim()).filter(Boolean))];
|
|
11115
|
+
const sortedModels = [...uniqueModels].sort((left, right) => left.localeCompare(right));
|
|
11116
|
+
return sortedModels.includes(preferredModel) ? [preferredModel, ...sortedModels.filter((model) => model !== preferredModel)] : sortedModels;
|
|
11117
|
+
}
|
|
10795
11118
|
function resolveAIConfig(config) {
|
|
10796
11119
|
const resolvedConfig = config ?? readConfig();
|
|
10797
11120
|
const provider = resolvedConfig?.aiProvider ?? "copilot";
|
|
@@ -10803,6 +11126,14 @@ function resolveAIConfig(config) {
|
|
|
10803
11126
|
host: DEFAULT_OLLAMA_CLOUD_HOST
|
|
10804
11127
|
};
|
|
10805
11128
|
}
|
|
11129
|
+
if (provider === "openrouter") {
|
|
11130
|
+
return {
|
|
11131
|
+
provider,
|
|
11132
|
+
providerLabel: "OpenRouter",
|
|
11133
|
+
model: resolvedConfig?.aiModel?.trim() || DEFAULT_OPENROUTER_MODEL,
|
|
11134
|
+
host: DEFAULT_OPENROUTER_HOST
|
|
11135
|
+
};
|
|
11136
|
+
}
|
|
10806
11137
|
return {
|
|
10807
11138
|
provider: "copilot",
|
|
10808
11139
|
providerLabel: "GitHub Copilot"
|
|
@@ -10996,6 +11327,28 @@ async function checkCopilotAvailable2() {
|
|
|
10996
11327
|
return `Could not reach Ollama Cloud API: ${msg}`;
|
|
10997
11328
|
}
|
|
10998
11329
|
}
|
|
11330
|
+
if (aiConfig.provider === "openrouter") {
|
|
11331
|
+
if (!await hasOpenRouterApiKey()) {
|
|
11332
|
+
return "OpenRouter API key not found. Run `cn setup` to save it.";
|
|
11333
|
+
}
|
|
11334
|
+
try {
|
|
11335
|
+
const apiKey = await getOpenRouterApiKey();
|
|
11336
|
+
if (!apiKey) {
|
|
11337
|
+
return "OpenRouter API key not found. Run `cn setup` to save it.";
|
|
11338
|
+
}
|
|
11339
|
+
await fetchOpenRouterModels(apiKey);
|
|
11340
|
+
return null;
|
|
11341
|
+
} catch (err) {
|
|
11342
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11343
|
+
if (msg === "OpenRouter authentication failed") {
|
|
11344
|
+
return "OpenRouter authentication failed. Update your saved API key with `cn setup`.";
|
|
11345
|
+
}
|
|
11346
|
+
if (msg.startsWith("OpenRouter model lookup failed")) {
|
|
11347
|
+
return msg.replace("model lookup", "health check");
|
|
11348
|
+
}
|
|
11349
|
+
return `Could not reach OpenRouter API: ${msg}`;
|
|
11350
|
+
}
|
|
11351
|
+
}
|
|
10999
11352
|
try {
|
|
11000
11353
|
const client = await getManagedClient();
|
|
11001
11354
|
try {
|
|
@@ -11097,21 +11450,64 @@ async function callOllamaCloud(systemMessage, userMessage, model, timeoutMs = CO
|
|
|
11097
11450
|
clearTimeout(timer);
|
|
11098
11451
|
}
|
|
11099
11452
|
}
|
|
11100
|
-
async function
|
|
11453
|
+
async function callOpenRouter(systemMessage, userMessage, model, timeoutMs = COPILOT_TIMEOUT_MS) {
|
|
11101
11454
|
const aiConfig = resolveAIConfig();
|
|
11102
|
-
|
|
11103
|
-
|
|
11455
|
+
const apiKey = await getOpenRouterApiKey();
|
|
11456
|
+
if (!apiKey) {
|
|
11457
|
+
throw new Error("OpenRouter API key is not configured");
|
|
11104
11458
|
}
|
|
11105
|
-
|
|
11106
|
-
|
|
11107
|
-
|
|
11108
|
-
|
|
11109
|
-
|
|
11110
|
-
|
|
11111
|
-
}
|
|
11112
|
-
|
|
11113
|
-
|
|
11114
|
-
|
|
11459
|
+
const controller = new AbortController;
|
|
11460
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
11461
|
+
try {
|
|
11462
|
+
const response = await fetch(`${DEFAULT_OPENROUTER_HOST}/chat/completions`, {
|
|
11463
|
+
method: "POST",
|
|
11464
|
+
headers: {
|
|
11465
|
+
Authorization: `Bearer ${apiKey}`,
|
|
11466
|
+
"Content-Type": "application/json",
|
|
11467
|
+
"HTTP-Referer": "https://github.com/warengonzaga/contribute-now",
|
|
11468
|
+
"X-Title": "contribute-now"
|
|
11469
|
+
},
|
|
11470
|
+
body: JSON.stringify({
|
|
11471
|
+
model: model?.trim() || aiConfig.model || DEFAULT_OPENROUTER_MODEL,
|
|
11472
|
+
messages: [
|
|
11473
|
+
{ role: "system", content: systemMessage },
|
|
11474
|
+
{ role: "user", content: userMessage }
|
|
11475
|
+
],
|
|
11476
|
+
stream: false
|
|
11477
|
+
}),
|
|
11478
|
+
signal: controller.signal
|
|
11479
|
+
});
|
|
11480
|
+
if (!response.ok) {
|
|
11481
|
+
const body = await response.text();
|
|
11482
|
+
if (response.status === 401 || response.status === 403) {
|
|
11483
|
+
throw new Error("OpenRouter authentication failed");
|
|
11484
|
+
}
|
|
11485
|
+
throw new Error(`OpenRouter request failed (${response.status} ${response.statusText}): ${body.slice(0, 200)}`);
|
|
11486
|
+
}
|
|
11487
|
+
const data = await response.json();
|
|
11488
|
+
return data.choices?.[0]?.message?.content?.trim() || null;
|
|
11489
|
+
} finally {
|
|
11490
|
+
clearTimeout(timer);
|
|
11491
|
+
}
|
|
11492
|
+
}
|
|
11493
|
+
async function callAI(systemMessage, userMessage, model, timeoutMs = COPILOT_TIMEOUT_MS) {
|
|
11494
|
+
const aiConfig = resolveAIConfig();
|
|
11495
|
+
if (aiConfig.provider === "ollama-cloud") {
|
|
11496
|
+
return callOllamaCloud(systemMessage, userMessage, model, timeoutMs);
|
|
11497
|
+
}
|
|
11498
|
+
if (aiConfig.provider === "openrouter") {
|
|
11499
|
+
return callOpenRouter(systemMessage, userMessage, model, timeoutMs);
|
|
11500
|
+
}
|
|
11501
|
+
return callCopilot(systemMessage, userMessage, model, timeoutMs);
|
|
11502
|
+
}
|
|
11503
|
+
function getCommitSystemPrompt(convention) {
|
|
11504
|
+
if (convention === "conventional")
|
|
11505
|
+
return CONVENTIONAL_COMMIT_SYSTEM_PROMPT;
|
|
11506
|
+
return CLEAN_COMMIT_SYSTEM_PROMPT;
|
|
11507
|
+
}
|
|
11508
|
+
function extractJson(raw) {
|
|
11509
|
+
let text = raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
|
|
11510
|
+
if (text.startsWith("[") || text.startsWith("{"))
|
|
11115
11511
|
return text;
|
|
11116
11512
|
const arrayStart = text.indexOf("[");
|
|
11117
11513
|
const objStart = text.indexOf("{");
|
|
@@ -11651,145 +12047,8 @@ async function promptForBranchName(options) {
|
|
|
11651
12047
|
}
|
|
11652
12048
|
}
|
|
11653
12049
|
|
|
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
12050
|
// src/commands/clean.ts
|
|
12051
|
+
init_gh();
|
|
11793
12052
|
async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
11794
12053
|
if (!config)
|
|
11795
12054
|
return "skipped";
|
|
@@ -11992,185 +12251,77 @@ ${import_picocolors8.default.bold("Stale branches (remote deleted, likely squash
|
|
|
11992
12251
|
}
|
|
11993
12252
|
});
|
|
11994
12253
|
|
|
11995
|
-
// src/commands/
|
|
12254
|
+
// src/commands/commit.ts
|
|
11996
12255
|
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
11997
|
-
|
|
12256
|
+
|
|
12257
|
+
// src/utils/convention.ts
|
|
12258
|
+
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;
|
|
12259
|
+
var CONVENTIONAL_COMMIT_PATTERN = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(!?)(\([a-zA-Z0-9][a-zA-Z0-9._-]*\))?: .{1,72}$/;
|
|
12260
|
+
var CONVENTION_LABELS = {
|
|
12261
|
+
conventional: "Conventional Commits",
|
|
12262
|
+
"clean-commit": "Clean Commit (by WGTech Labs)",
|
|
12263
|
+
none: "No convention"
|
|
12264
|
+
};
|
|
12265
|
+
var CONVENTION_DESCRIPTIONS = {
|
|
12266
|
+
conventional: "Conventional Commits \u2014 feat: | fix: | docs: | chore: etc. (conventionalcommits.org)",
|
|
12267
|
+
"clean-commit": "Clean Commit \u2014 \uD83D\uDCE6 new: | \uD83D\uDD27 update: | \uD83D\uDDD1\uFE0F remove: etc. (by WGTech Labs)",
|
|
12268
|
+
none: "No commit convention enforcement"
|
|
12269
|
+
};
|
|
12270
|
+
var CONVENTION_FORMAT_HINTS = {
|
|
12271
|
+
conventional: [
|
|
12272
|
+
"Format: <type>[!][(<scope>)]: <description>",
|
|
12273
|
+
"Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert",
|
|
12274
|
+
"Examples: feat: add login page | fix(auth): resolve token expiry | docs: update README",
|
|
12275
|
+
"Do not use backticks or markdown formatting in the message."
|
|
12276
|
+
],
|
|
12277
|
+
"clean-commit": [
|
|
12278
|
+
"Format: <emoji> <type>[!][(<scope>)]: <description>",
|
|
12279
|
+
"Types: \uD83D\uDCE6 new | \uD83D\uDD27 update | \uD83D\uDDD1\uFE0F remove | \uD83D\uDD12 security | \u2699\uFE0F setup | \u2615 chore | \uD83E\uDDEA test | \uD83D\uDCD6 docs | \uD83D\uDE80 release",
|
|
12280
|
+
"Examples: \uD83D\uDCE6 new: user auth | \uD83D\uDD27 update (api): improve errors | \u2699\uFE0F setup (ci): add workflow",
|
|
12281
|
+
"Do not use backticks or markdown formatting in the message."
|
|
12282
|
+
]
|
|
12283
|
+
};
|
|
12284
|
+
function hasUnsupportedCommitMessageChars(message) {
|
|
12285
|
+
return message.includes("`");
|
|
12286
|
+
}
|
|
12287
|
+
function validateCommitMessage(message, convention) {
|
|
12288
|
+
if (convention === "none")
|
|
12289
|
+
return true;
|
|
12290
|
+
if (hasUnsupportedCommitMessageChars(message))
|
|
12291
|
+
return false;
|
|
12292
|
+
if (convention === "clean-commit")
|
|
12293
|
+
return CLEAN_COMMIT_PATTERN.test(message);
|
|
12294
|
+
if (convention === "conventional")
|
|
12295
|
+
return CONVENTIONAL_COMMIT_PATTERN.test(message);
|
|
12296
|
+
return true;
|
|
12297
|
+
}
|
|
12298
|
+
function getValidationError(convention) {
|
|
12299
|
+
if (convention === "none")
|
|
12300
|
+
return [];
|
|
12301
|
+
return [
|
|
12302
|
+
`Commit message does not follow ${CONVENTION_LABELS[convention]} format.`,
|
|
12303
|
+
"Do not use backticks or markdown formatting in commit messages.",
|
|
12304
|
+
...CONVENTION_FORMAT_HINTS[convention]
|
|
12305
|
+
];
|
|
12306
|
+
}
|
|
12307
|
+
|
|
12308
|
+
// src/commands/commit.ts
|
|
12309
|
+
function isEmptyGroupCommitResult(detail) {
|
|
12310
|
+
return /no changes added to commit|nothing to commit/i.test(detail);
|
|
12311
|
+
}
|
|
12312
|
+
var commit_default = defineCommand({
|
|
11998
12313
|
meta: {
|
|
11999
|
-
name: "
|
|
12000
|
-
description: "
|
|
12314
|
+
name: "commit",
|
|
12315
|
+
description: "Stage changes and create a commit message (AI-powered)"
|
|
12001
12316
|
},
|
|
12002
12317
|
args: {
|
|
12003
|
-
|
|
12318
|
+
model: {
|
|
12319
|
+
type: "string",
|
|
12320
|
+
description: "AI model to use for commit message generation"
|
|
12321
|
+
},
|
|
12322
|
+
"no-ai": {
|
|
12004
12323
|
type: "boolean",
|
|
12005
|
-
|
|
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
|
-
|
|
12106
|
-
// src/utils/convention.ts
|
|
12107
|
-
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;
|
|
12108
|
-
var CONVENTIONAL_COMMIT_PATTERN = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(!?)(\([a-zA-Z0-9][a-zA-Z0-9._-]*\))?: .{1,72}$/;
|
|
12109
|
-
var CONVENTION_LABELS = {
|
|
12110
|
-
conventional: "Conventional Commits",
|
|
12111
|
-
"clean-commit": "Clean Commit (by WGTech Labs)",
|
|
12112
|
-
none: "No convention"
|
|
12113
|
-
};
|
|
12114
|
-
var CONVENTION_DESCRIPTIONS = {
|
|
12115
|
-
conventional: "Conventional Commits \u2014 feat: | fix: | docs: | chore: etc. (conventionalcommits.org)",
|
|
12116
|
-
"clean-commit": "Clean Commit \u2014 \uD83D\uDCE6 new: | \uD83D\uDD27 update: | \uD83D\uDDD1\uFE0F remove: etc. (by WGTech Labs)",
|
|
12117
|
-
none: "No commit convention enforcement"
|
|
12118
|
-
};
|
|
12119
|
-
var CONVENTION_FORMAT_HINTS = {
|
|
12120
|
-
conventional: [
|
|
12121
|
-
"Format: <type>[!][(<scope>)]: <description>",
|
|
12122
|
-
"Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert",
|
|
12123
|
-
"Examples: feat: add login page | fix(auth): resolve token expiry | docs: update README",
|
|
12124
|
-
"Do not use backticks or markdown formatting in the message."
|
|
12125
|
-
],
|
|
12126
|
-
"clean-commit": [
|
|
12127
|
-
"Format: <emoji> <type>[!][(<scope>)]: <description>",
|
|
12128
|
-
"Types: \uD83D\uDCE6 new | \uD83D\uDD27 update | \uD83D\uDDD1\uFE0F remove | \uD83D\uDD12 security | \u2699\uFE0F setup | \u2615 chore | \uD83E\uDDEA test | \uD83D\uDCD6 docs | \uD83D\uDE80 release",
|
|
12129
|
-
"Examples: \uD83D\uDCE6 new: user auth | \uD83D\uDD27 update (api): improve errors | \u2699\uFE0F setup (ci): add workflow",
|
|
12130
|
-
"Do not use backticks or markdown formatting in the message."
|
|
12131
|
-
]
|
|
12132
|
-
};
|
|
12133
|
-
function hasUnsupportedCommitMessageChars(message) {
|
|
12134
|
-
return message.includes("`");
|
|
12135
|
-
}
|
|
12136
|
-
function validateCommitMessage(message, convention) {
|
|
12137
|
-
if (convention === "none")
|
|
12138
|
-
return true;
|
|
12139
|
-
if (hasUnsupportedCommitMessageChars(message))
|
|
12140
|
-
return false;
|
|
12141
|
-
if (convention === "clean-commit")
|
|
12142
|
-
return CLEAN_COMMIT_PATTERN.test(message);
|
|
12143
|
-
if (convention === "conventional")
|
|
12144
|
-
return CONVENTIONAL_COMMIT_PATTERN.test(message);
|
|
12145
|
-
return true;
|
|
12146
|
-
}
|
|
12147
|
-
function getValidationError(convention) {
|
|
12148
|
-
if (convention === "none")
|
|
12149
|
-
return [];
|
|
12150
|
-
return [
|
|
12151
|
-
`Commit message does not follow ${CONVENTION_LABELS[convention]} format.`,
|
|
12152
|
-
"Do not use backticks or markdown formatting in commit messages.",
|
|
12153
|
-
...CONVENTION_FORMAT_HINTS[convention]
|
|
12154
|
-
];
|
|
12155
|
-
}
|
|
12156
|
-
|
|
12157
|
-
// src/commands/commit.ts
|
|
12158
|
-
function isEmptyGroupCommitResult(detail) {
|
|
12159
|
-
return /no changes added to commit|nothing to commit/i.test(detail);
|
|
12160
|
-
}
|
|
12161
|
-
var commit_default = defineCommand({
|
|
12162
|
-
meta: {
|
|
12163
|
-
name: "commit",
|
|
12164
|
-
description: "Stage changes and create a commit message (AI-powered)"
|
|
12165
|
-
},
|
|
12166
|
-
args: {
|
|
12167
|
-
model: {
|
|
12168
|
-
type: "string",
|
|
12169
|
-
description: "AI model to use for commit message generation"
|
|
12170
|
-
},
|
|
12171
|
-
"no-ai": {
|
|
12172
|
-
type: "boolean",
|
|
12173
|
-
description: "Skip AI and write commit message manually",
|
|
12324
|
+
description: "Skip AI and write commit message manually",
|
|
12174
12325
|
default: false
|
|
12175
12326
|
},
|
|
12176
12327
|
group: {
|
|
@@ -12208,9 +12359,9 @@ var commit_default = defineCommand({
|
|
|
12208
12359
|
process.exit(1);
|
|
12209
12360
|
}
|
|
12210
12361
|
console.log(`
|
|
12211
|
-
${
|
|
12362
|
+
${import_picocolors9.default.bold("Changed files:")}`);
|
|
12212
12363
|
for (const f3 of changedFiles) {
|
|
12213
|
-
console.log(` ${
|
|
12364
|
+
console.log(` ${import_picocolors9.default.dim("\u2022")} ${f3}`);
|
|
12214
12365
|
}
|
|
12215
12366
|
const stageAction = await selectPrompt("No staged changes. How would you like to stage?", [
|
|
12216
12367
|
"Stage all changes",
|
|
@@ -12252,8 +12403,8 @@ ${import_picocolors10.default.bold("Changed files:")}`);
|
|
|
12252
12403
|
const dirs = new Set(stagedFiles.map((f3) => f3.split("/")[0]));
|
|
12253
12404
|
if (dirs.size > 1) {
|
|
12254
12405
|
console.log();
|
|
12255
|
-
warn(`You're staging ${
|
|
12256
|
-
info(
|
|
12406
|
+
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.`);
|
|
12407
|
+
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
12408
|
const choice = await selectPrompt("How would you like to proceed?", [
|
|
12258
12409
|
"Continue as single commit",
|
|
12259
12410
|
"Switch to group mode (AI splits into atomic commits)",
|
|
@@ -12284,7 +12435,7 @@ ${import_picocolors10.default.bold("Changed files:")}`);
|
|
|
12284
12435
|
if (commitMessage) {
|
|
12285
12436
|
spinner.success("AI commit message generated.");
|
|
12286
12437
|
console.log(`
|
|
12287
|
-
${
|
|
12438
|
+
${import_picocolors9.default.dim("AI suggestion:")} ${import_picocolors9.default.bold(import_picocolors9.default.cyan(commitMessage))}`);
|
|
12288
12439
|
} else {
|
|
12289
12440
|
spinner.fail("AI did not return a commit message.");
|
|
12290
12441
|
warn("Falling back to manual entry.");
|
|
@@ -12312,7 +12463,7 @@ ${import_picocolors10.default.bold("Changed files:")}`);
|
|
|
12312
12463
|
if (regen) {
|
|
12313
12464
|
spinner.success("Commit message regenerated.");
|
|
12314
12465
|
console.log(`
|
|
12315
|
-
${
|
|
12466
|
+
${import_picocolors9.default.dim("AI suggestion:")} ${import_picocolors9.default.bold(import_picocolors9.default.cyan(regen))}`);
|
|
12316
12467
|
const ok = await confirmPrompt("Use this message?");
|
|
12317
12468
|
finalMessage = ok ? regen : await inputPrompt("Enter commit message manually");
|
|
12318
12469
|
} else {
|
|
@@ -12327,7 +12478,7 @@ ${import_picocolors10.default.bold("Changed files:")}`);
|
|
|
12327
12478
|
if (convention2 !== "none") {
|
|
12328
12479
|
console.log();
|
|
12329
12480
|
for (const hint of CONVENTION_FORMAT_HINTS[convention2]) {
|
|
12330
|
-
console.log(
|
|
12481
|
+
console.log(import_picocolors9.default.dim(hint));
|
|
12331
12482
|
}
|
|
12332
12483
|
console.log();
|
|
12333
12484
|
}
|
|
@@ -12351,7 +12502,7 @@ ${import_picocolors10.default.bold("Changed files:")}`);
|
|
|
12351
12502
|
error(`Failed to commit: ${result.stderr}`);
|
|
12352
12503
|
process.exit(1);
|
|
12353
12504
|
}
|
|
12354
|
-
success(`Committed: ${
|
|
12505
|
+
success(`Committed: ${import_picocolors9.default.bold(finalMessage)}`);
|
|
12355
12506
|
}
|
|
12356
12507
|
});
|
|
12357
12508
|
async function runGroupCommit(model, config) {
|
|
@@ -12368,9 +12519,9 @@ async function runGroupCommit(model, config) {
|
|
|
12368
12519
|
process.exit(1);
|
|
12369
12520
|
}
|
|
12370
12521
|
console.log(`
|
|
12371
|
-
${
|
|
12522
|
+
${import_picocolors9.default.bold("Changed files:")}`);
|
|
12372
12523
|
for (const f3 of changedFiles) {
|
|
12373
|
-
console.log(` ${
|
|
12524
|
+
console.log(` ${import_picocolors9.default.dim("\u2022")} ${f3}`);
|
|
12374
12525
|
}
|
|
12375
12526
|
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
12527
|
tips: LOADING_TIPS
|
|
@@ -12416,13 +12567,13 @@ ${import_picocolors10.default.bold("Changed files:")}`);
|
|
|
12416
12567
|
let commitAll = false;
|
|
12417
12568
|
while (!proceedToCommit) {
|
|
12418
12569
|
console.log(`
|
|
12419
|
-
${
|
|
12570
|
+
${import_picocolors9.default.bold(`AI suggested ${validGroups.length} commit group(s):`)}
|
|
12420
12571
|
`);
|
|
12421
12572
|
for (let i2 = 0;i2 < validGroups.length; i2++) {
|
|
12422
12573
|
const g3 = validGroups[i2];
|
|
12423
|
-
console.log(` ${
|
|
12574
|
+
console.log(` ${import_picocolors9.default.cyan(`Group ${i2 + 1}:`)} ${import_picocolors9.default.bold(g3.message)}`);
|
|
12424
12575
|
for (const f3 of g3.files) {
|
|
12425
|
-
console.log(` ${
|
|
12576
|
+
console.log(` ${import_picocolors9.default.dim("\u2022")} ${f3}`);
|
|
12426
12577
|
}
|
|
12427
12578
|
console.log();
|
|
12428
12579
|
}
|
|
@@ -12488,16 +12639,16 @@ ${import_picocolors10.default.bold(`AI suggested ${validGroups.length} commit gr
|
|
|
12488
12639
|
continue;
|
|
12489
12640
|
}
|
|
12490
12641
|
committed++;
|
|
12491
|
-
success(`Committed group ${i2 + 1}: ${
|
|
12642
|
+
success(`Committed group ${i2 + 1}: ${import_picocolors9.default.bold(group.message)}`);
|
|
12492
12643
|
}
|
|
12493
12644
|
} else {
|
|
12494
12645
|
for (let i2 = 0;i2 < validGroups.length; i2++) {
|
|
12495
12646
|
const group = validGroups[i2];
|
|
12496
|
-
console.log(
|
|
12647
|
+
console.log(import_picocolors9.default.bold(`
|
|
12497
12648
|
\u2500\u2500 Group ${i2 + 1}/${validGroups.length} \u2500\u2500`));
|
|
12498
|
-
console.log(` ${
|
|
12649
|
+
console.log(` ${import_picocolors9.default.cyan(group.message)}`);
|
|
12499
12650
|
for (const f3 of group.files) {
|
|
12500
|
-
console.log(` ${
|
|
12651
|
+
console.log(` ${import_picocolors9.default.dim("\u2022")} ${f3}`);
|
|
12501
12652
|
}
|
|
12502
12653
|
let message = group.message;
|
|
12503
12654
|
let actionDone = false;
|
|
@@ -12521,7 +12672,7 @@ ${import_picocolors10.default.bold(`AI suggested ${validGroups.length} commit gr
|
|
|
12521
12672
|
if (newMsg) {
|
|
12522
12673
|
message = newMsg;
|
|
12523
12674
|
group.message = newMsg;
|
|
12524
|
-
regenSpinner.success(`New message: ${
|
|
12675
|
+
regenSpinner.success(`New message: ${import_picocolors9.default.bold(message)}`);
|
|
12525
12676
|
} else {
|
|
12526
12677
|
regenSpinner.fail("AI could not generate a new message. Keeping current one.");
|
|
12527
12678
|
}
|
|
@@ -12584,7 +12735,7 @@ ${import_picocolors10.default.bold(`AI suggested ${validGroups.length} commit gr
|
|
|
12584
12735
|
continue;
|
|
12585
12736
|
}
|
|
12586
12737
|
committed++;
|
|
12587
|
-
success(`Committed group ${i2 + 1}: ${
|
|
12738
|
+
success(`Committed group ${i2 + 1}: ${import_picocolors9.default.bold(message)}`);
|
|
12588
12739
|
actionDone = true;
|
|
12589
12740
|
}
|
|
12590
12741
|
}
|
|
@@ -12599,7 +12750,7 @@ ${import_picocolors10.default.bold(`AI suggested ${validGroups.length} commit gr
|
|
|
12599
12750
|
}
|
|
12600
12751
|
|
|
12601
12752
|
// src/commands/config.ts
|
|
12602
|
-
var
|
|
12753
|
+
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
12603
12754
|
var WORKFLOW_OPTIONS = [
|
|
12604
12755
|
{ value: "clean-flow", label: WORKFLOW_DESCRIPTIONS["clean-flow"] },
|
|
12605
12756
|
{ value: "github-flow", label: WORKFLOW_DESCRIPTIONS["github-flow"] },
|
|
@@ -12616,7 +12767,8 @@ var CONVENTION_OPTIONS = [
|
|
|
12616
12767
|
];
|
|
12617
12768
|
var AI_PROVIDER_OPTIONS = [
|
|
12618
12769
|
{ value: "copilot", label: "GitHub Copilot" },
|
|
12619
|
-
{ value: "ollama-cloud", label: "Ollama Cloud" }
|
|
12770
|
+
{ value: "ollama-cloud", label: "Ollama Cloud" },
|
|
12771
|
+
{ value: "openrouter", label: "OpenRouter" }
|
|
12620
12772
|
];
|
|
12621
12773
|
function parseBranchPrefixesInput(input, fallback) {
|
|
12622
12774
|
const values = input.split(",").map((value) => value.trim()).filter(Boolean);
|
|
@@ -12650,6 +12802,10 @@ function finalizeEditedConfig(current, draft) {
|
|
|
12650
12802
|
next.aiModel = (draft.aiModel?.trim() || DEFAULT_OLLAMA_CLOUD_MODEL).trim();
|
|
12651
12803
|
return next;
|
|
12652
12804
|
}
|
|
12805
|
+
if (next.aiProvider === "openrouter") {
|
|
12806
|
+
next.aiModel = (draft.aiModel?.trim() || DEFAULT_OPENROUTER_MODEL).trim();
|
|
12807
|
+
return next;
|
|
12808
|
+
}
|
|
12653
12809
|
delete next.aiModel;
|
|
12654
12810
|
return next;
|
|
12655
12811
|
}
|
|
@@ -12657,6 +12813,8 @@ function buildConfigSnapshot(config, meta) {
|
|
|
12657
12813
|
const aiConfig = resolveAIConfig(config);
|
|
12658
12814
|
const aiEnabled = isAIEnabled(config);
|
|
12659
12815
|
const usingOllamaCloud = aiEnabled && aiConfig.provider === "ollama-cloud";
|
|
12816
|
+
const usingOpenRouter = aiEnabled && aiConfig.provider === "openrouter";
|
|
12817
|
+
const needsSecretInfo = usingOllamaCloud || usingOpenRouter;
|
|
12660
12818
|
return {
|
|
12661
12819
|
source: meta.source,
|
|
12662
12820
|
location: meta.location,
|
|
@@ -12677,7 +12835,8 @@ function buildConfigSnapshot(config, meta) {
|
|
|
12677
12835
|
providerLabel: aiEnabled ? aiConfig.providerLabel : null,
|
|
12678
12836
|
model: aiEnabled ? aiConfig.model ?? null : null,
|
|
12679
12837
|
ollamaCloudApiKeyPresent: usingOllamaCloud ? meta.hasOllamaCloudApiKey : null,
|
|
12680
|
-
|
|
12838
|
+
openrouterApiKeyPresent: usingOpenRouter ? meta.hasOpenRouterApiKey : null,
|
|
12839
|
+
secretsPath: needsSecretInfo ? meta.secretsPath : null
|
|
12681
12840
|
}
|
|
12682
12841
|
};
|
|
12683
12842
|
}
|
|
@@ -12714,6 +12873,36 @@ async function promptForOllamaCloudModelSelection(apiKey, fallbackModel) {
|
|
|
12714
12873
|
}
|
|
12715
12874
|
return inputPrompt("Ollama Cloud model", fallbackModel);
|
|
12716
12875
|
}
|
|
12876
|
+
async function promptForOpenRouterModelSelection(apiKey, fallbackModel) {
|
|
12877
|
+
if (apiKey) {
|
|
12878
|
+
try {
|
|
12879
|
+
info("Fetching available OpenRouter models...");
|
|
12880
|
+
const models = prioritizeOpenRouterModels(await fetchOpenRouterModels(apiKey));
|
|
12881
|
+
if (models.length > 0) {
|
|
12882
|
+
const manualChoice = "Enter model manually";
|
|
12883
|
+
const choices = models.map((model) => ({
|
|
12884
|
+
value: model,
|
|
12885
|
+
label: model === DEFAULT_OPENROUTER_MODEL ? `${model} (default)` : model
|
|
12886
|
+
}));
|
|
12887
|
+
const selected = await selectPrompt("OpenRouter model", [
|
|
12888
|
+
...choices.map((choice) => choice.label),
|
|
12889
|
+
manualChoice
|
|
12890
|
+
]);
|
|
12891
|
+
if (selected !== manualChoice) {
|
|
12892
|
+
return choices.find((choice) => choice.label === selected)?.value ?? fallbackModel;
|
|
12893
|
+
}
|
|
12894
|
+
} else {
|
|
12895
|
+
warn("OpenRouter returned no available models. Enter the model name manually.");
|
|
12896
|
+
}
|
|
12897
|
+
} catch (err) {
|
|
12898
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
12899
|
+
warn(`Could not fetch OpenRouter models: ${message}`);
|
|
12900
|
+
}
|
|
12901
|
+
} else {
|
|
12902
|
+
warn("No OpenRouter API key is available yet, so the model list cannot be fetched.");
|
|
12903
|
+
}
|
|
12904
|
+
return inputPrompt("OpenRouter model", fallbackModel);
|
|
12905
|
+
}
|
|
12717
12906
|
async function selectCurrentValue(message, options, current) {
|
|
12718
12907
|
const choices = options.map((option) => ({
|
|
12719
12908
|
value: option.value,
|
|
@@ -12728,7 +12917,7 @@ async function selectBooleanValue(message, current, trueLabel, falseLabel) {
|
|
|
12728
12917
|
{ value: "false", label: falseLabel }
|
|
12729
12918
|
], current ? "true" : "false").then((value) => value === "true");
|
|
12730
12919
|
}
|
|
12731
|
-
async function promptForConfigEdits(current, hasExistingOllamaApiKey) {
|
|
12920
|
+
async function promptForConfigEdits(current, hasExistingOllamaApiKey, hasExistingOpenRouterApiKey) {
|
|
12732
12921
|
const workflow = await selectCurrentValue("Workflow mode", WORKFLOW_OPTIONS, current.workflow);
|
|
12733
12922
|
const role = await selectCurrentValue("Your role in this clone", ROLE_OPTIONS, current.role);
|
|
12734
12923
|
const mainBranch = await inputPrompt("Main branch name", current.mainBranch);
|
|
@@ -12749,6 +12938,8 @@ async function promptForConfigEdits(current, hasExistingOllamaApiKey) {
|
|
|
12749
12938
|
let aiModel;
|
|
12750
12939
|
let ollamaApiKeyAction = "keep";
|
|
12751
12940
|
let ollamaApiKey;
|
|
12941
|
+
let openrouterApiKeyAction = "keep";
|
|
12942
|
+
let openrouterApiKey;
|
|
12752
12943
|
if (aiEnabled) {
|
|
12753
12944
|
const currentProvider = current.aiProvider ?? "copilot";
|
|
12754
12945
|
aiProvider = await selectCurrentValue("AI provider", AI_PROVIDER_OPTIONS, currentProvider);
|
|
@@ -12780,16 +12971,72 @@ async function promptForConfigEdits(current, hasExistingOllamaApiKey) {
|
|
|
12780
12971
|
}
|
|
12781
12972
|
const modelLookupApiKey = ollamaApiKeyAction === "set" ? ollamaApiKey ?? null : ollamaApiKeyAction === "keep" ? await getOllamaCloudApiKey() : null;
|
|
12782
12973
|
aiModel = await promptForOllamaCloudModelSelection(modelLookupApiKey, current.aiProvider === "ollama-cloud" ? current.aiModel ?? DEFAULT_OLLAMA_CLOUD_MODEL : DEFAULT_OLLAMA_CLOUD_MODEL);
|
|
12783
|
-
|
|
12784
|
-
|
|
12974
|
+
if (hasExistingOpenRouterApiKey) {
|
|
12975
|
+
const shouldDeleteOpenRouterKey = await confirmPrompt("Delete the stored OpenRouter API key from the local secrets store?");
|
|
12976
|
+
if (shouldDeleteOpenRouterKey) {
|
|
12977
|
+
openrouterApiKeyAction = "delete";
|
|
12978
|
+
}
|
|
12979
|
+
}
|
|
12980
|
+
} else if (aiProvider === "openrouter") {
|
|
12981
|
+
if (hasExistingOpenRouterApiKey) {
|
|
12982
|
+
const apiKeyChoice = await selectPrompt("OpenRouter API key", [
|
|
12983
|
+
"Keep existing stored key",
|
|
12984
|
+
"Replace stored key",
|
|
12985
|
+
"Delete stored key"
|
|
12986
|
+
]);
|
|
12987
|
+
if (apiKeyChoice === "Replace stored key") {
|
|
12988
|
+
openrouterApiKey = (await passwordPrompt("Enter the new OpenRouter API key")).trim();
|
|
12989
|
+
if (!openrouterApiKey) {
|
|
12990
|
+
throw new Error("OpenRouter API key cannot be empty when replacing the stored key.");
|
|
12991
|
+
}
|
|
12992
|
+
openrouterApiKeyAction = "set";
|
|
12993
|
+
} else if (apiKeyChoice === "Delete stored key") {
|
|
12994
|
+
openrouterApiKeyAction = "delete";
|
|
12995
|
+
}
|
|
12996
|
+
} else {
|
|
12997
|
+
const addApiKey = await confirmPrompt("No OpenRouter API key is stored. Add one now?");
|
|
12998
|
+
if (addApiKey) {
|
|
12999
|
+
openrouterApiKey = (await passwordPrompt("Enter your OpenRouter API key")).trim();
|
|
13000
|
+
if (!openrouterApiKey) {
|
|
13001
|
+
throw new Error("OpenRouter API key cannot be empty when enabling OpenRouter.");
|
|
13002
|
+
}
|
|
13003
|
+
openrouterApiKeyAction = "set";
|
|
13004
|
+
}
|
|
13005
|
+
}
|
|
13006
|
+
const modelLookupApiKey = openrouterApiKeyAction === "set" ? openrouterApiKey ?? null : openrouterApiKeyAction === "keep" ? await getOpenRouterApiKey() : null;
|
|
13007
|
+
aiModel = await promptForOpenRouterModelSelection(modelLookupApiKey, current.aiProvider === "openrouter" ? current.aiModel ?? DEFAULT_OPENROUTER_MODEL : DEFAULT_OPENROUTER_MODEL);
|
|
13008
|
+
if (hasExistingOllamaApiKey) {
|
|
13009
|
+
const shouldDeleteStoredKey = await confirmPrompt("Delete the stored Ollama Cloud API key from the local secrets store?");
|
|
13010
|
+
if (shouldDeleteStoredKey) {
|
|
13011
|
+
ollamaApiKeyAction = "delete";
|
|
13012
|
+
}
|
|
13013
|
+
}
|
|
13014
|
+
} else {
|
|
13015
|
+
if (hasExistingOllamaApiKey) {
|
|
13016
|
+
const shouldDeleteStoredKey = await confirmPrompt("Delete the stored Ollama Cloud API key from the local secrets store?");
|
|
13017
|
+
if (shouldDeleteStoredKey) {
|
|
13018
|
+
ollamaApiKeyAction = "delete";
|
|
13019
|
+
}
|
|
13020
|
+
}
|
|
13021
|
+
if (hasExistingOpenRouterApiKey) {
|
|
13022
|
+
const shouldDeleteOpenRouterKey = await confirmPrompt("Delete the stored OpenRouter API key from the local secrets store?");
|
|
13023
|
+
if (shouldDeleteOpenRouterKey) {
|
|
13024
|
+
openrouterApiKeyAction = "delete";
|
|
13025
|
+
}
|
|
13026
|
+
}
|
|
13027
|
+
}
|
|
13028
|
+
} else {
|
|
13029
|
+
if (hasExistingOllamaApiKey) {
|
|
13030
|
+
const shouldDeleteStoredKey = await confirmPrompt("AI is disabled. Delete the stored Ollama Cloud API key from the local secrets store?");
|
|
12785
13031
|
if (shouldDeleteStoredKey) {
|
|
12786
13032
|
ollamaApiKeyAction = "delete";
|
|
12787
13033
|
}
|
|
12788
13034
|
}
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
|
|
12792
|
-
|
|
13035
|
+
if (hasExistingOpenRouterApiKey) {
|
|
13036
|
+
const shouldDeleteOpenRouterKey = await confirmPrompt("AI is disabled. Delete the stored OpenRouter API key from the local secrets store?");
|
|
13037
|
+
if (shouldDeleteOpenRouterKey) {
|
|
13038
|
+
openrouterApiKeyAction = "delete";
|
|
13039
|
+
}
|
|
12793
13040
|
}
|
|
12794
13041
|
}
|
|
12795
13042
|
return {
|
|
@@ -12808,17 +13055,17 @@ async function promptForConfigEdits(current, hasExistingOllamaApiKey) {
|
|
|
12808
13055
|
showTips
|
|
12809
13056
|
}),
|
|
12810
13057
|
ollamaApiKeyAction,
|
|
12811
|
-
ollamaApiKey
|
|
13058
|
+
ollamaApiKey,
|
|
13059
|
+
openrouterApiKeyAction,
|
|
13060
|
+
openrouterApiKey
|
|
12812
13061
|
};
|
|
12813
13062
|
}
|
|
12814
|
-
async function
|
|
13063
|
+
async function applyApiKeyEdits(result) {
|
|
12815
13064
|
if (result.ollamaApiKeyAction === "set" && result.ollamaApiKey) {
|
|
12816
13065
|
await setOllamaCloudApiKey(result.ollamaApiKey);
|
|
12817
13066
|
success("Stored Ollama Cloud API key in the local secrets store.");
|
|
12818
|
-
info(`Secrets path: ${
|
|
12819
|
-
|
|
12820
|
-
}
|
|
12821
|
-
if (result.ollamaApiKeyAction === "delete") {
|
|
13067
|
+
info(`Secrets path: ${import_picocolors10.default.bold(getSecretsStorePath())}`);
|
|
13068
|
+
} else if (result.ollamaApiKeyAction === "delete") {
|
|
12822
13069
|
const deleted = await deleteOllamaCloudApiKey();
|
|
12823
13070
|
if (deleted) {
|
|
12824
13071
|
success("Deleted stored Ollama Cloud API key.");
|
|
@@ -12826,31 +13073,49 @@ async function applyOllamaApiKeyEdit(result) {
|
|
|
12826
13073
|
info("No stored Ollama Cloud API key was found to delete.");
|
|
12827
13074
|
}
|
|
12828
13075
|
}
|
|
13076
|
+
if (result.openrouterApiKeyAction === "set" && result.openrouterApiKey) {
|
|
13077
|
+
await setOpenRouterApiKey(result.openrouterApiKey);
|
|
13078
|
+
success("Stored OpenRouter API key in the local secrets store.");
|
|
13079
|
+
info(`Secrets path: ${import_picocolors10.default.bold(getSecretsStorePath())}`);
|
|
13080
|
+
} else if (result.openrouterApiKeyAction === "delete") {
|
|
13081
|
+
const deleted = await deleteOpenRouterApiKey();
|
|
13082
|
+
if (deleted) {
|
|
13083
|
+
success("Deleted stored OpenRouter API key.");
|
|
13084
|
+
} else {
|
|
13085
|
+
info("No stored OpenRouter API key was found to delete.");
|
|
13086
|
+
}
|
|
13087
|
+
}
|
|
12829
13088
|
}
|
|
12830
13089
|
function printConfigSummary(snapshot) {
|
|
12831
|
-
info(`Config source: ${
|
|
12832
|
-
info(`Config path: ${
|
|
12833
|
-
info(`Workflow: ${
|
|
12834
|
-
info(`Convention: ${
|
|
12835
|
-
info(`Role: ${
|
|
13090
|
+
info(`Config source: ${import_picocolors10.default.bold(snapshot.source)}`);
|
|
13091
|
+
info(`Config path: ${import_picocolors10.default.bold(snapshot.location)}`);
|
|
13092
|
+
info(`Workflow: ${import_picocolors10.default.bold(snapshot.workflowLabel)}`);
|
|
13093
|
+
info(`Convention: ${import_picocolors10.default.bold(snapshot.commitConventionLabel)}`);
|
|
13094
|
+
info(`Role: ${import_picocolors10.default.bold(snapshot.role)}`);
|
|
12836
13095
|
if (snapshot.devBranch) {
|
|
12837
|
-
info(`Main: ${
|
|
13096
|
+
info(`Main: ${import_picocolors10.default.bold(snapshot.mainBranch)} | Dev: ${import_picocolors10.default.bold(snapshot.devBranch)}`);
|
|
12838
13097
|
} else {
|
|
12839
|
-
info(`Main: ${
|
|
13098
|
+
info(`Main: ${import_picocolors10.default.bold(snapshot.mainBranch)}`);
|
|
12840
13099
|
}
|
|
12841
|
-
info(`Origin: ${
|
|
12842
|
-
info(`Branch prefixes: ${
|
|
12843
|
-
info(`Guides: ${
|
|
12844
|
-
info(`AI: ${
|
|
13100
|
+
info(`Origin: ${import_picocolors10.default.bold(snapshot.origin)} | Upstream: ${import_picocolors10.default.bold(snapshot.upstream)}`);
|
|
13101
|
+
info(`Branch prefixes: ${import_picocolors10.default.bold(snapshot.branchPrefixes.join(", "))}`);
|
|
13102
|
+
info(`Guides: ${import_picocolors10.default.bold(snapshot.showTips ? "shown" : "hidden")}`);
|
|
13103
|
+
info(`AI: ${import_picocolors10.default.bold(snapshot.ai.enabled ? "enabled" : "disabled")}`);
|
|
12845
13104
|
if (snapshot.ai.enabled && snapshot.ai.providerLabel) {
|
|
12846
|
-
info(`AI provider: ${
|
|
13105
|
+
info(`AI provider: ${import_picocolors10.default.bold(snapshot.ai.providerLabel)}`);
|
|
12847
13106
|
if (snapshot.ai.model) {
|
|
12848
|
-
info(`AI model: ${
|
|
13107
|
+
info(`AI model: ${import_picocolors10.default.bold(snapshot.ai.model)}`);
|
|
12849
13108
|
}
|
|
12850
13109
|
if (snapshot.ai.provider === "ollama-cloud") {
|
|
12851
|
-
info(`Ollama Cloud API key: ${
|
|
13110
|
+
info(`Ollama Cloud API key: ${import_picocolors10.default.bold(snapshot.ai.ollamaCloudApiKeyPresent ? "stored" : "missing")}`);
|
|
13111
|
+
if (snapshot.ai.secretsPath) {
|
|
13112
|
+
info(`Secrets path: ${import_picocolors10.default.bold(snapshot.ai.secretsPath)}`);
|
|
13113
|
+
}
|
|
13114
|
+
}
|
|
13115
|
+
if (snapshot.ai.provider === "openrouter") {
|
|
13116
|
+
info(`OpenRouter API key: ${import_picocolors10.default.bold(snapshot.ai.openrouterApiKeyPresent ? "stored" : "missing")}`);
|
|
12852
13117
|
if (snapshot.ai.secretsPath) {
|
|
12853
|
-
info(`Secrets path: ${
|
|
13118
|
+
info(`Secrets path: ${import_picocolors10.default.bold(snapshot.ai.secretsPath)}`);
|
|
12854
13119
|
}
|
|
12855
13120
|
}
|
|
12856
13121
|
}
|
|
@@ -12898,14 +13163,15 @@ var config_default = defineCommand({
|
|
|
12898
13163
|
}
|
|
12899
13164
|
if (args.edit) {
|
|
12900
13165
|
try {
|
|
12901
|
-
const editResult = await promptForConfigEdits(config, await hasOllamaCloudApiKey());
|
|
13166
|
+
const editResult = await promptForConfigEdits(config, await hasOllamaCloudApiKey(), await hasOpenRouterApiKey());
|
|
12902
13167
|
writeConfig(editResult.config);
|
|
12903
|
-
await
|
|
13168
|
+
await applyApiKeyEdits(editResult);
|
|
12904
13169
|
success("Updated repo config.");
|
|
12905
13170
|
printConfigSummary(buildConfigSnapshot(editResult.config, {
|
|
12906
13171
|
source,
|
|
12907
13172
|
location: getConfigLocationLabel(),
|
|
12908
13173
|
hasOllamaCloudApiKey: await hasOllamaCloudApiKey(),
|
|
13174
|
+
hasOpenRouterApiKey: await hasOpenRouterApiKey(),
|
|
12909
13175
|
secretsPath: getSecretsStorePath()
|
|
12910
13176
|
}));
|
|
12911
13177
|
return;
|
|
@@ -12918,6 +13184,7 @@ var config_default = defineCommand({
|
|
|
12918
13184
|
source,
|
|
12919
13185
|
location: getConfigLocationLabel(),
|
|
12920
13186
|
hasOllamaCloudApiKey: await hasOllamaCloudApiKey(),
|
|
13187
|
+
hasOpenRouterApiKey: await hasOpenRouterApiKey(),
|
|
12921
13188
|
secretsPath: getSecretsStorePath()
|
|
12922
13189
|
});
|
|
12923
13190
|
if (args.json) {
|
|
@@ -12926,40 +13193,148 @@ var config_default = defineCommand({
|
|
|
12926
13193
|
}
|
|
12927
13194
|
printConfigSummary(snapshot);
|
|
12928
13195
|
console.log();
|
|
12929
|
-
console.log(` ${
|
|
13196
|
+
console.log(` ${import_picocolors10.default.dim("Run `cn config --edit` to update these settings.")}`);
|
|
12930
13197
|
console.log();
|
|
12931
13198
|
}
|
|
12932
13199
|
});
|
|
12933
13200
|
|
|
12934
|
-
// src/commands/
|
|
12935
|
-
|
|
12936
|
-
var
|
|
12937
|
-
|
|
12938
|
-
|
|
12939
|
-
|
|
12940
|
-
version: "0.7.5",
|
|
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"
|
|
13201
|
+
// src/commands/discard.ts
|
|
13202
|
+
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
13203
|
+
var discard_default = defineCommand({
|
|
13204
|
+
meta: {
|
|
13205
|
+
name: "discard",
|
|
13206
|
+
description: "Discard the current feature branch and return to the base branch"
|
|
12947
13207
|
},
|
|
12948
|
-
|
|
12949
|
-
|
|
12950
|
-
|
|
12951
|
-
|
|
12952
|
-
|
|
12953
|
-
|
|
12954
|
-
|
|
12955
|
-
|
|
12956
|
-
|
|
12957
|
-
|
|
12958
|
-
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
|
|
12962
|
-
|
|
13208
|
+
args: {
|
|
13209
|
+
force: {
|
|
13210
|
+
type: "boolean",
|
|
13211
|
+
alias: "f",
|
|
13212
|
+
description: "Skip confirmation and discard immediately",
|
|
13213
|
+
default: false
|
|
13214
|
+
}
|
|
13215
|
+
},
|
|
13216
|
+
async run({ args }) {
|
|
13217
|
+
if (!await isGitRepo()) {
|
|
13218
|
+
error("Not inside a git repository.");
|
|
13219
|
+
process.exit(1);
|
|
13220
|
+
}
|
|
13221
|
+
await assertCleanGitState("discarding a branch");
|
|
13222
|
+
const config = readConfig();
|
|
13223
|
+
if (!config) {
|
|
13224
|
+
error("No repo config found. Run `cn setup` first.");
|
|
13225
|
+
process.exit(1);
|
|
13226
|
+
}
|
|
13227
|
+
const currentBranch = await getCurrentBranch();
|
|
13228
|
+
const baseBranch = getBaseBranch(config);
|
|
13229
|
+
await projectHeading("discard", "\uD83D\uDDD1\uFE0F");
|
|
13230
|
+
if (isBranchProtected(currentBranch, config)) {
|
|
13231
|
+
error(`${import_picocolors11.default.bold(currentBranch)} is a protected branch and cannot be discarded.`);
|
|
13232
|
+
info(`Switch to a feature branch first, then run ${import_picocolors11.default.bold("cn discard")}.`);
|
|
13233
|
+
process.exit(1);
|
|
13234
|
+
}
|
|
13235
|
+
if (currentBranch === baseBranch) {
|
|
13236
|
+
info(`You are already on ${import_picocolors11.default.bold(baseBranch)}.`);
|
|
13237
|
+
process.exit(0);
|
|
13238
|
+
}
|
|
13239
|
+
const { origin } = config;
|
|
13240
|
+
const localWork = await hasLocalWork(origin, currentBranch);
|
|
13241
|
+
const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
|
|
13242
|
+
if (hasWork) {
|
|
13243
|
+
if (localWork.uncommitted) {
|
|
13244
|
+
warn("You have uncommitted changes in your working tree.");
|
|
13245
|
+
}
|
|
13246
|
+
if (localWork.unpushedCommits > 0) {
|
|
13247
|
+
warn(`You have ${import_picocolors11.default.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on this branch.`);
|
|
13248
|
+
}
|
|
13249
|
+
warn("Discarding this branch will permanently lose that work.");
|
|
13250
|
+
const SAVE_FIRST = "Save my changes first (cn save), then discard";
|
|
13251
|
+
const DISCARD_ANYWAY = "Discard anyway \u2014 I do not need this work";
|
|
13252
|
+
const CANCEL = "Keep the branch, take me back";
|
|
13253
|
+
const action = await selectPrompt("This branch has unsaved work. What would you like to do?", [SAVE_FIRST, DISCARD_ANYWAY, CANCEL]);
|
|
13254
|
+
if (action === CANCEL) {
|
|
13255
|
+
info("Discard cancelled. Your branch is untouched.");
|
|
13256
|
+
process.exit(0);
|
|
13257
|
+
}
|
|
13258
|
+
if (action === SAVE_FIRST) {
|
|
13259
|
+
if (!localWork.uncommitted) {
|
|
13260
|
+
info("No uncommitted changes to stash \u2014 unpushed commits will still be lost.");
|
|
13261
|
+
const confirm = await confirmPrompt("Continue discarding the branch?");
|
|
13262
|
+
if (!confirm) {
|
|
13263
|
+
info("Discard cancelled.");
|
|
13264
|
+
process.exit(0);
|
|
13265
|
+
}
|
|
13266
|
+
} else {
|
|
13267
|
+
const stashResult = await stashChanges(`work-in-progress on ${currentBranch}`);
|
|
13268
|
+
if (stashResult.exitCode !== 0) {
|
|
13269
|
+
error(`Failed to save changes: ${stashResult.stderr}`);
|
|
13270
|
+
process.exit(1);
|
|
13271
|
+
}
|
|
13272
|
+
success(`Changes saved. Use ${import_picocolors11.default.bold("cn save --restore")} to bring them back.`);
|
|
13273
|
+
}
|
|
13274
|
+
}
|
|
13275
|
+
} else if (!args.force) {
|
|
13276
|
+
const confirmed = await confirmPrompt(`Discard ${import_picocolors11.default.bold(currentBranch)} and return to ${import_picocolors11.default.bold(baseBranch)}?`);
|
|
13277
|
+
if (!confirmed) {
|
|
13278
|
+
info("Discard cancelled.");
|
|
13279
|
+
process.exit(0);
|
|
13280
|
+
}
|
|
13281
|
+
}
|
|
13282
|
+
const upstreamRef = await getUpstreamRef();
|
|
13283
|
+
let deleteRemote = false;
|
|
13284
|
+
if (upstreamRef) {
|
|
13285
|
+
deleteRemote = await confirmPrompt(`Also delete the remote branch ${import_picocolors11.default.bold(upstreamRef)}?`);
|
|
13286
|
+
}
|
|
13287
|
+
const checkoutResult = await checkoutBranch(baseBranch);
|
|
13288
|
+
if (checkoutResult.exitCode !== 0) {
|
|
13289
|
+
error(`Failed to switch to ${import_picocolors11.default.bold(baseBranch)}: ${checkoutResult.stderr}`);
|
|
13290
|
+
process.exit(1);
|
|
13291
|
+
}
|
|
13292
|
+
const deleteResult = await forceDeleteBranch(currentBranch);
|
|
13293
|
+
if (deleteResult.exitCode !== 0) {
|
|
13294
|
+
error(`Failed to delete branch ${import_picocolors11.default.bold(currentBranch)}: ${deleteResult.stderr}`);
|
|
13295
|
+
process.exit(1);
|
|
13296
|
+
}
|
|
13297
|
+
success(`Discarded ${import_picocolors11.default.bold(currentBranch)} and switched back to ${import_picocolors11.default.bold(baseBranch)}`);
|
|
13298
|
+
if (deleteRemote) {
|
|
13299
|
+
const remoteDeleteResult = await deleteRemoteBranch(origin, currentBranch);
|
|
13300
|
+
if (remoteDeleteResult.exitCode !== 0) {
|
|
13301
|
+
warn(`Could not delete remote branch: ${remoteDeleteResult.stderr.trim()}`);
|
|
13302
|
+
} else {
|
|
13303
|
+
success(`Deleted remote branch ${import_picocolors11.default.bold(`${origin}/${currentBranch}`)}`);
|
|
13304
|
+
}
|
|
13305
|
+
}
|
|
13306
|
+
}
|
|
13307
|
+
});
|
|
13308
|
+
|
|
13309
|
+
// src/commands/doctor.ts
|
|
13310
|
+
import { execFile as execFileCb3 } from "child_process";
|
|
13311
|
+
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
13312
|
+
// package.json
|
|
13313
|
+
var package_default = {
|
|
13314
|
+
name: "contribute-now",
|
|
13315
|
+
version: "0.8.0-dev.1cf48ff",
|
|
13316
|
+
description: "Developer CLI that automates git workflows \u2014 branching, syncing, committing, and PRs \u2014 with multi-workflow and commit convention support.",
|
|
13317
|
+
type: "module",
|
|
13318
|
+
bin: {
|
|
13319
|
+
contrib: "dist/cli.js",
|
|
13320
|
+
contribute: "dist/cli.js",
|
|
13321
|
+
cn: "dist/cli.js"
|
|
13322
|
+
},
|
|
13323
|
+
files: [
|
|
13324
|
+
"dist"
|
|
13325
|
+
],
|
|
13326
|
+
scripts: {
|
|
13327
|
+
build: "bun build src/cli.ts --outfile dist/cli.js --target bun && bun run scripts/add-shebang.mjs",
|
|
13328
|
+
cli: "bun run src/cli.ts --",
|
|
13329
|
+
dev: "bun src/cli.ts",
|
|
13330
|
+
test: "bun test",
|
|
13331
|
+
lint: "biome check .",
|
|
13332
|
+
"lint:fix": "biome check --write .",
|
|
13333
|
+
format: "biome format --write .",
|
|
13334
|
+
"landing:install": "bun install --cwd landing",
|
|
13335
|
+
"landing:dev": "bun run --cwd landing dev",
|
|
13336
|
+
"landing:build": "bun run --cwd landing build",
|
|
13337
|
+
"landing:preview": "bun run --cwd landing preview"
|
|
12963
13338
|
},
|
|
12964
13339
|
engines: {
|
|
12965
13340
|
bun: ">=1.0"
|
|
@@ -12995,6 +13370,9 @@ var package_default = {
|
|
|
12995
13370
|
}
|
|
12996
13371
|
};
|
|
12997
13372
|
|
|
13373
|
+
// src/commands/doctor.ts
|
|
13374
|
+
init_gh();
|
|
13375
|
+
|
|
12998
13376
|
// src/utils/remote.ts
|
|
12999
13377
|
function parseRepoFromUrl(url) {
|
|
13000
13378
|
const httpsMatch = url.match(/https?:\/\/github\.com\/([^/]+)\/([^/.]+?)(?:\.git)?$/);
|
|
@@ -13190,6 +13568,15 @@ async function configSection() {
|
|
|
13190
13568
|
detail: hasSecretsStore() ? "stored in the local secrets store" : "run `cn setup` to save it"
|
|
13191
13569
|
});
|
|
13192
13570
|
}
|
|
13571
|
+
if (aiConfig.provider === "openrouter") {
|
|
13572
|
+
const hasApiKey = await hasOpenRouterApiKey();
|
|
13573
|
+
checks.push({
|
|
13574
|
+
label: hasApiKey ? "OpenRouter API key present" : "OpenRouter API key missing",
|
|
13575
|
+
ok: true,
|
|
13576
|
+
warning: !hasApiKey,
|
|
13577
|
+
detail: hasSecretsStore() ? "stored in the local secrets store" : "run `cn setup` to save it"
|
|
13578
|
+
});
|
|
13579
|
+
}
|
|
13193
13580
|
}
|
|
13194
13581
|
if (hasDevBranch(config.workflow)) {
|
|
13195
13582
|
checks.push({
|
|
@@ -13325,178 +13712,780 @@ function envSection() {
|
|
|
13325
13712
|
checks.push({ label: `${name} = ${display}`, ok: true });
|
|
13326
13713
|
}
|
|
13327
13714
|
}
|
|
13328
|
-
if (checks.length === 0) {
|
|
13329
|
-
checks.push({ label: "No relevant environment variables set", ok: true });
|
|
13715
|
+
if (checks.length === 0) {
|
|
13716
|
+
checks.push({ label: "No relevant environment variables set", ok: true });
|
|
13717
|
+
}
|
|
13718
|
+
return { title: "Environment", checks };
|
|
13719
|
+
}
|
|
13720
|
+
var doctor_default = defineCommand({
|
|
13721
|
+
meta: {
|
|
13722
|
+
name: "doctor",
|
|
13723
|
+
description: "Diagnose the contribute-now CLI environment and configuration"
|
|
13724
|
+
},
|
|
13725
|
+
args: {
|
|
13726
|
+
json: {
|
|
13727
|
+
type: "boolean",
|
|
13728
|
+
description: "Output report as JSON",
|
|
13729
|
+
default: false
|
|
13730
|
+
}
|
|
13731
|
+
},
|
|
13732
|
+
async run({ args }) {
|
|
13733
|
+
const isJson = args.json;
|
|
13734
|
+
const [tool, deps, config, git, fork, workflow] = await Promise.all([
|
|
13735
|
+
toolSection(),
|
|
13736
|
+
depsSection(),
|
|
13737
|
+
configSection(),
|
|
13738
|
+
gitSection(),
|
|
13739
|
+
forkSection(),
|
|
13740
|
+
workflowSection()
|
|
13741
|
+
]);
|
|
13742
|
+
const env2 = envSection();
|
|
13743
|
+
const report = {
|
|
13744
|
+
sections: [tool, deps, config, git, fork, workflow, env2]
|
|
13745
|
+
};
|
|
13746
|
+
if (isJson) {
|
|
13747
|
+
console.log(toJson(report));
|
|
13748
|
+
return;
|
|
13749
|
+
}
|
|
13750
|
+
await projectHeading("doctor", "\uD83E\uDE7A");
|
|
13751
|
+
printReport(report);
|
|
13752
|
+
const total = report.sections.flatMap((s2) => s2.checks);
|
|
13753
|
+
const failures = total.filter((c3) => !c3.ok);
|
|
13754
|
+
const warnings = total.filter((c3) => c3.ok && c3.warning);
|
|
13755
|
+
if (failures.length === 0 && warnings.length === 0) {
|
|
13756
|
+
console.log(` ${import_picocolors12.default.green("All checks passed!")} No issues detected.
|
|
13757
|
+
`);
|
|
13758
|
+
} else {
|
|
13759
|
+
if (failures.length > 0) {
|
|
13760
|
+
console.log(` ${import_picocolors12.default.red(`${failures.length} issue${failures.length !== 1 ? "s" : ""} found.`)}`);
|
|
13761
|
+
}
|
|
13762
|
+
if (warnings.length > 0) {
|
|
13763
|
+
console.log(` ${import_picocolors12.default.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}.`)}`);
|
|
13764
|
+
}
|
|
13765
|
+
console.log();
|
|
13766
|
+
}
|
|
13767
|
+
}
|
|
13768
|
+
});
|
|
13769
|
+
|
|
13770
|
+
// src/commands/hook.ts
|
|
13771
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
13772
|
+
import { join as join6 } from "path";
|
|
13773
|
+
var import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
13774
|
+
var HOOK_MARKER = "# managed by contribute-now";
|
|
13775
|
+
function getHooksDir(cwd = process.cwd()) {
|
|
13776
|
+
return join6(cwd, ".git", "hooks");
|
|
13777
|
+
}
|
|
13778
|
+
function getHookPath(cwd = process.cwd()) {
|
|
13779
|
+
return join6(getHooksDir(cwd), "commit-msg");
|
|
13780
|
+
}
|
|
13781
|
+
function generateHookScript() {
|
|
13782
|
+
return `#!/bin/sh
|
|
13783
|
+
${HOOK_MARKER}
|
|
13784
|
+
# Validates commit messages against your configured convention.
|
|
13785
|
+
# Install: cn hook install
|
|
13786
|
+
# Uninstall: cn hook uninstall
|
|
13787
|
+
|
|
13788
|
+
commit_msg_file="$1"
|
|
13789
|
+
commit_msg=$(head -1 "$commit_msg_file")
|
|
13790
|
+
|
|
13791
|
+
# Skip merge commits and fixup/squash commits
|
|
13792
|
+
case "$commit_msg" in
|
|
13793
|
+
Merge\\ *|fixup!*|squash!*|amend!*) exit 0 ;;
|
|
13794
|
+
esac
|
|
13795
|
+
|
|
13796
|
+
# Detect available package runner
|
|
13797
|
+
if command -v cn >/dev/null 2>&1; then
|
|
13798
|
+
cn validate --file "$commit_msg_file"
|
|
13799
|
+
elif command -v bunx >/dev/null 2>&1; then
|
|
13800
|
+
bunx cn validate --file "$commit_msg_file"
|
|
13801
|
+
else
|
|
13802
|
+
echo "Warning: Neither cn nor bunx is available. Skipping commit message validation."
|
|
13803
|
+
exit 0
|
|
13804
|
+
fi
|
|
13805
|
+
`;
|
|
13806
|
+
}
|
|
13807
|
+
var hook_default = defineCommand({
|
|
13808
|
+
meta: {
|
|
13809
|
+
name: "hook",
|
|
13810
|
+
description: "Install or uninstall the commit-msg git hook"
|
|
13811
|
+
},
|
|
13812
|
+
args: {
|
|
13813
|
+
action: {
|
|
13814
|
+
type: "positional",
|
|
13815
|
+
description: "Action to perform: install or uninstall",
|
|
13816
|
+
required: true
|
|
13817
|
+
}
|
|
13818
|
+
},
|
|
13819
|
+
async run({ args }) {
|
|
13820
|
+
if (!await isGitRepo()) {
|
|
13821
|
+
error("Not inside a git repository.");
|
|
13822
|
+
process.exit(1);
|
|
13823
|
+
}
|
|
13824
|
+
const action = args.action;
|
|
13825
|
+
if (action !== "install" && action !== "uninstall") {
|
|
13826
|
+
error(`Unknown action "${action}". Use "install" or "uninstall".`);
|
|
13827
|
+
process.exit(1);
|
|
13828
|
+
}
|
|
13829
|
+
if (action === "install") {
|
|
13830
|
+
await installHook();
|
|
13831
|
+
} else {
|
|
13832
|
+
await uninstallHook();
|
|
13833
|
+
}
|
|
13834
|
+
}
|
|
13835
|
+
});
|
|
13836
|
+
async function installHook() {
|
|
13837
|
+
await projectHeading("hook install", "\uD83E\uDE9D");
|
|
13838
|
+
const config = readConfig();
|
|
13839
|
+
if (!config) {
|
|
13840
|
+
error("No repo config found. Run `cn setup` first.");
|
|
13841
|
+
process.exit(1);
|
|
13842
|
+
}
|
|
13843
|
+
if (config.commitConvention === "none") {
|
|
13844
|
+
warn('Commit convention is set to "none". No hook to install.');
|
|
13845
|
+
info("Change your convention with `cn setup` first.", "");
|
|
13846
|
+
process.exit(0);
|
|
13847
|
+
}
|
|
13848
|
+
const hookPath = getHookPath();
|
|
13849
|
+
const hooksDir = getHooksDir();
|
|
13850
|
+
if (existsSync6(hookPath)) {
|
|
13851
|
+
const existing = readFileSync5(hookPath, "utf-8");
|
|
13852
|
+
if (!existing.includes(HOOK_MARKER)) {
|
|
13853
|
+
error("A commit-msg hook already exists and was not installed by contribute-now.");
|
|
13854
|
+
warn(`Path: ${hookPath}`);
|
|
13855
|
+
warn("Remove it manually or back it up before installing.");
|
|
13856
|
+
process.exit(1);
|
|
13857
|
+
}
|
|
13858
|
+
info("Updating existing contribute-now hook...");
|
|
13859
|
+
}
|
|
13860
|
+
if (!existsSync6(hooksDir)) {
|
|
13861
|
+
mkdirSync5(hooksDir, { recursive: true });
|
|
13862
|
+
}
|
|
13863
|
+
writeFileSync5(hookPath, generateHookScript(), { mode: 493 });
|
|
13864
|
+
success(`commit-msg hook installed.`);
|
|
13865
|
+
info(`Convention: ${import_picocolors13.default.bold(CONVENTION_LABELS[config.commitConvention])}`, "");
|
|
13866
|
+
info(`Path: ${import_picocolors13.default.dim(hookPath)}`, "");
|
|
13867
|
+
warn("Note: hooks can be bypassed with `git commit --no-verify`.");
|
|
13868
|
+
}
|
|
13869
|
+
async function uninstallHook() {
|
|
13870
|
+
await projectHeading("hook uninstall", "\uD83E\uDE9D");
|
|
13871
|
+
const hookPath = getHookPath();
|
|
13872
|
+
if (!existsSync6(hookPath)) {
|
|
13873
|
+
info("No commit-msg hook found. Nothing to uninstall.");
|
|
13874
|
+
return;
|
|
13875
|
+
}
|
|
13876
|
+
const content = readFileSync5(hookPath, "utf-8");
|
|
13877
|
+
if (!content.includes(HOOK_MARKER)) {
|
|
13878
|
+
error("The commit-msg hook was not installed by contribute-now. Leaving it untouched.");
|
|
13879
|
+
process.exit(1);
|
|
13880
|
+
}
|
|
13881
|
+
rmSync2(hookPath);
|
|
13882
|
+
success("commit-msg hook removed.");
|
|
13883
|
+
}
|
|
13884
|
+
|
|
13885
|
+
// src/commands/label.ts
|
|
13886
|
+
init_gh();
|
|
13887
|
+
var import_picocolors14 = __toESM(require_picocolors(), 1);
|
|
13888
|
+
|
|
13889
|
+
// src/utils/label.ts
|
|
13890
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync6, statSync as statSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
13891
|
+
import { dirname as dirname5, join as join7, resolve as resolve5 } from "path";
|
|
13892
|
+
|
|
13893
|
+
// src/data/clean-labels.ts
|
|
13894
|
+
var CLEAN_LABELS = [
|
|
13895
|
+
{
|
|
13896
|
+
name: "bug",
|
|
13897
|
+
description: "[Type] Something isn't working [issues, PRs]",
|
|
13898
|
+
color: "d73a4a"
|
|
13899
|
+
},
|
|
13900
|
+
{
|
|
13901
|
+
name: "enhancement",
|
|
13902
|
+
description: "[Type] New feature or improvement to existing functionality [issues, PRs]",
|
|
13903
|
+
color: "1a7f37"
|
|
13904
|
+
},
|
|
13905
|
+
{
|
|
13906
|
+
name: "documentation",
|
|
13907
|
+
description: "[Type] Improvements or additions to docs, README, or guides [issues, PRs]",
|
|
13908
|
+
color: "0075ca"
|
|
13909
|
+
},
|
|
13910
|
+
{
|
|
13911
|
+
name: "refactor",
|
|
13912
|
+
description: "[Type] Code improvement without changing functionality [PRs]",
|
|
13913
|
+
color: "8957e5"
|
|
13914
|
+
},
|
|
13915
|
+
{
|
|
13916
|
+
name: "performance",
|
|
13917
|
+
description: "[Type] Optimization, speed, or resource usage improvements [issues, PRs]",
|
|
13918
|
+
color: "e3795c"
|
|
13919
|
+
},
|
|
13920
|
+
{
|
|
13921
|
+
name: "security",
|
|
13922
|
+
description: "[Type] Security vulnerability or hardening [issues, PRs]",
|
|
13923
|
+
color: "d4a72c"
|
|
13924
|
+
},
|
|
13925
|
+
{
|
|
13926
|
+
name: "blocked",
|
|
13927
|
+
description: "[Status] Waiting on another issue, decision, or external factor [issues]",
|
|
13928
|
+
color: "cf222e"
|
|
13929
|
+
},
|
|
13930
|
+
{
|
|
13931
|
+
name: "needs triage",
|
|
13932
|
+
description: "[Status] New issue \u2014 needs review and categorization [issues]",
|
|
13933
|
+
color: "e16f24"
|
|
13934
|
+
},
|
|
13935
|
+
{
|
|
13936
|
+
name: "awaiting response",
|
|
13937
|
+
description: "[Status] Waiting for more information from the reporter [issues]",
|
|
13938
|
+
color: "1a7ec7"
|
|
13939
|
+
},
|
|
13940
|
+
{
|
|
13941
|
+
name: "ready",
|
|
13942
|
+
description: "[Status] Triaged and ready to be picked up [issues]",
|
|
13943
|
+
color: "2da44e"
|
|
13944
|
+
},
|
|
13945
|
+
{
|
|
13946
|
+
name: "good first issue",
|
|
13947
|
+
description: "[Community] Good for newcomers \u2014 well-scoped and documented [issues]",
|
|
13948
|
+
color: "7057ff"
|
|
13949
|
+
},
|
|
13950
|
+
{
|
|
13951
|
+
name: "help wanted",
|
|
13952
|
+
description: "[Community] Open for community contribution [issues]",
|
|
13953
|
+
color: "0e8a16"
|
|
13954
|
+
},
|
|
13955
|
+
{
|
|
13956
|
+
name: "maintainer only",
|
|
13957
|
+
description: "[Community] Reserved for maintainers \u2014 not open for external contribution [issues, PRs]",
|
|
13958
|
+
color: "b60205"
|
|
13959
|
+
},
|
|
13960
|
+
{
|
|
13961
|
+
name: "duplicate",
|
|
13962
|
+
description: "[Resolution] This issue or pull request already exists [issues, PRs]",
|
|
13963
|
+
color: "cfd3d7"
|
|
13964
|
+
},
|
|
13965
|
+
{
|
|
13966
|
+
name: "invalid",
|
|
13967
|
+
description: "[Resolution] This doesn't seem right [issues, PRs]",
|
|
13968
|
+
color: "cfd3d7"
|
|
13969
|
+
},
|
|
13970
|
+
{
|
|
13971
|
+
name: "wontfix",
|
|
13972
|
+
description: "[Resolution] This will not be worked on [issues]",
|
|
13973
|
+
color: "cfd3d7"
|
|
13974
|
+
},
|
|
13975
|
+
{
|
|
13976
|
+
name: "core",
|
|
13977
|
+
description: "[Area] Core logic, business rules, and primary functionality [issues, PRs]",
|
|
13978
|
+
color: "0052cc"
|
|
13979
|
+
},
|
|
13980
|
+
{
|
|
13981
|
+
name: "interface",
|
|
13982
|
+
description: "[Area] User-facing layer \u2014 UI, CLI, API endpoints, or SDK surface [issues, PRs]",
|
|
13983
|
+
color: "5319e7"
|
|
13984
|
+
},
|
|
13985
|
+
{
|
|
13986
|
+
name: "data",
|
|
13987
|
+
description: "[Area] Database, storage, caching, or data models [issues, PRs]",
|
|
13988
|
+
color: "006b75"
|
|
13989
|
+
},
|
|
13990
|
+
{
|
|
13991
|
+
name: "infra",
|
|
13992
|
+
description: "[Area] Build system, CI/CD, deployment, config, and DevOps [issues, PRs]",
|
|
13993
|
+
color: "e16f24"
|
|
13994
|
+
},
|
|
13995
|
+
{
|
|
13996
|
+
name: "testing",
|
|
13997
|
+
description: "[Area] Unit tests, integration tests, E2E, and test tooling [issues, PRs]",
|
|
13998
|
+
color: "1a7f37"
|
|
13999
|
+
}
|
|
14000
|
+
];
|
|
14001
|
+
|
|
14002
|
+
// src/utils/label.ts
|
|
14003
|
+
var LABEL_CACHE_DIRNAME = "contribute-now";
|
|
14004
|
+
var LABEL_CACHE_FILENAME = "labels.json";
|
|
14005
|
+
function findRepoRoot3(cwd = process.cwd()) {
|
|
14006
|
+
let current = resolve5(cwd);
|
|
14007
|
+
while (true) {
|
|
14008
|
+
if (existsSync7(join7(current, ".git"))) {
|
|
14009
|
+
return current;
|
|
14010
|
+
}
|
|
14011
|
+
const parent = dirname5(current);
|
|
14012
|
+
if (parent === current) {
|
|
14013
|
+
return null;
|
|
14014
|
+
}
|
|
14015
|
+
current = parent;
|
|
14016
|
+
}
|
|
14017
|
+
}
|
|
14018
|
+
function resolveGitDir3(cwd = process.cwd()) {
|
|
14019
|
+
const repoRoot = findRepoRoot3(cwd);
|
|
14020
|
+
if (!repoRoot) {
|
|
14021
|
+
return null;
|
|
14022
|
+
}
|
|
14023
|
+
const dotGitPath = join7(repoRoot, ".git");
|
|
14024
|
+
try {
|
|
14025
|
+
const stat = statSync4(dotGitPath);
|
|
14026
|
+
if (stat.isDirectory()) {
|
|
14027
|
+
return dotGitPath;
|
|
14028
|
+
}
|
|
14029
|
+
if (!stat.isFile()) {
|
|
14030
|
+
return null;
|
|
14031
|
+
}
|
|
14032
|
+
const content = readFileSync6(dotGitPath, "utf-8").trim();
|
|
14033
|
+
const match = /^gitdir:\s*(.+)$/i.exec(content);
|
|
14034
|
+
if (!match) {
|
|
14035
|
+
return null;
|
|
14036
|
+
}
|
|
14037
|
+
return resolve5(repoRoot, match[1].trim());
|
|
14038
|
+
} catch {
|
|
14039
|
+
return null;
|
|
14040
|
+
}
|
|
14041
|
+
}
|
|
14042
|
+
function getLabelCachePath(cwd = process.cwd()) {
|
|
14043
|
+
const gitDir = resolveGitDir3(cwd);
|
|
14044
|
+
if (!gitDir) {
|
|
14045
|
+
return null;
|
|
14046
|
+
}
|
|
14047
|
+
return join7(gitDir, LABEL_CACHE_DIRNAME, LABEL_CACHE_FILENAME);
|
|
14048
|
+
}
|
|
14049
|
+
function readLabelCache(cwd = process.cwd()) {
|
|
14050
|
+
const cachePath = getLabelCachePath(cwd);
|
|
14051
|
+
if (!cachePath || !existsSync7(cachePath)) {
|
|
14052
|
+
return null;
|
|
14053
|
+
}
|
|
14054
|
+
try {
|
|
14055
|
+
const raw = JSON.parse(readFileSync6(cachePath, "utf-8"));
|
|
14056
|
+
if (!Array.isArray(raw.labels) || typeof raw.fetchedAt !== "string" || raw.source !== "clean-labels" && raw.source !== "repo") {
|
|
14057
|
+
return null;
|
|
14058
|
+
}
|
|
14059
|
+
const validatedLabels = [];
|
|
14060
|
+
for (const entry of raw.labels) {
|
|
14061
|
+
if (typeof entry !== "object" || entry === null || typeof entry.name !== "string" || !entry.name) {
|
|
14062
|
+
continue;
|
|
14063
|
+
}
|
|
14064
|
+
const e3 = entry;
|
|
14065
|
+
validatedLabels.push({
|
|
14066
|
+
name: e3.name.trim(),
|
|
14067
|
+
description: typeof e3.description === "string" ? e3.description.trim() : "",
|
|
14068
|
+
color: typeof e3.color === "string" ? e3.color.trim() : ""
|
|
14069
|
+
});
|
|
14070
|
+
}
|
|
14071
|
+
if (validatedLabels.length === 0) {
|
|
14072
|
+
return null;
|
|
14073
|
+
}
|
|
14074
|
+
return {
|
|
14075
|
+
labels: validatedLabels,
|
|
14076
|
+
source: raw.source,
|
|
14077
|
+
fetchedAt: raw.fetchedAt
|
|
14078
|
+
};
|
|
14079
|
+
} catch {
|
|
14080
|
+
return null;
|
|
14081
|
+
}
|
|
14082
|
+
}
|
|
14083
|
+
function writeLabelCache(cache, cwd = process.cwd()) {
|
|
14084
|
+
const cachePath = getLabelCachePath(cwd);
|
|
14085
|
+
if (!cachePath) {
|
|
14086
|
+
return;
|
|
14087
|
+
}
|
|
14088
|
+
mkdirSync6(dirname5(cachePath), { recursive: true });
|
|
14089
|
+
writeFileSync6(cachePath, `${JSON.stringify(cache, null, 2)}
|
|
14090
|
+
`, "utf-8");
|
|
14091
|
+
}
|
|
14092
|
+
function normalizeLabelName(name) {
|
|
14093
|
+
return name.toLowerCase().trim();
|
|
14094
|
+
}
|
|
14095
|
+
function isCleanLabelsMatch(repoLabels) {
|
|
14096
|
+
const cleanNames = new Set(CLEAN_LABELS.map((l2) => normalizeLabelName(l2.name)));
|
|
14097
|
+
const repoNames = new Set(repoLabels.map((l2) => normalizeLabelName(l2.name)));
|
|
14098
|
+
if (cleanNames.size !== repoNames.size) {
|
|
14099
|
+
return false;
|
|
14100
|
+
}
|
|
14101
|
+
for (const name of cleanNames) {
|
|
14102
|
+
if (!repoNames.has(name)) {
|
|
14103
|
+
return false;
|
|
14104
|
+
}
|
|
14105
|
+
}
|
|
14106
|
+
return true;
|
|
14107
|
+
}
|
|
14108
|
+
function buildEffectiveLabelSource(repoLabels) {
|
|
14109
|
+
if (isCleanLabelsMatch(repoLabels)) {
|
|
14110
|
+
return {
|
|
14111
|
+
labels: CLEAN_LABELS.map((cl) => ({
|
|
14112
|
+
name: cl.name,
|
|
14113
|
+
description: cl.description,
|
|
14114
|
+
color: cl.color
|
|
14115
|
+
})),
|
|
14116
|
+
source: "clean-labels"
|
|
14117
|
+
};
|
|
14118
|
+
}
|
|
14119
|
+
return { labels: repoLabels, source: "repo" };
|
|
14120
|
+
}
|
|
14121
|
+
async function syncLabelCache(cwd = process.cwd()) {
|
|
14122
|
+
const { getRepoLabels: getRepoLabels2 } = await Promise.resolve().then(() => (init_gh(), exports_gh));
|
|
14123
|
+
const repoLabels = await getRepoLabels2();
|
|
14124
|
+
if (repoLabels.length === 0) {
|
|
14125
|
+
return null;
|
|
14126
|
+
}
|
|
14127
|
+
const { labels, source } = buildEffectiveLabelSource(repoLabels);
|
|
14128
|
+
const cache = {
|
|
14129
|
+
labels,
|
|
14130
|
+
source,
|
|
14131
|
+
fetchedAt: new Date().toISOString()
|
|
14132
|
+
};
|
|
14133
|
+
writeLabelCache(cache, cwd);
|
|
14134
|
+
return cache;
|
|
14135
|
+
}
|
|
14136
|
+
async function getActiveLabels(cwd = process.cwd(), force = false) {
|
|
14137
|
+
if (!force) {
|
|
14138
|
+
const cached = readLabelCache(cwd);
|
|
14139
|
+
if (cached) {
|
|
14140
|
+
return cached;
|
|
14141
|
+
}
|
|
14142
|
+
}
|
|
14143
|
+
return syncLabelCache(cwd);
|
|
14144
|
+
}
|
|
14145
|
+
function parseLabelsCsv(csv) {
|
|
14146
|
+
return csv.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
|
|
14147
|
+
}
|
|
14148
|
+
function validateLabels(requested, available) {
|
|
14149
|
+
const availableNormalized = new Map;
|
|
14150
|
+
for (const label of available) {
|
|
14151
|
+
availableNormalized.set(normalizeLabelName(label.name), label.name);
|
|
14152
|
+
}
|
|
14153
|
+
const valid = [];
|
|
14154
|
+
const invalid = [];
|
|
14155
|
+
for (const req of requested) {
|
|
14156
|
+
const normalized = normalizeLabelName(req);
|
|
14157
|
+
const canonical = availableNormalized.get(normalized);
|
|
14158
|
+
if (canonical !== undefined) {
|
|
14159
|
+
valid.push(canonical);
|
|
14160
|
+
} else {
|
|
14161
|
+
invalid.push(req);
|
|
14162
|
+
}
|
|
14163
|
+
}
|
|
14164
|
+
return { valid, invalid };
|
|
14165
|
+
}
|
|
14166
|
+
function findCloseMatches(input, available, maxResults = 3) {
|
|
14167
|
+
const needle = normalizeLabelName(input);
|
|
14168
|
+
const scored = available.map((label) => {
|
|
14169
|
+
const haystack = normalizeLabelName(label.name);
|
|
14170
|
+
let score = 0;
|
|
14171
|
+
if (haystack === needle) {
|
|
14172
|
+
score = 100;
|
|
14173
|
+
} else if (haystack.startsWith(needle) || needle.startsWith(haystack)) {
|
|
14174
|
+
score = 60;
|
|
14175
|
+
} else if (haystack.includes(needle) || needle.includes(haystack)) {
|
|
14176
|
+
score = 40;
|
|
14177
|
+
} else {
|
|
14178
|
+
const needleBigrams = toBigrams(needle);
|
|
14179
|
+
const haystackBigrams = toBigrams(haystack);
|
|
14180
|
+
const overlap = [...needleBigrams].filter((b2) => haystackBigrams.has(b2)).length;
|
|
14181
|
+
const union = new Set([...needleBigrams, ...haystackBigrams]).size;
|
|
14182
|
+
score = union > 0 ? Math.round(overlap / union * 30) : 0;
|
|
14183
|
+
}
|
|
14184
|
+
return { name: label.name, score };
|
|
14185
|
+
});
|
|
14186
|
+
return scored.filter((item) => item.score > 0).sort((a2, b2) => b2.score - a2.score).slice(0, maxResults).map((item) => item.name);
|
|
14187
|
+
}
|
|
14188
|
+
function scoreLabelsForContent(content, labels) {
|
|
14189
|
+
const contentTokens = tokenize(content.toLowerCase());
|
|
14190
|
+
const scored = labels.map((label) => {
|
|
14191
|
+
const nameTokens = tokenize(label.name.toLowerCase());
|
|
14192
|
+
const descTokens = [...tokenize(stripDescriptionMeta(label.description).toLowerCase())].filter((t2) => t2.length > 3 && !STOP_WORDS.has(t2));
|
|
14193
|
+
let score = 0;
|
|
14194
|
+
for (const token of nameTokens) {
|
|
14195
|
+
if (contentTokens.has(token))
|
|
14196
|
+
score += 3;
|
|
14197
|
+
}
|
|
14198
|
+
const normalizedName = normalizeLabelName(label.name);
|
|
14199
|
+
if (content.toLowerCase().includes(normalizedName)) {
|
|
14200
|
+
score += 5;
|
|
14201
|
+
}
|
|
14202
|
+
for (const token of descTokens) {
|
|
14203
|
+
if (contentTokens.has(token))
|
|
14204
|
+
score += 1;
|
|
14205
|
+
}
|
|
14206
|
+
return { label, score };
|
|
14207
|
+
});
|
|
14208
|
+
return scored.filter((item) => item.score > 0).sort((a2, b2) => b2.score - a2.score);
|
|
14209
|
+
}
|
|
14210
|
+
var STOP_WORDS = new Set([
|
|
14211
|
+
"the",
|
|
14212
|
+
"and",
|
|
14213
|
+
"for",
|
|
14214
|
+
"with",
|
|
14215
|
+
"this",
|
|
14216
|
+
"that",
|
|
14217
|
+
"from",
|
|
14218
|
+
"into",
|
|
14219
|
+
"over",
|
|
14220
|
+
"under",
|
|
14221
|
+
"have",
|
|
14222
|
+
"will",
|
|
14223
|
+
"been",
|
|
14224
|
+
"more",
|
|
14225
|
+
"than",
|
|
14226
|
+
"also",
|
|
14227
|
+
"when",
|
|
14228
|
+
"what",
|
|
14229
|
+
"which",
|
|
14230
|
+
"some",
|
|
14231
|
+
"such",
|
|
14232
|
+
"its",
|
|
14233
|
+
"not",
|
|
14234
|
+
"only",
|
|
14235
|
+
"any",
|
|
14236
|
+
"each",
|
|
14237
|
+
"both"
|
|
14238
|
+
]);
|
|
14239
|
+
function stripDescriptionMeta(description) {
|
|
14240
|
+
return description.replace(/^\[[\w\s]+\]\s*/u, "").replace(/\s*\[[\w,\s]+\]$/u, "").trim();
|
|
14241
|
+
}
|
|
14242
|
+
function tokenize(text) {
|
|
14243
|
+
return new Set(text.split(/[\s\-_/,.:;!?()[\]{}"']+/).filter((t2) => t2.length > 0));
|
|
14244
|
+
}
|
|
14245
|
+
function toBigrams(text) {
|
|
14246
|
+
const bigrams = new Set;
|
|
14247
|
+
for (let i2 = 0;i2 < text.length - 1; i2++) {
|
|
14248
|
+
bigrams.add(text.slice(i2, i2 + 2));
|
|
14249
|
+
}
|
|
14250
|
+
return bigrams;
|
|
14251
|
+
}
|
|
14252
|
+
|
|
14253
|
+
// src/commands/label.ts
|
|
14254
|
+
async function requireGitRepository() {
|
|
14255
|
+
if (!await isGitRepo()) {
|
|
14256
|
+
error("Not inside a git repository.");
|
|
14257
|
+
process.exit(1);
|
|
14258
|
+
}
|
|
14259
|
+
}
|
|
14260
|
+
async function requireGhCli() {
|
|
14261
|
+
if (!await checkGhInstalled()) {
|
|
14262
|
+
error("GitHub CLI (gh) is required. Install it at https://cli.github.com");
|
|
14263
|
+
process.exit(1);
|
|
14264
|
+
}
|
|
14265
|
+
if (!await checkGhAuth()) {
|
|
14266
|
+
error("Not authenticated with GitHub CLI. Run `gh auth login` first.");
|
|
14267
|
+
process.exit(1);
|
|
13330
14268
|
}
|
|
13331
|
-
return { title: "Environment", checks };
|
|
13332
14269
|
}
|
|
13333
|
-
|
|
14270
|
+
function extractLabelsCsv(rawArgs) {
|
|
14271
|
+
const knownFlagsWithValues = new Set(["--issue", "--pr", "-i", "-p"]);
|
|
14272
|
+
const parts = [];
|
|
14273
|
+
let skipNext = false;
|
|
14274
|
+
for (const arg of rawArgs) {
|
|
14275
|
+
if (skipNext) {
|
|
14276
|
+
skipNext = false;
|
|
14277
|
+
continue;
|
|
14278
|
+
}
|
|
14279
|
+
if (knownFlagsWithValues.has(arg)) {
|
|
14280
|
+
skipNext = true;
|
|
14281
|
+
continue;
|
|
14282
|
+
}
|
|
14283
|
+
if (arg.startsWith("-")) {
|
|
14284
|
+
continue;
|
|
14285
|
+
}
|
|
14286
|
+
parts.push(arg);
|
|
14287
|
+
}
|
|
14288
|
+
return parts.join(" ");
|
|
14289
|
+
}
|
|
14290
|
+
function formatSourceNote(source) {
|
|
14291
|
+
return source === "clean-labels" ? "(source: Clean Labels dataset)" : "(source: repo labels)";
|
|
14292
|
+
}
|
|
14293
|
+
var addCommand = defineCommand({
|
|
13334
14294
|
meta: {
|
|
13335
|
-
name: "
|
|
13336
|
-
description: "
|
|
14295
|
+
name: "add",
|
|
14296
|
+
description: "Apply existing labels to an issue or pull request"
|
|
13337
14297
|
},
|
|
13338
14298
|
args: {
|
|
13339
|
-
|
|
13340
|
-
type: "
|
|
13341
|
-
|
|
13342
|
-
|
|
14299
|
+
issue: {
|
|
14300
|
+
type: "string",
|
|
14301
|
+
alias: "i",
|
|
14302
|
+
description: "Issue number to label"
|
|
14303
|
+
},
|
|
14304
|
+
pr: {
|
|
14305
|
+
type: "string",
|
|
14306
|
+
alias: "p",
|
|
14307
|
+
description: "Pull request number to label"
|
|
14308
|
+
},
|
|
14309
|
+
labels: {
|
|
14310
|
+
type: "positional",
|
|
14311
|
+
description: "Comma-separated label names (spaces are part of label names)",
|
|
14312
|
+
required: false
|
|
13343
14313
|
}
|
|
13344
14314
|
},
|
|
13345
|
-
async run({ args }) {
|
|
13346
|
-
|
|
13347
|
-
|
|
13348
|
-
|
|
13349
|
-
|
|
13350
|
-
|
|
13351
|
-
|
|
13352
|
-
|
|
13353
|
-
|
|
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;
|
|
14315
|
+
async run({ args, rawArgs }) {
|
|
14316
|
+
await requireGitRepository();
|
|
14317
|
+
await requireGhCli();
|
|
14318
|
+
await projectHeading("label add", "\uD83C\uDFF7\uFE0F");
|
|
14319
|
+
const hasIssue = Boolean(args.issue);
|
|
14320
|
+
const hasPr = Boolean(args.pr);
|
|
14321
|
+
if (!hasIssue && !hasPr) {
|
|
14322
|
+
error("Provide a target: --issue <number> or --pr <number>");
|
|
14323
|
+
process.exit(1);
|
|
13362
14324
|
}
|
|
13363
|
-
|
|
13364
|
-
|
|
13365
|
-
|
|
13366
|
-
|
|
13367
|
-
const
|
|
13368
|
-
if (
|
|
13369
|
-
|
|
13370
|
-
|
|
13371
|
-
}
|
|
13372
|
-
|
|
13373
|
-
|
|
14325
|
+
if (hasIssue && hasPr) {
|
|
14326
|
+
error("Use either --issue or --pr, not both.");
|
|
14327
|
+
process.exit(1);
|
|
14328
|
+
}
|
|
14329
|
+
const targetNumber = Number(hasIssue ? args.issue : args.pr);
|
|
14330
|
+
if (!Number.isInteger(targetNumber) || targetNumber <= 0) {
|
|
14331
|
+
error(`Invalid ${hasIssue ? "issue" : "PR"} number: ${String(hasIssue ? args.issue : args.pr)}`);
|
|
14332
|
+
process.exit(1);
|
|
14333
|
+
}
|
|
14334
|
+
const labelsCsv = extractLabelsCsv(rawArgs);
|
|
14335
|
+
if (!labelsCsv) {
|
|
14336
|
+
error("No labels provided. Pass a comma-separated list after the target flag.");
|
|
14337
|
+
info("Example: cn label add --issue 42 bug,enhancement", "");
|
|
14338
|
+
process.exit(1);
|
|
14339
|
+
}
|
|
14340
|
+
const requested = parseLabelsCsv(labelsCsv);
|
|
14341
|
+
if (requested.length === 0) {
|
|
14342
|
+
error("No valid label names found in input.");
|
|
14343
|
+
process.exit(1);
|
|
14344
|
+
}
|
|
14345
|
+
let cache = await getActiveLabels();
|
|
14346
|
+
if (!cache) {
|
|
14347
|
+
error("Could not load repository labels. Make sure you are authenticated with `gh auth login`.");
|
|
14348
|
+
process.exit(1);
|
|
14349
|
+
}
|
|
14350
|
+
let { valid, invalid } = validateLabels(requested, cache.labels);
|
|
14351
|
+
if (invalid.length > 0) {
|
|
14352
|
+
warn(`Unknown label(s) detected \u2014 resyncing label cache\u2026`);
|
|
14353
|
+
const freshCache = await syncLabelCache();
|
|
14354
|
+
if (freshCache) {
|
|
14355
|
+
cache = freshCache;
|
|
14356
|
+
const revalidated = validateLabels(requested, cache.labels);
|
|
14357
|
+
valid = revalidated.valid;
|
|
14358
|
+
invalid = revalidated.invalid;
|
|
13374
14359
|
}
|
|
13375
|
-
|
|
13376
|
-
|
|
14360
|
+
}
|
|
14361
|
+
if (invalid.length > 0) {
|
|
14362
|
+
error(`Unknown label(s): ${invalid.map((l2) => import_picocolors14.default.bold(l2)).join(", ")}`);
|
|
14363
|
+
console.log();
|
|
14364
|
+
for (const label of invalid) {
|
|
14365
|
+
const suggestions = findCloseMatches(label, cache.labels);
|
|
14366
|
+
if (suggestions.length > 0) {
|
|
14367
|
+
info(` Did you mean for "${label}": ${suggestions.map((s2) => import_picocolors14.default.cyan(s2)).join(", ")}`, "");
|
|
14368
|
+
}
|
|
13377
14369
|
}
|
|
13378
14370
|
console.log();
|
|
14371
|
+
info(`Available labels: ${cache.labels.map((l2) => import_picocolors14.default.dim(l2.name)).join(", ")}`, "");
|
|
14372
|
+
process.exit(1);
|
|
14373
|
+
}
|
|
14374
|
+
const targetLabel = hasIssue ? `issue #${targetNumber}` : `PR #${targetNumber}`;
|
|
14375
|
+
info(`Applying ${valid.length} label(s) to ${import_picocolors14.default.bold(targetLabel)}\u2026`, "\uD83C\uDFF7\uFE0F");
|
|
14376
|
+
const result = hasIssue ? await addLabelsToIssue(targetNumber, valid) : await addLabelsToPR(targetNumber, valid);
|
|
14377
|
+
if (result.exitCode !== 0) {
|
|
14378
|
+
const stderr = result.stderr.trim();
|
|
14379
|
+
const isDrift = /not found|does not exist/i.test(stderr) || /not found|does not exist/i.test(result.stdout);
|
|
14380
|
+
if (isDrift) {
|
|
14381
|
+
warn("Label not found on remote \u2014 resyncing and retrying\u2026");
|
|
14382
|
+
const freshCache = await syncLabelCache();
|
|
14383
|
+
if (freshCache) {
|
|
14384
|
+
const revalidated = validateLabels(requested, freshCache.labels);
|
|
14385
|
+
if (revalidated.invalid.length === 0) {
|
|
14386
|
+
const retry = hasIssue ? await addLabelsToIssue(targetNumber, revalidated.valid) : await addLabelsToPR(targetNumber, revalidated.valid);
|
|
14387
|
+
if (retry.exitCode === 0) {
|
|
14388
|
+
success(`Applied to ${import_picocolors14.default.bold(targetLabel)}: ${revalidated.valid.map((l2) => import_picocolors14.default.cyan(l2)).join(", ")}`);
|
|
14389
|
+
return;
|
|
14390
|
+
}
|
|
14391
|
+
error(`Retry failed: ${retry.stderr.trim() || retry.stdout.trim()}`);
|
|
14392
|
+
process.exit(1);
|
|
14393
|
+
}
|
|
14394
|
+
}
|
|
14395
|
+
}
|
|
14396
|
+
error(`Failed to apply labels: ${stderr || result.stdout.trim()}`);
|
|
14397
|
+
info("Run `cn label add --help` for usage guidance.", "");
|
|
14398
|
+
process.exit(1);
|
|
13379
14399
|
}
|
|
14400
|
+
success(`Applied to ${import_picocolors14.default.bold(targetLabel)}: ${valid.map((l2) => import_picocolors14.default.cyan(l2)).join(", ")}`);
|
|
14401
|
+
const sourceNote = formatSourceNote(cache.source);
|
|
14402
|
+
info(sourceNote, "");
|
|
13380
14403
|
}
|
|
13381
14404
|
});
|
|
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({
|
|
14405
|
+
var suggestCommand = defineCommand({
|
|
13421
14406
|
meta: {
|
|
13422
|
-
name: "
|
|
13423
|
-
description: "
|
|
14407
|
+
name: "suggest",
|
|
14408
|
+
description: "Suggest labels for an issue or pull request based on its content"
|
|
13424
14409
|
},
|
|
13425
14410
|
args: {
|
|
13426
|
-
|
|
13427
|
-
type: "
|
|
13428
|
-
|
|
13429
|
-
|
|
14411
|
+
issue: {
|
|
14412
|
+
type: "string",
|
|
14413
|
+
alias: "i",
|
|
14414
|
+
description: "Issue number to suggest labels for"
|
|
14415
|
+
},
|
|
14416
|
+
pr: {
|
|
14417
|
+
type: "string",
|
|
14418
|
+
alias: "p",
|
|
14419
|
+
description: "Pull request number to suggest labels for"
|
|
13430
14420
|
}
|
|
13431
14421
|
},
|
|
13432
14422
|
async run({ args }) {
|
|
13433
|
-
|
|
13434
|
-
|
|
14423
|
+
await requireGitRepository();
|
|
14424
|
+
await requireGhCli();
|
|
14425
|
+
await projectHeading("label suggest", "\uD83C\uDFF7\uFE0F");
|
|
14426
|
+
const hasIssue = Boolean(args.issue);
|
|
14427
|
+
const hasPr = Boolean(args.pr);
|
|
14428
|
+
if (!hasIssue && !hasPr) {
|
|
14429
|
+
error("Provide a target: --issue <number> or --pr <number>");
|
|
13435
14430
|
process.exit(1);
|
|
13436
14431
|
}
|
|
13437
|
-
|
|
13438
|
-
|
|
13439
|
-
error(`Unknown action "${action}". Use "install" or "uninstall".`);
|
|
14432
|
+
if (hasIssue && hasPr) {
|
|
14433
|
+
error("Use either --issue or --pr, not both.");
|
|
13440
14434
|
process.exit(1);
|
|
13441
14435
|
}
|
|
13442
|
-
|
|
13443
|
-
|
|
13444
|
-
|
|
13445
|
-
|
|
14436
|
+
const targetNumber = Number(hasIssue ? args.issue : args.pr);
|
|
14437
|
+
if (!Number.isInteger(targetNumber) || targetNumber <= 0) {
|
|
14438
|
+
error(`Invalid ${hasIssue ? "issue" : "PR"} number: ${String(hasIssue ? args.issue : args.pr)}`);
|
|
14439
|
+
process.exit(1);
|
|
13446
14440
|
}
|
|
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.");
|
|
14441
|
+
const targetLabel = hasIssue ? `issue #${targetNumber}` : `PR #${targetNumber}`;
|
|
14442
|
+
info(`Fetching ${import_picocolors14.default.bold(targetLabel)} content\u2026`, "");
|
|
14443
|
+
const content = hasIssue ? await getIssueContent(targetNumber) : await getPRContent(targetNumber);
|
|
14444
|
+
if (!content) {
|
|
14445
|
+
error(`Could not fetch content for ${targetLabel}. Verify the number and your gh auth.`);
|
|
13469
14446
|
process.exit(1);
|
|
13470
14447
|
}
|
|
13471
|
-
|
|
13472
|
-
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
|
|
13479
|
-
|
|
13480
|
-
|
|
13481
|
-
}
|
|
13482
|
-
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
14448
|
+
const fullText = `${content.title}
|
|
14449
|
+
|
|
14450
|
+
${content.body}`;
|
|
14451
|
+
const cache = await getActiveLabels();
|
|
14452
|
+
if (!cache) {
|
|
14453
|
+
error("Could not load repository labels. Run `cn label add --help` for setup guidance.");
|
|
14454
|
+
process.exit(1);
|
|
14455
|
+
}
|
|
14456
|
+
const ranked = scoreLabelsForContent(fullText, cache.labels);
|
|
14457
|
+
if (ranked.length === 0) {
|
|
14458
|
+
info(`No label suggestions found for ${import_picocolors14.default.bold(targetLabel)}.`);
|
|
14459
|
+
info(`Total labels available: ${cache.labels.length}`, "");
|
|
14460
|
+
return;
|
|
14461
|
+
}
|
|
14462
|
+
const sourceNote = formatSourceNote(cache.source);
|
|
14463
|
+
console.log();
|
|
14464
|
+
console.log(` ${import_picocolors14.default.bold(`Suggested labels for ${import_picocolors14.default.cyan(targetLabel)}:`)} ${import_picocolors14.default.dim(sourceNote)}`);
|
|
14465
|
+
console.log();
|
|
14466
|
+
const topN = ranked.slice(0, 5);
|
|
14467
|
+
for (const { label, score } of topN) {
|
|
14468
|
+
const descPart = label.description ? import_picocolors14.default.dim(` \u2014 ${label.description}`) : "";
|
|
14469
|
+
const scorePart = import_picocolors14.default.dim(` [score: ${score}]`);
|
|
14470
|
+
console.log(` ${import_picocolors14.default.cyan("\u2022")} ${import_picocolors14.default.bold(label.name)}${descPart}${scorePart}`);
|
|
14471
|
+
}
|
|
14472
|
+
console.log();
|
|
14473
|
+
info(`Apply a label: cn label add --${hasIssue ? "issue" : "pr"} ${targetNumber} <label>`, "");
|
|
13488
14474
|
}
|
|
13489
|
-
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
|
|
14475
|
+
});
|
|
14476
|
+
var label_default = defineCommand({
|
|
14477
|
+
meta: {
|
|
14478
|
+
name: "label",
|
|
14479
|
+
description: "Manage labels on issues and pull requests"
|
|
14480
|
+
},
|
|
14481
|
+
subCommands: {
|
|
14482
|
+
add: addCommand,
|
|
14483
|
+
suggest: suggestCommand
|
|
13493
14484
|
}
|
|
13494
|
-
|
|
13495
|
-
success("commit-msg hook removed.");
|
|
13496
|
-
}
|
|
14485
|
+
});
|
|
13497
14486
|
|
|
13498
14487
|
// src/commands/log.ts
|
|
13499
|
-
var
|
|
14488
|
+
var import_picocolors15 = __toESM(require_picocolors(), 1);
|
|
13500
14489
|
function getDefaultOverviewRemoteCommitCount(hasLocalUnpushedCommits) {
|
|
13501
14490
|
return hasLocalUnpushedCommits ? 10 : 20;
|
|
13502
14491
|
}
|
|
@@ -13594,9 +14583,9 @@ var log_default = defineCommand({
|
|
|
13594
14583
|
} else if (mode === "local") {
|
|
13595
14584
|
if (!compareRef) {
|
|
13596
14585
|
console.log();
|
|
13597
|
-
console.log(
|
|
13598
|
-
console.log(
|
|
13599
|
-
console.log(
|
|
14586
|
+
console.log(import_picocolors15.default.yellow(" \u26A0 Could not determine a comparison branch."));
|
|
14587
|
+
console.log(import_picocolors15.default.dim(" No upstream tracking set and no remote base branch found."));
|
|
14588
|
+
console.log(import_picocolors15.default.dim(` Use ${import_picocolors15.default.bold("cn log --full")} to see the full commit history instead.`));
|
|
13600
14589
|
console.log();
|
|
13601
14590
|
return;
|
|
13602
14591
|
}
|
|
@@ -13615,8 +14604,8 @@ var log_default = defineCommand({
|
|
|
13615
14604
|
const remoteBranch = compareRef ?? targetBranch;
|
|
13616
14605
|
if (!remoteBranch) {
|
|
13617
14606
|
console.log();
|
|
13618
|
-
console.log(
|
|
13619
|
-
console.log(
|
|
14607
|
+
console.log(import_picocolors15.default.yellow(" \u26A0 Could not determine a remote branch to display."));
|
|
14608
|
+
console.log(import_picocolors15.default.dim(" Set an upstream tracking branch or configure your base branch first."));
|
|
13620
14609
|
console.log();
|
|
13621
14610
|
return;
|
|
13622
14611
|
}
|
|
@@ -13675,31 +14664,31 @@ async function getOverviewRemoteCommitCount(currentBranch, compareRef) {
|
|
|
13675
14664
|
}
|
|
13676
14665
|
function printModeHeader(mode, currentBranch, compareRef, usingFallback = false) {
|
|
13677
14666
|
const branch = currentBranch ?? "HEAD";
|
|
13678
|
-
const fallbackNote = usingFallback ?
|
|
14667
|
+
const fallbackNote = usingFallback ? import_picocolors15.default.yellow(" (no upstream \u2014 comparing against base branch)") : "";
|
|
13679
14668
|
switch (mode) {
|
|
13680
14669
|
case "overview":
|
|
13681
|
-
console.log(
|
|
14670
|
+
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
14671
|
if (compareRef) {
|
|
13683
|
-
console.log(
|
|
14672
|
+
console.log(import_picocolors15.default.dim(` remote source: ${import_picocolors15.default.bold(compareRef)}`));
|
|
13684
14673
|
}
|
|
13685
14674
|
break;
|
|
13686
14675
|
case "local":
|
|
13687
|
-
console.log(
|
|
14676
|
+
console.log(import_picocolors15.default.dim(` mode: ${import_picocolors15.default.bold("local")} \u2014 unpushed commits on ${import_picocolors15.default.bold(branch)}`) + fallbackNote);
|
|
13688
14677
|
if (compareRef) {
|
|
13689
|
-
console.log(
|
|
14678
|
+
console.log(import_picocolors15.default.dim(` comparing: ${import_picocolors15.default.bold(compareRef)} \u279C ${import_picocolors15.default.bold("HEAD")}`));
|
|
13690
14679
|
}
|
|
13691
14680
|
break;
|
|
13692
14681
|
case "remote":
|
|
13693
|
-
console.log(
|
|
14682
|
+
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
14683
|
if (compareRef) {
|
|
13695
|
-
console.log(
|
|
14684
|
+
console.log(import_picocolors15.default.dim(` branch: ${import_picocolors15.default.bold(compareRef)}`));
|
|
13696
14685
|
}
|
|
13697
14686
|
break;
|
|
13698
14687
|
case "full":
|
|
13699
|
-
console.log(
|
|
14688
|
+
console.log(import_picocolors15.default.dim(` mode: ${import_picocolors15.default.bold("full")} \u2014 complete commit history for ${import_picocolors15.default.bold(branch)}`));
|
|
13700
14689
|
break;
|
|
13701
14690
|
case "all":
|
|
13702
|
-
console.log(
|
|
14691
|
+
console.log(import_picocolors15.default.dim(` mode: ${import_picocolors15.default.bold("all")} \u2014 commits across all branches`));
|
|
13703
14692
|
break;
|
|
13704
14693
|
}
|
|
13705
14694
|
}
|
|
@@ -13723,7 +14712,7 @@ async function renderScopedLog(options) {
|
|
|
13723
14712
|
}
|
|
13724
14713
|
console.log();
|
|
13725
14714
|
for (const entry of entries) {
|
|
13726
|
-
const hashStr =
|
|
14715
|
+
const hashStr = import_picocolors15.default.yellow(entry.hash);
|
|
13727
14716
|
const refsStr = entry.refs ? ` ${colorizeRefs(entry.refs, protectedBranches, currentBranch)}` : "";
|
|
13728
14717
|
const subjectStr = colorizeSubject(entry.subject);
|
|
13729
14718
|
console.log(` ${hashStr}${refsStr} ${subjectStr}`);
|
|
@@ -13742,10 +14731,10 @@ async function renderOverviewLog(options) {
|
|
|
13742
14731
|
usingFallback
|
|
13743
14732
|
} = options;
|
|
13744
14733
|
console.log();
|
|
13745
|
-
console.log(
|
|
14734
|
+
console.log(import_picocolors15.default.bold(import_picocolors15.default.cyan(" Local Unpushed Commits")));
|
|
13746
14735
|
if (!compareRef) {
|
|
13747
|
-
console.log(
|
|
13748
|
-
console.log(
|
|
14736
|
+
console.log(import_picocolors15.default.dim(" No comparison branch detected for local commit status."));
|
|
14737
|
+
console.log(import_picocolors15.default.dim(" Set an upstream tracking branch or run cn log --full to inspect the current branch history."));
|
|
13749
14738
|
} else {
|
|
13750
14739
|
await renderScopedLog({
|
|
13751
14740
|
mode: "local",
|
|
@@ -13757,11 +14746,11 @@ async function renderOverviewLog(options) {
|
|
|
13757
14746
|
});
|
|
13758
14747
|
}
|
|
13759
14748
|
console.log();
|
|
13760
|
-
console.log(
|
|
14749
|
+
console.log(import_picocolors15.default.bold(import_picocolors15.default.cyan(" Remote Branch History")));
|
|
13761
14750
|
if (!compareRef) {
|
|
13762
|
-
console.log(
|
|
14751
|
+
console.log(import_picocolors15.default.dim(" No remote branch detected."));
|
|
13763
14752
|
if (usingFallback) {
|
|
13764
|
-
console.log(
|
|
14753
|
+
console.log(import_picocolors15.default.dim(" Configure your base branch or upstream tracking to enable the split view."));
|
|
13765
14754
|
}
|
|
13766
14755
|
return;
|
|
13767
14756
|
}
|
|
@@ -13774,15 +14763,15 @@ async function renderOverviewLog(options) {
|
|
|
13774
14763
|
currentBranch
|
|
13775
14764
|
});
|
|
13776
14765
|
if (!hasRemoteHistory) {
|
|
13777
|
-
console.log(
|
|
14766
|
+
console.log(import_picocolors15.default.dim(" No remote history found for the selected branch."));
|
|
13778
14767
|
}
|
|
13779
14768
|
}
|
|
13780
14769
|
function printEmptyState(mode) {
|
|
13781
14770
|
console.log();
|
|
13782
14771
|
if (mode === "local") {
|
|
13783
|
-
console.log(
|
|
14772
|
+
console.log(import_picocolors15.default.dim(" No local unpushed commits \u2014 you're up to date with remote!"));
|
|
13784
14773
|
} else {
|
|
13785
|
-
console.log(
|
|
14774
|
+
console.log(import_picocolors15.default.dim(" No remote-only commits \u2014 your local branch is up to date!"));
|
|
13786
14775
|
}
|
|
13787
14776
|
console.log();
|
|
13788
14777
|
}
|
|
@@ -13791,7 +14780,7 @@ async function renderFullLog(options) {
|
|
|
13791
14780
|
if (showGraph) {
|
|
13792
14781
|
const lines = await getLogGraph({ count, all, branch: targetBranch });
|
|
13793
14782
|
if (lines.length === 0) {
|
|
13794
|
-
console.log(
|
|
14783
|
+
console.log(import_picocolors15.default.dim(" No commits found."));
|
|
13795
14784
|
console.log();
|
|
13796
14785
|
return false;
|
|
13797
14786
|
}
|
|
@@ -13802,13 +14791,13 @@ async function renderFullLog(options) {
|
|
|
13802
14791
|
} else {
|
|
13803
14792
|
const entries = await getLogEntries({ count, all, branch: targetBranch });
|
|
13804
14793
|
if (entries.length === 0) {
|
|
13805
|
-
console.log(
|
|
14794
|
+
console.log(import_picocolors15.default.dim(" No commits found."));
|
|
13806
14795
|
console.log();
|
|
13807
14796
|
return false;
|
|
13808
14797
|
}
|
|
13809
14798
|
console.log();
|
|
13810
14799
|
for (const entry of entries) {
|
|
13811
|
-
const hashStr =
|
|
14800
|
+
const hashStr = import_picocolors15.default.yellow(entry.hash);
|
|
13812
14801
|
const refsStr = entry.refs ? ` ${colorizeRefs(entry.refs, protectedBranches, currentBranch)}` : "";
|
|
13813
14802
|
const subjectStr = colorizeSubject(entry.subject);
|
|
13814
14803
|
console.log(` ${hashStr}${refsStr} ${subjectStr}`);
|
|
@@ -13820,33 +14809,33 @@ function printFooter(mode, count, overviewRemoteCount, targetBranch) {
|
|
|
13820
14809
|
console.log();
|
|
13821
14810
|
switch (mode) {
|
|
13822
14811
|
case "overview":
|
|
13823
|
-
console.log(
|
|
14812
|
+
console.log(import_picocolors15.default.dim(` Showing up to ${count} local commits and ${overviewRemoteCount} remote commits`));
|
|
13824
14813
|
break;
|
|
13825
14814
|
case "local":
|
|
13826
|
-
console.log(
|
|
14815
|
+
console.log(import_picocolors15.default.dim(` Showing up to ${count} unpushed commits`));
|
|
13827
14816
|
break;
|
|
13828
14817
|
case "remote":
|
|
13829
|
-
console.log(
|
|
14818
|
+
console.log(import_picocolors15.default.dim(` Showing ${count} most recent commits from the remote branch`));
|
|
13830
14819
|
break;
|
|
13831
14820
|
case "full":
|
|
13832
|
-
console.log(
|
|
14821
|
+
console.log(import_picocolors15.default.dim(` Showing ${count} most recent commits${targetBranch ? ` (${targetBranch})` : ""}`));
|
|
13833
14822
|
break;
|
|
13834
14823
|
case "all":
|
|
13835
|
-
console.log(
|
|
14824
|
+
console.log(import_picocolors15.default.dim(` Showing ${count} most recent commits (all branches)`));
|
|
13836
14825
|
break;
|
|
13837
14826
|
}
|
|
13838
14827
|
}
|
|
13839
14828
|
function colorizeGraphLine(line, protectedBranches, currentBranch) {
|
|
13840
14829
|
const match = line.match(/^([|/\\*\s_.-]*)([a-f0-9]{7,12})(\s+\(([^)]+)\))?\s*(.*)/);
|
|
13841
14830
|
if (!match) {
|
|
13842
|
-
return
|
|
14831
|
+
return import_picocolors15.default.cyan(line);
|
|
13843
14832
|
}
|
|
13844
14833
|
const [, graphPart = "", hash, , refs, subject = ""] = match;
|
|
13845
14834
|
const parts = [];
|
|
13846
14835
|
if (graphPart) {
|
|
13847
14836
|
parts.push(colorizeGraphChars(graphPart));
|
|
13848
14837
|
}
|
|
13849
|
-
parts.push(
|
|
14838
|
+
parts.push(import_picocolors15.default.yellow(hash));
|
|
13850
14839
|
if (refs) {
|
|
13851
14840
|
parts.push(` (${colorizeRefs(refs, protectedBranches, currentBranch)})`);
|
|
13852
14841
|
}
|
|
@@ -13857,15 +14846,15 @@ function colorizeGraphChars(graphPart) {
|
|
|
13857
14846
|
return graphPart.split("").map((ch) => {
|
|
13858
14847
|
switch (ch) {
|
|
13859
14848
|
case "*":
|
|
13860
|
-
return
|
|
14849
|
+
return import_picocolors15.default.green(ch);
|
|
13861
14850
|
case "|":
|
|
13862
|
-
return
|
|
14851
|
+
return import_picocolors15.default.cyan(ch);
|
|
13863
14852
|
case "/":
|
|
13864
14853
|
case "\\":
|
|
13865
|
-
return
|
|
14854
|
+
return import_picocolors15.default.cyan(ch);
|
|
13866
14855
|
case "-":
|
|
13867
14856
|
case "_":
|
|
13868
|
-
return
|
|
14857
|
+
return import_picocolors15.default.cyan(ch);
|
|
13869
14858
|
default:
|
|
13870
14859
|
return ch;
|
|
13871
14860
|
}
|
|
@@ -13877,50 +14866,50 @@ function colorizeRefs(refs, protectedBranches, currentBranch) {
|
|
|
13877
14866
|
if (trimmed.startsWith("HEAD ->") || trimmed === "HEAD") {
|
|
13878
14867
|
const branchName = trimmed.replace("HEAD -> ", "");
|
|
13879
14868
|
if (trimmed === "HEAD") {
|
|
13880
|
-
return
|
|
14869
|
+
return import_picocolors15.default.bold(import_picocolors15.default.cyan("HEAD"));
|
|
13881
14870
|
}
|
|
13882
|
-
return `${
|
|
14871
|
+
return `${import_picocolors15.default.bold(import_picocolors15.default.cyan("HEAD"))} ${import_picocolors15.default.dim("->")} ${colorizeRefName(branchName, protectedBranches, currentBranch)}`;
|
|
13883
14872
|
}
|
|
13884
14873
|
if (trimmed.startsWith("tag:")) {
|
|
13885
|
-
return
|
|
14874
|
+
return import_picocolors15.default.bold(import_picocolors15.default.magenta(trimmed));
|
|
13886
14875
|
}
|
|
13887
14876
|
return colorizeRefName(trimmed, protectedBranches, currentBranch);
|
|
13888
|
-
}).join(
|
|
14877
|
+
}).join(import_picocolors15.default.dim(", "));
|
|
13889
14878
|
}
|
|
13890
14879
|
function colorizeRefName(name, protectedBranches, currentBranch) {
|
|
13891
14880
|
const isRemote = name.includes("/");
|
|
13892
14881
|
const localName = isRemote ? name.split("/").slice(1).join("/") : name;
|
|
13893
14882
|
if (protectedBranches.includes(localName)) {
|
|
13894
|
-
return isRemote ?
|
|
14883
|
+
return isRemote ? import_picocolors15.default.bold(import_picocolors15.default.red(name)) : import_picocolors15.default.bold(import_picocolors15.default.red(name));
|
|
13895
14884
|
}
|
|
13896
14885
|
if (localName === currentBranch) {
|
|
13897
|
-
return
|
|
14886
|
+
return import_picocolors15.default.bold(import_picocolors15.default.green(name));
|
|
13898
14887
|
}
|
|
13899
14888
|
if (isRemote) {
|
|
13900
|
-
return
|
|
14889
|
+
return import_picocolors15.default.blue(name);
|
|
13901
14890
|
}
|
|
13902
|
-
return
|
|
14891
|
+
return import_picocolors15.default.green(name);
|
|
13903
14892
|
}
|
|
13904
14893
|
function colorizeSubject(subject) {
|
|
13905
14894
|
const emojiMatch = subject.match(/^((?:\p{Emoji_Presentation}|\p{Emoji}\uFE0F)+\s*)/u);
|
|
13906
14895
|
if (emojiMatch) {
|
|
13907
14896
|
const emoji = emojiMatch[1];
|
|
13908
14897
|
const rest = subject.slice(emoji.length);
|
|
13909
|
-
return `${emoji}${
|
|
14898
|
+
return `${emoji}${import_picocolors15.default.white(rest)}`;
|
|
13910
14899
|
}
|
|
13911
14900
|
if (subject.startsWith("Merge ")) {
|
|
13912
|
-
return
|
|
14901
|
+
return import_picocolors15.default.dim(subject);
|
|
13913
14902
|
}
|
|
13914
|
-
return
|
|
14903
|
+
return import_picocolors15.default.white(subject);
|
|
13915
14904
|
}
|
|
13916
14905
|
|
|
13917
14906
|
// src/commands/save.ts
|
|
13918
14907
|
import { execFile as execFileCb4 } from "child_process";
|
|
13919
|
-
var
|
|
14908
|
+
var import_picocolors16 = __toESM(require_picocolors(), 1);
|
|
13920
14909
|
function gitRun(args) {
|
|
13921
|
-
return new Promise((
|
|
14910
|
+
return new Promise((resolve6) => {
|
|
13922
14911
|
execFileCb4("git", args, (err, stdout2, stderr) => {
|
|
13923
|
-
|
|
14912
|
+
resolve6({
|
|
13924
14913
|
exitCode: err ? err.code === "ENOENT" ? 127 : err.status ?? 1 : 0,
|
|
13925
14914
|
stdout: stdout2 ?? "",
|
|
13926
14915
|
stderr: stderr ?? ""
|
|
@@ -13990,8 +14979,8 @@ async function handleSave(message) {
|
|
|
13990
14979
|
info("No uncommitted changes to save.");
|
|
13991
14980
|
return;
|
|
13992
14981
|
}
|
|
13993
|
-
success(`Saved: ${
|
|
13994
|
-
info(`Use ${
|
|
14982
|
+
success(`Saved: ${import_picocolors16.default.dim(label)}`);
|
|
14983
|
+
info(`Use ${import_picocolors16.default.bold("cn save --restore")} to bring them back.`, "");
|
|
13995
14984
|
}
|
|
13996
14985
|
async function handleRestore() {
|
|
13997
14986
|
await projectHeading("save --restore", "\uD83D\uDCBE");
|
|
@@ -14007,7 +14996,7 @@ async function handleRestore() {
|
|
|
14007
14996
|
warn("You may have conflicts. Resolve them and run `git stash drop` when done.");
|
|
14008
14997
|
process.exit(1);
|
|
14009
14998
|
}
|
|
14010
|
-
success(`Restored: ${
|
|
14999
|
+
success(`Restored: ${import_picocolors16.default.dim(stashes[0].message)}`);
|
|
14011
15000
|
return;
|
|
14012
15001
|
}
|
|
14013
15002
|
const choices = stashes.map((s2) => `${s2.index} ${s2.message}`);
|
|
@@ -14020,7 +15009,7 @@ async function handleRestore() {
|
|
|
14020
15009
|
process.exit(1);
|
|
14021
15010
|
}
|
|
14022
15011
|
const match = stashes.find((s2) => String(s2.index) === idx);
|
|
14023
|
-
success(`Restored: ${
|
|
15012
|
+
success(`Restored: ${import_picocolors16.default.dim(match?.message ?? "saved changes")}`);
|
|
14024
15013
|
}
|
|
14025
15014
|
async function handleList() {
|
|
14026
15015
|
await projectHeading("save --list", "\uD83D\uDCBE");
|
|
@@ -14031,13 +15020,13 @@ async function handleList() {
|
|
|
14031
15020
|
}
|
|
14032
15021
|
console.log();
|
|
14033
15022
|
for (const s2 of stashes) {
|
|
14034
|
-
const idx =
|
|
15023
|
+
const idx = import_picocolors16.default.dim(`[${s2.index}]`);
|
|
14035
15024
|
const msg = s2.message;
|
|
14036
15025
|
console.log(` ${idx} ${msg}`);
|
|
14037
15026
|
}
|
|
14038
15027
|
console.log();
|
|
14039
|
-
info(`Use ${
|
|
14040
|
-
info(`Use ${
|
|
15028
|
+
info(`Use ${import_picocolors16.default.bold("cn save --restore")} to bring changes back.`, "");
|
|
15029
|
+
info(`Use ${import_picocolors16.default.bold("cn save --drop")} to discard saved changes.`, "");
|
|
14041
15030
|
}
|
|
14042
15031
|
async function handleDrop() {
|
|
14043
15032
|
await projectHeading("save --drop", "\uD83D\uDCBE");
|
|
@@ -14055,7 +15044,7 @@ async function handleDrop() {
|
|
|
14055
15044
|
process.exit(1);
|
|
14056
15045
|
}
|
|
14057
15046
|
const match = stashes.find((s2) => String(s2.index) === idx);
|
|
14058
|
-
success(`Dropped: ${
|
|
15047
|
+
success(`Dropped: ${import_picocolors16.default.dim(match?.message ?? "saved changes")}`);
|
|
14059
15048
|
}
|
|
14060
15049
|
async function getStashList() {
|
|
14061
15050
|
const result = await gitRun(["stash", "list"]);
|
|
@@ -14072,7 +15061,8 @@ async function getStashList() {
|
|
|
14072
15061
|
}
|
|
14073
15062
|
|
|
14074
15063
|
// src/commands/setup.ts
|
|
14075
|
-
var
|
|
15064
|
+
var import_picocolors17 = __toESM(require_picocolors(), 1);
|
|
15065
|
+
init_gh();
|
|
14076
15066
|
async function shouldContinueSetupWithExistingConfig(options) {
|
|
14077
15067
|
const { existingConfig, hasConfigFile, confirm, onInfo, onWarn, onSuccess, summary } = options;
|
|
14078
15068
|
if (existingConfig) {
|
|
@@ -14121,6 +15111,32 @@ async function promptForOllamaCloudModel(apiKey, host = DEFAULT_OLLAMA_CLOUD_HOS
|
|
|
14121
15111
|
}
|
|
14122
15112
|
return inputPrompt(`Ollama Cloud model (default: ${DEFAULT_OLLAMA_CLOUD_MODEL} \u2014 press Enter to keep)`, DEFAULT_OLLAMA_CLOUD_MODEL);
|
|
14123
15113
|
}
|
|
15114
|
+
async function promptForOpenRouterModel(apiKey) {
|
|
15115
|
+
try {
|
|
15116
|
+
info("Fetching available OpenRouter models...");
|
|
15117
|
+
const models = prioritizeOpenRouterModels(await fetchOpenRouterModels(apiKey));
|
|
15118
|
+
if (models.length > 0) {
|
|
15119
|
+
const manualChoice = "Enter model manually";
|
|
15120
|
+
const choices = models.map((model) => ({
|
|
15121
|
+
value: model,
|
|
15122
|
+
label: model === DEFAULT_OPENROUTER_MODEL ? `${model} (default)` : model
|
|
15123
|
+
}));
|
|
15124
|
+
const selected = await selectPrompt("Which OpenRouter model should this clone use?", [
|
|
15125
|
+
...choices.map((choice) => choice.label),
|
|
15126
|
+
manualChoice
|
|
15127
|
+
]);
|
|
15128
|
+
if (selected !== manualChoice) {
|
|
15129
|
+
return choices.find((choice) => choice.label === selected)?.value ?? DEFAULT_OPENROUTER_MODEL;
|
|
15130
|
+
}
|
|
15131
|
+
} else {
|
|
15132
|
+
warn("OpenRouter returned no available models. Enter the model name manually.");
|
|
15133
|
+
}
|
|
15134
|
+
} catch (err) {
|
|
15135
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
15136
|
+
warn(`Could not fetch OpenRouter models: ${message}`);
|
|
15137
|
+
}
|
|
15138
|
+
return inputPrompt(`OpenRouter model (default: ${DEFAULT_OPENROUTER_MODEL} \u2014 press Enter to keep)`, DEFAULT_OPENROUTER_MODEL);
|
|
15139
|
+
}
|
|
14124
15140
|
var setup_default = defineCommand({
|
|
14125
15141
|
meta: {
|
|
14126
15142
|
name: "setup",
|
|
@@ -14155,7 +15171,7 @@ var setup_default = defineCommand({
|
|
|
14155
15171
|
workflow = "github-flow";
|
|
14156
15172
|
else if (workflowChoice.startsWith("Git Flow"))
|
|
14157
15173
|
workflow = "git-flow";
|
|
14158
|
-
info(`Workflow: ${
|
|
15174
|
+
info(`Workflow: ${import_picocolors17.default.bold(WORKFLOW_DESCRIPTIONS[workflow])}`);
|
|
14159
15175
|
const conventionChoice = await selectPrompt("Which commit convention should this project use?", [
|
|
14160
15176
|
`${CONVENTION_DESCRIPTIONS["clean-commit"]} (recommended)`,
|
|
14161
15177
|
CONVENTION_DESCRIPTIONS.conventional,
|
|
@@ -14172,9 +15188,16 @@ var setup_default = defineCommand({
|
|
|
14172
15188
|
if (enableAI) {
|
|
14173
15189
|
const providerChoice = await selectPrompt("Which AI provider should this clone use?", [
|
|
14174
15190
|
"GitHub Copilot \u2014 use your existing GitHub/Copilot auth",
|
|
14175
|
-
"Ollama Cloud \u2014 use an API key stored in the local secrets store"
|
|
15191
|
+
"Ollama Cloud \u2014 use an API key stored in the local secrets store",
|
|
15192
|
+
"OpenRouter \u2014 use an API key stored in the local secrets store"
|
|
14176
15193
|
]);
|
|
14177
|
-
|
|
15194
|
+
if (providerChoice.startsWith("Ollama Cloud")) {
|
|
15195
|
+
aiProvider = "ollama-cloud";
|
|
15196
|
+
} else if (providerChoice.startsWith("OpenRouter")) {
|
|
15197
|
+
aiProvider = "openrouter";
|
|
15198
|
+
} else {
|
|
15199
|
+
aiProvider = "copilot";
|
|
15200
|
+
}
|
|
14178
15201
|
if (aiProvider === "ollama-cloud") {
|
|
14179
15202
|
const apiKey = (await passwordPrompt("Enter your Ollama Cloud API key")).trim();
|
|
14180
15203
|
if (!apiKey) {
|
|
@@ -14185,12 +15208,28 @@ var setup_default = defineCommand({
|
|
|
14185
15208
|
try {
|
|
14186
15209
|
await setOllamaCloudApiKey(apiKey);
|
|
14187
15210
|
success("Stored Ollama Cloud API key in the local secrets store.");
|
|
14188
|
-
info(`Secrets path: ${
|
|
15211
|
+
info(`Secrets path: ${import_picocolors17.default.bold(getSecretsStorePath())}`);
|
|
14189
15212
|
} catch (err) {
|
|
14190
15213
|
const message = err instanceof Error ? err.message : String(err);
|
|
14191
15214
|
error(`Failed to store Ollama Cloud API key: ${message}`);
|
|
14192
15215
|
process.exit(1);
|
|
14193
15216
|
}
|
|
15217
|
+
} else if (aiProvider === "openrouter") {
|
|
15218
|
+
const apiKey = (await passwordPrompt("Enter your OpenRouter API key")).trim();
|
|
15219
|
+
if (!apiKey) {
|
|
15220
|
+
error("OpenRouter API key is required when OpenRouter is selected.");
|
|
15221
|
+
process.exit(1);
|
|
15222
|
+
}
|
|
15223
|
+
aiModel = await promptForOpenRouterModel(apiKey);
|
|
15224
|
+
try {
|
|
15225
|
+
await setOpenRouterApiKey(apiKey);
|
|
15226
|
+
success("Stored OpenRouter API key in the local secrets store.");
|
|
15227
|
+
info(`Secrets path: ${import_picocolors17.default.bold(getSecretsStorePath())}`);
|
|
15228
|
+
} catch (err) {
|
|
15229
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
15230
|
+
error(`Failed to store OpenRouter API key: ${message}`);
|
|
15231
|
+
process.exit(1);
|
|
15232
|
+
}
|
|
14194
15233
|
}
|
|
14195
15234
|
}
|
|
14196
15235
|
const showTips = await confirmPrompt("Show beginner quick guides and loading tips in command output?");
|
|
@@ -14247,15 +15286,15 @@ var setup_default = defineCommand({
|
|
|
14247
15286
|
detectedRole = roleChoice;
|
|
14248
15287
|
detectionSource = "user selection";
|
|
14249
15288
|
} else {
|
|
14250
|
-
info(`Detected role: ${
|
|
14251
|
-
const confirmed = await confirmPrompt(`Role detected as ${
|
|
15289
|
+
info(`Detected role: ${import_picocolors17.default.bold(detectedRole)} (via ${detectionSource})`);
|
|
15290
|
+
const confirmed = await confirmPrompt(`Role detected as ${import_picocolors17.default.bold(detectedRole)}. Is this correct?`);
|
|
14252
15291
|
if (!confirmed) {
|
|
14253
15292
|
const roleChoice = await selectPrompt("Select your role:", ["maintainer", "contributor"]);
|
|
14254
15293
|
detectedRole = roleChoice;
|
|
14255
15294
|
}
|
|
14256
15295
|
}
|
|
14257
15296
|
const defaultConfig = getDefaultConfig();
|
|
14258
|
-
info(
|
|
15297
|
+
info(import_picocolors17.default.dim("Tip: press Enter to keep the default branch name shown in each prompt."));
|
|
14259
15298
|
const mainBranchDefault = defaultConfig.mainBranch;
|
|
14260
15299
|
const mainBranch = await inputPrompt(`Main branch name (default: ${mainBranchDefault} \u2014 press Enter to keep)`, mainBranchDefault);
|
|
14261
15300
|
let devBranch;
|
|
@@ -14281,7 +15320,7 @@ var setup_default = defineCommand({
|
|
|
14281
15320
|
error("Setup cannot continue without the upstream remote for contributors.");
|
|
14282
15321
|
process.exit(1);
|
|
14283
15322
|
}
|
|
14284
|
-
success(`Added remote ${
|
|
15323
|
+
success(`Added remote ${import_picocolors17.default.bold(upstreamRemote)} \u2192 ${upstreamUrl}`);
|
|
14285
15324
|
} else {
|
|
14286
15325
|
error("An upstream remote URL is required for contributors.");
|
|
14287
15326
|
info("Add it manually: git remote add upstream <url>", "");
|
|
@@ -14304,67 +15343,67 @@ var setup_default = defineCommand({
|
|
|
14304
15343
|
showTips
|
|
14305
15344
|
};
|
|
14306
15345
|
writeConfig(config);
|
|
14307
|
-
success(`Config written to ${
|
|
15346
|
+
success(`Config written to ${import_picocolors17.default.bold(getConfigLocationLabel())}`);
|
|
14308
15347
|
info("This setup is stored locally for this clone and does not modify tracked files.", "");
|
|
14309
15348
|
const syncRemote = config.role === "contributor" ? config.upstream : config.origin;
|
|
14310
|
-
info(`Fetching ${
|
|
15349
|
+
info(`Fetching ${import_picocolors17.default.bold(syncRemote)} to verify branch configuration...`, "");
|
|
14311
15350
|
await fetchRemote(syncRemote);
|
|
14312
15351
|
const mainRef = `${syncRemote}/${config.mainBranch}`;
|
|
14313
15352
|
if (!await refExists(mainRef)) {
|
|
14314
|
-
warn(`Main branch ref ${
|
|
15353
|
+
warn(`Main branch ref ${import_picocolors17.default.bold(mainRef)} not found on remote.`);
|
|
14315
15354
|
warn("Config was saved \u2014 verify the branch name and re-run setup if needed.");
|
|
14316
15355
|
}
|
|
14317
15356
|
if (config.devBranch) {
|
|
14318
15357
|
const devRef = `${syncRemote}/${config.devBranch}`;
|
|
14319
15358
|
if (!await refExists(devRef)) {
|
|
14320
|
-
warn(`Dev branch ref ${
|
|
15359
|
+
warn(`Dev branch ref ${import_picocolors17.default.bold(devRef)} not found on remote.`);
|
|
14321
15360
|
warn("Config was saved \u2014 verify the branch name and re-run setup if needed.");
|
|
14322
15361
|
}
|
|
14323
15362
|
}
|
|
14324
15363
|
console.log();
|
|
14325
15364
|
const resolvedAIConfig = resolveAIConfig(config);
|
|
14326
|
-
info(`Workflow: ${
|
|
14327
|
-
info(`Convention: ${
|
|
14328
|
-
info(`AI: ${
|
|
15365
|
+
info(`Workflow: ${import_picocolors17.default.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
|
|
15366
|
+
info(`Convention: ${import_picocolors17.default.bold(CONVENTION_DESCRIPTIONS[config.commitConvention])}`);
|
|
15367
|
+
info(`AI: ${import_picocolors17.default.bold(isAIEnabled(config) ? "enabled" : "disabled")}`);
|
|
14329
15368
|
if (isAIEnabled(config)) {
|
|
14330
|
-
info(`AI provider: ${
|
|
15369
|
+
info(`AI provider: ${import_picocolors17.default.bold(resolvedAIConfig.providerLabel)}`);
|
|
14331
15370
|
if (resolvedAIConfig.model) {
|
|
14332
|
-
info(`AI model: ${
|
|
15371
|
+
info(`AI model: ${import_picocolors17.default.bold(resolvedAIConfig.model)}`);
|
|
14333
15372
|
}
|
|
14334
15373
|
}
|
|
14335
|
-
info(`Guides: ${
|
|
14336
|
-
info(`Role: ${
|
|
15374
|
+
info(`Guides: ${import_picocolors17.default.bold(shouldShowTips(config) ? "shown" : "hidden")}`);
|
|
15375
|
+
info(`Role: ${import_picocolors17.default.bold(config.role)}`);
|
|
14337
15376
|
if (config.devBranch) {
|
|
14338
|
-
info(`Main: ${
|
|
15377
|
+
info(`Main: ${import_picocolors17.default.bold(config.mainBranch)} | Dev: ${import_picocolors17.default.bold(config.devBranch)}`);
|
|
14339
15378
|
} else {
|
|
14340
|
-
info(`Main: ${
|
|
15379
|
+
info(`Main: ${import_picocolors17.default.bold(config.mainBranch)}`);
|
|
14341
15380
|
}
|
|
14342
|
-
info(`Origin: ${
|
|
15381
|
+
info(`Origin: ${import_picocolors17.default.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${import_picocolors17.default.bold(config.upstream)}` : ""}`);
|
|
14343
15382
|
}
|
|
14344
15383
|
});
|
|
14345
15384
|
function logConfigSummary(config) {
|
|
14346
15385
|
const aiConfig = resolveAIConfig(config);
|
|
14347
|
-
info(`Workflow: ${
|
|
14348
|
-
info(`Convention: ${
|
|
14349
|
-
info(`AI: ${
|
|
15386
|
+
info(`Workflow: ${import_picocolors17.default.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
|
|
15387
|
+
info(`Convention: ${import_picocolors17.default.bold(CONVENTION_DESCRIPTIONS[config.commitConvention])}`);
|
|
15388
|
+
info(`AI: ${import_picocolors17.default.bold(isAIEnabled(config) ? "enabled" : "disabled")}`);
|
|
14350
15389
|
if (isAIEnabled(config)) {
|
|
14351
|
-
info(`AI provider: ${
|
|
15390
|
+
info(`AI provider: ${import_picocolors17.default.bold(aiConfig.providerLabel)}`);
|
|
14352
15391
|
if (aiConfig.model) {
|
|
14353
|
-
info(`AI model: ${
|
|
15392
|
+
info(`AI model: ${import_picocolors17.default.bold(aiConfig.model)}`);
|
|
14354
15393
|
}
|
|
14355
15394
|
}
|
|
14356
|
-
info(`Guides: ${
|
|
14357
|
-
info(`Role: ${
|
|
15395
|
+
info(`Guides: ${import_picocolors17.default.bold(shouldShowTips(config) ? "shown" : "hidden")}`);
|
|
15396
|
+
info(`Role: ${import_picocolors17.default.bold(config.role)}`);
|
|
14358
15397
|
if (config.devBranch) {
|
|
14359
|
-
info(`Main: ${
|
|
15398
|
+
info(`Main: ${import_picocolors17.default.bold(config.mainBranch)} | Dev: ${import_picocolors17.default.bold(config.devBranch)}`);
|
|
14360
15399
|
} else {
|
|
14361
|
-
info(`Main: ${
|
|
15400
|
+
info(`Main: ${import_picocolors17.default.bold(config.mainBranch)}`);
|
|
14362
15401
|
}
|
|
14363
|
-
info(`Origin: ${
|
|
15402
|
+
info(`Origin: ${import_picocolors17.default.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${import_picocolors17.default.bold(config.upstream)}` : ""}`);
|
|
14364
15403
|
}
|
|
14365
15404
|
|
|
14366
15405
|
// src/commands/start.ts
|
|
14367
|
-
var
|
|
15406
|
+
var import_picocolors18 = __toESM(require_picocolors(), 1);
|
|
14368
15407
|
var start_default = defineCommand({
|
|
14369
15408
|
meta: {
|
|
14370
15409
|
name: "start",
|
|
@@ -14416,16 +15455,16 @@ var start_default = defineCommand({
|
|
|
14416
15455
|
warn("Start cancelled.");
|
|
14417
15456
|
process.exit(0);
|
|
14418
15457
|
}
|
|
14419
|
-
info(`Creating branch: ${
|
|
15458
|
+
info(`Creating branch: ${import_picocolors18.default.bold(branchName)}`);
|
|
14420
15459
|
await fetchRemote(syncSource.remote);
|
|
14421
15460
|
if (!await refExists(syncSource.ref)) {
|
|
14422
|
-
warn(`Remote ref ${
|
|
15461
|
+
warn(`Remote ref ${import_picocolors18.default.bold(syncSource.ref)} not found. Creating branch from local ${import_picocolors18.default.bold(baseBranch)}.`);
|
|
14423
15462
|
}
|
|
14424
15463
|
const currentBranch = await getCurrentBranch();
|
|
14425
15464
|
if (currentBranch === baseBranch && await refExists(syncSource.ref)) {
|
|
14426
15465
|
const ahead = await countCommitsAhead(baseBranch, syncSource.ref);
|
|
14427
15466
|
if (ahead > 0) {
|
|
14428
|
-
warn(`You are on ${
|
|
15467
|
+
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
15468
|
info(" Syncing will discard those commits. Consider backing them up first (e.g. create a branch).");
|
|
14430
15469
|
const proceed = await confirmPrompt("Discard local commits and sync to remote?");
|
|
14431
15470
|
if (!proceed) {
|
|
@@ -14442,10 +15481,10 @@ var start_default = defineCommand({
|
|
|
14442
15481
|
error(`Failed to create branch: ${result2.stderr}`);
|
|
14443
15482
|
process.exit(1);
|
|
14444
15483
|
}
|
|
14445
|
-
success(`Created ${
|
|
15484
|
+
success(`Created ${import_picocolors18.default.bold(branchName)} from ${import_picocolors18.default.bold(syncSource.ref)}`);
|
|
14446
15485
|
return;
|
|
14447
15486
|
}
|
|
14448
|
-
error(`Failed to update ${
|
|
15487
|
+
error(`Failed to update ${import_picocolors18.default.bold(baseBranch)}: ${updateResult.stderr}`);
|
|
14449
15488
|
info("Make sure your base branch exists locally or the remote ref is available.", "");
|
|
14450
15489
|
process.exit(1);
|
|
14451
15490
|
}
|
|
@@ -14454,12 +15493,13 @@ var start_default = defineCommand({
|
|
|
14454
15493
|
error(`Failed to create branch: ${result.stderr}`);
|
|
14455
15494
|
process.exit(1);
|
|
14456
15495
|
}
|
|
14457
|
-
success(`Created ${
|
|
15496
|
+
success(`Created ${import_picocolors18.default.bold(branchName)} from latest ${import_picocolors18.default.bold(baseBranch)}`);
|
|
14458
15497
|
}
|
|
14459
15498
|
});
|
|
14460
15499
|
|
|
14461
15500
|
// src/commands/status.ts
|
|
14462
|
-
var
|
|
15501
|
+
var import_picocolors19 = __toESM(require_picocolors(), 1);
|
|
15502
|
+
init_gh();
|
|
14463
15503
|
var status_default = defineCommand({
|
|
14464
15504
|
meta: {
|
|
14465
15505
|
name: "status",
|
|
@@ -14476,8 +15516,8 @@ var status_default = defineCommand({
|
|
|
14476
15516
|
process.exit(1);
|
|
14477
15517
|
}
|
|
14478
15518
|
await projectHeading("status", "\uD83D\uDCCA");
|
|
14479
|
-
console.log(` ${
|
|
14480
|
-
console.log(` ${
|
|
15519
|
+
console.log(` ${import_picocolors19.default.dim("Workflow:")} ${import_picocolors19.default.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
|
|
15520
|
+
console.log(` ${import_picocolors19.default.dim("Role:")} ${import_picocolors19.default.bold(config.role)}`);
|
|
14481
15521
|
console.log();
|
|
14482
15522
|
await fetchAll();
|
|
14483
15523
|
const currentBranch = await getCurrentBranch();
|
|
@@ -14486,7 +15526,7 @@ var status_default = defineCommand({
|
|
|
14486
15526
|
const isContributor = config.role === "contributor";
|
|
14487
15527
|
const [dirty, fileStatus] = await Promise.all([hasUncommittedChanges(), getFileStatus()]);
|
|
14488
15528
|
if (dirty) {
|
|
14489
|
-
console.log(` ${
|
|
15529
|
+
console.log(` ${import_picocolors19.default.yellow("\u26A0")} ${import_picocolors19.default.yellow("Uncommitted changes in working tree")}`);
|
|
14490
15530
|
console.log();
|
|
14491
15531
|
}
|
|
14492
15532
|
const mainRemote = `${origin}/${mainBranch}`;
|
|
@@ -14505,16 +15545,16 @@ var status_default = defineCommand({
|
|
|
14505
15545
|
if (isFeatureBranch) {
|
|
14506
15546
|
const branchDiv = await getDivergence(currentBranch, baseBranch);
|
|
14507
15547
|
const branchLine = formatStatus(currentBranch, baseBranch, branchDiv.ahead, branchDiv.behind);
|
|
14508
|
-
console.log(branchLine +
|
|
15548
|
+
console.log(branchLine + import_picocolors19.default.dim(` (current ${import_picocolors19.default.green("*")})`));
|
|
14509
15549
|
branchStatus = await detectBranchStatus(currentBranch, baseBranch);
|
|
14510
15550
|
if (branchStatus.merged) {
|
|
14511
|
-
console.log(` ${
|
|
15551
|
+
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
15552
|
}
|
|
14513
15553
|
if (branchStatus.stale) {
|
|
14514
|
-
console.log(` ${
|
|
15554
|
+
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
15555
|
}
|
|
14516
15556
|
} else if (currentBranch) {
|
|
14517
|
-
console.log(
|
|
15557
|
+
console.log(import_picocolors19.default.dim(` (on ${import_picocolors19.default.bold(currentBranch)} branch)`));
|
|
14518
15558
|
}
|
|
14519
15559
|
let branchesAligned = true;
|
|
14520
15560
|
{
|
|
@@ -14551,20 +15591,20 @@ var status_default = defineCommand({
|
|
|
14551
15591
|
}
|
|
14552
15592
|
branchesAligned = groups.size === 1;
|
|
14553
15593
|
console.log();
|
|
14554
|
-
console.log(` ${
|
|
15594
|
+
console.log(` ${import_picocolors19.default.bold("\uD83D\uDD17 Branch Alignment")}`);
|
|
14555
15595
|
for (const [hash, names] of groups) {
|
|
14556
15596
|
const short = hash.slice(0, 7);
|
|
14557
|
-
const nameStr = names.map((n2) =>
|
|
14558
|
-
console.log(` ${
|
|
15597
|
+
const nameStr = names.map((n2) => import_picocolors19.default.bold(n2)).join(import_picocolors19.default.dim(" \xB7 "));
|
|
15598
|
+
console.log(` ${import_picocolors19.default.yellow(short)} ${import_picocolors19.default.dim("\u2500\u2500")} ${nameStr}`);
|
|
14559
15599
|
const subject = await getCommitSubject(hash);
|
|
14560
15600
|
if (subject) {
|
|
14561
|
-
console.log(` ${
|
|
15601
|
+
console.log(` ${import_picocolors19.default.dim(subject)}`);
|
|
14562
15602
|
}
|
|
14563
15603
|
}
|
|
14564
15604
|
if (branchesAligned) {
|
|
14565
|
-
console.log(` ${
|
|
15605
|
+
console.log(` ${import_picocolors19.default.green("\u2713")} ${import_picocolors19.default.green("All branches aligned")} ${import_picocolors19.default.dim("\u2014 ready to start")}`);
|
|
14566
15606
|
} else {
|
|
14567
|
-
console.log(` ${
|
|
15607
|
+
console.log(` ${import_picocolors19.default.yellow("\u26A0")} ${import_picocolors19.default.yellow("Branches are not fully aligned")}`);
|
|
14568
15608
|
}
|
|
14569
15609
|
}
|
|
14570
15610
|
}
|
|
@@ -14572,41 +15612,41 @@ var status_default = defineCommand({
|
|
|
14572
15612
|
if (hasFiles) {
|
|
14573
15613
|
console.log();
|
|
14574
15614
|
if (fileStatus.staged.length > 0) {
|
|
14575
|
-
console.log(` ${
|
|
15615
|
+
console.log(` ${import_picocolors19.default.green("Staged for commit:")}`);
|
|
14576
15616
|
for (const { file, status } of fileStatus.staged) {
|
|
14577
|
-
console.log(` ${
|
|
15617
|
+
console.log(` ${import_picocolors19.default.green("+")} ${import_picocolors19.default.dim(`${status}:`)} ${file}`);
|
|
14578
15618
|
}
|
|
14579
15619
|
}
|
|
14580
15620
|
if (fileStatus.modified.length > 0) {
|
|
14581
|
-
console.log(` ${
|
|
15621
|
+
console.log(` ${import_picocolors19.default.yellow("Unstaged changes:")}`);
|
|
14582
15622
|
for (const { file, status } of fileStatus.modified) {
|
|
14583
|
-
console.log(` ${
|
|
15623
|
+
console.log(` ${import_picocolors19.default.yellow("~")} ${import_picocolors19.default.dim(`${status}:`)} ${file}`);
|
|
14584
15624
|
}
|
|
14585
15625
|
}
|
|
14586
15626
|
if (fileStatus.untracked.length > 0) {
|
|
14587
|
-
console.log(` ${
|
|
15627
|
+
console.log(` ${import_picocolors19.default.red("Untracked files:")}`);
|
|
14588
15628
|
for (const file of fileStatus.untracked) {
|
|
14589
|
-
console.log(` ${
|
|
15629
|
+
console.log(` ${import_picocolors19.default.red("?")} ${file}`);
|
|
14590
15630
|
}
|
|
14591
15631
|
}
|
|
14592
15632
|
} else if (!dirty) {
|
|
14593
|
-
console.log(` ${
|
|
15633
|
+
console.log(` ${import_picocolors19.default.green("\u2713")} ${import_picocolors19.default.dim("Working tree clean")}`);
|
|
14594
15634
|
}
|
|
14595
15635
|
console.log();
|
|
14596
15636
|
}
|
|
14597
15637
|
});
|
|
14598
15638
|
function formatStatus(branch, base, ahead, behind) {
|
|
14599
|
-
const label =
|
|
15639
|
+
const label = import_picocolors19.default.bold(branch.padEnd(20));
|
|
14600
15640
|
if (ahead === 0 && behind === 0) {
|
|
14601
|
-
return ` ${
|
|
15641
|
+
return ` ${import_picocolors19.default.green("\u2713")} ${label} ${import_picocolors19.default.dim(`in sync with ${base}`)}`;
|
|
14602
15642
|
}
|
|
14603
15643
|
if (ahead > 0 && behind === 0) {
|
|
14604
|
-
return ` ${
|
|
15644
|
+
return ` ${import_picocolors19.default.yellow("\u2191")} ${label} ${import_picocolors19.default.yellow(`${ahead} commit${ahead !== 1 ? "s" : ""} ahead of ${base}`)}`;
|
|
14605
15645
|
}
|
|
14606
15646
|
if (behind > 0 && ahead === 0) {
|
|
14607
|
-
return ` ${
|
|
15647
|
+
return ` ${import_picocolors19.default.red("\u2193")} ${label} ${import_picocolors19.default.red(`${behind} commit${behind !== 1 ? "s" : ""} behind ${base}`)}`;
|
|
14608
15648
|
}
|
|
14609
|
-
return ` ${
|
|
15649
|
+
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
15650
|
}
|
|
14611
15651
|
var STALE_THRESHOLD_DAYS = 14;
|
|
14612
15652
|
async function detectBranchStatus(branch, baseBranch) {
|
|
@@ -14657,15 +15697,16 @@ async function detectBranchStatus(branch, baseBranch) {
|
|
|
14657
15697
|
}
|
|
14658
15698
|
|
|
14659
15699
|
// src/commands/submit.ts
|
|
14660
|
-
var
|
|
15700
|
+
var import_picocolors20 = __toESM(require_picocolors(), 1);
|
|
15701
|
+
init_gh();
|
|
14661
15702
|
async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
14662
|
-
info(`Checking out ${
|
|
15703
|
+
info(`Checking out ${import_picocolors20.default.bold(baseBranch)}...`);
|
|
14663
15704
|
const coResult = await checkoutBranch(baseBranch);
|
|
14664
15705
|
if (coResult.exitCode !== 0) {
|
|
14665
15706
|
error(`Failed to checkout ${baseBranch}: ${coResult.stderr}`);
|
|
14666
15707
|
process.exit(1);
|
|
14667
15708
|
}
|
|
14668
|
-
info(`Squash merging ${
|
|
15709
|
+
info(`Squash merging ${import_picocolors20.default.bold(featureBranch)} into ${import_picocolors20.default.bold(baseBranch)}...`);
|
|
14669
15710
|
const mergeResult = await mergeSquash(featureBranch);
|
|
14670
15711
|
if (mergeResult.exitCode !== 0) {
|
|
14671
15712
|
error(`Squash merge failed: ${mergeResult.stderr}`);
|
|
@@ -14685,7 +15726,7 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
14685
15726
|
message = aiMsg;
|
|
14686
15727
|
spinner.success("AI commit message generated.");
|
|
14687
15728
|
console.log(`
|
|
14688
|
-
${
|
|
15729
|
+
${import_picocolors20.default.dim("AI suggestion:")} ${import_picocolors20.default.bold(import_picocolors20.default.cyan(message))}`);
|
|
14689
15730
|
break;
|
|
14690
15731
|
}
|
|
14691
15732
|
spinner.fail("AI did not return a commit message.");
|
|
@@ -14725,7 +15766,7 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
14725
15766
|
message = regen;
|
|
14726
15767
|
spinner.success("Commit message regenerated.");
|
|
14727
15768
|
console.log(`
|
|
14728
|
-
${
|
|
15769
|
+
${import_picocolors20.default.dim("AI suggestion:")} ${import_picocolors20.default.bold(import_picocolors20.default.cyan(regen))}`);
|
|
14729
15770
|
} else {
|
|
14730
15771
|
spinner.fail("Regeneration failed.");
|
|
14731
15772
|
}
|
|
@@ -14741,13 +15782,13 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
14741
15782
|
error(`Commit failed: ${commitResult.stderr}`);
|
|
14742
15783
|
process.exit(1);
|
|
14743
15784
|
}
|
|
14744
|
-
info(`Pushing ${
|
|
15785
|
+
info(`Pushing ${import_picocolors20.default.bold(baseBranch)} to ${origin}...`);
|
|
14745
15786
|
const pushResult = await pushBranch(origin, baseBranch);
|
|
14746
15787
|
if (pushResult.exitCode !== 0) {
|
|
14747
15788
|
error(`Failed to push ${baseBranch}: ${pushResult.stderr}`);
|
|
14748
15789
|
process.exit(1);
|
|
14749
15790
|
}
|
|
14750
|
-
info(`Deleting local branch ${
|
|
15791
|
+
info(`Deleting local branch ${import_picocolors20.default.bold(featureBranch)}...`);
|
|
14751
15792
|
const delLocal = await forceDeleteBranch(featureBranch);
|
|
14752
15793
|
if (delLocal.exitCode !== 0) {
|
|
14753
15794
|
warn(`Could not delete local branch: ${delLocal.stderr.trim()}`);
|
|
@@ -14755,14 +15796,14 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
14755
15796
|
const remoteBranchRef = `${origin}/${featureBranch}`;
|
|
14756
15797
|
const remoteExists = await branchExists(remoteBranchRef);
|
|
14757
15798
|
if (remoteExists) {
|
|
14758
|
-
info(`Deleting remote branch ${
|
|
15799
|
+
info(`Deleting remote branch ${import_picocolors20.default.bold(featureBranch)}...`);
|
|
14759
15800
|
const delRemote = await deleteRemoteBranch(origin, featureBranch);
|
|
14760
15801
|
if (delRemote.exitCode !== 0) {
|
|
14761
15802
|
warn(`Could not delete remote branch: ${delRemote.stderr.trim()}`);
|
|
14762
15803
|
}
|
|
14763
15804
|
}
|
|
14764
|
-
success(`Squash merged ${
|
|
14765
|
-
info(`Run ${
|
|
15805
|
+
success(`Squash merged ${import_picocolors20.default.bold(featureBranch)} into ${import_picocolors20.default.bold(baseBranch)} and pushed.`);
|
|
15806
|
+
info(`Run ${import_picocolors20.default.bold("cn start")} to begin a new feature.`, "");
|
|
14766
15807
|
}
|
|
14767
15808
|
var submit_default = defineCommand({
|
|
14768
15809
|
meta: {
|
|
@@ -14819,7 +15860,7 @@ var submit_default = defineCommand({
|
|
|
14819
15860
|
}
|
|
14820
15861
|
if (protectedBranches.includes(currentBranch)) {
|
|
14821
15862
|
await projectHeading("submit", "\uD83D\uDE80");
|
|
14822
|
-
warn(`You're on ${
|
|
15863
|
+
warn(`You're on ${import_picocolors20.default.bold(currentBranch)}, which is a protected branch. PRs should come from feature branches.`);
|
|
14823
15864
|
await fetchAll();
|
|
14824
15865
|
const remoteRef = `${origin}/${currentBranch}`;
|
|
14825
15866
|
const localWork = await hasLocalWork(origin, currentBranch);
|
|
@@ -14828,11 +15869,11 @@ var submit_default = defineCommand({
|
|
|
14828
15869
|
const hasAnything = hasCommits || dirty;
|
|
14829
15870
|
if (!hasAnything) {
|
|
14830
15871
|
error("No local changes or commits to move. Switch to a feature branch first.");
|
|
14831
|
-
info(` Run ${
|
|
15872
|
+
info(` Run ${import_picocolors20.default.bold("cn start")} to create a new feature branch.`, "");
|
|
14832
15873
|
process.exit(1);
|
|
14833
15874
|
}
|
|
14834
15875
|
if (hasCommits) {
|
|
14835
|
-
info(`Found ${
|
|
15876
|
+
info(`Found ${import_picocolors20.default.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${import_picocolors20.default.bold(currentBranch)}.`);
|
|
14836
15877
|
}
|
|
14837
15878
|
if (dirty) {
|
|
14838
15879
|
info("You also have uncommitted changes in the working tree.");
|
|
@@ -14862,12 +15903,12 @@ var submit_default = defineCommand({
|
|
|
14862
15903
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
14863
15904
|
process.exit(1);
|
|
14864
15905
|
}
|
|
14865
|
-
success(`Created ${
|
|
15906
|
+
success(`Created ${import_picocolors20.default.bold(newBranchName)} with your changes.`);
|
|
14866
15907
|
await updateLocalBranch(currentBranch, remoteRef);
|
|
14867
|
-
info(`Reset ${
|
|
15908
|
+
info(`Reset ${import_picocolors20.default.bold(currentBranch)} back to ${import_picocolors20.default.bold(remoteRef)} \u2014 no damage done.`, "");
|
|
14868
15909
|
console.log();
|
|
14869
|
-
success(`You're now on ${
|
|
14870
|
-
info(`Run ${
|
|
15910
|
+
success(`You're now on ${import_picocolors20.default.bold(newBranchName)} with all your work intact.`);
|
|
15911
|
+
info(`Run ${import_picocolors20.default.bold("cn submit")} again to push and create your PR.`, "");
|
|
14871
15912
|
return;
|
|
14872
15913
|
}
|
|
14873
15914
|
await projectHeading("submit", "\uD83D\uDE80");
|
|
@@ -14876,7 +15917,7 @@ var submit_default = defineCommand({
|
|
|
14876
15917
|
if (ghInstalled && ghAuthed) {
|
|
14877
15918
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
14878
15919
|
if (mergedPR) {
|
|
14879
|
-
warn(`PR #${mergedPR.number} (${
|
|
15920
|
+
warn(`PR #${mergedPR.number} (${import_picocolors20.default.bold(mergedPR.title)}) was already merged.`);
|
|
14880
15921
|
const localWork = await hasLocalWork(origin, currentBranch);
|
|
14881
15922
|
const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
|
|
14882
15923
|
if (hasWork) {
|
|
@@ -14884,7 +15925,7 @@ var submit_default = defineCommand({
|
|
|
14884
15925
|
warn("You have uncommitted changes in your working tree.");
|
|
14885
15926
|
}
|
|
14886
15927
|
if (localWork.unpushedCommits > 0) {
|
|
14887
|
-
warn(`You have ${
|
|
15928
|
+
warn(`You have ${import_picocolors20.default.bold(String(localWork.unpushedCommits))} local commit${localWork.unpushedCommits !== 1 ? "s" : ""} not in the merged PR.`);
|
|
14888
15929
|
}
|
|
14889
15930
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
14890
15931
|
const DISCARD = "Discard all changes and clean up";
|
|
@@ -14911,10 +15952,10 @@ var submit_default = defineCommand({
|
|
|
14911
15952
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
14912
15953
|
process.exit(1);
|
|
14913
15954
|
}
|
|
14914
|
-
success(`Renamed ${
|
|
15955
|
+
success(`Renamed ${import_picocolors20.default.bold(currentBranch)} \u2192 ${import_picocolors20.default.bold(newBranchName)}`);
|
|
14915
15956
|
await unsetUpstream();
|
|
14916
15957
|
const syncSource2 = getSyncSource(config);
|
|
14917
|
-
info(`Syncing ${
|
|
15958
|
+
info(`Syncing ${import_picocolors20.default.bold(newBranchName)} with latest ${import_picocolors20.default.bold(baseBranch)}...`);
|
|
14918
15959
|
await fetchRemote(syncSource2.remote);
|
|
14919
15960
|
let rebaseResult;
|
|
14920
15961
|
if (staleUpstreamHash) {
|
|
@@ -14925,17 +15966,17 @@ var submit_default = defineCommand({
|
|
|
14925
15966
|
}
|
|
14926
15967
|
if (rebaseResult.exitCode !== 0) {
|
|
14927
15968
|
warn("Rebase encountered conflicts. Resolve them manually, then run:");
|
|
14928
|
-
info(` ${
|
|
15969
|
+
info(` ${import_picocolors20.default.bold("git rebase --continue")}`, "");
|
|
14929
15970
|
} else {
|
|
14930
|
-
success(`Rebased ${
|
|
15971
|
+
success(`Rebased ${import_picocolors20.default.bold(newBranchName)} onto ${import_picocolors20.default.bold(syncSource2.ref)}.`);
|
|
14931
15972
|
}
|
|
14932
|
-
info(`All your changes are preserved. Run ${
|
|
15973
|
+
info(`All your changes are preserved. Run ${import_picocolors20.default.bold("cn submit")} when ready to create a new PR.`, "");
|
|
14933
15974
|
return;
|
|
14934
15975
|
}
|
|
14935
15976
|
warn("Discarding local changes...");
|
|
14936
15977
|
}
|
|
14937
15978
|
const syncSource = getSyncSource(config);
|
|
14938
|
-
info(`Switching to ${
|
|
15979
|
+
info(`Switching to ${import_picocolors20.default.bold(baseBranch)} and syncing...`);
|
|
14939
15980
|
await fetchRemote(syncSource.remote);
|
|
14940
15981
|
await resetHard("HEAD");
|
|
14941
15982
|
const coResult = await checkoutBranch(baseBranch);
|
|
@@ -14944,23 +15985,23 @@ var submit_default = defineCommand({
|
|
|
14944
15985
|
process.exit(1);
|
|
14945
15986
|
}
|
|
14946
15987
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
14947
|
-
success(`Synced ${
|
|
14948
|
-
info(`Deleting stale branch ${
|
|
15988
|
+
success(`Synced ${import_picocolors20.default.bold(baseBranch)} with ${import_picocolors20.default.bold(syncSource.ref)}.`);
|
|
15989
|
+
info(`Deleting stale branch ${import_picocolors20.default.bold(currentBranch)}...`);
|
|
14949
15990
|
const delResult = await forceDeleteBranch(currentBranch);
|
|
14950
15991
|
if (delResult.exitCode === 0) {
|
|
14951
|
-
success(`Deleted ${
|
|
15992
|
+
success(`Deleted ${import_picocolors20.default.bold(currentBranch)}.`);
|
|
14952
15993
|
} else {
|
|
14953
15994
|
warn(`Could not delete branch: ${delResult.stderr.trim()}`);
|
|
14954
15995
|
}
|
|
14955
15996
|
console.log();
|
|
14956
|
-
info(`You're now on ${
|
|
15997
|
+
info(`You're now on ${import_picocolors20.default.bold(baseBranch)}. Run ${import_picocolors20.default.bold("cn start")} to begin a new feature.`);
|
|
14957
15998
|
return;
|
|
14958
15999
|
}
|
|
14959
16000
|
}
|
|
14960
16001
|
if (ghInstalled && ghAuthed) {
|
|
14961
16002
|
const existingPR = await getPRForBranch(currentBranch);
|
|
14962
16003
|
if (existingPR) {
|
|
14963
|
-
info(`Pushing ${
|
|
16004
|
+
info(`Pushing ${import_picocolors20.default.bold(currentBranch)} to ${origin}...`);
|
|
14964
16005
|
const pushResult2 = await pushSetUpstream(origin, currentBranch);
|
|
14965
16006
|
if (pushResult2.exitCode !== 0) {
|
|
14966
16007
|
error(`Failed to push: ${pushResult2.stderr}`);
|
|
@@ -14971,8 +16012,8 @@ var submit_default = defineCommand({
|
|
|
14971
16012
|
}
|
|
14972
16013
|
process.exit(1);
|
|
14973
16014
|
}
|
|
14974
|
-
success(`Pushed changes to existing PR #${existingPR.number}: ${
|
|
14975
|
-
console.log(` ${
|
|
16015
|
+
success(`Pushed changes to existing PR #${existingPR.number}: ${import_picocolors20.default.bold(existingPR.title)}`);
|
|
16016
|
+
console.log(` ${import_picocolors20.default.cyan(existingPR.url)}`);
|
|
14976
16017
|
return;
|
|
14977
16018
|
}
|
|
14978
16019
|
}
|
|
@@ -14994,10 +16035,10 @@ var submit_default = defineCommand({
|
|
|
14994
16035
|
prBody = result.body;
|
|
14995
16036
|
spinner.success("PR description generated.");
|
|
14996
16037
|
console.log(`
|
|
14997
|
-
${
|
|
16038
|
+
${import_picocolors20.default.dim("AI title:")} ${import_picocolors20.default.bold(import_picocolors20.default.cyan(prTitle))}`);
|
|
14998
16039
|
console.log(`
|
|
14999
|
-
${
|
|
15000
|
-
console.log(
|
|
16040
|
+
${import_picocolors20.default.dim("AI body preview:")}`);
|
|
16041
|
+
console.log(import_picocolors20.default.dim(prBody.slice(0, 300) + (prBody.length > 300 ? "..." : "")));
|
|
15001
16042
|
} else {
|
|
15002
16043
|
spinner.fail("AI did not return a PR description.");
|
|
15003
16044
|
}
|
|
@@ -15108,7 +16149,7 @@ ${import_picocolors19.default.dim("AI body preview:")}`);
|
|
|
15108
16149
|
warn("Submit cancelled.");
|
|
15109
16150
|
return;
|
|
15110
16151
|
}
|
|
15111
|
-
info(`Pushing ${
|
|
16152
|
+
info(`Pushing ${import_picocolors20.default.bold(currentBranch)} to ${origin}...`);
|
|
15112
16153
|
const pushResult = await pushSetUpstream(origin, currentBranch);
|
|
15113
16154
|
if (pushResult.exitCode !== 0) {
|
|
15114
16155
|
error(`Failed to push: ${pushResult.stderr}`);
|
|
@@ -15127,7 +16168,7 @@ ${import_picocolors19.default.dim("AI body preview:")}`);
|
|
|
15127
16168
|
const prUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}/compare/${baseBranch}...${currentBranch}?expand=1`;
|
|
15128
16169
|
console.log();
|
|
15129
16170
|
info("Create your PR manually:", "");
|
|
15130
|
-
console.log(` ${
|
|
16171
|
+
console.log(` ${import_picocolors20.default.cyan(prUrl)}`);
|
|
15131
16172
|
} else {
|
|
15132
16173
|
info("gh CLI not available. Create your PR manually on GitHub.", "");
|
|
15133
16174
|
}
|
|
@@ -15161,7 +16202,7 @@ ${import_picocolors19.default.dim("AI body preview:")}`);
|
|
|
15161
16202
|
});
|
|
15162
16203
|
|
|
15163
16204
|
// src/commands/switch.ts
|
|
15164
|
-
var
|
|
16205
|
+
var import_picocolors21 = __toESM(require_picocolors(), 1);
|
|
15165
16206
|
var switch_default = defineCommand({
|
|
15166
16207
|
meta: {
|
|
15167
16208
|
name: "switch",
|
|
@@ -15193,11 +16234,11 @@ var switch_default = defineCommand({
|
|
|
15193
16234
|
const choices = localBranches.filter((b2) => b2.name !== currentBranch).map((b2) => {
|
|
15194
16235
|
const labels = [];
|
|
15195
16236
|
if (protectedBranches.includes(b2.name))
|
|
15196
|
-
labels.push(
|
|
16237
|
+
labels.push(import_picocolors21.default.red("protected"));
|
|
15197
16238
|
if (b2.upstream)
|
|
15198
|
-
labels.push(
|
|
16239
|
+
labels.push(import_picocolors21.default.dim(`\u2192 ${b2.upstream}`));
|
|
15199
16240
|
if (b2.gone)
|
|
15200
|
-
labels.push(
|
|
16241
|
+
labels.push(import_picocolors21.default.red("remote gone"));
|
|
15201
16242
|
const suffix = labels.length > 0 ? ` ${labels.join(" \xB7 ")}` : "";
|
|
15202
16243
|
return `${b2.name}${suffix}`;
|
|
15203
16244
|
});
|
|
@@ -15209,7 +16250,7 @@ var switch_default = defineCommand({
|
|
|
15209
16250
|
targetBranch = selected.split(/\s{2,}/)[0].trim();
|
|
15210
16251
|
}
|
|
15211
16252
|
if (targetBranch === currentBranch) {
|
|
15212
|
-
info(`Already on ${
|
|
16253
|
+
info(`Already on ${import_picocolors21.default.bold(targetBranch)}.`);
|
|
15213
16254
|
return;
|
|
15214
16255
|
}
|
|
15215
16256
|
if (await hasUncommittedChanges()) {
|
|
@@ -15228,7 +16269,7 @@ var switch_default = defineCommand({
|
|
|
15228
16269
|
const stashMsg = `contrib-save: auto-save from ${currentBranch}`;
|
|
15229
16270
|
try {
|
|
15230
16271
|
await exec("git", ["stash", "push", "-m", stashMsg]);
|
|
15231
|
-
info(`Saved changes: ${
|
|
16272
|
+
info(`Saved changes: ${import_picocolors21.default.dim(stashMsg)}`);
|
|
15232
16273
|
} catch {
|
|
15233
16274
|
error("Failed to save changes. Please commit or save manually.");
|
|
15234
16275
|
process.exit(1);
|
|
@@ -15244,9 +16285,9 @@ var switch_default = defineCommand({
|
|
|
15244
16285
|
}
|
|
15245
16286
|
process.exit(1);
|
|
15246
16287
|
}
|
|
15247
|
-
success(`Switched to ${
|
|
15248
|
-
info(`Your changes from ${
|
|
15249
|
-
info(`Use ${
|
|
16288
|
+
success(`Switched to ${import_picocolors21.default.bold(targetBranch)}`);
|
|
16289
|
+
info(`Your changes from ${import_picocolors21.default.bold(currentBranch ?? "previous branch")} are saved.`, "");
|
|
16290
|
+
info(`Use ${import_picocolors21.default.bold("cn save --restore")} to bring them back.`, "");
|
|
15250
16291
|
return;
|
|
15251
16292
|
}
|
|
15252
16293
|
const result = await checkoutBranch(targetBranch);
|
|
@@ -15254,12 +16295,12 @@ var switch_default = defineCommand({
|
|
|
15254
16295
|
error(`Failed to switch to ${targetBranch}: ${result.stderr}`);
|
|
15255
16296
|
process.exit(1);
|
|
15256
16297
|
}
|
|
15257
|
-
success(`Switched to ${
|
|
16298
|
+
success(`Switched to ${import_picocolors21.default.bold(targetBranch)}`);
|
|
15258
16299
|
}
|
|
15259
16300
|
});
|
|
15260
16301
|
|
|
15261
16302
|
// src/commands/sync.ts
|
|
15262
|
-
var
|
|
16303
|
+
var import_picocolors22 = __toESM(require_picocolors(), 1);
|
|
15263
16304
|
var sync_default = defineCommand({
|
|
15264
16305
|
meta: {
|
|
15265
16306
|
name: "sync",
|
|
@@ -15311,24 +16352,24 @@ var sync_default = defineCommand({
|
|
|
15311
16352
|
await fetchRemote(origin);
|
|
15312
16353
|
}
|
|
15313
16354
|
if (!await refExists(syncSource.ref)) {
|
|
15314
|
-
error(`Remote ref ${
|
|
16355
|
+
error(`Remote ref ${import_picocolors22.default.bold(syncSource.ref)} does not exist.`);
|
|
15315
16356
|
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 ${
|
|
16357
|
+
info(`Check your config: the base branch may need updating via ${import_picocolors22.default.bold("cn setup")}.`, "");
|
|
15317
16358
|
process.exit(1);
|
|
15318
16359
|
}
|
|
15319
16360
|
let allowMergeCommit = false;
|
|
15320
16361
|
const div = await getDivergence(baseBranch, syncSource.ref);
|
|
15321
16362
|
if (div.ahead > 0 || div.behind > 0) {
|
|
15322
|
-
info(`${
|
|
16363
|
+
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
16364
|
} else {
|
|
15324
|
-
info(`${
|
|
16365
|
+
info(`${import_picocolors22.default.bold(baseBranch)} is already in sync with ${syncSource.ref}`);
|
|
15325
16366
|
}
|
|
15326
16367
|
if (div.ahead > 0) {
|
|
15327
16368
|
const currentBranch = await getCurrentBranch();
|
|
15328
16369
|
const protectedBranches = getProtectedBranches(config);
|
|
15329
16370
|
const isOnProtected = currentBranch && protectedBranches.includes(currentBranch);
|
|
15330
16371
|
if (isOnProtected) {
|
|
15331
|
-
warn(`You have ${
|
|
16372
|
+
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
16373
|
info("Pulling now could create a merge commit, which breaks clean history.");
|
|
15333
16374
|
console.log();
|
|
15334
16375
|
const MOVE_BRANCH = "Move my commits to a new feature branch, then sync";
|
|
@@ -15358,7 +16399,7 @@ var sync_default = defineCommand({
|
|
|
15358
16399
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
15359
16400
|
process.exit(1);
|
|
15360
16401
|
}
|
|
15361
|
-
success(`Created ${
|
|
16402
|
+
success(`Created ${import_picocolors22.default.bold(newBranchName)} with your commits.`);
|
|
15362
16403
|
const coResult2 = await checkoutBranch(baseBranch);
|
|
15363
16404
|
if (coResult2.exitCode !== 0) {
|
|
15364
16405
|
error(`Failed to checkout ${baseBranch}: ${coResult2.stderr}`);
|
|
@@ -15366,11 +16407,11 @@ var sync_default = defineCommand({
|
|
|
15366
16407
|
}
|
|
15367
16408
|
const remoteRef = syncSource.ref;
|
|
15368
16409
|
await updateLocalBranch(baseBranch, remoteRef);
|
|
15369
|
-
success(`Reset ${
|
|
15370
|
-
success(`${
|
|
16410
|
+
success(`Reset ${import_picocolors22.default.bold(baseBranch)} to ${import_picocolors22.default.bold(remoteRef)}.`);
|
|
16411
|
+
success(`${import_picocolors22.default.bold(baseBranch)} is now in sync with ${syncSource.ref}`);
|
|
15371
16412
|
console.log();
|
|
15372
|
-
info(`Your commits are safe on ${
|
|
15373
|
-
info(`Run ${
|
|
16413
|
+
info(`Your commits are safe on ${import_picocolors22.default.bold(newBranchName)}.`, "");
|
|
16414
|
+
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
16415
|
return;
|
|
15375
16416
|
}
|
|
15376
16417
|
allowMergeCommit = true;
|
|
@@ -15378,7 +16419,7 @@ var sync_default = defineCommand({
|
|
|
15378
16419
|
}
|
|
15379
16420
|
}
|
|
15380
16421
|
if (!args.yes) {
|
|
15381
|
-
const ok = await confirmPrompt(`This will pull ${
|
|
16422
|
+
const ok = await confirmPrompt(`This will pull ${import_picocolors22.default.bold(syncSource.ref)} into local ${import_picocolors22.default.bold(baseBranch)}.`);
|
|
15382
16423
|
if (!ok)
|
|
15383
16424
|
process.exit(0);
|
|
15384
16425
|
}
|
|
@@ -15392,8 +16433,8 @@ var sync_default = defineCommand({
|
|
|
15392
16433
|
if (allowMergeCommit) {
|
|
15393
16434
|
error(`Pull failed: ${pullResult.stderr.trim()}`);
|
|
15394
16435
|
} else {
|
|
15395
|
-
error(`Fast-forward pull failed. Your local ${
|
|
15396
|
-
info(`Use ${
|
|
16436
|
+
error(`Fast-forward pull failed. Your local ${import_picocolors22.default.bold(baseBranch)} may have diverged.`);
|
|
16437
|
+
info(`Use ${import_picocolors22.default.bold("cn sync")} again and choose "Move my commits to a new feature branch" to fix this.`, "");
|
|
15397
16438
|
}
|
|
15398
16439
|
process.exit(1);
|
|
15399
16440
|
}
|
|
@@ -15401,7 +16442,7 @@ var sync_default = defineCommand({
|
|
|
15401
16442
|
if (hasDevBranch(workflow) && role === "maintainer") {
|
|
15402
16443
|
const mainDiv = await getDivergence(config.mainBranch, `${origin}/${config.mainBranch}`);
|
|
15403
16444
|
if (mainDiv.behind > 0) {
|
|
15404
|
-
info(`Also syncing ${
|
|
16445
|
+
info(`Also syncing ${import_picocolors22.default.bold(config.mainBranch)}...`);
|
|
15405
16446
|
const mainCoResult = await checkoutBranch(config.mainBranch);
|
|
15406
16447
|
if (mainCoResult.exitCode === 0) {
|
|
15407
16448
|
const mainPullResult = await pullFastForwardOnly(origin, config.mainBranch);
|
|
@@ -15442,20 +16483,20 @@ var sync_default = defineCommand({
|
|
|
15442
16483
|
}
|
|
15443
16484
|
}
|
|
15444
16485
|
console.log();
|
|
15445
|
-
console.log(` ${
|
|
16486
|
+
console.log(` ${import_picocolors22.default.bold("\uD83D\uDD17 Branch Alignment")}`);
|
|
15446
16487
|
for (const [hash, names] of groups) {
|
|
15447
16488
|
const short = hash.slice(0, 7);
|
|
15448
|
-
const nameStr = names.map((n2) =>
|
|
15449
|
-
console.log(` ${
|
|
16489
|
+
const nameStr = names.map((n2) => import_picocolors22.default.bold(n2)).join(import_picocolors22.default.dim(" \xB7 "));
|
|
16490
|
+
console.log(` ${import_picocolors22.default.yellow(short)} ${import_picocolors22.default.dim("\u2500\u2500")} ${nameStr}`);
|
|
15450
16491
|
const subject = await getCommitSubject(hash);
|
|
15451
16492
|
if (subject) {
|
|
15452
|
-
console.log(` ${
|
|
16493
|
+
console.log(` ${import_picocolors22.default.dim(subject)}`);
|
|
15453
16494
|
}
|
|
15454
16495
|
}
|
|
15455
16496
|
if (groups.size === 1) {
|
|
15456
|
-
console.log(` ${
|
|
16497
|
+
console.log(` ${import_picocolors22.default.green("\u2713")} ${import_picocolors22.default.green("All branches aligned")} ${import_picocolors22.default.dim("\u2014 ready to start")}`);
|
|
15457
16498
|
} else {
|
|
15458
|
-
console.log(` ${
|
|
16499
|
+
console.log(` ${import_picocolors22.default.yellow("\u26A0")} ${import_picocolors22.default.yellow("Branches are not fully aligned")}`);
|
|
15459
16500
|
}
|
|
15460
16501
|
}
|
|
15461
16502
|
}
|
|
@@ -15463,8 +16504,9 @@ var sync_default = defineCommand({
|
|
|
15463
16504
|
});
|
|
15464
16505
|
|
|
15465
16506
|
// src/commands/update.ts
|
|
15466
|
-
import { readFileSync as
|
|
15467
|
-
var
|
|
16507
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
16508
|
+
var import_picocolors23 = __toESM(require_picocolors(), 1);
|
|
16509
|
+
init_gh();
|
|
15468
16510
|
function hasStaleBranchWorkToPreserve(uniqueCommitsAheadOfBase, hasUncommittedChanges2) {
|
|
15469
16511
|
return hasUncommittedChanges2 || uniqueCommitsAheadOfBase > 0;
|
|
15470
16512
|
}
|
|
@@ -15505,7 +16547,7 @@ var update_default = defineCommand({
|
|
|
15505
16547
|
}
|
|
15506
16548
|
if (protectedBranches.includes(currentBranch)) {
|
|
15507
16549
|
await projectHeading("update", "\uD83D\uDD03");
|
|
15508
|
-
warn(`You're on ${
|
|
16550
|
+
warn(`You're on ${import_picocolors23.default.bold(currentBranch)}, which is a protected branch. Updates (rebase) apply to feature branches.`);
|
|
15509
16551
|
await fetchAll();
|
|
15510
16552
|
const { origin } = config;
|
|
15511
16553
|
const remoteRef = `${origin}/${currentBranch}`;
|
|
@@ -15514,12 +16556,12 @@ var update_default = defineCommand({
|
|
|
15514
16556
|
const hasCommits = localWork.unpushedCommits > 0;
|
|
15515
16557
|
const hasAnything = hasCommits || dirty;
|
|
15516
16558
|
if (!hasAnything) {
|
|
15517
|
-
info(`No local changes found on ${
|
|
15518
|
-
info(`Use ${
|
|
16559
|
+
info(`No local changes found on ${import_picocolors23.default.bold(currentBranch)}.`);
|
|
16560
|
+
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
16561
|
process.exit(1);
|
|
15520
16562
|
}
|
|
15521
16563
|
if (hasCommits) {
|
|
15522
|
-
info(`Found ${
|
|
16564
|
+
info(`Found ${import_picocolors23.default.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${import_picocolors23.default.bold(currentBranch)}.`);
|
|
15523
16565
|
}
|
|
15524
16566
|
if (dirty) {
|
|
15525
16567
|
info("You also have uncommitted changes in the working tree.");
|
|
@@ -15549,12 +16591,12 @@ var update_default = defineCommand({
|
|
|
15549
16591
|
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
15550
16592
|
process.exit(1);
|
|
15551
16593
|
}
|
|
15552
|
-
success(`Created ${
|
|
16594
|
+
success(`Created ${import_picocolors23.default.bold(newBranchName)} with your changes.`);
|
|
15553
16595
|
await updateLocalBranch(currentBranch, remoteRef);
|
|
15554
|
-
info(`Reset ${
|
|
16596
|
+
info(`Reset ${import_picocolors23.default.bold(currentBranch)} back to ${import_picocolors23.default.bold(remoteRef)} \u2014 no damage done.`, "");
|
|
15555
16597
|
console.log();
|
|
15556
|
-
success(`You're now on ${
|
|
15557
|
-
info(`Run ${
|
|
16598
|
+
success(`You're now on ${import_picocolors23.default.bold(newBranchName)} with all your work intact.`);
|
|
16599
|
+
info(`Run ${import_picocolors23.default.bold("cn update")} again to rebase onto latest ${import_picocolors23.default.bold(baseBranch)}.`, "");
|
|
15558
16600
|
return;
|
|
15559
16601
|
}
|
|
15560
16602
|
if (await hasUncommittedChanges()) {
|
|
@@ -15564,8 +16606,8 @@ var update_default = defineCommand({
|
|
|
15564
16606
|
await projectHeading("update", "\uD83D\uDD03");
|
|
15565
16607
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
15566
16608
|
if (mergedPR) {
|
|
15567
|
-
warn(`PR #${mergedPR.number} (${
|
|
15568
|
-
info(`Link: ${
|
|
16609
|
+
warn(`PR #${mergedPR.number} (${import_picocolors23.default.bold(mergedPR.title)}) has already been merged.`);
|
|
16610
|
+
info(`Link: ${import_picocolors23.default.underline(mergedPR.url)}`, "");
|
|
15569
16611
|
const uniqueCommitsAheadOfBase = await countCommitsAhead(currentBranch, syncSource.ref);
|
|
15570
16612
|
const dirty = await hasUncommittedChanges();
|
|
15571
16613
|
const hasWork = hasStaleBranchWorkToPreserve(uniqueCommitsAheadOfBase, dirty);
|
|
@@ -15574,12 +16616,12 @@ var update_default = defineCommand({
|
|
|
15574
16616
|
info("You have uncommitted local changes.");
|
|
15575
16617
|
}
|
|
15576
16618
|
if (uniqueCommitsAheadOfBase > 0) {
|
|
15577
|
-
info(`You have ${uniqueCommitsAheadOfBase} local commit(s) not in ${
|
|
16619
|
+
info(`You have ${uniqueCommitsAheadOfBase} local commit(s) not in ${import_picocolors23.default.bold(syncSource.ref)}.`);
|
|
15578
16620
|
}
|
|
15579
16621
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
15580
16622
|
const DISCARD = "Discard all changes and clean up";
|
|
15581
16623
|
const CANCEL = "Cancel";
|
|
15582
|
-
const action = await selectPrompt(`${
|
|
16624
|
+
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
16625
|
if (action === CANCEL) {
|
|
15584
16626
|
info("No changes made. You are still on your current branch.");
|
|
15585
16627
|
return;
|
|
@@ -15601,7 +16643,7 @@ var update_default = defineCommand({
|
|
|
15601
16643
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
15602
16644
|
process.exit(1);
|
|
15603
16645
|
}
|
|
15604
|
-
success(`Renamed ${
|
|
16646
|
+
success(`Renamed ${import_picocolors23.default.bold(currentBranch)} \u2192 ${import_picocolors23.default.bold(newBranchName)}`);
|
|
15605
16647
|
await unsetUpstream();
|
|
15606
16648
|
await fetchRemote(syncSource.remote);
|
|
15607
16649
|
let rebaseResult2;
|
|
@@ -15613,11 +16655,11 @@ var update_default = defineCommand({
|
|
|
15613
16655
|
}
|
|
15614
16656
|
if (rebaseResult2.exitCode !== 0) {
|
|
15615
16657
|
warn("Rebase encountered conflicts. Resolve them manually, then run:");
|
|
15616
|
-
info(` ${
|
|
16658
|
+
info(` ${import_picocolors23.default.bold("git rebase --continue")}`, "");
|
|
15617
16659
|
} else {
|
|
15618
|
-
success(`Rebased ${
|
|
16660
|
+
success(`Rebased ${import_picocolors23.default.bold(newBranchName)} onto ${import_picocolors23.default.bold(syncSource.ref)}.`);
|
|
15619
16661
|
}
|
|
15620
|
-
info(`All your changes are preserved. Run ${
|
|
16662
|
+
info(`All your changes are preserved. Run ${import_picocolors23.default.bold("cn submit")} when ready to create a new PR.`, "");
|
|
15621
16663
|
return;
|
|
15622
16664
|
}
|
|
15623
16665
|
warn("Discarding local changes...");
|
|
@@ -15636,24 +16678,24 @@ var update_default = defineCommand({
|
|
|
15636
16678
|
process.exit(1);
|
|
15637
16679
|
}
|
|
15638
16680
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
15639
|
-
success(`Synced ${
|
|
15640
|
-
info(`Deleting stale branch ${
|
|
16681
|
+
success(`Synced ${import_picocolors23.default.bold(baseBranch)} with ${import_picocolors23.default.bold(syncSource.ref)}.`);
|
|
16682
|
+
info(`Deleting stale branch ${import_picocolors23.default.bold(currentBranch)}...`);
|
|
15641
16683
|
await forceDeleteBranch(currentBranch);
|
|
15642
|
-
success(`Deleted ${
|
|
15643
|
-
info(`Run ${
|
|
16684
|
+
success(`Deleted ${import_picocolors23.default.bold(currentBranch)}.`);
|
|
16685
|
+
info(`Run ${import_picocolors23.default.bold("cn start")} to begin a new feature branch.`, "");
|
|
15644
16686
|
return;
|
|
15645
16687
|
}
|
|
15646
|
-
info(`Updating ${
|
|
16688
|
+
info(`Updating ${import_picocolors23.default.bold(currentBranch)} with latest ${import_picocolors23.default.bold(baseBranch)}...`);
|
|
15647
16689
|
await fetchRemote(syncSource.remote);
|
|
15648
16690
|
if (!await refExists(syncSource.ref)) {
|
|
15649
|
-
error(`Remote ref ${
|
|
16691
|
+
error(`Remote ref ${import_picocolors23.default.bold(syncSource.ref)} does not exist.`);
|
|
15650
16692
|
error("Run `git fetch --all` and verify your remote configuration.");
|
|
15651
16693
|
process.exit(1);
|
|
15652
16694
|
}
|
|
15653
16695
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
15654
16696
|
const rebaseStrategy = await determineRebaseStrategy(currentBranch, syncSource.ref);
|
|
15655
16697
|
if (rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase) {
|
|
15656
|
-
info(
|
|
16698
|
+
info(import_picocolors23.default.dim(`Using --onto rebase (branch was based on a different ref)`));
|
|
15657
16699
|
}
|
|
15658
16700
|
const rebaseResult = rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase ? await rebaseOnto(syncSource.ref, rebaseStrategy.ontoOldBase) : await rebase(syncSource.ref);
|
|
15659
16701
|
if (rebaseResult.exitCode !== 0) {
|
|
@@ -15667,7 +16709,7 @@ var update_default = defineCommand({
|
|
|
15667
16709
|
let conflictDiff = "";
|
|
15668
16710
|
for (const file of conflictFiles.slice(0, 3)) {
|
|
15669
16711
|
try {
|
|
15670
|
-
const content =
|
|
16712
|
+
const content = readFileSync7(file, "utf-8");
|
|
15671
16713
|
if (content.includes("<<<<<<<")) {
|
|
15672
16714
|
conflictDiff += `
|
|
15673
16715
|
--- ${file} ---
|
|
@@ -15684,10 +16726,10 @@ ${content.slice(0, 2000)}
|
|
|
15684
16726
|
if (suggestion) {
|
|
15685
16727
|
spinner.success("AI conflict guidance ready.");
|
|
15686
16728
|
console.log(`
|
|
15687
|
-
${
|
|
15688
|
-
console.log(
|
|
16729
|
+
${import_picocolors23.default.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
|
|
16730
|
+
console.log(import_picocolors23.default.dim("\u2500".repeat(60)));
|
|
15689
16731
|
console.log(suggestion);
|
|
15690
|
-
console.log(
|
|
16732
|
+
console.log(import_picocolors23.default.dim("\u2500".repeat(60)));
|
|
15691
16733
|
console.log();
|
|
15692
16734
|
} else {
|
|
15693
16735
|
spinner.fail("AI could not analyze the conflicts.");
|
|
@@ -15695,21 +16737,21 @@ ${import_picocolors22.default.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance
|
|
|
15695
16737
|
}
|
|
15696
16738
|
}
|
|
15697
16739
|
}
|
|
15698
|
-
console.log(
|
|
16740
|
+
console.log(import_picocolors23.default.bold("To resolve:"));
|
|
15699
16741
|
console.log(` 1. Fix conflicts in the affected files`);
|
|
15700
|
-
console.log(` 2. ${
|
|
15701
|
-
console.log(` 3. ${
|
|
16742
|
+
console.log(` 2. ${import_picocolors23.default.cyan("git add <resolved-files>")}`);
|
|
16743
|
+
console.log(` 3. ${import_picocolors23.default.cyan("git rebase --continue")}`);
|
|
15702
16744
|
console.log();
|
|
15703
|
-
console.log(` Or abort: ${
|
|
16745
|
+
console.log(` Or abort: ${import_picocolors23.default.cyan("git rebase --abort")}`);
|
|
15704
16746
|
process.exit(1);
|
|
15705
16747
|
}
|
|
15706
|
-
success(`${
|
|
16748
|
+
success(`${import_picocolors23.default.bold(currentBranch)} has been rebased onto latest ${import_picocolors23.default.bold(baseBranch)}`);
|
|
15707
16749
|
}
|
|
15708
16750
|
});
|
|
15709
16751
|
|
|
15710
16752
|
// src/commands/validate.ts
|
|
15711
|
-
import { readFileSync as
|
|
15712
|
-
var
|
|
16753
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
16754
|
+
var import_picocolors24 = __toESM(require_picocolors(), 1);
|
|
15713
16755
|
var validate_default = defineCommand({
|
|
15714
16756
|
meta: {
|
|
15715
16757
|
name: "validate",
|
|
@@ -15738,7 +16780,7 @@ var validate_default = defineCommand({
|
|
|
15738
16780
|
info('Commit convention is set to "none". All messages are accepted.');
|
|
15739
16781
|
process.exit(0);
|
|
15740
16782
|
}
|
|
15741
|
-
const message = args.file ?
|
|
16783
|
+
const message = args.file ? readFileSync8(args.file, "utf-8").split(/\r?\n/, 1)[0] ?? "" : args.message;
|
|
15742
16784
|
if (!message) {
|
|
15743
16785
|
error("No commit message provided. Pass a message or use --file <path>.");
|
|
15744
16786
|
process.exit(1);
|
|
@@ -15749,14 +16791,14 @@ var validate_default = defineCommand({
|
|
|
15749
16791
|
}
|
|
15750
16792
|
const errors = getValidationError(convention);
|
|
15751
16793
|
for (const line of errors) {
|
|
15752
|
-
console.error(
|
|
16794
|
+
console.error(import_picocolors24.default.red(` \u2717 ${line}`));
|
|
15753
16795
|
}
|
|
15754
16796
|
process.exit(1);
|
|
15755
16797
|
}
|
|
15756
16798
|
});
|
|
15757
16799
|
|
|
15758
16800
|
// src/ui/banner.ts
|
|
15759
|
-
var
|
|
16801
|
+
var import_picocolors25 = __toESM(require_picocolors(), 1);
|
|
15760
16802
|
|
|
15761
16803
|
// src/data/announcements.json
|
|
15762
16804
|
var announcements_default = [
|
|
@@ -15817,9 +16859,9 @@ function getAuthor() {
|
|
|
15817
16859
|
return typeof package_default.author === "string" ? package_default.author : "unknown";
|
|
15818
16860
|
}
|
|
15819
16861
|
function showBanner(variant = "small") {
|
|
15820
|
-
console.log(
|
|
16862
|
+
console.log(import_picocolors25.default.cyan(`
|
|
15821
16863
|
${LOGO}`));
|
|
15822
|
-
console.log(` ${
|
|
16864
|
+
console.log(` ${import_picocolors25.default.dim(`v${getVersion()}`)} ${import_picocolors25.default.dim("\u2014")} ${import_picocolors25.default.dim(`Built by ${getAuthor()}`)}`);
|
|
15823
16865
|
const announcements = getActiveAnnouncements();
|
|
15824
16866
|
if (announcements.length > 0) {
|
|
15825
16867
|
console.log();
|
|
@@ -15828,27 +16870,27 @@ ${LOGO}`));
|
|
|
15828
16870
|
if (variant === "big") {
|
|
15829
16871
|
const panelLines = [
|
|
15830
16872
|
{
|
|
15831
|
-
label:
|
|
16873
|
+
label: import_picocolors25.default.bold(import_picocolors25.default.cyan("Getting Started")),
|
|
15832
16874
|
rawLabel: "Getting Started",
|
|
15833
16875
|
value: "",
|
|
15834
16876
|
rawValue: ""
|
|
15835
16877
|
},
|
|
15836
16878
|
{
|
|
15837
|
-
label:
|
|
16879
|
+
label: import_picocolors25.default.cyan("cn setup"),
|
|
15838
16880
|
rawLabel: "cn setup",
|
|
15839
|
-
value:
|
|
16881
|
+
value: import_picocolors25.default.dim("configure workflow, remotes, and defaults"),
|
|
15840
16882
|
rawValue: "configure workflow, remotes, and defaults"
|
|
15841
16883
|
},
|
|
15842
16884
|
{
|
|
15843
|
-
label:
|
|
16885
|
+
label: import_picocolors25.default.cyan("cn doctor"),
|
|
15844
16886
|
rawLabel: "cn doctor",
|
|
15845
|
-
value:
|
|
16887
|
+
value: import_picocolors25.default.dim("verify your environment before doing any work"),
|
|
15846
16888
|
rawValue: "verify your environment before doing any work"
|
|
15847
16889
|
},
|
|
15848
16890
|
{
|
|
15849
|
-
label:
|
|
16891
|
+
label: import_picocolors25.default.cyan("cn start"),
|
|
15850
16892
|
rawLabel: "cn start",
|
|
15851
|
-
value:
|
|
16893
|
+
value: import_picocolors25.default.dim("create a branch and begin the next task"),
|
|
15852
16894
|
rawValue: "create a branch and begin the next task"
|
|
15853
16895
|
},
|
|
15854
16896
|
{
|
|
@@ -15858,13 +16900,13 @@ ${LOGO}`));
|
|
|
15858
16900
|
rawValue: ""
|
|
15859
16901
|
},
|
|
15860
16902
|
{
|
|
15861
|
-
label:
|
|
16903
|
+
label: import_picocolors25.default.bold(import_picocolors25.default.cyan("Workflow")),
|
|
15862
16904
|
rawLabel: "Workflow",
|
|
15863
16905
|
value: "",
|
|
15864
16906
|
rawValue: ""
|
|
15865
16907
|
},
|
|
15866
16908
|
{
|
|
15867
|
-
label:
|
|
16909
|
+
label: import_picocolors25.default.dim("cn setup \u2192 cn commit \u2192 cn update \u2192 cn submit"),
|
|
15868
16910
|
rawLabel: "cn setup \u2192 cn commit \u2192 cn update \u2192 cn submit",
|
|
15869
16911
|
value: "",
|
|
15870
16912
|
rawValue: ""
|
|
@@ -15889,22 +16931,22 @@ ${LOGO}`));
|
|
|
15889
16931
|
return Math.max(max, lineLength);
|
|
15890
16932
|
}, 0));
|
|
15891
16933
|
console.log();
|
|
15892
|
-
console.log(` ${
|
|
16934
|
+
console.log(` ${import_picocolors25.default.dim(`\u250C${"\u2500".repeat(contentWidth + 2)}\u2510`)}`);
|
|
15893
16935
|
for (const line of rows) {
|
|
15894
16936
|
if (!line.rawLabel && !line.rawValue) {
|
|
15895
|
-
console.log(` ${
|
|
16937
|
+
console.log(` ${import_picocolors25.default.dim("\u2502")} ${" ".repeat(contentWidth)} ${import_picocolors25.default.dim("\u2502")}`);
|
|
15896
16938
|
continue;
|
|
15897
16939
|
}
|
|
15898
16940
|
const left = line.rawValue ? `${line.label}${" ".repeat(Math.max(0, labelWidth - line.rawLabel.length + 2))}` : line.label;
|
|
15899
|
-
const value = line.rawValue ?
|
|
16941
|
+
const value = line.rawValue ? import_picocolors25.default.dim(line.rawValue) : "";
|
|
15900
16942
|
const rawLength = line.rawValue ? labelWidth + 2 + line.rawValue.length : line.rawLabel.length;
|
|
15901
16943
|
const trailing = " ".repeat(Math.max(0, contentWidth - rawLength));
|
|
15902
|
-
console.log(` ${
|
|
16944
|
+
console.log(` ${import_picocolors25.default.dim("\u2502")} ${left}${value}${trailing} ${import_picocolors25.default.dim("\u2502")}`);
|
|
15903
16945
|
}
|
|
15904
|
-
console.log(` ${
|
|
16946
|
+
console.log(` ${import_picocolors25.default.dim(`\u2514${"\u2500".repeat(contentWidth + 2)}\u2518`)}`);
|
|
15905
16947
|
console.log();
|
|
15906
|
-
console.log(` ${
|
|
15907
|
-
console.log(` ${
|
|
16948
|
+
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"))}`);
|
|
16949
|
+
console.log(` ${import_picocolors25.default.dim("Sponsor:")} ${import_picocolors25.default.dim(linkify("warengonzaga.com/sponsor", "https://warengonzaga.com/sponsor"))}`);
|
|
15908
16950
|
}
|
|
15909
16951
|
console.log();
|
|
15910
16952
|
}
|
|
@@ -15936,7 +16978,7 @@ function renderAnnouncementBanner(announcement) {
|
|
|
15936
16978
|
console.log(` ${tone.border(`\u250C${"\u2500".repeat(rawWidth + 2)}\u2510`)}`);
|
|
15937
16979
|
for (const line of lines) {
|
|
15938
16980
|
const trailing = " ".repeat(Math.max(0, rawWidth - line.length));
|
|
15939
|
-
const content = line === title ? tone.title(line) :
|
|
16981
|
+
const content = line === title ? tone.title(line) : import_picocolors25.default.dim(line);
|
|
15940
16982
|
console.log(` ${tone.border("\u2502")} ${content}${trailing} ${tone.border("\u2502")}`);
|
|
15941
16983
|
}
|
|
15942
16984
|
console.log(` ${tone.border(`\u2514${"\u2500".repeat(rawWidth + 2)}\u2518`)}`);
|
|
@@ -15972,20 +17014,20 @@ function getAnnouncementTone(kind) {
|
|
|
15972
17014
|
case "info":
|
|
15973
17015
|
return {
|
|
15974
17016
|
emoji: "\u2139",
|
|
15975
|
-
border:
|
|
15976
|
-
title: (value) =>
|
|
17017
|
+
border: import_picocolors25.default.blue,
|
|
17018
|
+
title: (value) => import_picocolors25.default.bold(import_picocolors25.default.blue(value))
|
|
15977
17019
|
};
|
|
15978
17020
|
case "warning":
|
|
15979
17021
|
return {
|
|
15980
17022
|
emoji: "\uD83D\uDEA8",
|
|
15981
|
-
border:
|
|
15982
|
-
title: (value) =>
|
|
17023
|
+
border: import_picocolors25.default.red,
|
|
17024
|
+
title: (value) => import_picocolors25.default.bold(import_picocolors25.default.red(value))
|
|
15983
17025
|
};
|
|
15984
17026
|
default:
|
|
15985
17027
|
return {
|
|
15986
17028
|
emoji: "\u26A0",
|
|
15987
|
-
border:
|
|
15988
|
-
title: (value) =>
|
|
17029
|
+
border: import_picocolors25.default.yellow,
|
|
17030
|
+
title: (value) => import_picocolors25.default.bold(import_picocolors25.default.yellow(value))
|
|
15989
17031
|
};
|
|
15990
17032
|
}
|
|
15991
17033
|
}
|
|
@@ -16024,7 +17066,8 @@ if (!isVersion) {
|
|
|
16024
17066
|
"branch",
|
|
16025
17067
|
"hook",
|
|
16026
17068
|
"validate",
|
|
16027
|
-
"doctor"
|
|
17069
|
+
"doctor",
|
|
17070
|
+
"label"
|
|
16028
17071
|
];
|
|
16029
17072
|
const isHelp = process.argv.includes("--help") || process.argv.includes("-h");
|
|
16030
17073
|
const hasSubCommand = subCommands.some((cmd) => process.argv.includes(cmd));
|
|
@@ -16061,7 +17104,8 @@ var main = defineCommand({
|
|
|
16061
17104
|
log: log_default,
|
|
16062
17105
|
hook: hook_default,
|
|
16063
17106
|
validate: validate_default,
|
|
16064
|
-
doctor: doctor_default
|
|
17107
|
+
doctor: doctor_default,
|
|
17108
|
+
label: label_default
|
|
16065
17109
|
},
|
|
16066
17110
|
run({ args }) {
|
|
16067
17111
|
if (args.version) {
|