@kolatts/pncli 1.10.1 → 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 +376 -7
- 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 {
|
|
@@ -4793,7 +5008,8 @@ async function initGlobalConfig(start) {
|
|
|
4793
5008
|
if (useContrast) {
|
|
4794
5009
|
process.stderr.write(" Find your credentials in Contrast under User Settings \u2192 Your Keys.\n");
|
|
4795
5010
|
contrastBaseUrl = await input({
|
|
4796
|
-
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",
|
|
4797
5013
|
default: ""
|
|
4798
5014
|
});
|
|
4799
5015
|
contrastOrgUuid = await input({
|
|
@@ -5854,11 +6070,11 @@ function registerAdoRepoCommands(ado) {
|
|
|
5854
6070
|
const start = Date.now();
|
|
5855
6071
|
try {
|
|
5856
6072
|
const globalOpts = ado.optsWithGlobals();
|
|
5857
|
-
const { loadConfig: lc } = await import("./config-
|
|
6073
|
+
const { loadConfig: lc } = await import("./config-OFLPO5DN.js");
|
|
5858
6074
|
const config = lc({ configPath: globalOpts.config });
|
|
5859
6075
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
5860
6076
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
5861
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
6077
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-LNDRNPP7.js");
|
|
5862
6078
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
5863
6079
|
const coreClient = new AdoCoreClient2(http);
|
|
5864
6080
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -6966,6 +7182,19 @@ import fs3 from "fs";
|
|
|
6966
7182
|
import path5 from "path";
|
|
6967
7183
|
import os from "os";
|
|
6968
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
|
+
}
|
|
6969
7198
|
var AGENT_PATHS = {
|
|
6970
7199
|
"github-copilot": { project: ".agents/skills", user: path5.join(os.homedir(), ".copilot/skills") },
|
|
6971
7200
|
"claude-code": { project: ".claude/skills", user: path5.join(os.homedir(), ".claude/skills") }
|
|
@@ -7025,9 +7254,12 @@ function registerSkillsCommands(program2) {
|
|
|
7025
7254
|
continue;
|
|
7026
7255
|
}
|
|
7027
7256
|
try {
|
|
7028
|
-
const
|
|
7257
|
+
const mdFiles = fs3.readdirSync(path5.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
|
|
7029
7258
|
fs3.mkdirSync(skillDir, { recursive: true });
|
|
7030
|
-
|
|
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
|
+
}
|
|
7031
7263
|
installed.push(skillName);
|
|
7032
7264
|
} catch {
|
|
7033
7265
|
failed.push(skillName);
|
|
@@ -7111,6 +7343,143 @@ function registerSkillsCommands(program2) {
|
|
|
7111
7343
|
fail(err, "skills", "list", start);
|
|
7112
7344
|
}
|
|
7113
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 };
|
|
7114
7483
|
}
|
|
7115
7484
|
|
|
7116
7485
|
// src/cli.ts
|