@kolatts/pncli 1.10.0 → 1.11.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/copilot-instructions.md +34 -1
- package/dist/{chunk-O3LWAWSN.js → chunk-AWSXVWRN.js} +2 -1
- package/dist/{chunk-O3LWAWSN.js.map → chunk-AWSXVWRN.js.map} +1 -1
- package/dist/{chunk-3XHTRFRX.js → chunk-VPUWMP2M.js} +3 -2
- package/dist/chunk-VPUWMP2M.js.map +1 -0
- package/dist/cli.js +410 -36
- package/dist/cli.js.map +1 -1
- package/dist/{config-NCI2BTQG.js → config-OFLPO5DN.js} +4 -2
- package/dist/{http-L2P23H2D.js → http-LNDRNPP7.js} +2 -2
- package/package.json +1 -1
- package/skills/pncli/SKILL.md +92 -0
- package/skills/pncli/ado.md +33 -0
- package/skills/pncli/artifactory.md +35 -0
- package/skills/pncli/bitbucket.md +30 -0
- package/skills/pncli/checkmarx.md +31 -0
- package/skills/pncli/confluence.md +24 -0
- package/skills/pncli/contrast.md +39 -0
- package/skills/pncli/jenkins.md +29 -0
- package/skills/pncli/jira.md +35 -0
- package/skills/pncli/marketplace.md +41 -0
- package/skills/pncli/sde.md +29 -0
- package/skills/pncli/servicenow.md +33 -0
- package/skills/pncli/sonarqube.md +30 -0
- package/skills/pncli/sonatypeiq.md +39 -0
- package/skills/pncli/udeploy.md +48 -0
- package/dist/chunk-3XHTRFRX.js.map +0 -1
- package/skills/address-pr-feedback/SKILL.md +0 -87
- package/skills/code-review/SKILL.md +0 -82
- package/skills/local-setup/SKILL.md +0 -225
- package/skills/plan/SKILL.md +0 -120
- package/skills/security-review/SKILL.md +0 -108
- package/skills/ship/SKILL.md +0 -170
- /package/dist/{config-NCI2BTQG.js.map → config-OFLPO5DN.js.map} +0 -0
- /package/dist/{http-L2P23H2D.js.map → http-LNDRNPP7.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
import {
|
|
3
3
|
getGlobalConfigPath,
|
|
4
4
|
loadConfig,
|
|
5
|
+
loadJsonFile,
|
|
5
6
|
maskConfig,
|
|
6
7
|
setConfigValue,
|
|
7
8
|
setRepoConfigValue,
|
|
8
9
|
writeGlobalConfig,
|
|
9
10
|
writeRepoConfig
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-AWSXVWRN.js";
|
|
11
12
|
import {
|
|
12
13
|
ExitCode,
|
|
13
14
|
PncliError,
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
setGlobalUser,
|
|
19
20
|
success,
|
|
20
21
|
warn
|
|
21
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-VPUWMP2M.js";
|
|
22
23
|
import {
|
|
23
24
|
AdoCoreClient
|
|
24
25
|
} from "./chunk-VDL32PSG.js";
|
|
@@ -161,6 +162,201 @@ function getBranches(root) {
|
|
|
161
162
|
}
|
|
162
163
|
return { current, local, remote };
|
|
163
164
|
}
|
|
165
|
+
var COMMIT_PREFIX = "__PNCLI_COMMIT__";
|
|
166
|
+
function getWeekMonday(dateStr) {
|
|
167
|
+
const d = new Date(dateStr);
|
|
168
|
+
const day = d.getUTCDay();
|
|
169
|
+
const diff = day === 0 ? -6 : 1 - day;
|
|
170
|
+
const monday = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + diff));
|
|
171
|
+
return monday.toISOString().slice(0, 10);
|
|
172
|
+
}
|
|
173
|
+
function getWeekSunday(mondayStr) {
|
|
174
|
+
const d = new Date(mondayStr);
|
|
175
|
+
const sunday = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + 6));
|
|
176
|
+
return sunday.toISOString().slice(0, 10);
|
|
177
|
+
}
|
|
178
|
+
function getISOWeekLabel(mondayStr) {
|
|
179
|
+
const d = new Date(mondayStr);
|
|
180
|
+
const thursday = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + 3));
|
|
181
|
+
const year = thursday.getUTCFullYear();
|
|
182
|
+
const jan4 = new Date(Date.UTC(year, 0, 4));
|
|
183
|
+
const jan4Monday = new Date(Date.UTC(year, 0, 4 - (jan4.getUTCDay() === 0 ? 6 : jan4.getUTCDay() - 1)));
|
|
184
|
+
const weekNum = Math.round((d.getTime() - jan4Monday.getTime()) / (7 * 864e5)) + 1;
|
|
185
|
+
return `${year}-W${String(weekNum).padStart(2, "0")}`;
|
|
186
|
+
}
|
|
187
|
+
function buildWeeklyBreakdown(commits) {
|
|
188
|
+
const weekMap = /* @__PURE__ */ new Map();
|
|
189
|
+
for (const commit of commits) {
|
|
190
|
+
const monday = getWeekMonday(commit.date);
|
|
191
|
+
if (!weekMap.has(monday)) weekMap.set(monday, /* @__PURE__ */ new Map());
|
|
192
|
+
const authorMap = weekMap.get(monday);
|
|
193
|
+
if (!authorMap.has(commit.author)) {
|
|
194
|
+
authorMap.set(commit.author, {
|
|
195
|
+
author: commit.author,
|
|
196
|
+
commitCount: 0,
|
|
197
|
+
insertions: 0,
|
|
198
|
+
deletions: 0,
|
|
199
|
+
netLines: 0,
|
|
200
|
+
filesChanged: 0
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const stats = authorMap.get(commit.author);
|
|
204
|
+
stats.commitCount++;
|
|
205
|
+
stats.insertions += commit.insertions;
|
|
206
|
+
stats.deletions += commit.deletions;
|
|
207
|
+
stats.netLines += commit.netLines;
|
|
208
|
+
stats.filesChanged += commit.filesChanged;
|
|
209
|
+
}
|
|
210
|
+
return [...weekMap.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([monday, authorMap]) => {
|
|
211
|
+
const authors = [...authorMap.values()].sort((a, b) => a.author.localeCompare(b.author));
|
|
212
|
+
return {
|
|
213
|
+
week: getISOWeekLabel(monday),
|
|
214
|
+
weekStart: monday,
|
|
215
|
+
weekEnd: getWeekSunday(monday),
|
|
216
|
+
authors,
|
|
217
|
+
summary: {
|
|
218
|
+
commitCount: authors.reduce((s, a) => s + a.commitCount, 0),
|
|
219
|
+
totalInsertions: authors.reduce((s, a) => s + a.insertions, 0),
|
|
220
|
+
totalDeletions: authors.reduce((s, a) => s + a.deletions, 0),
|
|
221
|
+
totalNetLines: authors.reduce((s, a) => s + a.netLines, 0),
|
|
222
|
+
totalFilesChanged: authors.reduce((s, a) => s + a.filesChanged, 0)
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
function buildByAuthor(commits) {
|
|
228
|
+
const map = /* @__PURE__ */ new Map();
|
|
229
|
+
for (const commit of commits) {
|
|
230
|
+
if (!map.has(commit.author)) {
|
|
231
|
+
map.set(commit.author, {
|
|
232
|
+
author: commit.author,
|
|
233
|
+
commitCount: 0,
|
|
234
|
+
insertions: 0,
|
|
235
|
+
deletions: 0,
|
|
236
|
+
netLines: 0,
|
|
237
|
+
filesChanged: 0
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
const stats = map.get(commit.author);
|
|
241
|
+
stats.commitCount++;
|
|
242
|
+
stats.insertions += commit.insertions;
|
|
243
|
+
stats.deletions += commit.deletions;
|
|
244
|
+
stats.netLines += commit.netLines;
|
|
245
|
+
stats.filesChanged += commit.filesChanged;
|
|
246
|
+
}
|
|
247
|
+
return [...map.values()].sort((a, b) => a.author.localeCompare(b.author));
|
|
248
|
+
}
|
|
249
|
+
function parseBranchReportOutput(raw, branchName, opts) {
|
|
250
|
+
const commits = [];
|
|
251
|
+
let current = null;
|
|
252
|
+
for (const line of raw.split("\n")) {
|
|
253
|
+
if (line.startsWith(COMMIT_PREFIX)) {
|
|
254
|
+
if (current) commits.push(current);
|
|
255
|
+
const parts = line.slice(COMMIT_PREFIX.length).split(" ");
|
|
256
|
+
current = {
|
|
257
|
+
hash: parts[0] ?? "",
|
|
258
|
+
author: parts[1] ?? "",
|
|
259
|
+
date: parts[2] ?? "",
|
|
260
|
+
message: parts[3] ?? "",
|
|
261
|
+
filesChanged: 0,
|
|
262
|
+
insertions: 0,
|
|
263
|
+
deletions: 0,
|
|
264
|
+
netLines: 0
|
|
265
|
+
};
|
|
266
|
+
} else if (current && /^(\d+|-)\t(\d+|-)/.test(line)) {
|
|
267
|
+
const [ins, del] = line.split(" ");
|
|
268
|
+
const insertions = ins === "-" ? 0 : parseInt(ins ?? "0", 10);
|
|
269
|
+
const deletions = del === "-" ? 0 : parseInt(del ?? "0", 10);
|
|
270
|
+
if (!isNaN(insertions) && !isNaN(deletions)) {
|
|
271
|
+
current.insertions += insertions;
|
|
272
|
+
current.deletions += deletions;
|
|
273
|
+
current.filesChanged++;
|
|
274
|
+
current.netLines = current.insertions - current.deletions;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (current) commits.push(current);
|
|
279
|
+
const totalInsertions = commits.reduce((sum, c) => sum + c.insertions, 0);
|
|
280
|
+
const totalDeletions = commits.reduce((sum, c) => sum + c.deletions, 0);
|
|
281
|
+
return {
|
|
282
|
+
branch: branchName,
|
|
283
|
+
since: opts.since ?? null,
|
|
284
|
+
until: opts.until ?? null,
|
|
285
|
+
commits,
|
|
286
|
+
weeks: buildWeeklyBreakdown(commits),
|
|
287
|
+
byAuthor: buildByAuthor(commits),
|
|
288
|
+
summary: {
|
|
289
|
+
commitCount: commits.length,
|
|
290
|
+
totalInsertions,
|
|
291
|
+
totalDeletions,
|
|
292
|
+
totalNetLines: totalInsertions - totalDeletions,
|
|
293
|
+
totalFilesChanged: commits.reduce((sum, c) => sum + c.filesChanged, 0)
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function buildReportArgs(opts) {
|
|
298
|
+
const fmt = `${COMMIT_PREFIX}%H %an %aI %s`;
|
|
299
|
+
const args = ["log", `--format=${fmt}`, "--numstat"];
|
|
300
|
+
if (opts.since) args.push(`--since=${opts.since}`);
|
|
301
|
+
if (opts.until) args.push(`--until=${opts.until}`);
|
|
302
|
+
if (opts.base && opts.branch) {
|
|
303
|
+
args.push(`${opts.base}..${opts.branch}`);
|
|
304
|
+
} else if (opts.base) {
|
|
305
|
+
args.push(`${opts.base}..HEAD`);
|
|
306
|
+
} else if (opts.branch) {
|
|
307
|
+
args.push(opts.branch);
|
|
308
|
+
}
|
|
309
|
+
return args;
|
|
310
|
+
}
|
|
311
|
+
function getBranchReport(root, opts) {
|
|
312
|
+
const args = buildReportArgs(opts);
|
|
313
|
+
let raw = "";
|
|
314
|
+
try {
|
|
315
|
+
raw = execFileSync("git", args, { encoding: "utf8", cwd: root }).trim();
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
318
|
+
let branchName = opts.branch ?? "";
|
|
319
|
+
if (!branchName) {
|
|
320
|
+
try {
|
|
321
|
+
branchName = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
322
|
+
encoding: "utf8",
|
|
323
|
+
cwd: root
|
|
324
|
+
}).trim();
|
|
325
|
+
} catch {
|
|
326
|
+
branchName = "HEAD";
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return parseBranchReportOutput(raw, branchName, opts);
|
|
330
|
+
}
|
|
331
|
+
function escapeCsvField(value) {
|
|
332
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
|
|
333
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
334
|
+
}
|
|
335
|
+
return value;
|
|
336
|
+
}
|
|
337
|
+
function formatBranchReportCsv(report) {
|
|
338
|
+
const header = "week,week_start,author,commits,insertions,deletions,net_lines,files_changed";
|
|
339
|
+
const rows = [];
|
|
340
|
+
for (const week of report.weeks) {
|
|
341
|
+
for (const a of week.authors) {
|
|
342
|
+
rows.push(
|
|
343
|
+
[
|
|
344
|
+
week.week,
|
|
345
|
+
week.weekStart,
|
|
346
|
+
escapeCsvField(a.author),
|
|
347
|
+
a.commitCount,
|
|
348
|
+
a.insertions,
|
|
349
|
+
a.deletions,
|
|
350
|
+
a.netLines,
|
|
351
|
+
a.filesChanged
|
|
352
|
+
].join(",")
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const s = report.summary;
|
|
357
|
+
const totalRow = ["TOTAL", "", "", s.commitCount, s.totalInsertions, s.totalDeletions, s.totalNetLines, s.totalFilesChanged].join(",");
|
|
358
|
+
return [header, ...rows, totalRow].join("\n") + "\n";
|
|
359
|
+
}
|
|
164
360
|
|
|
165
361
|
// src/lib/git-context.ts
|
|
166
362
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -529,6 +725,25 @@ function registerGitCommands(program2) {
|
|
|
529
725
|
fail(err, "git", "branch", start);
|
|
530
726
|
}
|
|
531
727
|
});
|
|
728
|
+
git.command("report").description("Report lines of code and commit counts for a branch, optionally filtered by date").option("--branch <name>", "Branch to report on (defaults to current branch)").option("--base <ref>", 'Base ref to compare against, e.g. "main" (base..branch, or base..HEAD if --branch is omitted)').option("--since <date>", 'Include commits on or after this date (e.g. "2024-01-01")').option("--until <date>", 'Include commits on or before this date (e.g. "2024-12-31")').option("--csv", "Output as CSV instead of JSON").action((opts) => {
|
|
729
|
+
const start = Date.now();
|
|
730
|
+
try {
|
|
731
|
+
const root = requireRepoRoot();
|
|
732
|
+
const report = getBranchReport(root, {
|
|
733
|
+
branch: opts.branch,
|
|
734
|
+
base: opts.base,
|
|
735
|
+
since: opts.since,
|
|
736
|
+
until: opts.until
|
|
737
|
+
});
|
|
738
|
+
if (opts.csv) {
|
|
739
|
+
process.stdout.write(formatBranchReportCsv(report));
|
|
740
|
+
} else {
|
|
741
|
+
success(report, "git", "report", start);
|
|
742
|
+
}
|
|
743
|
+
} catch (err) {
|
|
744
|
+
fail(err, "git", "report", start);
|
|
745
|
+
}
|
|
746
|
+
});
|
|
532
747
|
git.command("current-pr").description("Find the open PR for the current branch").action(async () => {
|
|
533
748
|
const start = Date.now();
|
|
534
749
|
try {
|
|
@@ -4366,6 +4581,11 @@ function registerConfigCommands(program2) {
|
|
|
4366
4581
|
}
|
|
4367
4582
|
});
|
|
4368
4583
|
}
|
|
4584
|
+
function normalizeBaseUrl(raw) {
|
|
4585
|
+
const trimmed = raw.trim();
|
|
4586
|
+
if (!trimmed) return trimmed;
|
|
4587
|
+
return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
4588
|
+
}
|
|
4369
4589
|
async function initGlobalConfig(start) {
|
|
4370
4590
|
process.stderr.write("pncli config init \u2014 Global configuration\n\n");
|
|
4371
4591
|
process.stderr.write("\u2500\u2500 Identity \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
@@ -4379,7 +4599,7 @@ async function initGlobalConfig(start) {
|
|
|
4379
4599
|
});
|
|
4380
4600
|
process.stderr.write("\n\u2500\u2500 Jira \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
4381
4601
|
const jiraBaseUrl = await input({
|
|
4382
|
-
message: "Jira base URL (e.g.
|
|
4602
|
+
message: "Jira base URL (e.g. jira.your-company.com):",
|
|
4383
4603
|
default: ""
|
|
4384
4604
|
});
|
|
4385
4605
|
const jiraApiToken = await password({
|
|
@@ -4387,7 +4607,7 @@ async function initGlobalConfig(start) {
|
|
|
4387
4607
|
});
|
|
4388
4608
|
process.stderr.write("\n\u2500\u2500 Bitbucket \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
4389
4609
|
const bitbucketBaseUrl = await input({
|
|
4390
|
-
message: "Bitbucket Server base URL (e.g.
|
|
4610
|
+
message: "Bitbucket Server base URL (e.g. bitbucket.your-company.com):",
|
|
4391
4611
|
default: ""
|
|
4392
4612
|
});
|
|
4393
4613
|
const bitbucketPat = await password({
|
|
@@ -4395,7 +4615,7 @@ async function initGlobalConfig(start) {
|
|
|
4395
4615
|
});
|
|
4396
4616
|
process.stderr.write("\n\u2500\u2500 Confluence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
4397
4617
|
const confluenceBaseUrl = await input({
|
|
4398
|
-
message: "Confluence base URL (e.g.
|
|
4618
|
+
message: "Confluence base URL (e.g. confluence.your-company.com):",
|
|
4399
4619
|
default: ""
|
|
4400
4620
|
});
|
|
4401
4621
|
const confluenceApiToken = await password({
|
|
@@ -4413,7 +4633,7 @@ async function initGlobalConfig(start) {
|
|
|
4413
4633
|
let mavenRepo = "";
|
|
4414
4634
|
if (useArtifactory) {
|
|
4415
4635
|
artifactoryBaseUrl = await input({
|
|
4416
|
-
message: "Artifactory base URL (e.g.
|
|
4636
|
+
message: "Artifactory base URL (e.g. artifactory.company.com):",
|
|
4417
4637
|
default: ""
|
|
4418
4638
|
});
|
|
4419
4639
|
artifactoryToken = await password({
|
|
@@ -4442,7 +4662,7 @@ async function initGlobalConfig(start) {
|
|
|
4442
4662
|
let sonarToken = "";
|
|
4443
4663
|
if (useSonar) {
|
|
4444
4664
|
sonarBaseUrl = await input({
|
|
4445
|
-
message: "SonarQube Server base URL (e.g.
|
|
4665
|
+
message: "SonarQube Server base URL (e.g. sonar.your-company.com):",
|
|
4446
4666
|
default: ""
|
|
4447
4667
|
});
|
|
4448
4668
|
sonarToken = await password({
|
|
@@ -4495,7 +4715,7 @@ async function initGlobalConfig(start) {
|
|
|
4495
4715
|
let adoDiscoveredTypes = [];
|
|
4496
4716
|
if (useAdo) {
|
|
4497
4717
|
adoBaseUrl = await input({
|
|
4498
|
-
message: "Azure DevOps Server base URL (e.g.
|
|
4718
|
+
message: "Azure DevOps Server base URL (e.g. tfs.company.com or devops.company.com/tfs):",
|
|
4499
4719
|
default: ""
|
|
4500
4720
|
});
|
|
4501
4721
|
adoPat = await password({
|
|
@@ -4515,7 +4735,7 @@ async function initGlobalConfig(start) {
|
|
|
4515
4735
|
const tempConfig = {
|
|
4516
4736
|
...loadConfig(),
|
|
4517
4737
|
ado: {
|
|
4518
|
-
baseUrl: adoBaseUrl,
|
|
4738
|
+
baseUrl: normalizeBaseUrl(adoBaseUrl),
|
|
4519
4739
|
pat: adoPat || void 0,
|
|
4520
4740
|
fieldAliases: {},
|
|
4521
4741
|
discoveredFields: [],
|
|
@@ -4567,7 +4787,7 @@ async function initGlobalConfig(start) {
|
|
|
4567
4787
|
let jenkinsApiToken = "";
|
|
4568
4788
|
if (useJenkins) {
|
|
4569
4789
|
jenkinsBaseUrl = await input({
|
|
4570
|
-
message: "Jenkins base URL (e.g.
|
|
4790
|
+
message: "Jenkins base URL (e.g. jenkins.your-company.com):",
|
|
4571
4791
|
default: ""
|
|
4572
4792
|
});
|
|
4573
4793
|
jenkinsUsername = await input({
|
|
@@ -4591,7 +4811,7 @@ async function initGlobalConfig(start) {
|
|
|
4591
4811
|
let udeployDefaultEnv = "";
|
|
4592
4812
|
if (useUdeploy) {
|
|
4593
4813
|
udeployBaseUrl = await input({
|
|
4594
|
-
message: "UDeploy base URL (e.g.
|
|
4814
|
+
message: "UDeploy base URL (e.g. ucd.company.com:8443):",
|
|
4595
4815
|
default: ""
|
|
4596
4816
|
});
|
|
4597
4817
|
udeployUsername = await input({
|
|
@@ -4623,7 +4843,7 @@ async function initGlobalConfig(start) {
|
|
|
4623
4843
|
const tempConfig = {
|
|
4624
4844
|
...loadConfig(),
|
|
4625
4845
|
udeploy: {
|
|
4626
|
-
baseUrl: udeployBaseUrl,
|
|
4846
|
+
baseUrl: normalizeBaseUrl(udeployBaseUrl),
|
|
4627
4847
|
pat: udeployPat || void 0,
|
|
4628
4848
|
username: udeployUsername || void 0,
|
|
4629
4849
|
password: udeployPassword || void 0
|
|
@@ -4648,7 +4868,7 @@ async function initGlobalConfig(start) {
|
|
|
4648
4868
|
let checkmarxPassword = "";
|
|
4649
4869
|
if (useCheckmarx) {
|
|
4650
4870
|
checkmarxBaseUrl = await input({
|
|
4651
|
-
message: "Checkmarx base URL (e.g.
|
|
4871
|
+
message: "Checkmarx base URL (e.g. cx.company.com):",
|
|
4652
4872
|
default: ""
|
|
4653
4873
|
});
|
|
4654
4874
|
checkmarxUsername = await input({
|
|
@@ -4665,7 +4885,7 @@ async function initGlobalConfig(start) {
|
|
|
4665
4885
|
const tempConfig = {
|
|
4666
4886
|
...loadConfig(),
|
|
4667
4887
|
checkmarx: {
|
|
4668
|
-
baseUrl: checkmarxBaseUrl,
|
|
4888
|
+
baseUrl: normalizeBaseUrl(checkmarxBaseUrl),
|
|
4669
4889
|
username: checkmarxUsername,
|
|
4670
4890
|
password: checkmarxPassword
|
|
4671
4891
|
}
|
|
@@ -4690,7 +4910,7 @@ async function initGlobalConfig(start) {
|
|
|
4690
4910
|
let servicenowApiToken = "";
|
|
4691
4911
|
if (useServiceNow) {
|
|
4692
4912
|
servicenowBaseUrl = await input({
|
|
4693
|
-
message: "ServiceNow instance URL (e.g.
|
|
4913
|
+
message: "ServiceNow instance URL (e.g. mycompany.service-now.com):",
|
|
4694
4914
|
default: ""
|
|
4695
4915
|
});
|
|
4696
4916
|
servicenowUsername = await input({
|
|
@@ -4719,7 +4939,7 @@ async function initGlobalConfig(start) {
|
|
|
4719
4939
|
const tempConfig = {
|
|
4720
4940
|
...loadConfig(),
|
|
4721
4941
|
servicenow: {
|
|
4722
|
-
baseUrl: servicenowBaseUrl,
|
|
4942
|
+
baseUrl: normalizeBaseUrl(servicenowBaseUrl),
|
|
4723
4943
|
username: servicenowUsername,
|
|
4724
4944
|
password: servicenowPassword || void 0,
|
|
4725
4945
|
apiToken: servicenowApiToken || void 0
|
|
@@ -4745,7 +4965,7 @@ async function initGlobalConfig(start) {
|
|
|
4745
4965
|
if (useSonatypeIq) {
|
|
4746
4966
|
process.stderr.write(" User Token credentials are found in your IQ Server profile under User Token.\n");
|
|
4747
4967
|
sonatypeIqBaseUrl = await input({
|
|
4748
|
-
message: "Sonatype IQ Server base URL (e.g.
|
|
4968
|
+
message: "Sonatype IQ Server base URL (e.g. iq.your-company.com):",
|
|
4749
4969
|
default: ""
|
|
4750
4970
|
});
|
|
4751
4971
|
sonatypeIqUserCode = await input({
|
|
@@ -4761,7 +4981,7 @@ async function initGlobalConfig(start) {
|
|
|
4761
4981
|
const tempConfig = {
|
|
4762
4982
|
...loadConfig(),
|
|
4763
4983
|
sonatypeiq: {
|
|
4764
|
-
baseUrl: sonatypeIqBaseUrl,
|
|
4984
|
+
baseUrl: normalizeBaseUrl(sonatypeIqBaseUrl),
|
|
4765
4985
|
userCode: sonatypeIqUserCode,
|
|
4766
4986
|
passcode: sonatypeIqPasscode
|
|
4767
4987
|
}
|
|
@@ -4788,7 +5008,8 @@ async function initGlobalConfig(start) {
|
|
|
4788
5008
|
if (useContrast) {
|
|
4789
5009
|
process.stderr.write(" Find your credentials in Contrast under User Settings \u2192 Your Keys.\n");
|
|
4790
5010
|
contrastBaseUrl = await input({
|
|
4791
|
-
message: "Contrast base URL (
|
|
5011
|
+
message: "Contrast base URL (e.g. https://app.contrastsecurity.com for cloud SaaS):",
|
|
5012
|
+
validate: (v) => v.trim() ? true : "Base URL is required",
|
|
4792
5013
|
default: ""
|
|
4793
5014
|
});
|
|
4794
5015
|
contrastOrgUuid = await input({
|
|
@@ -4812,7 +5033,7 @@ async function initGlobalConfig(start) {
|
|
|
4812
5033
|
const tempConfig = {
|
|
4813
5034
|
...loadConfig(),
|
|
4814
5035
|
contrast: {
|
|
4815
|
-
baseUrl: contrastBaseUrl || void 0,
|
|
5036
|
+
baseUrl: normalizeBaseUrl(contrastBaseUrl) || void 0,
|
|
4816
5037
|
orgUuid: contrastOrgUuid,
|
|
4817
5038
|
username: contrastUsername,
|
|
4818
5039
|
apiKey: contrastApiKey,
|
|
@@ -4857,22 +5078,22 @@ async function initGlobalConfig(start) {
|
|
|
4857
5078
|
userId: userId || void 0
|
|
4858
5079
|
},
|
|
4859
5080
|
jira: {
|
|
4860
|
-
baseUrl: jiraBaseUrl || void 0,
|
|
5081
|
+
baseUrl: normalizeBaseUrl(jiraBaseUrl) || void 0,
|
|
4861
5082
|
apiToken: jiraApiToken || void 0
|
|
4862
5083
|
},
|
|
4863
5084
|
bitbucket: {
|
|
4864
|
-
baseUrl: bitbucketBaseUrl || void 0,
|
|
5085
|
+
baseUrl: normalizeBaseUrl(bitbucketBaseUrl) || void 0,
|
|
4865
5086
|
pat: bitbucketPat || void 0
|
|
4866
5087
|
},
|
|
4867
5088
|
...confluenceBaseUrl || confluenceApiToken ? {
|
|
4868
5089
|
confluence: {
|
|
4869
|
-
baseUrl: confluenceBaseUrl || void 0,
|
|
5090
|
+
baseUrl: normalizeBaseUrl(confluenceBaseUrl) || void 0,
|
|
4870
5091
|
apiToken: confluenceApiToken || void 0
|
|
4871
5092
|
}
|
|
4872
5093
|
} : {},
|
|
4873
5094
|
...useArtifactory ? {
|
|
4874
5095
|
artifactory: {
|
|
4875
|
-
baseUrl: artifactoryBaseUrl || void 0,
|
|
5096
|
+
baseUrl: normalizeBaseUrl(artifactoryBaseUrl) || void 0,
|
|
4876
5097
|
token: artifactoryToken || void 0,
|
|
4877
5098
|
npmRepo: npmRepo || void 0,
|
|
4878
5099
|
nugetRepo: nugetRepo || void 0,
|
|
@@ -4881,7 +5102,7 @@ async function initGlobalConfig(start) {
|
|
|
4881
5102
|
} : {},
|
|
4882
5103
|
...useSonar ? {
|
|
4883
5104
|
sonar: {
|
|
4884
|
-
baseUrl: sonarBaseUrl || void 0,
|
|
5105
|
+
baseUrl: normalizeBaseUrl(sonarBaseUrl) || void 0,
|
|
4885
5106
|
token: sonarToken || void 0
|
|
4886
5107
|
}
|
|
4887
5108
|
} : {},
|
|
@@ -4892,7 +5113,7 @@ async function initGlobalConfig(start) {
|
|
|
4892
5113
|
} : {},
|
|
4893
5114
|
...useAdo && adoBaseUrl ? {
|
|
4894
5115
|
ado: {
|
|
4895
|
-
baseUrl: adoBaseUrl,
|
|
5116
|
+
baseUrl: normalizeBaseUrl(adoBaseUrl),
|
|
4896
5117
|
...adoPat ? { pat: adoPat } : {},
|
|
4897
5118
|
...Object.keys(adoFieldAliases).length ? { fieldAliases: adoFieldAliases } : {},
|
|
4898
5119
|
...adoDiscoveredFields.length ? { discoveredFields: adoDiscoveredFields } : {},
|
|
@@ -4901,14 +5122,14 @@ async function initGlobalConfig(start) {
|
|
|
4901
5122
|
} : {},
|
|
4902
5123
|
...useJenkins && jenkinsBaseUrl ? {
|
|
4903
5124
|
jenkins: {
|
|
4904
|
-
baseUrl: jenkinsBaseUrl,
|
|
5125
|
+
baseUrl: normalizeBaseUrl(jenkinsBaseUrl),
|
|
4905
5126
|
...jenkinsUsername ? { username: jenkinsUsername } : {},
|
|
4906
5127
|
...jenkinsApiToken ? { apiToken: jenkinsApiToken } : {}
|
|
4907
5128
|
}
|
|
4908
5129
|
} : {},
|
|
4909
5130
|
...useUdeploy && udeployBaseUrl ? {
|
|
4910
5131
|
udeploy: {
|
|
4911
|
-
baseUrl: udeployBaseUrl,
|
|
5132
|
+
baseUrl: normalizeBaseUrl(udeployBaseUrl),
|
|
4912
5133
|
...udeployPat ? { pat: udeployPat } : {},
|
|
4913
5134
|
...udeployUsername ? { username: udeployUsername } : {},
|
|
4914
5135
|
...udeployPassword ? { password: udeployPassword } : {}
|
|
@@ -4916,14 +5137,14 @@ async function initGlobalConfig(start) {
|
|
|
4916
5137
|
} : {},
|
|
4917
5138
|
...useCheckmarx && checkmarxBaseUrl ? {
|
|
4918
5139
|
checkmarx: {
|
|
4919
|
-
baseUrl: checkmarxBaseUrl,
|
|
5140
|
+
baseUrl: normalizeBaseUrl(checkmarxBaseUrl),
|
|
4920
5141
|
username: checkmarxUsername || void 0,
|
|
4921
5142
|
password: checkmarxPassword || void 0
|
|
4922
5143
|
}
|
|
4923
5144
|
} : {},
|
|
4924
5145
|
...useServiceNow && servicenowBaseUrl ? {
|
|
4925
5146
|
servicenow: {
|
|
4926
|
-
baseUrl: servicenowBaseUrl,
|
|
5147
|
+
baseUrl: normalizeBaseUrl(servicenowBaseUrl),
|
|
4927
5148
|
username: servicenowUsername || void 0,
|
|
4928
5149
|
...servicenowApiToken ? { apiToken: servicenowApiToken } : {},
|
|
4929
5150
|
...servicenowPassword ? { password: servicenowPassword } : {}
|
|
@@ -4931,7 +5152,7 @@ async function initGlobalConfig(start) {
|
|
|
4931
5152
|
} : {},
|
|
4932
5153
|
...useContrast && contrastOrgUuid ? {
|
|
4933
5154
|
contrast: {
|
|
4934
|
-
...contrastBaseUrl ? { baseUrl: contrastBaseUrl } : {},
|
|
5155
|
+
...contrastBaseUrl ? { baseUrl: normalizeBaseUrl(contrastBaseUrl) } : {},
|
|
4935
5156
|
orgUuid: contrastOrgUuid,
|
|
4936
5157
|
username: contrastUsername || void 0,
|
|
4937
5158
|
apiKey: contrastApiKey || void 0,
|
|
@@ -4940,7 +5161,7 @@ async function initGlobalConfig(start) {
|
|
|
4940
5161
|
} : {},
|
|
4941
5162
|
...useSonatypeIq && sonatypeIqBaseUrl ? {
|
|
4942
5163
|
sonatypeiq: {
|
|
4943
|
-
baseUrl: sonatypeIqBaseUrl,
|
|
5164
|
+
baseUrl: normalizeBaseUrl(sonatypeIqBaseUrl),
|
|
4944
5165
|
userCode: sonatypeIqUserCode || void 0,
|
|
4945
5166
|
passcode: sonatypeIqPasscode || void 0
|
|
4946
5167
|
}
|
|
@@ -5849,11 +6070,11 @@ function registerAdoRepoCommands(ado) {
|
|
|
5849
6070
|
const start = Date.now();
|
|
5850
6071
|
try {
|
|
5851
6072
|
const globalOpts = ado.optsWithGlobals();
|
|
5852
|
-
const { loadConfig: lc } = await import("./config-
|
|
6073
|
+
const { loadConfig: lc } = await import("./config-OFLPO5DN.js");
|
|
5853
6074
|
const config = lc({ configPath: globalOpts.config });
|
|
5854
6075
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
5855
6076
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
5856
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
6077
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-LNDRNPP7.js");
|
|
5857
6078
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
5858
6079
|
const coreClient = new AdoCoreClient2(http);
|
|
5859
6080
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -6961,6 +7182,19 @@ import fs3 from "fs";
|
|
|
6961
7182
|
import path5 from "path";
|
|
6962
7183
|
import os from "os";
|
|
6963
7184
|
import { fileURLToPath } from "url";
|
|
7185
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
7186
|
+
import { select } from "@inquirer/prompts";
|
|
7187
|
+
function injectTokenIntoUrl(url, token) {
|
|
7188
|
+
let parsed;
|
|
7189
|
+
try {
|
|
7190
|
+
parsed = new URL(url);
|
|
7191
|
+
} catch {
|
|
7192
|
+
throw new Error(`--token requires an HTTPS clone URL; got: ${url}`);
|
|
7193
|
+
}
|
|
7194
|
+
parsed.username = "x-token-auth";
|
|
7195
|
+
parsed.password = token;
|
|
7196
|
+
return parsed.toString();
|
|
7197
|
+
}
|
|
6964
7198
|
var AGENT_PATHS = {
|
|
6965
7199
|
"github-copilot": { project: ".agents/skills", user: path5.join(os.homedir(), ".copilot/skills") },
|
|
6966
7200
|
"claude-code": { project: ".claude/skills", user: path5.join(os.homedir(), ".claude/skills") }
|
|
@@ -7020,9 +7254,12 @@ function registerSkillsCommands(program2) {
|
|
|
7020
7254
|
continue;
|
|
7021
7255
|
}
|
|
7022
7256
|
try {
|
|
7023
|
-
const
|
|
7257
|
+
const mdFiles = fs3.readdirSync(path5.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
|
|
7024
7258
|
fs3.mkdirSync(skillDir, { recursive: true });
|
|
7025
|
-
|
|
7259
|
+
for (const mdFile of mdFiles) {
|
|
7260
|
+
const content = fs3.readFileSync(path5.join(bundledDir, skillName, mdFile), "utf8");
|
|
7261
|
+
fs3.writeFileSync(path5.join(skillDir, mdFile), content, "utf8");
|
|
7262
|
+
}
|
|
7026
7263
|
installed.push(skillName);
|
|
7027
7264
|
} catch {
|
|
7028
7265
|
failed.push(skillName);
|
|
@@ -7106,6 +7343,143 @@ function registerSkillsCommands(program2) {
|
|
|
7106
7343
|
fail(err, "skills", "list", start);
|
|
7107
7344
|
}
|
|
7108
7345
|
});
|
|
7346
|
+
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("<localPath>", "Local directory to clone the marketplace into").option("--branch <branch>", "Branch to clone", "master").option("--token <token>", "Bitbucket HTTP access token for authenticated clone and pull").action(async (url, localPath, opts) => {
|
|
7348
|
+
const start = Date.now();
|
|
7349
|
+
try {
|
|
7350
|
+
const resolvedPath = path5.resolve(localPath);
|
|
7351
|
+
const hasGit = fs3.existsSync(path5.join(resolvedPath, ".git"));
|
|
7352
|
+
if (fs3.existsSync(resolvedPath) && !hasGit && fs3.readdirSync(resolvedPath).length > 0) {
|
|
7353
|
+
throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
|
|
7354
|
+
}
|
|
7355
|
+
if (hasGit) {
|
|
7356
|
+
warn(`Directory already contains a git repo at ${resolvedPath} \u2014 skipping clone, updating config only.`);
|
|
7357
|
+
} else {
|
|
7358
|
+
warn(`Cloning ${url} (branch: ${opts.branch}) \u2192 ${resolvedPath}...`);
|
|
7359
|
+
const cloneUrl = opts.token ? injectTokenIntoUrl(url, opts.token) : url;
|
|
7360
|
+
try {
|
|
7361
|
+
execFileSync3("git", ["clone", "--branch", opts.branch, cloneUrl, resolvedPath], { stdio: ["inherit", "inherit", "pipe"] });
|
|
7362
|
+
} catch (e) {
|
|
7363
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
7364
|
+
throw new Error(msg.replace(/x-token-auth:[^@]+@/g, "x-token-auth:***@"));
|
|
7365
|
+
}
|
|
7366
|
+
}
|
|
7367
|
+
const configPath = getGlobalConfigPath();
|
|
7368
|
+
const existing = loadJsonFile(configPath) ?? {};
|
|
7369
|
+
existing.marketplace = { repoUrl: url, localPath: resolvedPath, ...opts.token ? { token: opts.token } : {} };
|
|
7370
|
+
writeGlobalConfig(existing, configPath);
|
|
7371
|
+
success({ repoUrl: url, localPath: resolvedPath, branch: opts.branch, tokenConfigured: !!opts.token }, "skills", "marketplace-setup", start);
|
|
7372
|
+
} catch (err) {
|
|
7373
|
+
fail(err, "skills", "marketplace-setup", start);
|
|
7374
|
+
}
|
|
7375
|
+
});
|
|
7376
|
+
marketplace.command("sync").description("Pull latest marketplace content and install a plugin's skills").argument("[plugin]", "Plugin name to install (skips interactive selection)").option("--claude", "Install to ~/.claude/skills instead of ~/.agents/skills").action(async (plugin, opts) => {
|
|
7377
|
+
const start = Date.now();
|
|
7378
|
+
try {
|
|
7379
|
+
const configPath = getGlobalConfigPath();
|
|
7380
|
+
const globalConfig = loadJsonFile(configPath) ?? {};
|
|
7381
|
+
const marketplacePath = globalConfig.marketplace?.localPath;
|
|
7382
|
+
if (!marketplacePath || !fs3.existsSync(marketplacePath)) {
|
|
7383
|
+
throw new Error("Marketplace not configured. Run: pncli skills marketplace setup <url> <localPath>");
|
|
7384
|
+
}
|
|
7385
|
+
warn("Pulling latest marketplace content...");
|
|
7386
|
+
const repoUrl = globalConfig.marketplace?.repoUrl;
|
|
7387
|
+
const token = globalConfig.marketplace?.token;
|
|
7388
|
+
const gitArgs = ["-C", marketplacePath];
|
|
7389
|
+
if (repoUrl && token) {
|
|
7390
|
+
gitArgs.push("-c", `remote.origin.url=${injectTokenIntoUrl(repoUrl, token)}`);
|
|
7391
|
+
}
|
|
7392
|
+
gitArgs.push("pull");
|
|
7393
|
+
try {
|
|
7394
|
+
execFileSync3("git", gitArgs, { stdio: ["inherit", "inherit", "pipe"] });
|
|
7395
|
+
} catch (e) {
|
|
7396
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
7397
|
+
throw new Error(msg.replace(/x-token-auth:[^@]+@/g, "x-token-auth:***@"));
|
|
7398
|
+
}
|
|
7399
|
+
const pluginChoices = resolvePluginChoices(marketplacePath);
|
|
7400
|
+
if (pluginChoices.length === 0) {
|
|
7401
|
+
throw new Error("No plugins found in marketplace. Check the marketplace repository structure.");
|
|
7402
|
+
}
|
|
7403
|
+
let selectedPlugin;
|
|
7404
|
+
if (plugin) {
|
|
7405
|
+
if (!pluginChoices.some((p) => p.name === plugin)) {
|
|
7406
|
+
throw new Error(`Plugin "${plugin}" not found. Available: ${pluginChoices.map((p) => p.name).join(", ")}`);
|
|
7407
|
+
}
|
|
7408
|
+
selectedPlugin = plugin;
|
|
7409
|
+
} else {
|
|
7410
|
+
selectedPlugin = await select({
|
|
7411
|
+
message: "Select a plugin to install:",
|
|
7412
|
+
choices: pluginChoices.map((p) => ({
|
|
7413
|
+
value: p.name,
|
|
7414
|
+
name: p.description ? `${p.name} \u2014 ${p.description}` : p.name
|
|
7415
|
+
}))
|
|
7416
|
+
});
|
|
7417
|
+
}
|
|
7418
|
+
const skillsSrc = resolveSkillsSrc(marketplacePath, selectedPlugin);
|
|
7419
|
+
if (!fs3.existsSync(skillsSrc)) {
|
|
7420
|
+
throw new Error(`No skills directory found at ${skillsSrc}`);
|
|
7421
|
+
}
|
|
7422
|
+
const targetDir = opts.claude ? path5.join(os.homedir(), ".claude", "skills") : path5.join(os.homedir(), ".agents", "skills");
|
|
7423
|
+
const { installed, failed } = copyPluginSkills(skillsSrc, targetDir);
|
|
7424
|
+
if (failed.length > 0) {
|
|
7425
|
+
warn(`Skipped ${failed.length} skill(s) with invalid names: ${failed.join(", ")}`);
|
|
7426
|
+
}
|
|
7427
|
+
success({
|
|
7428
|
+
plugin: selectedPlugin,
|
|
7429
|
+
installed,
|
|
7430
|
+
failed,
|
|
7431
|
+
total: installed.length,
|
|
7432
|
+
target: targetDir
|
|
7433
|
+
}, "skills", "marketplace-sync", start);
|
|
7434
|
+
} catch (err) {
|
|
7435
|
+
fail(err, "skills", "marketplace-sync", start);
|
|
7436
|
+
}
|
|
7437
|
+
});
|
|
7438
|
+
}
|
|
7439
|
+
function resolvePluginChoices(marketplacePath) {
|
|
7440
|
+
const marketplaceJsonPath = path5.join(marketplacePath, ".claude-plugin", "marketplace.json");
|
|
7441
|
+
if (fs3.existsSync(marketplaceJsonPath)) {
|
|
7442
|
+
try {
|
|
7443
|
+
const meta = JSON.parse(fs3.readFileSync(marketplaceJsonPath, "utf8"));
|
|
7444
|
+
if (Array.isArray(meta.plugins) && meta.plugins.length > 0) {
|
|
7445
|
+
return meta.plugins.map((p) => ({ name: p.name, description: p.description ?? "" }));
|
|
7446
|
+
}
|
|
7447
|
+
} catch {
|
|
7448
|
+
}
|
|
7449
|
+
}
|
|
7450
|
+
const pluginsDir = path5.join(marketplacePath, "plugins");
|
|
7451
|
+
if (fs3.existsSync(pluginsDir)) {
|
|
7452
|
+
return fs3.readdirSync(pluginsDir).filter((name) => fs3.statSync(path5.join(pluginsDir, name)).isDirectory()).map((name) => ({ name, description: "" }));
|
|
7453
|
+
}
|
|
7454
|
+
return [];
|
|
7455
|
+
}
|
|
7456
|
+
function resolveSkillsSrc(marketplacePath, selectedPlugin) {
|
|
7457
|
+
const pluginsBase = path5.resolve(marketplacePath, "plugins");
|
|
7458
|
+
const skillsSrc = path5.resolve(pluginsBase, selectedPlugin, "skills");
|
|
7459
|
+
if (!skillsSrc.startsWith(pluginsBase + path5.sep)) {
|
|
7460
|
+
throw new Error(`Invalid plugin name: "${selectedPlugin}"`);
|
|
7461
|
+
}
|
|
7462
|
+
return skillsSrc;
|
|
7463
|
+
}
|
|
7464
|
+
function copyPluginSkills(skillsSrc, targetDir) {
|
|
7465
|
+
fs3.mkdirSync(targetDir, { recursive: true });
|
|
7466
|
+
const resolvedTarget = path5.resolve(targetDir);
|
|
7467
|
+
const skillNames = fs3.readdirSync(skillsSrc).filter(
|
|
7468
|
+
(name) => fs3.statSync(path5.join(skillsSrc, name)).isDirectory()
|
|
7469
|
+
);
|
|
7470
|
+
const installed = [];
|
|
7471
|
+
const failed = [];
|
|
7472
|
+
for (const skillName of skillNames) {
|
|
7473
|
+
const dest = path5.resolve(targetDir, skillName);
|
|
7474
|
+
if (!dest.startsWith(resolvedTarget + path5.sep)) {
|
|
7475
|
+
failed.push(skillName);
|
|
7476
|
+
continue;
|
|
7477
|
+
}
|
|
7478
|
+
fs3.rmSync(dest, { recursive: true, force: true });
|
|
7479
|
+
fs3.cpSync(path5.join(skillsSrc, skillName), dest, { recursive: true });
|
|
7480
|
+
installed.push(skillName);
|
|
7481
|
+
}
|
|
7482
|
+
return { installed, failed };
|
|
7109
7483
|
}
|
|
7110
7484
|
|
|
7111
7485
|
// src/cli.ts
|