@kolatts/pncli 1.11.1 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-VPUWMP2M.js → chunk-SLQOSGZJ.js} +4 -4
- package/dist/{chunk-VPUWMP2M.js.map → chunk-SLQOSGZJ.js.map} +1 -1
- package/dist/cli.js +332 -72
- package/dist/cli.js.map +1 -1
- package/dist/{http-LNDRNPP7.js → http-5GDQYH62.js} +2 -2
- package/package.json +1 -1
- /package/dist/{http-LNDRNPP7.js.map → http-5GDQYH62.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
setGlobalUser,
|
|
20
20
|
success,
|
|
21
21
|
warn
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-SLQOSGZJ.js";
|
|
23
23
|
import {
|
|
24
24
|
AdoCoreClient
|
|
25
25
|
} from "./chunk-VDL32PSG.js";
|
|
@@ -294,11 +294,17 @@ function parseBranchReportOutput(raw, branchName, opts) {
|
|
|
294
294
|
}
|
|
295
295
|
};
|
|
296
296
|
}
|
|
297
|
+
function normalizeGitDate(dateStr) {
|
|
298
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
|
|
299
|
+
return `${dateStr}T00:00:00`;
|
|
300
|
+
}
|
|
301
|
+
return dateStr;
|
|
302
|
+
}
|
|
297
303
|
function buildReportArgs(opts) {
|
|
298
304
|
const fmt = `${COMMIT_PREFIX}%H %an %aI %s`;
|
|
299
305
|
const args = ["log", `--format=${fmt}`, "--numstat"];
|
|
300
|
-
if (opts.since) args.push(`--since=${opts.since}`);
|
|
301
|
-
if (opts.until) args.push(`--until=${opts.until}`);
|
|
306
|
+
if (opts.since) args.push(`--since=${normalizeGitDate(opts.since)}`);
|
|
307
|
+
if (opts.until) args.push(`--until=${normalizeGitDate(opts.until)}`);
|
|
302
308
|
if (opts.base && opts.branch) {
|
|
303
309
|
args.push(`${opts.base}..${opts.branch}`);
|
|
304
310
|
} else if (opts.base) {
|
|
@@ -430,13 +436,13 @@ function parseAdoRemote(remoteUrl, adoBaseUrl) {
|
|
|
430
436
|
const sshIdx = path6.indexOf("/_ssh/");
|
|
431
437
|
const markerIdx = gitIdx !== -1 ? gitIdx : sshIdx;
|
|
432
438
|
if (markerIdx === -1) return null;
|
|
433
|
-
const repo = path6.slice(markerIdx + 6);
|
|
439
|
+
const repo = decodeURIComponent(path6.slice(markerIdx + 6));
|
|
434
440
|
if (!repo) return null;
|
|
435
441
|
const before = path6.slice(1, markerIdx);
|
|
436
442
|
const parts = before.split("/").filter(Boolean);
|
|
437
443
|
if (parts.length < 2) return null;
|
|
438
|
-
const project = parts[parts.length - 1];
|
|
439
|
-
const collection = parts[parts.length - 2];
|
|
444
|
+
const project = decodeURIComponent(parts[parts.length - 1]);
|
|
445
|
+
const collection = decodeURIComponent(parts[parts.length - 2]);
|
|
440
446
|
return { collection, project, repo };
|
|
441
447
|
}
|
|
442
448
|
function getRemoteUrls(repoRoot) {
|
|
@@ -776,6 +782,9 @@ function registerGitCommands(program2) {
|
|
|
776
782
|
});
|
|
777
783
|
}
|
|
778
784
|
|
|
785
|
+
// src/services/jira/commands.ts
|
|
786
|
+
import { readFileSync } from "fs";
|
|
787
|
+
|
|
779
788
|
// src/services/jira/client.ts
|
|
780
789
|
var API2 = "/rest/api/2";
|
|
781
790
|
var JiraClient = class {
|
|
@@ -796,6 +805,7 @@ var JiraClient = class {
|
|
|
796
805
|
...opts.priority ? { priority: { name: opts.priority } } : {},
|
|
797
806
|
...opts.assignee ? { assignee: { name: opts.assignee } } : {},
|
|
798
807
|
...opts.labels?.length ? { labels: opts.labels } : {},
|
|
808
|
+
...opts.parent ? { parent: { key: opts.parent } } : {},
|
|
799
809
|
...opts.customFieldValues ?? {}
|
|
800
810
|
}
|
|
801
811
|
};
|
|
@@ -864,7 +874,56 @@ var JiraClient = class {
|
|
|
864
874
|
return { issues: allIssues, total: allIssues.length, startAt: 0, maxResults: allIssues.length };
|
|
865
875
|
}
|
|
866
876
|
async fetchFields() {
|
|
867
|
-
|
|
877
|
+
const fields = await this.http.jira(`${API2}/field`);
|
|
878
|
+
return fields.map((f) => ({ ...f, pncliType: schemaToPncliType(f.schema) }));
|
|
879
|
+
}
|
|
880
|
+
async fetchFieldsWithAllowedValues(project, issueType) {
|
|
881
|
+
const fields = await this.fetchFields();
|
|
882
|
+
let metaFields = {};
|
|
883
|
+
try {
|
|
884
|
+
const meta = await this.http.jira(`${API2}/issue/createmeta`, {
|
|
885
|
+
params: { projectKeys: project, expand: "projects.issuetypes.fields" }
|
|
886
|
+
});
|
|
887
|
+
metaFields = collectMetaFields(meta.projects, issueType);
|
|
888
|
+
} catch {
|
|
889
|
+
try {
|
|
890
|
+
metaFields = await this.fetchCreateMetaViaIssuetypes(project, issueType);
|
|
891
|
+
} catch {
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return fields.map((f) => {
|
|
895
|
+
const av = metaFields[f.id]?.allowedValues;
|
|
896
|
+
return av?.length ? { ...f, allowedValues: av } : f;
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Fallback for instances where the classic createmeta endpoint returns 404.
|
|
901
|
+
* Uses the paginated endpoint available in Jira Datacenter 8.4+.
|
|
902
|
+
*/
|
|
903
|
+
async fetchCreateMetaViaIssuetypes(project, issueType) {
|
|
904
|
+
const itList = await this.http.jira(
|
|
905
|
+
`${API2}/issue/createmeta/${encodeURIComponent(project)}/issuetypes`
|
|
906
|
+
);
|
|
907
|
+
const matchingTypes = issueType ? itList.values.filter((it) => it.name.toLowerCase() === issueType.toLowerCase()) : itList.values;
|
|
908
|
+
const result = {};
|
|
909
|
+
for (const it of matchingTypes) {
|
|
910
|
+
const fieldList = await this.http.jira(
|
|
911
|
+
`${API2}/issue/createmeta/${encodeURIComponent(project)}/issuetypes/${it.id}`
|
|
912
|
+
);
|
|
913
|
+
for (const field of fieldList.values) {
|
|
914
|
+
if (!field.allowedValues?.length) continue;
|
|
915
|
+
const existing = result[field.fieldId]?.allowedValues ?? [];
|
|
916
|
+
const seen = new Set(existing.map((v) => v.id));
|
|
917
|
+
for (const av of field.allowedValues) {
|
|
918
|
+
if (!seen.has(av.id)) {
|
|
919
|
+
existing.push(av);
|
|
920
|
+
seen.add(av.id);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
result[field.fieldId] = { allowedValues: existing };
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return result;
|
|
868
927
|
}
|
|
869
928
|
async assignIssue(key, accountId) {
|
|
870
929
|
await this.http.jira(`${API2}/issue/${key}/assignee`, {
|
|
@@ -894,6 +953,43 @@ var JiraClient = class {
|
|
|
894
953
|
});
|
|
895
954
|
}
|
|
896
955
|
};
|
|
956
|
+
function schemaToPncliType(schema) {
|
|
957
|
+
if (!schema) return void 0;
|
|
958
|
+
const custom = schema.custom ?? "";
|
|
959
|
+
if (custom.includes("cascadingselect")) return "cascading-select";
|
|
960
|
+
if (custom.includes("multicheckboxes") || custom.includes("multiselect")) return "multi-select";
|
|
961
|
+
if (custom.includes("select") || custom.includes("radiobuttons")) return "select";
|
|
962
|
+
if (custom.includes("userpicker") || custom.includes("multiuserpicker")) return "user";
|
|
963
|
+
if (custom.includes("labels")) return "labels";
|
|
964
|
+
if (custom.includes("url")) return "url";
|
|
965
|
+
if (custom.includes("textarea") || custom.includes("textfield")) return "textarea";
|
|
966
|
+
if (schema.type === "number") return "number";
|
|
967
|
+
if (schema.type === "date") return "date";
|
|
968
|
+
if (schema.type === "datetime") return "datetime";
|
|
969
|
+
if (schema.type === "string") return "string";
|
|
970
|
+
return void 0;
|
|
971
|
+
}
|
|
972
|
+
function collectMetaFields(projects, issueType) {
|
|
973
|
+
const result = {};
|
|
974
|
+
for (const proj of projects) {
|
|
975
|
+
for (const it of proj.issuetypes) {
|
|
976
|
+
if (issueType && it.name.toLowerCase() !== issueType.toLowerCase()) continue;
|
|
977
|
+
for (const [fieldId, fieldMeta] of Object.entries(it.fields)) {
|
|
978
|
+
if (!fieldMeta.allowedValues?.length) continue;
|
|
979
|
+
const existing = result[fieldId]?.allowedValues ?? [];
|
|
980
|
+
const seen = new Set(existing.map((v) => v.id));
|
|
981
|
+
for (const av of fieldMeta.allowedValues) {
|
|
982
|
+
if (!seen.has(av.id)) {
|
|
983
|
+
existing.push(av);
|
|
984
|
+
seen.add(av.id);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
result[fieldId] = { allowedValues: existing };
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return result;
|
|
992
|
+
}
|
|
897
993
|
|
|
898
994
|
// src/services/jira/custom-fields.ts
|
|
899
995
|
function buildFieldMap(fields) {
|
|
@@ -937,6 +1033,15 @@ function formatFieldValue(value, type) {
|
|
|
937
1033
|
return { value };
|
|
938
1034
|
case "multi-select":
|
|
939
1035
|
return value.split(",").map((v) => ({ value: v.trim() }));
|
|
1036
|
+
case "option-id":
|
|
1037
|
+
return { id: value };
|
|
1038
|
+
case "cascading-select": {
|
|
1039
|
+
const colon = value.indexOf(":");
|
|
1040
|
+
if (colon !== -1) {
|
|
1041
|
+
return { id: value.slice(0, colon).trim(), child: { id: value.slice(colon + 1).trim() } };
|
|
1042
|
+
}
|
|
1043
|
+
return { id: value.trim() };
|
|
1044
|
+
}
|
|
940
1045
|
case "labels":
|
|
941
1046
|
return value.split(",").map((v) => v.trim());
|
|
942
1047
|
case "user":
|
|
@@ -986,41 +1091,41 @@ function registerJiraCommands(program2) {
|
|
|
986
1091
|
fail(err, "jira", "get-issue", start);
|
|
987
1092
|
}
|
|
988
1093
|
});
|
|
989
|
-
jira.command("create-issue").description("Create a Jira issue").option("--project <key>", "Project key").option("--type <type>", "Issue type (Bug, Story, Task, ...)").requiredOption("--summary <text>", "Issue summary").option("--description <text>", "Issue description").option("--priority <name>", "Priority name").option("--assignee <accountId>", "Assignee account ID").option("--labels <labels>", "Comma-separated labels").option("--parent <key>",
|
|
1094
|
+
jira.command("create-issue").description("Create a Jira issue").option("--project <key>", "Project key").option("--type <type>", "Issue type (Bug, Story, Task, ...)").requiredOption("--summary <text>", "Issue summary").option("--description <text>", "Issue description").option("--priority <name>", "Priority name").option("--assignee <accountId>", "Assignee account ID").option("--labels <labels>", "Comma-separated labels").option("--parent <key>", "Parent issue key \u2014 sets fields.parent in the create payload").option("--field <Name=value>", "Custom field value; use Name=@file.json to read value from file (repeatable)", (val, acc) => [...acc, val], []).option("--fields-file <path>", "Path to a JSON file mapping field names/IDs to their Jira API values").action(async (opts) => {
|
|
990
1095
|
const start = Date.now();
|
|
1096
|
+
let fieldMap = buildFieldMap([]);
|
|
991
1097
|
try {
|
|
992
|
-
const
|
|
1098
|
+
const resolved = getClientAndFields(program2);
|
|
1099
|
+
const client = resolved.client;
|
|
1100
|
+
fieldMap = resolved.fieldMap;
|
|
993
1101
|
const defaults = getDefaults(program2);
|
|
994
1102
|
const project = opts.project ?? defaults.project;
|
|
995
1103
|
const issueType = opts.type ?? defaults.issueType ?? "Task";
|
|
996
1104
|
const priority = opts.priority ?? defaults.priority;
|
|
997
1105
|
if (!project) throw new PncliError("--project required (or set defaults.jira.project in config)", 1);
|
|
998
1106
|
const labels = opts.labels ? opts.labels.split(",").map((s) => s.trim()) : void 0;
|
|
999
|
-
const
|
|
1000
|
-
const
|
|
1107
|
+
const fileFields = opts.fieldsFile ? parseFieldsFile(opts.fieldsFile, fieldMap) : {};
|
|
1108
|
+
const customFieldValues = { ...fileFields, ...parseFieldArgs(opts.field, fieldMap) };
|
|
1109
|
+
const data = await client.createIssue({ project, issueType, summary: opts.summary, description: opts.description, priority, assignee: opts.assignee, labels, parent: opts.parent, customFieldValues });
|
|
1001
1110
|
success(data, "jira", "create-issue", start);
|
|
1002
|
-
if (opts.parent) {
|
|
1003
|
-
try {
|
|
1004
|
-
await client.linkIssue({ key: data.key, linkType: "is child of", target: opts.parent });
|
|
1005
|
-
} catch (linkErr) {
|
|
1006
|
-
warn(`Created ${data.key} but failed to link to parent ${opts.parent}: ${linkErr instanceof Error ? linkErr.message : linkErr}`);
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
return;
|
|
1010
1111
|
} catch (err) {
|
|
1011
|
-
fail(err, "jira", "create-issue", start);
|
|
1112
|
+
fail(translateFieldErrors(err, fieldMap), "jira", "create-issue", start);
|
|
1012
1113
|
}
|
|
1013
1114
|
});
|
|
1014
|
-
jira.command("update-issue").description("Update a Jira issue").requiredOption("--key <issue-key>", "Issue key").option("--summary <text>", "New summary").option("--description <text>", "New description").option("--priority <name>", "New priority").option("--assignee <accountId>", "New assignee account ID").option("--labels <labels>", "Comma-separated labels").option("--field <Name=value>", "Custom field value (repeatable)", (val, acc) => [...acc, val], []).action(async (opts) => {
|
|
1115
|
+
jira.command("update-issue").description("Update a Jira issue").requiredOption("--key <issue-key>", "Issue key").option("--summary <text>", "New summary").option("--description <text>", "New description").option("--priority <name>", "New priority").option("--assignee <accountId>", "New assignee account ID").option("--labels <labels>", "Comma-separated labels").option("--field <Name=value>", "Custom field value; use Name=@file.json to read value from file (repeatable)", (val, acc) => [...acc, val], []).option("--fields-file <path>", "Path to a JSON file mapping field names/IDs to their Jira API values").action(async (opts) => {
|
|
1015
1116
|
const start = Date.now();
|
|
1117
|
+
let fieldMap = buildFieldMap([]);
|
|
1016
1118
|
try {
|
|
1017
|
-
const
|
|
1119
|
+
const resolved = getClientAndFields(program2);
|
|
1120
|
+
const client = resolved.client;
|
|
1121
|
+
fieldMap = resolved.fieldMap;
|
|
1018
1122
|
const labels = opts.labels ? opts.labels.split(",").map((s) => s.trim()) : void 0;
|
|
1019
|
-
const
|
|
1123
|
+
const fileFields = opts.fieldsFile ? parseFieldsFile(opts.fieldsFile, fieldMap) : {};
|
|
1124
|
+
const customFieldValues = { ...fileFields, ...parseFieldArgs(opts.field, fieldMap) };
|
|
1020
1125
|
await client.updateIssue(opts.key, { summary: opts.summary, description: opts.description, priority: opts.priority, assignee: opts.assignee, labels, customFieldValues });
|
|
1021
1126
|
success({ updated: opts.key }, "jira", "update-issue", start);
|
|
1022
1127
|
} catch (err) {
|
|
1023
|
-
fail(err, "jira", "update-issue", start);
|
|
1128
|
+
fail(translateFieldErrors(err, fieldMap), "jira", "update-issue", start);
|
|
1024
1129
|
}
|
|
1025
1130
|
});
|
|
1026
1131
|
jira.command("transition-issue").description("Transition a Jira issue to a new status").requiredOption("--key <issue-key>", "Issue key").requiredOption("--transition <name-or-id>", "Transition name or ID").action(async (opts) => {
|
|
@@ -1106,12 +1211,12 @@ function registerJiraCommands(program2) {
|
|
|
1106
1211
|
fail(err, "jira", "link-issue", start);
|
|
1107
1212
|
}
|
|
1108
1213
|
});
|
|
1109
|
-
jira.command("fields").description("List custom fields (configured or discovered from Jira API) (consider --output-file for large results)").option("--discover", "Fetch field metadata from Jira API").option("--custom-only", "Show only custom fields (requires --discover)").action(async (opts) => {
|
|
1214
|
+
jira.command("fields").description("List custom fields (configured or discovered from Jira API) (consider --output-file for large results)").option("--discover", "Fetch field metadata from Jira API").option("--custom-only", "Show only custom fields (requires --discover)").option("--project <key>", "Project key \u2014 fetches allowedValues for select fields via createmeta (requires --discover)").option("--issue-type <type>", "Filter allowedValues to a specific issue type (requires --project)").action(async (opts) => {
|
|
1110
1215
|
const start = Date.now();
|
|
1111
1216
|
try {
|
|
1112
1217
|
if (opts.discover) {
|
|
1113
1218
|
const client = getClient(program2);
|
|
1114
|
-
const fields = await client.fetchFields();
|
|
1219
|
+
const fields = opts.project ? await client.fetchFieldsWithAllowedValues(opts.project, opts.issueType) : await client.fetchFields();
|
|
1115
1220
|
const result = opts.customOnly ? fields.filter((f) => f.custom) : fields;
|
|
1116
1221
|
success(result, "jira", "fields", start);
|
|
1117
1222
|
} else {
|
|
@@ -1130,22 +1235,76 @@ function parseFieldArgs(fieldArgs, fieldMap) {
|
|
|
1130
1235
|
if (eq === -1) throw new PncliError(`Invalid --field format (expected Name=value): ${arg}`, 1);
|
|
1131
1236
|
const name = arg.slice(0, eq).trim();
|
|
1132
1237
|
const value = arg.slice(eq + 1);
|
|
1133
|
-
const def = fieldMap.byName.get(name.toLowerCase());
|
|
1134
|
-
if (!def) throw new PncliError(
|
|
1135
|
-
|
|
1238
|
+
const def = fieldMap.byName.get(name.toLowerCase()) ?? fieldMap.byId.get(name);
|
|
1239
|
+
if (!def) throw new PncliError(
|
|
1240
|
+
`Unknown custom field: "${name}". Fields must be registered in config by friendly name or ID. Run: pncli jira fields`,
|
|
1241
|
+
1
|
|
1242
|
+
);
|
|
1243
|
+
if (value.startsWith("@")) {
|
|
1244
|
+
const filePath = value.slice(1);
|
|
1245
|
+
let raw;
|
|
1246
|
+
try {
|
|
1247
|
+
raw = readFileSync(filePath, "utf8").trim();
|
|
1248
|
+
} catch (e) {
|
|
1249
|
+
throw new PncliError(`Cannot read file "${filePath}": ${e.message}`, 1);
|
|
1250
|
+
}
|
|
1251
|
+
try {
|
|
1252
|
+
result[def.id] = JSON.parse(raw);
|
|
1253
|
+
} catch {
|
|
1254
|
+
result[def.id] = raw;
|
|
1255
|
+
}
|
|
1256
|
+
} else {
|
|
1257
|
+
result[def.id] = formatFieldValue(value, def.type);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
return result;
|
|
1261
|
+
}
|
|
1262
|
+
function parseFieldsFile(filePath, fieldMap) {
|
|
1263
|
+
let raw;
|
|
1264
|
+
try {
|
|
1265
|
+
raw = readFileSync(filePath, "utf8");
|
|
1266
|
+
} catch (e) {
|
|
1267
|
+
throw new PncliError(`Cannot read fields file "${filePath}": ${e.message}`, 1);
|
|
1268
|
+
}
|
|
1269
|
+
let data;
|
|
1270
|
+
try {
|
|
1271
|
+
data = JSON.parse(raw);
|
|
1272
|
+
} catch {
|
|
1273
|
+
throw new PncliError(`Fields file "${filePath}" must be a valid JSON object`, 1);
|
|
1274
|
+
}
|
|
1275
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
1276
|
+
throw new PncliError(`Fields file "${filePath}" must be a JSON object`, 1);
|
|
1277
|
+
}
|
|
1278
|
+
const result = {};
|
|
1279
|
+
for (const [key, val] of Object.entries(data)) {
|
|
1280
|
+
const def = fieldMap.byName.get(key.toLowerCase()) ?? fieldMap.byId.get(key);
|
|
1281
|
+
if (!def) throw new PncliError(
|
|
1282
|
+
`Unknown field in "${filePath}": "${key}". Fields must be registered in config by friendly name or ID. Run: pncli jira fields`,
|
|
1283
|
+
1
|
|
1284
|
+
);
|
|
1285
|
+
result[def.id] = val;
|
|
1136
1286
|
}
|
|
1137
1287
|
return result;
|
|
1138
1288
|
}
|
|
1289
|
+
function translateFieldErrors(err, fieldMap) {
|
|
1290
|
+
if (!(err instanceof PncliError) || fieldMap.byId.size === 0) return err;
|
|
1291
|
+
const translated = err.message.replace(/\bcustomfield_\d+\b/g, (id) => {
|
|
1292
|
+
const def = fieldMap.byId.get(id);
|
|
1293
|
+
return def ? `${def.name} (${id})` : id;
|
|
1294
|
+
});
|
|
1295
|
+
if (translated === err.message) return err;
|
|
1296
|
+
return new PncliError(translated, err.status, err.url);
|
|
1297
|
+
}
|
|
1139
1298
|
|
|
1140
1299
|
// src/services/bitbucket/commands.ts
|
|
1141
|
-
function getClient2(program2) {
|
|
1300
|
+
function getClient2(program2, overrides) {
|
|
1142
1301
|
const opts = program2.optsWithGlobals();
|
|
1143
1302
|
const config = loadConfig({ configPath: opts.config });
|
|
1144
1303
|
const http = createHttpClient(config, Boolean(opts.dryRun));
|
|
1145
1304
|
const client = new BitbucketClient(http);
|
|
1146
1305
|
const ctx = getGitContext(config);
|
|
1147
|
-
const project = opts.project ?? ctx?.project ?? config.defaults.bitbucket?.project ?? "";
|
|
1148
|
-
const repo = opts.repo ?? ctx?.repo ?? config.defaults.bitbucket?.repo ?? "";
|
|
1306
|
+
const project = overrides?.project ?? opts.project ?? ctx?.project ?? config.defaults.bitbucket?.project ?? "";
|
|
1307
|
+
const repo = overrides?.repo ?? opts.repo ?? ctx?.repo ?? config.defaults.bitbucket?.repo ?? "";
|
|
1149
1308
|
if (!project || !repo) {
|
|
1150
1309
|
throw new PncliError(
|
|
1151
1310
|
"Could not determine Bitbucket project/repo. Pass --project and --repo, or run pncli config init.",
|
|
@@ -1176,10 +1335,10 @@ function registerBitbucketCommands(program2) {
|
|
|
1176
1335
|
fail(err, "bitbucket", "get-pr", start);
|
|
1177
1336
|
}
|
|
1178
1337
|
});
|
|
1179
|
-
bb.command("create-pr").description("Create a pull request").requiredOption("--title <title>", "PR title").requiredOption("--source <branch>", "Source branch").option("--target <branch>", "Target branch (defaults to config)").option("--description <desc>", "PR description").option("--reviewers <users>", "Comma-separated reviewer usernames").action(async (opts) => {
|
|
1338
|
+
bb.command("create-pr").description("Create a pull request").requiredOption("--title <title>", "PR title").requiredOption("--source <branch>", "Source branch").option("--target <branch>", "Target branch (defaults to config)").option("--description <desc>", "PR description").option("--reviewers <users>", "Comma-separated reviewer usernames").option("--project <key>", "Bitbucket project key (overrides parent --project; use ~username for personal repos)").option("--repo <slug>", "Bitbucket repository slug (overrides parent --repo)").action(async (opts) => {
|
|
1180
1339
|
const start = Date.now();
|
|
1181
1340
|
try {
|
|
1182
|
-
const { client, project, repo } = getClient2(bb);
|
|
1341
|
+
const { client, project, repo } = getClient2(bb, { project: opts.project, repo: opts.repo });
|
|
1183
1342
|
const config = loadConfig({ configPath: bb.optsWithGlobals().config });
|
|
1184
1343
|
const target = opts.target ?? config.defaults.bitbucket?.targetBranch ?? "main";
|
|
1185
1344
|
const reviewers = opts.reviewers ? opts.reviewers.split(",").map((s) => s.trim()) : [];
|
|
@@ -5452,15 +5611,22 @@ var AdoBuildClient = class {
|
|
|
5452
5611
|
);
|
|
5453
5612
|
}
|
|
5454
5613
|
async listBuilds(collection, project, opts = {}) {
|
|
5614
|
+
const baseParams = {
|
|
5615
|
+
"api-version": API8,
|
|
5616
|
+
...opts.definitionIds?.length ? { definitions: opts.definitionIds.join(",") } : {},
|
|
5617
|
+
...opts.branchName ? { branchName: opts.branchName } : {},
|
|
5618
|
+
...opts.statusFilter ? { statusFilter: opts.statusFilter } : {},
|
|
5619
|
+
...opts.top ? { "$top": opts.top } : {}
|
|
5620
|
+
};
|
|
5621
|
+
if (opts.top) {
|
|
5622
|
+
const { data } = await this.http.adoRaw(
|
|
5623
|
+
`/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/build/builds`,
|
|
5624
|
+
{ params: baseParams }
|
|
5625
|
+
);
|
|
5626
|
+
return (data.value ?? []).slice(0, opts.top);
|
|
5627
|
+
}
|
|
5455
5628
|
return this.http.adoPaginate(async (token) => {
|
|
5456
|
-
const params = {
|
|
5457
|
-
"api-version": API8,
|
|
5458
|
-
...opts.definitionIds?.length ? { definitionIds: opts.definitionIds.join(",") } : {},
|
|
5459
|
-
...opts.branchName ? { branchName: opts.branchName } : {},
|
|
5460
|
-
...opts.statusFilter ? { statusFilter: opts.statusFilter } : {},
|
|
5461
|
-
...opts.top ? { "$top": opts.top } : {},
|
|
5462
|
-
...token ? { continuationToken: token } : {}
|
|
5463
|
-
};
|
|
5629
|
+
const params = { ...baseParams, ...token ? { continuationToken: token } : {} };
|
|
5464
5630
|
return this.http.adoRaw(
|
|
5465
5631
|
`/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/build/builds`,
|
|
5466
5632
|
{ params }
|
|
@@ -6076,7 +6242,7 @@ function registerAdoRepoCommands(ado) {
|
|
|
6076
6242
|
const config = lc({ configPath: globalOpts.config });
|
|
6077
6243
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
6078
6244
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
6079
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
6245
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-5GDQYH62.js");
|
|
6080
6246
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
6081
6247
|
const coreClient = new AdoCoreClient2(http);
|
|
6082
6248
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -6169,12 +6335,32 @@ function registerAdoPipelineCommands(ado) {
|
|
|
6169
6335
|
fail(err, "ado", "pipeline-run", start);
|
|
6170
6336
|
}
|
|
6171
6337
|
});
|
|
6172
|
-
pipeline.command("list-runs").description("List pipeline runs").option("--definition <id>", "Filter by definition ID").option("--branch <ref>", "Filter by branch name").option("--status <filter>", "Filter by status (inProgress|completed|cancelling|...)").option("--top <n>", "Maximum results", "50").action(async (opts) => {
|
|
6338
|
+
pipeline.command("list-runs").description("List pipeline runs").option("--definition <id>", "Filter by definition ID").option("--name <name>", "Filter by pipeline name (resolved to definition ID; use pipeline list to see names)").option("--branch <ref>", "Filter by branch name").option("--status <filter>", "Filter by status (inProgress|completed|cancelling|...)").option("--top <n>", "Maximum results", "50").action(async (opts) => {
|
|
6173
6339
|
const start = Date.now();
|
|
6174
6340
|
try {
|
|
6175
6341
|
const { collection, project, buildClient } = getAdoContext(ado);
|
|
6342
|
+
let definitionId;
|
|
6343
|
+
if (opts.definition && opts.name) {
|
|
6344
|
+
throw new PncliError("Cannot specify both --definition and --name. Use one or the other.", 1);
|
|
6345
|
+
}
|
|
6346
|
+
if (opts.definition) {
|
|
6347
|
+
definitionId = parseInt(opts.definition, 10);
|
|
6348
|
+
if (isNaN(definitionId)) {
|
|
6349
|
+
throw new PncliError(`--definition must be a numeric ID. Got "${opts.definition}". Use --name to filter by pipeline name instead.`, 1);
|
|
6350
|
+
}
|
|
6351
|
+
} else if (opts.name) {
|
|
6352
|
+
const defs = await buildClient.listDefinitions(collection, project);
|
|
6353
|
+
const match = defs.find((d) => d.name.toLowerCase() === opts.name.toLowerCase());
|
|
6354
|
+
if (!match) {
|
|
6355
|
+
throw new PncliError(
|
|
6356
|
+
`Pipeline "${opts.name}" not found. Run 'pncli ado pipeline list' to see available pipelines.`,
|
|
6357
|
+
1
|
|
6358
|
+
);
|
|
6359
|
+
}
|
|
6360
|
+
definitionId = match.id;
|
|
6361
|
+
}
|
|
6176
6362
|
const data = await buildClient.listBuilds(collection, project, {
|
|
6177
|
-
definitionIds:
|
|
6363
|
+
definitionIds: definitionId !== void 0 ? [definitionId] : void 0,
|
|
6178
6364
|
branchName: opts.branch,
|
|
6179
6365
|
statusFilter: opts.status,
|
|
6180
6366
|
top: parseInt(opts.top, 10)
|
|
@@ -6204,11 +6390,13 @@ function registerAdoPipelineCommands(ado) {
|
|
|
6204
6390
|
fail(err, "ado", "pipeline-cancel-run", start);
|
|
6205
6391
|
}
|
|
6206
6392
|
});
|
|
6207
|
-
pipeline.command("logs").description("List or retrieve build logs (consider --output-file for large logs)").
|
|
6393
|
+
pipeline.command("logs").description("List or retrieve build logs (consider --output-file for large logs)").option("--build-id <n>", "Build (run) ID \u2014 from pipeline list-runs or get-run").option("--id <n>", "Build (run) ID \u2014 alias for --build-id (kept for backwards compatibility)").option("--log-id <n>", "Specific log ID (omit to list all logs)").action(async (opts) => {
|
|
6208
6394
|
const start = Date.now();
|
|
6209
6395
|
try {
|
|
6396
|
+
const rawId = opts.buildId ?? opts.id;
|
|
6397
|
+
if (!rawId) throw new PncliError("--build-id is required (build run ID from pipeline list-runs or get-run)", 1);
|
|
6210
6398
|
const { collection, project, buildClient } = getAdoContext(ado);
|
|
6211
|
-
const buildId = parseInt(
|
|
6399
|
+
const buildId = parseInt(rawId, 10);
|
|
6212
6400
|
if (opts.logId) {
|
|
6213
6401
|
const data = await buildClient.getLog(collection, project, buildId, parseInt(opts.logId, 10));
|
|
6214
6402
|
success({ log: data }, "ado", "pipeline-logs", start);
|
|
@@ -6260,9 +6448,10 @@ var JenkinsClient = class {
|
|
|
6260
6448
|
this.http = http;
|
|
6261
6449
|
}
|
|
6262
6450
|
http;
|
|
6263
|
-
async listJobs() {
|
|
6451
|
+
async listJobs(folder) {
|
|
6452
|
+
const endpoint = folder ? `/job/${jobPath(folder)}/api/json` : "/api/json";
|
|
6264
6453
|
const data = await this.http.jenkins(
|
|
6265
|
-
|
|
6454
|
+
endpoint,
|
|
6266
6455
|
{ params: { tree: "jobs[name,url,color,description,buildable,lastBuild[number,url,result]]" } }
|
|
6267
6456
|
);
|
|
6268
6457
|
return data.jobs;
|
|
@@ -6342,17 +6531,17 @@ async function pollBuildComplete(client, jobName, buildNumber, pollMs, timeoutMs
|
|
|
6342
6531
|
function registerJenkinsCommands(program2) {
|
|
6343
6532
|
const jenkins = program2.command("jenkins").description("Jenkins Data Center operations");
|
|
6344
6533
|
const pipeline = jenkins.command("pipeline").description("Pipeline (job/build) operations");
|
|
6345
|
-
pipeline.command("list").description("List all jobs on the Jenkins instance").action(async () => {
|
|
6534
|
+
pipeline.command("list").description("List all jobs on the Jenkins instance (or within a folder)").option("--folder <name>", "Folder name to enumerate (supports nested folders: parentFolder/childFolder)").action(async (opts) => {
|
|
6346
6535
|
const start = Date.now();
|
|
6347
6536
|
try {
|
|
6348
6537
|
const client = getClient6(program2);
|
|
6349
|
-
const data = await client.listJobs();
|
|
6538
|
+
const data = await client.listJobs(opts.folder);
|
|
6350
6539
|
success(data, "jenkins", "pipeline-list", start);
|
|
6351
6540
|
} catch (err) {
|
|
6352
6541
|
fail(err, "jenkins", "pipeline-list", start);
|
|
6353
6542
|
}
|
|
6354
6543
|
});
|
|
6355
|
-
pipeline.command("get").description("Get details for a specific job").requiredOption("--name <job>", "Job name").action(async (opts) => {
|
|
6544
|
+
pipeline.command("get").description("Get details for a specific job").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").action(async (opts) => {
|
|
6356
6545
|
const start = Date.now();
|
|
6357
6546
|
try {
|
|
6358
6547
|
const client = getClient6(program2);
|
|
@@ -6362,7 +6551,7 @@ function registerJenkinsCommands(program2) {
|
|
|
6362
6551
|
fail(err, "jenkins", "pipeline-get", start);
|
|
6363
6552
|
}
|
|
6364
6553
|
});
|
|
6365
|
-
pipeline.command("run").description("Trigger a build").requiredOption("--name <job>", "Job name").option("--parameter <k=v>", "Build parameter (repeatable)", (v, acc) => {
|
|
6554
|
+
pipeline.command("run").description("Trigger a build").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").option("--parameter <k=v>", "Build parameter (repeatable)", (v, acc) => {
|
|
6366
6555
|
acc.push(v);
|
|
6367
6556
|
return acc;
|
|
6368
6557
|
}, []).option("--wait", "Wait for the build to complete before returning").option("--timeout <s>", "Max wait time in seconds per phase (queue-wait and build-wait each get this budget; default 600)", "600").option("--poll <s>", "Poll interval in seconds (default 10)", "10").action(async (opts) => {
|
|
@@ -6398,7 +6587,7 @@ function registerJenkinsCommands(program2) {
|
|
|
6398
6587
|
fail(err, "jenkins", "pipeline-run", start);
|
|
6399
6588
|
}
|
|
6400
6589
|
});
|
|
6401
|
-
pipeline.command("list-runs").description("List recent builds for a job").requiredOption("--name <job>", "Job name").option("--top <n>", "Maximum number of builds to return (default 25)", "25").action(async (opts) => {
|
|
6590
|
+
pipeline.command("list-runs").description("List recent builds for a job").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").option("--top <n>", "Maximum number of builds to return (default 25)", "25").action(async (opts) => {
|
|
6402
6591
|
const start = Date.now();
|
|
6403
6592
|
try {
|
|
6404
6593
|
const client = getClient6(program2);
|
|
@@ -6410,7 +6599,7 @@ function registerJenkinsCommands(program2) {
|
|
|
6410
6599
|
fail(err, "jenkins", "pipeline-list-runs", start);
|
|
6411
6600
|
}
|
|
6412
6601
|
});
|
|
6413
|
-
pipeline.command("get-run").description("Get details for a specific build").requiredOption("--name <job>", "Job name").requiredOption("--number <n>", "Build number").action(async (opts) => {
|
|
6602
|
+
pipeline.command("get-run").description("Get details for a specific build").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").requiredOption("--number <n>", "Build number").action(async (opts) => {
|
|
6414
6603
|
const start = Date.now();
|
|
6415
6604
|
try {
|
|
6416
6605
|
const client = getClient6(program2);
|
|
@@ -6422,7 +6611,7 @@ function registerJenkinsCommands(program2) {
|
|
|
6422
6611
|
fail(err, "jenkins", "pipeline-get-run", start);
|
|
6423
6612
|
}
|
|
6424
6613
|
});
|
|
6425
|
-
pipeline.command("logs").description("Fetch console log for a build (consider --output-file for large logs)").requiredOption("--name <job>", "Job name").requiredOption("--number <n>", "Build number").action(async (opts) => {
|
|
6614
|
+
pipeline.command("logs").description("Fetch console log for a build (consider --output-file for large logs)").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").requiredOption("--number <n>", "Build number").action(async (opts) => {
|
|
6426
6615
|
const start = Date.now();
|
|
6427
6616
|
try {
|
|
6428
6617
|
const client = getClient6(program2);
|
|
@@ -6493,8 +6682,18 @@ var ArtifactoryClient = class {
|
|
|
6493
6682
|
headers: { "Content-Type": "text/plain" }
|
|
6494
6683
|
});
|
|
6495
6684
|
}
|
|
6496
|
-
async listBuilds() {
|
|
6497
|
-
|
|
6685
|
+
async listBuilds(opts = {}) {
|
|
6686
|
+
const raw = await this.http.artifactory(`${API9}/build`, { timeoutMs: opts.timeoutMs });
|
|
6687
|
+
const builds = (raw.builds ?? []).map((b) => ({
|
|
6688
|
+
name: b.uri.replace(/^\/|\/$/g, ""),
|
|
6689
|
+
uri: b.uri,
|
|
6690
|
+
lastStarted: b.lastStarted
|
|
6691
|
+
}));
|
|
6692
|
+
const result = { builds, uri: raw.uri };
|
|
6693
|
+
if (builds.length === 0) {
|
|
6694
|
+
result.hint = 'No builds are registered in Artifactory Build Integration. Build names may differ from ADO pipeline or uDeploy component names. Use "artifactory repos" to list repositories, then "artifactory search --repo <repo>" to locate artifacts directly.';
|
|
6695
|
+
}
|
|
6696
|
+
return result;
|
|
6498
6697
|
}
|
|
6499
6698
|
async listBuildRuns(buildName) {
|
|
6500
6699
|
return this.http.artifactory(`${API9}/build/${encodeURIComponent(buildName)}`);
|
|
@@ -6595,11 +6794,14 @@ function registerArtifactoryCommands(program2) {
|
|
|
6595
6794
|
fail(err, "artifactory", "search", start);
|
|
6596
6795
|
}
|
|
6597
6796
|
});
|
|
6598
|
-
art.command("builds").description(
|
|
6797
|
+
art.command("builds").description('List builds registered in Artifactory Build Integration (names may differ from ADO pipeline or uDeploy component names; use "search --repo" when empty)').option("--timeout <s>", "Request timeout in seconds (default 60; increase for large Artifactory instances)", "60").action(async (opts) => {
|
|
6599
6798
|
const start = Date.now();
|
|
6600
6799
|
try {
|
|
6601
6800
|
const client = getClient7(program2);
|
|
6602
|
-
const
|
|
6801
|
+
const timeoutSecs = parseInt(opts.timeout, 10);
|
|
6802
|
+
if (isNaN(timeoutSecs) || timeoutSecs <= 0) throw new PncliError("--timeout must be a positive integer (seconds)", 1);
|
|
6803
|
+
const timeoutMs = timeoutSecs * 1e3;
|
|
6804
|
+
const data = await client.listBuilds({ timeoutMs });
|
|
6603
6805
|
success(data, "artifactory", "builds", start);
|
|
6604
6806
|
} catch (err) {
|
|
6605
6807
|
fail(err, "artifactory", "builds", start);
|
|
@@ -7193,10 +7395,22 @@ function injectTokenIntoUrl(url, token) {
|
|
|
7193
7395
|
} catch {
|
|
7194
7396
|
throw new Error(`--token requires an HTTPS clone URL; got: ${url}`);
|
|
7195
7397
|
}
|
|
7196
|
-
parsed.username = "x-token-auth";
|
|
7398
|
+
parsed.username = parsed.hostname === "github.com" ? "x-access-token" : "x-token-auth";
|
|
7197
7399
|
parsed.password = token;
|
|
7198
7400
|
return parsed.toString();
|
|
7199
7401
|
}
|
|
7402
|
+
function repoNameFromUrl(url) {
|
|
7403
|
+
try {
|
|
7404
|
+
const parsed = new URL(url);
|
|
7405
|
+
const segment = parsed.pathname.split("/").filter(Boolean).pop() ?? "marketplace";
|
|
7406
|
+
return segment.replace(/\.git$/i, "") || "marketplace";
|
|
7407
|
+
} catch {
|
|
7408
|
+
return "marketplace";
|
|
7409
|
+
}
|
|
7410
|
+
}
|
|
7411
|
+
function defaultMarketplacePath(url) {
|
|
7412
|
+
return path5.join(os.homedir(), ".agents", "marketplaces", repoNameFromUrl(url));
|
|
7413
|
+
}
|
|
7200
7414
|
var AGENT_PATHS = {
|
|
7201
7415
|
"github-copilot": { project: ".agents/skills", user: path5.join(os.homedir(), ".copilot/skills") },
|
|
7202
7416
|
"claude-code": { project: ".claude/skills", user: path5.join(os.homedir(), ".claude/skills") }
|
|
@@ -7346,10 +7560,10 @@ function registerSkillsCommands(program2) {
|
|
|
7346
7560
|
}
|
|
7347
7561
|
});
|
|
7348
7562
|
const marketplace = skills.command("marketplace").description("Manage a git-hosted skills marketplace");
|
|
7349
|
-
marketplace.command("setup").description("Clone a skills marketplace repo and register it in global config").argument("<url>", "Git clone URL of the marketplace repository").argument("
|
|
7563
|
+
marketplace.command("setup").description("Clone a skills marketplace repo and register it in global config").argument("<url>", "Git clone URL of the marketplace repository").argument("[localPath]", "Local directory to clone into (default: ~/.agents/marketplaces/<repo-name>)").option("--branch <branch>", "Branch to clone (default: remote HEAD)").option("--token <token>", "HTTP access token for authenticated clone and pull (GitHub PAT or Bitbucket token)").action(async (url, localPath, opts) => {
|
|
7350
7564
|
const start = Date.now();
|
|
7351
7565
|
try {
|
|
7352
|
-
const resolvedPath = path5.resolve(localPath);
|
|
7566
|
+
const resolvedPath = path5.resolve(localPath ?? defaultMarketplacePath(url));
|
|
7353
7567
|
const hasGit = fs3.existsSync(path5.join(resolvedPath, ".git"));
|
|
7354
7568
|
if (fs3.existsSync(resolvedPath) && !hasGit && fs3.readdirSync(resolvedPath).length > 0) {
|
|
7355
7569
|
throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
|
|
@@ -7357,25 +7571,29 @@ function registerSkillsCommands(program2) {
|
|
|
7357
7571
|
if (hasGit) {
|
|
7358
7572
|
warn(`Directory already contains a git repo at ${resolvedPath} \u2014 skipping clone, updating config only.`);
|
|
7359
7573
|
} else {
|
|
7360
|
-
|
|
7574
|
+
const branchLabel = opts.branch ?? "remote default";
|
|
7575
|
+
warn(`Cloning ${url} (branch: ${branchLabel}) \u2192 ${resolvedPath}...`);
|
|
7361
7576
|
const cloneUrl = opts.token ? injectTokenIntoUrl(url, opts.token) : url;
|
|
7577
|
+
const cloneArgs = ["clone"];
|
|
7578
|
+
if (opts.branch) cloneArgs.push("--branch", opts.branch);
|
|
7579
|
+
cloneArgs.push(cloneUrl, resolvedPath);
|
|
7362
7580
|
try {
|
|
7363
|
-
execFileSync3("git",
|
|
7581
|
+
execFileSync3("git", cloneArgs, { stdio: ["inherit", "inherit", "pipe"] });
|
|
7364
7582
|
} catch (e) {
|
|
7365
7583
|
const msg = e instanceof Error ? e.message : String(e);
|
|
7366
|
-
throw new Error(msg.replace(/x-token-auth:[^@]+@/g, "x-token-auth:***@"));
|
|
7584
|
+
throw new Error(msg.replace(/x-(?:token-auth|access-token):[^@]+@/g, "x-token-auth:***@"));
|
|
7367
7585
|
}
|
|
7368
7586
|
}
|
|
7369
7587
|
const configPath = getGlobalConfigPath();
|
|
7370
7588
|
const existing = loadJsonFile(configPath) ?? {};
|
|
7371
7589
|
existing.marketplace = { repoUrl: url, localPath: resolvedPath, ...opts.token ? { token: opts.token } : {} };
|
|
7372
7590
|
writeGlobalConfig(existing, configPath);
|
|
7373
|
-
success({ repoUrl: url, localPath: resolvedPath, branch: opts.branch, tokenConfigured: !!opts.token }, "skills", "marketplace-setup", start);
|
|
7591
|
+
success({ repoUrl: url, localPath: resolvedPath, branch: opts.branch ?? null, tokenConfigured: !!opts.token }, "skills", "marketplace-setup", start);
|
|
7374
7592
|
} catch (err) {
|
|
7375
7593
|
fail(err, "skills", "marketplace-setup", start);
|
|
7376
7594
|
}
|
|
7377
7595
|
});
|
|
7378
|
-
marketplace.command("sync").description("Pull latest marketplace content and install a plugin's skills").argument("[plugin]",
|
|
7596
|
+
marketplace.command("sync").description("Pull latest marketplace content and install a plugin's skills").argument("[plugin]", 'Plugin name to install, or "all" to install every plugin (skips interactive selection)').option("--claude", "Install to ~/.claude/skills instead of ~/.agents/skills").option("--force", "Force reinstall even if the marketplace repo has no new changes").action(async (plugin, opts) => {
|
|
7379
7597
|
const start = Date.now();
|
|
7380
7598
|
try {
|
|
7381
7599
|
const configPath = getGlobalConfigPath();
|
|
@@ -7392,16 +7610,58 @@ function registerSkillsCommands(program2) {
|
|
|
7392
7610
|
gitArgs.push("-c", `remote.origin.url=${injectTokenIntoUrl(repoUrl, token)}`);
|
|
7393
7611
|
}
|
|
7394
7612
|
gitArgs.push("pull");
|
|
7613
|
+
let pullOutput;
|
|
7395
7614
|
try {
|
|
7396
|
-
execFileSync3("git", gitArgs, { stdio: ["inherit", "
|
|
7615
|
+
pullOutput = execFileSync3("git", gitArgs, { encoding: "utf8", stdio: ["inherit", "pipe", "pipe"], env: { ...process.env, LANG: "C", LC_ALL: "C" } });
|
|
7397
7616
|
} catch (e) {
|
|
7398
7617
|
const msg = e instanceof Error ? e.message : String(e);
|
|
7399
|
-
throw new Error(msg.replace(/x-token-auth:[^@]+@/g, "x-token-auth:***@"));
|
|
7618
|
+
throw new Error(msg.replace(/x-(?:token-auth|access-token):[^@]+@/g, "x-token-auth:***@"));
|
|
7619
|
+
}
|
|
7620
|
+
const marketplaceUpdated = !pullOutput.includes("Already up to date");
|
|
7621
|
+
if (pullOutput.trim() && marketplaceUpdated) {
|
|
7622
|
+
warn(pullOutput.trim());
|
|
7623
|
+
}
|
|
7624
|
+
warn(marketplaceUpdated ? "Marketplace updated." : "Marketplace already up to date \u2014 no changes to sync.");
|
|
7625
|
+
if (!marketplaceUpdated && !plugin && !opts.force) {
|
|
7626
|
+
success({
|
|
7627
|
+
marketplace: marketplacePath,
|
|
7628
|
+
marketplaceUpdated: false,
|
|
7629
|
+
updated: false,
|
|
7630
|
+
skipped: true,
|
|
7631
|
+
message: "No marketplace changes detected \u2014 skipping plugin selection and install."
|
|
7632
|
+
}, "skills", "marketplace-sync", start);
|
|
7633
|
+
return;
|
|
7400
7634
|
}
|
|
7401
7635
|
const pluginChoices = resolvePluginChoices(marketplacePath);
|
|
7402
7636
|
if (pluginChoices.length === 0) {
|
|
7403
7637
|
throw new Error("No plugins found in marketplace. Check the marketplace repository structure.");
|
|
7404
7638
|
}
|
|
7639
|
+
const targetDir = opts.claude ? path5.join(os.homedir(), ".claude", "skills") : path5.join(os.homedir(), ".agents", "skills");
|
|
7640
|
+
if (plugin === "all") {
|
|
7641
|
+
const results = {};
|
|
7642
|
+
let totalInstalled = 0;
|
|
7643
|
+
for (const pluginChoice of pluginChoices) {
|
|
7644
|
+
const skillsSrc2 = resolveSkillsSrc(marketplacePath, pluginChoice.name);
|
|
7645
|
+
if (!fs3.existsSync(skillsSrc2)) {
|
|
7646
|
+
results[pluginChoice.name] = { installed: [], failed: [] };
|
|
7647
|
+
warn(`No skills directory found for plugin "${pluginChoice.name}" \u2014 skipping.`);
|
|
7648
|
+
continue;
|
|
7649
|
+
}
|
|
7650
|
+
const { installed: installed2, failed: failed2 } = copyPluginSkills(skillsSrc2, targetDir);
|
|
7651
|
+
results[pluginChoice.name] = { installed: installed2, failed: failed2 };
|
|
7652
|
+
totalInstalled += installed2.length;
|
|
7653
|
+
if (failed2.length > 0) {
|
|
7654
|
+
warn(`Skipped ${failed2.length} skill(s) with invalid names in "${pluginChoice.name}": ${failed2.join(", ")}`);
|
|
7655
|
+
}
|
|
7656
|
+
}
|
|
7657
|
+
success({
|
|
7658
|
+
plugins: results,
|
|
7659
|
+
total: totalInstalled,
|
|
7660
|
+
target: targetDir,
|
|
7661
|
+
marketplaceUpdated
|
|
7662
|
+
}, "skills", "marketplace-sync", start);
|
|
7663
|
+
return;
|
|
7664
|
+
}
|
|
7405
7665
|
let selectedPlugin;
|
|
7406
7666
|
if (plugin) {
|
|
7407
7667
|
if (!pluginChoices.some((p) => p.name === plugin)) {
|
|
@@ -7421,7 +7681,6 @@ function registerSkillsCommands(program2) {
|
|
|
7421
7681
|
if (!fs3.existsSync(skillsSrc)) {
|
|
7422
7682
|
throw new Error(`No skills directory found at ${skillsSrc}`);
|
|
7423
7683
|
}
|
|
7424
|
-
const targetDir = opts.claude ? path5.join(os.homedir(), ".claude", "skills") : path5.join(os.homedir(), ".agents", "skills");
|
|
7425
7684
|
const { installed, failed } = copyPluginSkills(skillsSrc, targetDir);
|
|
7426
7685
|
if (failed.length > 0) {
|
|
7427
7686
|
warn(`Skipped ${failed.length} skill(s) with invalid names: ${failed.join(", ")}`);
|
|
@@ -7431,7 +7690,8 @@ function registerSkillsCommands(program2) {
|
|
|
7431
7690
|
installed,
|
|
7432
7691
|
failed,
|
|
7433
7692
|
total: installed.length,
|
|
7434
|
-
target: targetDir
|
|
7693
|
+
target: targetDir,
|
|
7694
|
+
marketplaceUpdated
|
|
7435
7695
|
}, "skills", "marketplace-sync", start);
|
|
7436
7696
|
} catch (err) {
|
|
7437
7697
|
fail(err, "skills", "marketplace-sync", start);
|