@kolatts/pncli 1.10.1 → 1.11.1
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 +379 -8
- 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 +5 -2
- 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 {
|
|
@@ -3822,7 +4037,9 @@ function registerDepsCommands(program2) {
|
|
|
3822
4037
|
}
|
|
3823
4038
|
|
|
3824
4039
|
// src/services/config/commands.ts
|
|
3825
|
-
import
|
|
4040
|
+
import input from "@inquirer/input";
|
|
4041
|
+
import password from "@inquirer/password";
|
|
4042
|
+
import confirm from "@inquirer/confirm";
|
|
3826
4043
|
|
|
3827
4044
|
// src/services/ado/client/work.ts
|
|
3828
4045
|
var API6 = "7.1";
|
|
@@ -4793,7 +5010,8 @@ async function initGlobalConfig(start) {
|
|
|
4793
5010
|
if (useContrast) {
|
|
4794
5011
|
process.stderr.write(" Find your credentials in Contrast under User Settings \u2192 Your Keys.\n");
|
|
4795
5012
|
contrastBaseUrl = await input({
|
|
4796
|
-
message: "Contrast base URL (
|
|
5013
|
+
message: "Contrast base URL (e.g. https://app.contrastsecurity.com for cloud SaaS):",
|
|
5014
|
+
validate: (v) => v.trim() ? true : "Base URL is required",
|
|
4797
5015
|
default: ""
|
|
4798
5016
|
});
|
|
4799
5017
|
contrastOrgUuid = await input({
|
|
@@ -5854,11 +6072,11 @@ function registerAdoRepoCommands(ado) {
|
|
|
5854
6072
|
const start = Date.now();
|
|
5855
6073
|
try {
|
|
5856
6074
|
const globalOpts = ado.optsWithGlobals();
|
|
5857
|
-
const { loadConfig: lc } = await import("./config-
|
|
6075
|
+
const { loadConfig: lc } = await import("./config-OFLPO5DN.js");
|
|
5858
6076
|
const config = lc({ configPath: globalOpts.config });
|
|
5859
6077
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
5860
6078
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
5861
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
6079
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-LNDRNPP7.js");
|
|
5862
6080
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
5863
6081
|
const coreClient = new AdoCoreClient2(http);
|
|
5864
6082
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -6966,6 +7184,19 @@ import fs3 from "fs";
|
|
|
6966
7184
|
import path5 from "path";
|
|
6967
7185
|
import os from "os";
|
|
6968
7186
|
import { fileURLToPath } from "url";
|
|
7187
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
7188
|
+
import select from "@inquirer/select";
|
|
7189
|
+
function injectTokenIntoUrl(url, token) {
|
|
7190
|
+
let parsed;
|
|
7191
|
+
try {
|
|
7192
|
+
parsed = new URL(url);
|
|
7193
|
+
} catch {
|
|
7194
|
+
throw new Error(`--token requires an HTTPS clone URL; got: ${url}`);
|
|
7195
|
+
}
|
|
7196
|
+
parsed.username = "x-token-auth";
|
|
7197
|
+
parsed.password = token;
|
|
7198
|
+
return parsed.toString();
|
|
7199
|
+
}
|
|
6969
7200
|
var AGENT_PATHS = {
|
|
6970
7201
|
"github-copilot": { project: ".agents/skills", user: path5.join(os.homedir(), ".copilot/skills") },
|
|
6971
7202
|
"claude-code": { project: ".claude/skills", user: path5.join(os.homedir(), ".claude/skills") }
|
|
@@ -7025,9 +7256,12 @@ function registerSkillsCommands(program2) {
|
|
|
7025
7256
|
continue;
|
|
7026
7257
|
}
|
|
7027
7258
|
try {
|
|
7028
|
-
const
|
|
7259
|
+
const mdFiles = fs3.readdirSync(path5.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
|
|
7029
7260
|
fs3.mkdirSync(skillDir, { recursive: true });
|
|
7030
|
-
|
|
7261
|
+
for (const mdFile of mdFiles) {
|
|
7262
|
+
const content = fs3.readFileSync(path5.join(bundledDir, skillName, mdFile), "utf8");
|
|
7263
|
+
fs3.writeFileSync(path5.join(skillDir, mdFile), content, "utf8");
|
|
7264
|
+
}
|
|
7031
7265
|
installed.push(skillName);
|
|
7032
7266
|
} catch {
|
|
7033
7267
|
failed.push(skillName);
|
|
@@ -7111,6 +7345,143 @@ function registerSkillsCommands(program2) {
|
|
|
7111
7345
|
fail(err, "skills", "list", start);
|
|
7112
7346
|
}
|
|
7113
7347
|
});
|
|
7348
|
+
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("<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) => {
|
|
7350
|
+
const start = Date.now();
|
|
7351
|
+
try {
|
|
7352
|
+
const resolvedPath = path5.resolve(localPath);
|
|
7353
|
+
const hasGit = fs3.existsSync(path5.join(resolvedPath, ".git"));
|
|
7354
|
+
if (fs3.existsSync(resolvedPath) && !hasGit && fs3.readdirSync(resolvedPath).length > 0) {
|
|
7355
|
+
throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
|
|
7356
|
+
}
|
|
7357
|
+
if (hasGit) {
|
|
7358
|
+
warn(`Directory already contains a git repo at ${resolvedPath} \u2014 skipping clone, updating config only.`);
|
|
7359
|
+
} else {
|
|
7360
|
+
warn(`Cloning ${url} (branch: ${opts.branch}) \u2192 ${resolvedPath}...`);
|
|
7361
|
+
const cloneUrl = opts.token ? injectTokenIntoUrl(url, opts.token) : url;
|
|
7362
|
+
try {
|
|
7363
|
+
execFileSync3("git", ["clone", "--branch", opts.branch, cloneUrl, resolvedPath], { stdio: ["inherit", "inherit", "pipe"] });
|
|
7364
|
+
} catch (e) {
|
|
7365
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
7366
|
+
throw new Error(msg.replace(/x-token-auth:[^@]+@/g, "x-token-auth:***@"));
|
|
7367
|
+
}
|
|
7368
|
+
}
|
|
7369
|
+
const configPath = getGlobalConfigPath();
|
|
7370
|
+
const existing = loadJsonFile(configPath) ?? {};
|
|
7371
|
+
existing.marketplace = { repoUrl: url, localPath: resolvedPath, ...opts.token ? { token: opts.token } : {} };
|
|
7372
|
+
writeGlobalConfig(existing, configPath);
|
|
7373
|
+
success({ repoUrl: url, localPath: resolvedPath, branch: opts.branch, tokenConfigured: !!opts.token }, "skills", "marketplace-setup", start);
|
|
7374
|
+
} catch (err) {
|
|
7375
|
+
fail(err, "skills", "marketplace-setup", start);
|
|
7376
|
+
}
|
|
7377
|
+
});
|
|
7378
|
+
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) => {
|
|
7379
|
+
const start = Date.now();
|
|
7380
|
+
try {
|
|
7381
|
+
const configPath = getGlobalConfigPath();
|
|
7382
|
+
const globalConfig = loadJsonFile(configPath) ?? {};
|
|
7383
|
+
const marketplacePath = globalConfig.marketplace?.localPath;
|
|
7384
|
+
if (!marketplacePath || !fs3.existsSync(marketplacePath)) {
|
|
7385
|
+
throw new Error("Marketplace not configured. Run: pncli skills marketplace setup <url> <localPath>");
|
|
7386
|
+
}
|
|
7387
|
+
warn("Pulling latest marketplace content...");
|
|
7388
|
+
const repoUrl = globalConfig.marketplace?.repoUrl;
|
|
7389
|
+
const token = globalConfig.marketplace?.token;
|
|
7390
|
+
const gitArgs = ["-C", marketplacePath];
|
|
7391
|
+
if (repoUrl && token) {
|
|
7392
|
+
gitArgs.push("-c", `remote.origin.url=${injectTokenIntoUrl(repoUrl, token)}`);
|
|
7393
|
+
}
|
|
7394
|
+
gitArgs.push("pull");
|
|
7395
|
+
try {
|
|
7396
|
+
execFileSync3("git", gitArgs, { stdio: ["inherit", "inherit", "pipe"] });
|
|
7397
|
+
} catch (e) {
|
|
7398
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
7399
|
+
throw new Error(msg.replace(/x-token-auth:[^@]+@/g, "x-token-auth:***@"));
|
|
7400
|
+
}
|
|
7401
|
+
const pluginChoices = resolvePluginChoices(marketplacePath);
|
|
7402
|
+
if (pluginChoices.length === 0) {
|
|
7403
|
+
throw new Error("No plugins found in marketplace. Check the marketplace repository structure.");
|
|
7404
|
+
}
|
|
7405
|
+
let selectedPlugin;
|
|
7406
|
+
if (plugin) {
|
|
7407
|
+
if (!pluginChoices.some((p) => p.name === plugin)) {
|
|
7408
|
+
throw new Error(`Plugin "${plugin}" not found. Available: ${pluginChoices.map((p) => p.name).join(", ")}`);
|
|
7409
|
+
}
|
|
7410
|
+
selectedPlugin = plugin;
|
|
7411
|
+
} else {
|
|
7412
|
+
selectedPlugin = await select({
|
|
7413
|
+
message: "Select a plugin to install:",
|
|
7414
|
+
choices: pluginChoices.map((p) => ({
|
|
7415
|
+
value: p.name,
|
|
7416
|
+
name: p.description ? `${p.name} \u2014 ${p.description}` : p.name
|
|
7417
|
+
}))
|
|
7418
|
+
});
|
|
7419
|
+
}
|
|
7420
|
+
const skillsSrc = resolveSkillsSrc(marketplacePath, selectedPlugin);
|
|
7421
|
+
if (!fs3.existsSync(skillsSrc)) {
|
|
7422
|
+
throw new Error(`No skills directory found at ${skillsSrc}`);
|
|
7423
|
+
}
|
|
7424
|
+
const targetDir = opts.claude ? path5.join(os.homedir(), ".claude", "skills") : path5.join(os.homedir(), ".agents", "skills");
|
|
7425
|
+
const { installed, failed } = copyPluginSkills(skillsSrc, targetDir);
|
|
7426
|
+
if (failed.length > 0) {
|
|
7427
|
+
warn(`Skipped ${failed.length} skill(s) with invalid names: ${failed.join(", ")}`);
|
|
7428
|
+
}
|
|
7429
|
+
success({
|
|
7430
|
+
plugin: selectedPlugin,
|
|
7431
|
+
installed,
|
|
7432
|
+
failed,
|
|
7433
|
+
total: installed.length,
|
|
7434
|
+
target: targetDir
|
|
7435
|
+
}, "skills", "marketplace-sync", start);
|
|
7436
|
+
} catch (err) {
|
|
7437
|
+
fail(err, "skills", "marketplace-sync", start);
|
|
7438
|
+
}
|
|
7439
|
+
});
|
|
7440
|
+
}
|
|
7441
|
+
function resolvePluginChoices(marketplacePath) {
|
|
7442
|
+
const marketplaceJsonPath = path5.join(marketplacePath, ".claude-plugin", "marketplace.json");
|
|
7443
|
+
if (fs3.existsSync(marketplaceJsonPath)) {
|
|
7444
|
+
try {
|
|
7445
|
+
const meta = JSON.parse(fs3.readFileSync(marketplaceJsonPath, "utf8"));
|
|
7446
|
+
if (Array.isArray(meta.plugins) && meta.plugins.length > 0) {
|
|
7447
|
+
return meta.plugins.map((p) => ({ name: p.name, description: p.description ?? "" }));
|
|
7448
|
+
}
|
|
7449
|
+
} catch {
|
|
7450
|
+
}
|
|
7451
|
+
}
|
|
7452
|
+
const pluginsDir = path5.join(marketplacePath, "plugins");
|
|
7453
|
+
if (fs3.existsSync(pluginsDir)) {
|
|
7454
|
+
return fs3.readdirSync(pluginsDir).filter((name) => fs3.statSync(path5.join(pluginsDir, name)).isDirectory()).map((name) => ({ name, description: "" }));
|
|
7455
|
+
}
|
|
7456
|
+
return [];
|
|
7457
|
+
}
|
|
7458
|
+
function resolveSkillsSrc(marketplacePath, selectedPlugin) {
|
|
7459
|
+
const pluginsBase = path5.resolve(marketplacePath, "plugins");
|
|
7460
|
+
const skillsSrc = path5.resolve(pluginsBase, selectedPlugin, "skills");
|
|
7461
|
+
if (!skillsSrc.startsWith(pluginsBase + path5.sep)) {
|
|
7462
|
+
throw new Error(`Invalid plugin name: "${selectedPlugin}"`);
|
|
7463
|
+
}
|
|
7464
|
+
return skillsSrc;
|
|
7465
|
+
}
|
|
7466
|
+
function copyPluginSkills(skillsSrc, targetDir) {
|
|
7467
|
+
fs3.mkdirSync(targetDir, { recursive: true });
|
|
7468
|
+
const resolvedTarget = path5.resolve(targetDir);
|
|
7469
|
+
const skillNames = fs3.readdirSync(skillsSrc).filter(
|
|
7470
|
+
(name) => fs3.statSync(path5.join(skillsSrc, name)).isDirectory()
|
|
7471
|
+
);
|
|
7472
|
+
const installed = [];
|
|
7473
|
+
const failed = [];
|
|
7474
|
+
for (const skillName of skillNames) {
|
|
7475
|
+
const dest = path5.resolve(targetDir, skillName);
|
|
7476
|
+
if (!dest.startsWith(resolvedTarget + path5.sep)) {
|
|
7477
|
+
failed.push(skillName);
|
|
7478
|
+
continue;
|
|
7479
|
+
}
|
|
7480
|
+
fs3.rmSync(dest, { recursive: true, force: true });
|
|
7481
|
+
fs3.cpSync(path5.join(skillsSrc, skillName), dest, { recursive: true });
|
|
7482
|
+
installed.push(skillName);
|
|
7483
|
+
}
|
|
7484
|
+
return { installed, failed };
|
|
7114
7485
|
}
|
|
7115
7486
|
|
|
7116
7487
|
// src/cli.ts
|