@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.
Files changed (34) hide show
  1. package/copilot-instructions.md +34 -1
  2. package/dist/{chunk-O3LWAWSN.js → chunk-AWSXVWRN.js} +2 -1
  3. package/dist/{chunk-O3LWAWSN.js.map → chunk-AWSXVWRN.js.map} +1 -1
  4. package/dist/{chunk-3XHTRFRX.js → chunk-VPUWMP2M.js} +3 -2
  5. package/dist/chunk-VPUWMP2M.js.map +1 -0
  6. package/dist/cli.js +379 -8
  7. package/dist/cli.js.map +1 -1
  8. package/dist/{config-NCI2BTQG.js → config-OFLPO5DN.js} +4 -2
  9. package/dist/{http-L2P23H2D.js → http-LNDRNPP7.js} +2 -2
  10. package/package.json +5 -2
  11. package/skills/pncli/SKILL.md +92 -0
  12. package/skills/pncli/ado.md +33 -0
  13. package/skills/pncli/artifactory.md +35 -0
  14. package/skills/pncli/bitbucket.md +30 -0
  15. package/skills/pncli/checkmarx.md +31 -0
  16. package/skills/pncli/confluence.md +24 -0
  17. package/skills/pncli/contrast.md +39 -0
  18. package/skills/pncli/jenkins.md +29 -0
  19. package/skills/pncli/jira.md +35 -0
  20. package/skills/pncli/marketplace.md +41 -0
  21. package/skills/pncli/sde.md +29 -0
  22. package/skills/pncli/servicenow.md +33 -0
  23. package/skills/pncli/sonarqube.md +30 -0
  24. package/skills/pncli/sonatypeiq.md +39 -0
  25. package/skills/pncli/udeploy.md +48 -0
  26. package/dist/chunk-3XHTRFRX.js.map +0 -1
  27. package/skills/address-pr-feedback/SKILL.md +0 -87
  28. package/skills/code-review/SKILL.md +0 -82
  29. package/skills/local-setup/SKILL.md +0 -225
  30. package/skills/plan/SKILL.md +0 -120
  31. package/skills/security-review/SKILL.md +0 -108
  32. package/skills/ship/SKILL.md +0 -170
  33. /package/dist/{config-NCI2BTQG.js.map → config-OFLPO5DN.js.map} +0 -0
  34. /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-O3LWAWSN.js";
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-3XHTRFRX.js";
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 { input, password, confirm } from "@inquirer/prompts";
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 (leave blank for cloud: https://app.contrastsecurity.com):",
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-NCI2BTQG.js");
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-L2P23H2D.js");
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 content = fs3.readFileSync(path5.join(bundledDir, skillName, "SKILL.md"), "utf8");
7259
+ const mdFiles = fs3.readdirSync(path5.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
7029
7260
  fs3.mkdirSync(skillDir, { recursive: true });
7030
- fs3.writeFileSync(path5.join(skillDir, "SKILL.md"), content, "utf8");
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