@kolatts/pncli 1.11.0 → 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 +336 -74
- package/dist/cli.js.map +1 -1
- package/dist/{http-LNDRNPP7.js → http-5GDQYH62.js} +2 -2
- package/package.json +5 -2
- /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()) : [];
|
|
@@ -4037,7 +4196,9 @@ function registerDepsCommands(program2) {
|
|
|
4037
4196
|
}
|
|
4038
4197
|
|
|
4039
4198
|
// src/services/config/commands.ts
|
|
4040
|
-
import
|
|
4199
|
+
import input from "@inquirer/input";
|
|
4200
|
+
import password from "@inquirer/password";
|
|
4201
|
+
import confirm from "@inquirer/confirm";
|
|
4041
4202
|
|
|
4042
4203
|
// src/services/ado/client/work.ts
|
|
4043
4204
|
var API6 = "7.1";
|
|
@@ -5450,15 +5611,22 @@ var AdoBuildClient = class {
|
|
|
5450
5611
|
);
|
|
5451
5612
|
}
|
|
5452
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
|
+
}
|
|
5453
5628
|
return this.http.adoPaginate(async (token) => {
|
|
5454
|
-
const params = {
|
|
5455
|
-
"api-version": API8,
|
|
5456
|
-
...opts.definitionIds?.length ? { definitionIds: opts.definitionIds.join(",") } : {},
|
|
5457
|
-
...opts.branchName ? { branchName: opts.branchName } : {},
|
|
5458
|
-
...opts.statusFilter ? { statusFilter: opts.statusFilter } : {},
|
|
5459
|
-
...opts.top ? { "$top": opts.top } : {},
|
|
5460
|
-
...token ? { continuationToken: token } : {}
|
|
5461
|
-
};
|
|
5629
|
+
const params = { ...baseParams, ...token ? { continuationToken: token } : {} };
|
|
5462
5630
|
return this.http.adoRaw(
|
|
5463
5631
|
`/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/build/builds`,
|
|
5464
5632
|
{ params }
|
|
@@ -6074,7 +6242,7 @@ function registerAdoRepoCommands(ado) {
|
|
|
6074
6242
|
const config = lc({ configPath: globalOpts.config });
|
|
6075
6243
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
6076
6244
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
6077
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
6245
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-5GDQYH62.js");
|
|
6078
6246
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
6079
6247
|
const coreClient = new AdoCoreClient2(http);
|
|
6080
6248
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -6167,12 +6335,32 @@ function registerAdoPipelineCommands(ado) {
|
|
|
6167
6335
|
fail(err, "ado", "pipeline-run", start);
|
|
6168
6336
|
}
|
|
6169
6337
|
});
|
|
6170
|
-
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) => {
|
|
6171
6339
|
const start = Date.now();
|
|
6172
6340
|
try {
|
|
6173
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
|
+
}
|
|
6174
6362
|
const data = await buildClient.listBuilds(collection, project, {
|
|
6175
|
-
definitionIds:
|
|
6363
|
+
definitionIds: definitionId !== void 0 ? [definitionId] : void 0,
|
|
6176
6364
|
branchName: opts.branch,
|
|
6177
6365
|
statusFilter: opts.status,
|
|
6178
6366
|
top: parseInt(opts.top, 10)
|
|
@@ -6202,11 +6390,13 @@ function registerAdoPipelineCommands(ado) {
|
|
|
6202
6390
|
fail(err, "ado", "pipeline-cancel-run", start);
|
|
6203
6391
|
}
|
|
6204
6392
|
});
|
|
6205
|
-
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) => {
|
|
6206
6394
|
const start = Date.now();
|
|
6207
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);
|
|
6208
6398
|
const { collection, project, buildClient } = getAdoContext(ado);
|
|
6209
|
-
const buildId = parseInt(
|
|
6399
|
+
const buildId = parseInt(rawId, 10);
|
|
6210
6400
|
if (opts.logId) {
|
|
6211
6401
|
const data = await buildClient.getLog(collection, project, buildId, parseInt(opts.logId, 10));
|
|
6212
6402
|
success({ log: data }, "ado", "pipeline-logs", start);
|
|
@@ -6258,9 +6448,10 @@ var JenkinsClient = class {
|
|
|
6258
6448
|
this.http = http;
|
|
6259
6449
|
}
|
|
6260
6450
|
http;
|
|
6261
|
-
async listJobs() {
|
|
6451
|
+
async listJobs(folder) {
|
|
6452
|
+
const endpoint = folder ? `/job/${jobPath(folder)}/api/json` : "/api/json";
|
|
6262
6453
|
const data = await this.http.jenkins(
|
|
6263
|
-
|
|
6454
|
+
endpoint,
|
|
6264
6455
|
{ params: { tree: "jobs[name,url,color,description,buildable,lastBuild[number,url,result]]" } }
|
|
6265
6456
|
);
|
|
6266
6457
|
return data.jobs;
|
|
@@ -6340,17 +6531,17 @@ async function pollBuildComplete(client, jobName, buildNumber, pollMs, timeoutMs
|
|
|
6340
6531
|
function registerJenkinsCommands(program2) {
|
|
6341
6532
|
const jenkins = program2.command("jenkins").description("Jenkins Data Center operations");
|
|
6342
6533
|
const pipeline = jenkins.command("pipeline").description("Pipeline (job/build) operations");
|
|
6343
|
-
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) => {
|
|
6344
6535
|
const start = Date.now();
|
|
6345
6536
|
try {
|
|
6346
6537
|
const client = getClient6(program2);
|
|
6347
|
-
const data = await client.listJobs();
|
|
6538
|
+
const data = await client.listJobs(opts.folder);
|
|
6348
6539
|
success(data, "jenkins", "pipeline-list", start);
|
|
6349
6540
|
} catch (err) {
|
|
6350
6541
|
fail(err, "jenkins", "pipeline-list", start);
|
|
6351
6542
|
}
|
|
6352
6543
|
});
|
|
6353
|
-
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) => {
|
|
6354
6545
|
const start = Date.now();
|
|
6355
6546
|
try {
|
|
6356
6547
|
const client = getClient6(program2);
|
|
@@ -6360,7 +6551,7 @@ function registerJenkinsCommands(program2) {
|
|
|
6360
6551
|
fail(err, "jenkins", "pipeline-get", start);
|
|
6361
6552
|
}
|
|
6362
6553
|
});
|
|
6363
|
-
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) => {
|
|
6364
6555
|
acc.push(v);
|
|
6365
6556
|
return acc;
|
|
6366
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) => {
|
|
@@ -6396,7 +6587,7 @@ function registerJenkinsCommands(program2) {
|
|
|
6396
6587
|
fail(err, "jenkins", "pipeline-run", start);
|
|
6397
6588
|
}
|
|
6398
6589
|
});
|
|
6399
|
-
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) => {
|
|
6400
6591
|
const start = Date.now();
|
|
6401
6592
|
try {
|
|
6402
6593
|
const client = getClient6(program2);
|
|
@@ -6408,7 +6599,7 @@ function registerJenkinsCommands(program2) {
|
|
|
6408
6599
|
fail(err, "jenkins", "pipeline-list-runs", start);
|
|
6409
6600
|
}
|
|
6410
6601
|
});
|
|
6411
|
-
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) => {
|
|
6412
6603
|
const start = Date.now();
|
|
6413
6604
|
try {
|
|
6414
6605
|
const client = getClient6(program2);
|
|
@@ -6420,7 +6611,7 @@ function registerJenkinsCommands(program2) {
|
|
|
6420
6611
|
fail(err, "jenkins", "pipeline-get-run", start);
|
|
6421
6612
|
}
|
|
6422
6613
|
});
|
|
6423
|
-
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) => {
|
|
6424
6615
|
const start = Date.now();
|
|
6425
6616
|
try {
|
|
6426
6617
|
const client = getClient6(program2);
|
|
@@ -6491,8 +6682,18 @@ var ArtifactoryClient = class {
|
|
|
6491
6682
|
headers: { "Content-Type": "text/plain" }
|
|
6492
6683
|
});
|
|
6493
6684
|
}
|
|
6494
|
-
async listBuilds() {
|
|
6495
|
-
|
|
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;
|
|
6496
6697
|
}
|
|
6497
6698
|
async listBuildRuns(buildName) {
|
|
6498
6699
|
return this.http.artifactory(`${API9}/build/${encodeURIComponent(buildName)}`);
|
|
@@ -6593,11 +6794,14 @@ function registerArtifactoryCommands(program2) {
|
|
|
6593
6794
|
fail(err, "artifactory", "search", start);
|
|
6594
6795
|
}
|
|
6595
6796
|
});
|
|
6596
|
-
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) => {
|
|
6597
6798
|
const start = Date.now();
|
|
6598
6799
|
try {
|
|
6599
6800
|
const client = getClient7(program2);
|
|
6600
|
-
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 });
|
|
6601
6805
|
success(data, "artifactory", "builds", start);
|
|
6602
6806
|
} catch (err) {
|
|
6603
6807
|
fail(err, "artifactory", "builds", start);
|
|
@@ -7183,7 +7387,7 @@ import path5 from "path";
|
|
|
7183
7387
|
import os from "os";
|
|
7184
7388
|
import { fileURLToPath } from "url";
|
|
7185
7389
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
7186
|
-
import
|
|
7390
|
+
import select from "@inquirer/select";
|
|
7187
7391
|
function injectTokenIntoUrl(url, token) {
|
|
7188
7392
|
let parsed;
|
|
7189
7393
|
try {
|
|
@@ -7191,10 +7395,22 @@ function injectTokenIntoUrl(url, token) {
|
|
|
7191
7395
|
} catch {
|
|
7192
7396
|
throw new Error(`--token requires an HTTPS clone URL; got: ${url}`);
|
|
7193
7397
|
}
|
|
7194
|
-
parsed.username = "x-token-auth";
|
|
7398
|
+
parsed.username = parsed.hostname === "github.com" ? "x-access-token" : "x-token-auth";
|
|
7195
7399
|
parsed.password = token;
|
|
7196
7400
|
return parsed.toString();
|
|
7197
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
|
+
}
|
|
7198
7414
|
var AGENT_PATHS = {
|
|
7199
7415
|
"github-copilot": { project: ".agents/skills", user: path5.join(os.homedir(), ".copilot/skills") },
|
|
7200
7416
|
"claude-code": { project: ".claude/skills", user: path5.join(os.homedir(), ".claude/skills") }
|
|
@@ -7344,10 +7560,10 @@ function registerSkillsCommands(program2) {
|
|
|
7344
7560
|
}
|
|
7345
7561
|
});
|
|
7346
7562
|
const marketplace = skills.command("marketplace").description("Manage a git-hosted skills marketplace");
|
|
7347
|
-
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) => {
|
|
7348
7564
|
const start = Date.now();
|
|
7349
7565
|
try {
|
|
7350
|
-
const resolvedPath = path5.resolve(localPath);
|
|
7566
|
+
const resolvedPath = path5.resolve(localPath ?? defaultMarketplacePath(url));
|
|
7351
7567
|
const hasGit = fs3.existsSync(path5.join(resolvedPath, ".git"));
|
|
7352
7568
|
if (fs3.existsSync(resolvedPath) && !hasGit && fs3.readdirSync(resolvedPath).length > 0) {
|
|
7353
7569
|
throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
|
|
@@ -7355,25 +7571,29 @@ function registerSkillsCommands(program2) {
|
|
|
7355
7571
|
if (hasGit) {
|
|
7356
7572
|
warn(`Directory already contains a git repo at ${resolvedPath} \u2014 skipping clone, updating config only.`);
|
|
7357
7573
|
} else {
|
|
7358
|
-
|
|
7574
|
+
const branchLabel = opts.branch ?? "remote default";
|
|
7575
|
+
warn(`Cloning ${url} (branch: ${branchLabel}) \u2192 ${resolvedPath}...`);
|
|
7359
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);
|
|
7360
7580
|
try {
|
|
7361
|
-
execFileSync3("git",
|
|
7581
|
+
execFileSync3("git", cloneArgs, { stdio: ["inherit", "inherit", "pipe"] });
|
|
7362
7582
|
} catch (e) {
|
|
7363
7583
|
const msg = e instanceof Error ? e.message : String(e);
|
|
7364
|
-
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:***@"));
|
|
7365
7585
|
}
|
|
7366
7586
|
}
|
|
7367
7587
|
const configPath = getGlobalConfigPath();
|
|
7368
7588
|
const existing = loadJsonFile(configPath) ?? {};
|
|
7369
7589
|
existing.marketplace = { repoUrl: url, localPath: resolvedPath, ...opts.token ? { token: opts.token } : {} };
|
|
7370
7590
|
writeGlobalConfig(existing, configPath);
|
|
7371
|
-
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);
|
|
7372
7592
|
} catch (err) {
|
|
7373
7593
|
fail(err, "skills", "marketplace-setup", start);
|
|
7374
7594
|
}
|
|
7375
7595
|
});
|
|
7376
|
-
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) => {
|
|
7377
7597
|
const start = Date.now();
|
|
7378
7598
|
try {
|
|
7379
7599
|
const configPath = getGlobalConfigPath();
|
|
@@ -7390,16 +7610,58 @@ function registerSkillsCommands(program2) {
|
|
|
7390
7610
|
gitArgs.push("-c", `remote.origin.url=${injectTokenIntoUrl(repoUrl, token)}`);
|
|
7391
7611
|
}
|
|
7392
7612
|
gitArgs.push("pull");
|
|
7613
|
+
let pullOutput;
|
|
7393
7614
|
try {
|
|
7394
|
-
execFileSync3("git", gitArgs, { stdio: ["inherit", "
|
|
7615
|
+
pullOutput = execFileSync3("git", gitArgs, { encoding: "utf8", stdio: ["inherit", "pipe", "pipe"], env: { ...process.env, LANG: "C", LC_ALL: "C" } });
|
|
7395
7616
|
} catch (e) {
|
|
7396
7617
|
const msg = e instanceof Error ? e.message : String(e);
|
|
7397
|
-
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;
|
|
7398
7634
|
}
|
|
7399
7635
|
const pluginChoices = resolvePluginChoices(marketplacePath);
|
|
7400
7636
|
if (pluginChoices.length === 0) {
|
|
7401
7637
|
throw new Error("No plugins found in marketplace. Check the marketplace repository structure.");
|
|
7402
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
|
+
}
|
|
7403
7665
|
let selectedPlugin;
|
|
7404
7666
|
if (plugin) {
|
|
7405
7667
|
if (!pluginChoices.some((p) => p.name === plugin)) {
|
|
@@ -7419,7 +7681,6 @@ function registerSkillsCommands(program2) {
|
|
|
7419
7681
|
if (!fs3.existsSync(skillsSrc)) {
|
|
7420
7682
|
throw new Error(`No skills directory found at ${skillsSrc}`);
|
|
7421
7683
|
}
|
|
7422
|
-
const targetDir = opts.claude ? path5.join(os.homedir(), ".claude", "skills") : path5.join(os.homedir(), ".agents", "skills");
|
|
7423
7684
|
const { installed, failed } = copyPluginSkills(skillsSrc, targetDir);
|
|
7424
7685
|
if (failed.length > 0) {
|
|
7425
7686
|
warn(`Skipped ${failed.length} skill(s) with invalid names: ${failed.join(", ")}`);
|
|
@@ -7429,7 +7690,8 @@ function registerSkillsCommands(program2) {
|
|
|
7429
7690
|
installed,
|
|
7430
7691
|
failed,
|
|
7431
7692
|
total: installed.length,
|
|
7432
|
-
target: targetDir
|
|
7693
|
+
target: targetDir,
|
|
7694
|
+
marketplaceUpdated
|
|
7433
7695
|
}, "skills", "marketplace-sync", start);
|
|
7434
7696
|
} catch (err) {
|
|
7435
7697
|
fail(err, "skills", "marketplace-sync", start);
|