@kolatts/pncli 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -18,8 +18,9 @@ import {
18
18
  setGlobalOptions,
19
19
  setGlobalUser,
20
20
  success,
21
- warn
22
- } from "./chunk-SLQOSGZJ.js";
21
+ warn,
22
+ writeRawOutput
23
+ } from "./chunk-BNT4SEZD.js";
23
24
  import {
24
25
  AdoCoreClient
25
26
  } from "./chunk-VDL32PSG.js";
@@ -276,21 +277,30 @@ function parseBranchReportOutput(raw, branchName, opts) {
276
277
  }
277
278
  }
278
279
  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);
280
+ const isAbsoluteDate = (s) => /^\d{4}-\d{2}-\d{2}/.test(s);
281
+ const sinceDate = opts.since && isAbsoluteDate(opts.since) ? opts.since.slice(0, 10) : null;
282
+ const untilDate = opts.until && isAbsoluteDate(opts.until) ? opts.until.slice(0, 10) : null;
283
+ const filteredCommits = sinceDate || untilDate ? commits.filter((c) => {
284
+ const d = c.date.slice(0, 10);
285
+ if (sinceDate && d < sinceDate) return false;
286
+ if (untilDate && d > untilDate) return false;
287
+ return true;
288
+ }) : commits;
289
+ const totalInsertions = filteredCommits.reduce((sum, c) => sum + c.insertions, 0);
290
+ const totalDeletions = filteredCommits.reduce((sum, c) => sum + c.deletions, 0);
281
291
  return {
282
292
  branch: branchName,
283
293
  since: opts.since ?? null,
284
294
  until: opts.until ?? null,
285
- commits,
286
- weeks: buildWeeklyBreakdown(commits),
287
- byAuthor: buildByAuthor(commits),
295
+ commits: filteredCommits,
296
+ weeks: buildWeeklyBreakdown(filteredCommits),
297
+ byAuthor: buildByAuthor(filteredCommits),
288
298
  summary: {
289
- commitCount: commits.length,
299
+ commitCount: filteredCommits.length,
290
300
  totalInsertions,
291
301
  totalDeletions,
292
302
  totalNetLines: totalInsertions - totalDeletions,
293
- totalFilesChanged: commits.reduce((sum, c) => sum + c.filesChanged, 0)
303
+ totalFilesChanged: filteredCommits.reduce((sum, c) => sum + c.filesChanged, 0)
294
304
  }
295
305
  };
296
306
  }
@@ -411,34 +421,34 @@ function parseAdoRemote(remoteUrl, adoBaseUrl) {
411
421
  } catch {
412
422
  baseHostname = adoBaseUrl.replace(/\/$/, "").replace(/^https?:\/\//, "").split(/[:/]/)[0].toLowerCase();
413
423
  }
414
- let path6 = remoteUrl;
424
+ let path8 = remoteUrl;
415
425
  let remoteHostname = "";
416
- const sshProtoMatch = path6.match(/^ssh:\/\/[^@]*@?([^/:]+)(?::\d+)?(.*)/);
426
+ const sshProtoMatch = path8.match(/^ssh:\/\/[^@]*@?([^/:]+)(?::\d+)?(.*)/);
417
427
  if (sshProtoMatch) {
418
428
  remoteHostname = sshProtoMatch[1].toLowerCase();
419
- path6 = sshProtoMatch[2];
429
+ path8 = sshProtoMatch[2];
420
430
  } else {
421
- const gitAtMatch = path6.match(/^git@([^:]+)(?::\d+)?:(.*)/);
431
+ const gitAtMatch = path8.match(/^git@([^:]+)(?::\d+)?:(.*)/);
422
432
  if (gitAtMatch) {
423
433
  remoteHostname = gitAtMatch[1].toLowerCase();
424
- path6 = "/" + gitAtMatch[2];
434
+ path8 = "/" + gitAtMatch[2];
425
435
  } else {
426
- const httpsMatch = path6.match(/^https?:\/\/([^/:]+)(?::\d+)?(.*)/);
436
+ const httpsMatch = path8.match(/^https?:\/\/([^/:]+)(?::\d+)?(.*)/);
427
437
  if (httpsMatch) {
428
438
  remoteHostname = httpsMatch[1].toLowerCase();
429
- path6 = httpsMatch[2];
439
+ path8 = httpsMatch[2];
430
440
  }
431
441
  }
432
442
  }
433
443
  if (!remoteHostname || remoteHostname !== baseHostname) return null;
434
- path6 = path6.replace(/\.git$/, "");
435
- const gitIdx = path6.indexOf("/_git/");
436
- const sshIdx = path6.indexOf("/_ssh/");
444
+ path8 = path8.replace(/\.git$/, "");
445
+ const gitIdx = path8.indexOf("/_git/");
446
+ const sshIdx = path8.indexOf("/_ssh/");
437
447
  const markerIdx = gitIdx !== -1 ? gitIdx : sshIdx;
438
448
  if (markerIdx === -1) return null;
439
- const repo = decodeURIComponent(path6.slice(markerIdx + 6));
449
+ const repo = decodeURIComponent(path8.slice(markerIdx + 6));
440
450
  if (!repo) return null;
441
- const before = path6.slice(1, markerIdx);
451
+ const before = path8.slice(1, markerIdx);
442
452
  const parts = before.split("/").filter(Boolean);
443
453
  if (parts.length < 2) return null;
444
454
  const project = decodeURIComponent(parts[parts.length - 1]);
@@ -742,7 +752,7 @@ function registerGitCommands(program2) {
742
752
  until: opts.until
743
753
  });
744
754
  if (opts.csv) {
745
- process.stdout.write(formatBranchReportCsv(report));
755
+ writeRawOutput(formatBranchReportCsv(report));
746
756
  } else {
747
757
  success(report, "git", "report", start);
748
758
  }
@@ -783,9 +793,12 @@ function registerGitCommands(program2) {
783
793
  }
784
794
 
785
795
  // src/services/jira/commands.ts
786
- import { readFileSync } from "fs";
796
+ import { readFileSync as readFileSync2, existsSync, writeFileSync, mkdirSync } from "fs";
797
+ import path from "path";
787
798
 
788
799
  // src/services/jira/client.ts
800
+ import { readFileSync } from "fs";
801
+ import { basename, extname } from "path";
789
802
  var API2 = "/rest/api/2";
790
803
  var JiraClient = class {
791
804
  constructor(http) {
@@ -925,6 +938,36 @@ var JiraClient = class {
925
938
  }
926
939
  return result;
927
940
  }
941
+ async addLabels(key, labels) {
942
+ await this.http.jira(`${API2}/issue/${key}`, {
943
+ method: "PUT",
944
+ body: { update: { labels: labels.map((l) => ({ add: l })) } }
945
+ });
946
+ }
947
+ async removeLabels(key, labels) {
948
+ await this.http.jira(`${API2}/issue/${key}`, {
949
+ method: "PUT",
950
+ body: { update: { labels: labels.map((l) => ({ remove: l })) } }
951
+ });
952
+ }
953
+ async uploadAttachment(key, filePath) {
954
+ const fileContent = readFileSync(filePath);
955
+ const fileName = basename(filePath);
956
+ const mimeType = guessMimeType(filePath);
957
+ const formData = new FormData();
958
+ formData.append("file", new Blob([fileContent], { type: mimeType }), fileName);
959
+ return this.http.jiraUpload(`${API2}/issue/${key}/attachments`, formData);
960
+ }
961
+ async listAttachments(key) {
962
+ const result = await this.http.jira(
963
+ `${API2}/issue/${key}`,
964
+ { params: { fields: "attachment" } }
965
+ );
966
+ return result.fields.attachment ?? [];
967
+ }
968
+ async downloadAttachment(contentUrl) {
969
+ return this.http.jiraBuffer(contentUrl);
970
+ }
928
971
  async assignIssue(key, accountId) {
929
972
  await this.http.jira(`${API2}/issue/${key}/assignee`, {
930
973
  method: "PUT",
@@ -953,6 +996,33 @@ var JiraClient = class {
953
996
  });
954
997
  }
955
998
  };
999
+ function guessMimeType(filePath) {
1000
+ const ext = extname(filePath).toLowerCase();
1001
+ const map = {
1002
+ ".jpg": "image/jpeg",
1003
+ ".jpeg": "image/jpeg",
1004
+ ".png": "image/png",
1005
+ ".gif": "image/gif",
1006
+ ".svg": "image/svg+xml",
1007
+ ".pdf": "application/pdf",
1008
+ ".txt": "text/plain",
1009
+ ".log": "text/plain",
1010
+ ".csv": "text/csv",
1011
+ ".json": "application/json",
1012
+ ".xml": "application/xml",
1013
+ ".zip": "application/zip",
1014
+ ".tar": "application/x-tar",
1015
+ ".gz": "application/gzip",
1016
+ ".md": "text/markdown",
1017
+ ".html": "text/html",
1018
+ ".htm": "text/html",
1019
+ ".doc": "application/msword",
1020
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1021
+ ".xls": "application/vnd.ms-excel",
1022
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
1023
+ };
1024
+ return map[ext] ?? "application/octet-stream";
1025
+ }
956
1026
  function schemaToPncliType(schema) {
957
1027
  if (!schema) return void 0;
958
1028
  const custom = schema.custom ?? "";
@@ -1191,6 +1261,28 @@ function registerJiraCommands(program2) {
1191
1261
  fail(err, "jira", "search", start);
1192
1262
  }
1193
1263
  });
1264
+ jira.command("add-label").description("Add one or more labels to a Jira issue (non-destructive)").requiredOption("--key <issue-key>", "Issue key").requiredOption("--labels <labels>", "Comma-separated labels to add").action(async (opts) => {
1265
+ const start = Date.now();
1266
+ try {
1267
+ const client = getClient(program2);
1268
+ const labels = opts.labels.split(",").map((s) => s.trim()).filter(Boolean);
1269
+ await client.addLabels(opts.key, labels);
1270
+ success({ updated: opts.key, labelsAdded: labels }, "jira", "add-label", start);
1271
+ } catch (err) {
1272
+ fail(err, "jira", "add-label", start);
1273
+ }
1274
+ });
1275
+ jira.command("remove-label").description("Remove one or more labels from a Jira issue (non-destructive)").requiredOption("--key <issue-key>", "Issue key").requiredOption("--labels <labels>", "Comma-separated labels to remove").action(async (opts) => {
1276
+ const start = Date.now();
1277
+ try {
1278
+ const client = getClient(program2);
1279
+ const labels = opts.labels.split(",").map((s) => s.trim()).filter(Boolean);
1280
+ await client.removeLabels(opts.key, labels);
1281
+ success({ updated: opts.key, labelsRemoved: labels }, "jira", "remove-label", start);
1282
+ } catch (err) {
1283
+ fail(err, "jira", "remove-label", start);
1284
+ }
1285
+ });
1194
1286
  jira.command("assign").description("Assign a Jira issue to a user").requiredOption("--key <issue-key>", "Issue key").requiredOption("--assignee <accountId>", "Assignee account ID").action(async (opts) => {
1195
1287
  const start = Date.now();
1196
1288
  try {
@@ -1211,6 +1303,44 @@ function registerJiraCommands(program2) {
1211
1303
  fail(err, "jira", "link-issue", start);
1212
1304
  }
1213
1305
  });
1306
+ jira.command("add-attachment").description("Upload a file as an attachment to a Jira issue").requiredOption("--key <issue-key>", "Issue key (e.g. PROJ-123)").requiredOption("--file <path>", "Path to the file to upload").action(async (opts) => {
1307
+ const start = Date.now();
1308
+ try {
1309
+ if (!existsSync(opts.file)) throw new PncliError(`File not found: ${opts.file}`, 1);
1310
+ const client = getClient(program2);
1311
+ const data = await client.uploadAttachment(opts.key, opts.file);
1312
+ success(data, "jira", "add-attachment", start);
1313
+ } catch (err) {
1314
+ fail(err, "jira", "add-attachment", start);
1315
+ }
1316
+ });
1317
+ jira.command("list-attachments").description("List attachments on a Jira issue").requiredOption("--key <issue-key>", "Issue key (e.g. PROJ-123)").action(async (opts) => {
1318
+ const start = Date.now();
1319
+ try {
1320
+ const client = getClient(program2);
1321
+ const data = await client.listAttachments(opts.key);
1322
+ success(data, "jira", "list-attachments", start);
1323
+ } catch (err) {
1324
+ fail(err, "jira", "list-attachments", start);
1325
+ }
1326
+ });
1327
+ jira.command("download-attachment").description("Download a Jira issue attachment to .pncli/ (or --dir)").requiredOption("--key <issue-key>", "Issue key (e.g. PROJ-123)").requiredOption("--attachment-id <id>", "Attachment ID from list-attachments output").option("--dir <path>", "Output directory (default: .pncli relative to cwd)").action(async (opts) => {
1328
+ const start = Date.now();
1329
+ try {
1330
+ const client = getClient(program2);
1331
+ const attachments = await client.listAttachments(opts.key);
1332
+ const attachment = attachments.find((a) => a.id === opts.attachmentId);
1333
+ if (!attachment) throw new PncliError(`Attachment not found: ${opts.attachmentId}`, 1);
1334
+ const outDir = opts.dir ?? path.join(process.cwd(), ".pncli");
1335
+ mkdirSync(outDir, { recursive: true });
1336
+ const outPath = path.join(outDir, path.basename(attachment.filename));
1337
+ const buffer = await client.downloadAttachment(attachment.content);
1338
+ writeFileSync(outPath, buffer);
1339
+ success({ saved: outPath, filename: attachment.filename, size: buffer.length }, "jira", "download-attachment", start);
1340
+ } catch (err) {
1341
+ fail(err, "jira", "download-attachment", start);
1342
+ }
1343
+ });
1214
1344
  jira.command("fields").description("List custom fields (configured or discovered from Jira API) (consider --output-file for large results)").option("--discover", "Fetch field metadata from Jira API").option("--custom-only", "Show only custom fields (requires --discover)").option("--project <key>", "Project key \u2014 fetches allowedValues for select fields via createmeta (requires --discover)").option("--issue-type <type>", "Filter allowedValues to a specific issue type (requires --project)").action(async (opts) => {
1215
1345
  const start = Date.now();
1216
1346
  try {
@@ -1244,7 +1374,7 @@ function parseFieldArgs(fieldArgs, fieldMap) {
1244
1374
  const filePath = value.slice(1);
1245
1375
  let raw;
1246
1376
  try {
1247
- raw = readFileSync(filePath, "utf8").trim();
1377
+ raw = readFileSync2(filePath, "utf8").trim();
1248
1378
  } catch (e) {
1249
1379
  throw new PncliError(`Cannot read file "${filePath}": ${e.message}`, 1);
1250
1380
  }
@@ -1262,7 +1392,7 @@ function parseFieldArgs(fieldArgs, fieldMap) {
1262
1392
  function parseFieldsFile(filePath, fieldMap) {
1263
1393
  let raw;
1264
1394
  try {
1265
- raw = readFileSync(filePath, "utf8");
1395
+ raw = readFileSync2(filePath, "utf8");
1266
1396
  } catch (e) {
1267
1397
  throw new PncliError(`Cannot read fields file "${filePath}": ${e.message}`, 1);
1268
1398
  }
@@ -1562,9 +1692,9 @@ var ConfluenceClient = class {
1562
1692
  const initialStart = opts.start ?? 0;
1563
1693
  return this.http.confluencePaginate(async (start, limit) => {
1564
1694
  return this.http.confluence(`${API3}/content`, {
1565
- params: { spaceKey, type: "page", expand: "version", start: initialStart + start, limit: opts.limit ?? limit }
1695
+ params: { spaceKey, type: "page", expand: "version", start: initialStart + start, limit }
1566
1696
  });
1567
- });
1697
+ }, opts.limit);
1568
1698
  }
1569
1699
  async getPageChildren(id) {
1570
1700
  return this.http.confluencePaginate(async (start, limit) => {
@@ -1667,11 +1797,11 @@ var ConfluenceClient = class {
1667
1797
  return this.http.confluence(`${API3}/space`, {
1668
1798
  params: {
1669
1799
  start: initialStart + start,
1670
- limit: opts.limit ?? limit,
1800
+ limit,
1671
1801
  ...opts.type ? { type: opts.type } : {}
1672
1802
  }
1673
1803
  });
1674
- });
1804
+ }, opts.limit);
1675
1805
  }
1676
1806
  async getSpace(key) {
1677
1807
  return this.http.confluence(`${API3}/space/${key}`, {
@@ -1718,7 +1848,7 @@ function registerConfluenceCommands(program2) {
1718
1848
  fail(err, "confluence", "get-page-by-title", start);
1719
1849
  }
1720
1850
  });
1721
- confluence.command("list-pages").description("List pages in a Confluence space (consider --output-file for large results)").requiredOption("--space <key>", "Space key").option("--limit <n>", "Max results per page (default: all)").option("--start <n>", "Offset for first result").action(async (opts) => {
1851
+ confluence.command("list-pages").description("List pages in a Confluence space (consider --output-file for large results)").requiredOption("--space <key>", "Space key").option("--limit <n>", "Max total pages to return (default: all)").option("--start <n>", "Offset for first result").action(async (opts) => {
1722
1852
  const start = Date.now();
1723
1853
  try {
1724
1854
  const client = getClient3(program2);
@@ -1850,7 +1980,7 @@ function registerConfluenceCommands(program2) {
1850
1980
  fail(err, "confluence", "remove-label", start);
1851
1981
  }
1852
1982
  });
1853
- confluence.command("list-spaces").description("List Confluence spaces").option("--type <type>", "Space type: global or personal").option("--limit <n>", "Max results (default: all)").action(async (opts) => {
1983
+ confluence.command("list-spaces").description("List Confluence spaces").option("--type <type>", "Space type: global or personal").option("--limit <n>", "Max total spaces to return (default: all)").action(async (opts) => {
1854
1984
  const start = Date.now();
1855
1985
  try {
1856
1986
  const client = getClient3(program2);
@@ -2426,23 +2556,23 @@ import { Option } from "commander";
2426
2556
 
2427
2557
  // src/services/deps/parsers/index.ts
2428
2558
  import fs from "fs";
2429
- import path4 from "path";
2559
+ import path5 from "path";
2430
2560
  import { execSync as execSync3, execFileSync as execFileSync2 } from "child_process";
2431
2561
 
2432
2562
  // src/services/deps/parsers/npm.ts
2433
- import path from "path";
2563
+ import path2 from "path";
2434
2564
  function findNpmManifests(files) {
2435
2565
  const lockFiles = new Set(
2436
2566
  files.filter((f) => {
2437
- const base = path.basename(f);
2567
+ const base = path2.basename(f);
2438
2568
  return base === "package-lock.json" || base === "yarn.lock" || base === "pnpm-lock.yaml";
2439
2569
  })
2440
2570
  );
2441
2571
  const manifests = [];
2442
2572
  for (const file of files) {
2443
- if (path.basename(file) !== "package.json") continue;
2573
+ if (path2.basename(file) !== "package.json") continue;
2444
2574
  if (file.split("/").includes("node_modules")) continue;
2445
- const dir = path.dirname(file);
2575
+ const dir = path2.dirname(file);
2446
2576
  let lockFile;
2447
2577
  for (const lf of ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"]) {
2448
2578
  const candidate = dir === "." ? lf : `${dir}/${lf}`;
@@ -2457,7 +2587,7 @@ function findNpmManifests(files) {
2457
2587
  }
2458
2588
  function parseNpmPackages(content, manifest, opts, lockContent) {
2459
2589
  if (lockContent && manifest.lockFile) {
2460
- const base = path.basename(manifest.lockFile);
2590
+ const base = path2.basename(manifest.lockFile);
2461
2591
  if (base === "package-lock.json") {
2462
2592
  const pkgs = parsePackageLock(lockContent, manifest.lockFile, opts);
2463
2593
  if (pkgs.length > 0) return pkgs;
@@ -2605,26 +2735,26 @@ function parsePnpmLock(content, lockFilePath, opts, packageJsonContent) {
2605
2735
  }
2606
2736
 
2607
2737
  // src/services/deps/parsers/nuget.ts
2608
- import path2 from "path";
2738
+ import path3 from "path";
2609
2739
  var PROJ_EXTENSIONS = /* @__PURE__ */ new Set([".csproj", ".fsproj", ".vbproj"]);
2610
2740
  function findNugetManifests(files) {
2611
2741
  const lockFiles = /* @__PURE__ */ new Map();
2612
2742
  for (const f of files) {
2613
- if (path2.basename(f) === "packages.lock.json") {
2614
- lockFiles.set(path2.dirname(f), f);
2743
+ if (path3.basename(f) === "packages.lock.json") {
2744
+ lockFiles.set(path3.dirname(f), f);
2615
2745
  }
2616
2746
  }
2617
2747
  const manifests = [];
2618
2748
  for (const file of files) {
2619
- const base = path2.basename(file);
2620
- const ext = path2.extname(file);
2749
+ const base = path3.basename(file);
2750
+ const ext = path3.extname(file);
2621
2751
  if (PROJ_EXTENSIONS.has(ext) || base === "packages.config") {
2622
- const dir = path2.dirname(file);
2752
+ const dir = path3.dirname(file);
2623
2753
  const lockFile = lockFiles.get(dir);
2624
2754
  manifests.push({ file, ecosystem: "nuget", lockFile });
2625
2755
  }
2626
2756
  if (base === "Directory.Packages.props") {
2627
- const dir = path2.dirname(file);
2757
+ const dir = path3.dirname(file);
2628
2758
  manifests.push({ file, ecosystem: "nuget", lockFile: lockFiles.get(dir) });
2629
2759
  }
2630
2760
  }
@@ -2643,7 +2773,7 @@ function findNugetManifests(files) {
2643
2773
  return deduped;
2644
2774
  }
2645
2775
  function parseNugetPackages(content, manifest, opts, lockContent, propsContent) {
2646
- const base = path2.basename(manifest.file);
2776
+ const base = path3.basename(manifest.file);
2647
2777
  if (lockContent) {
2648
2778
  return parsePackagesLock(lockContent, manifest.lockFile ?? manifest.file, opts);
2649
2779
  }
@@ -2764,14 +2894,14 @@ function parsePackagesLock(content, filePath, opts) {
2764
2894
  }
2765
2895
 
2766
2896
  // src/services/deps/parsers/maven.ts
2767
- import path3 from "path";
2897
+ import path4 from "path";
2768
2898
  function findMavenManifests(files) {
2769
- const lockFiles = new Set(files.filter((f) => path3.basename(f) === "gradle.lockfile"));
2899
+ const lockFiles = new Set(files.filter((f) => path4.basename(f) === "gradle.lockfile"));
2770
2900
  const manifests = [];
2771
2901
  for (const file of files) {
2772
- const base = path3.basename(file);
2902
+ const base = path4.basename(file);
2773
2903
  if (base === "pom.xml" || base === "build.gradle" || base === "build.gradle.kts") {
2774
- const dir = path3.dirname(file);
2904
+ const dir = path4.dirname(file);
2775
2905
  const lockCandidate = dir === "." ? "gradle.lockfile" : `${dir}/gradle.lockfile`;
2776
2906
  const lockFile = lockFiles.has(lockCandidate) ? lockCandidate : void 0;
2777
2907
  manifests.push({ file, ecosystem: "maven", lockFile });
@@ -2780,7 +2910,7 @@ function findMavenManifests(files) {
2780
2910
  return manifests;
2781
2911
  }
2782
2912
  function parseMavenPackages(content, manifest, opts, lockContent) {
2783
- const base = path3.basename(manifest.file);
2913
+ const base = path4.basename(manifest.file);
2784
2914
  if (lockContent && manifest.lockFile) {
2785
2915
  return parseGradleLock(lockContent, manifest.lockFile, opts);
2786
2916
  }
@@ -2941,7 +3071,7 @@ function getRepoFiles(repoRoot) {
2941
3071
  }
2942
3072
  function readFile(repoRoot, relPath) {
2943
3073
  try {
2944
- return fs.readFileSync(path4.join(repoRoot, relPath), "utf8");
3074
+ return fs.readFileSync(path5.join(repoRoot, relPath), "utf8");
2945
3075
  } catch {
2946
3076
  return null;
2947
3077
  }
@@ -2959,13 +3089,13 @@ function readFileAtRef(repoRoot, ref, relPath) {
2959
3089
  }
2960
3090
  }
2961
3091
  function findPropsContent(manifestFile, readFn) {
2962
- let dir = path4.dirname(manifestFile);
3092
+ let dir = path5.dirname(manifestFile);
2963
3093
  for (let depth = 0; depth < 10; depth++) {
2964
3094
  const candidate = dir === "." ? "Directory.Packages.props" : `${dir}/Directory.Packages.props`;
2965
3095
  const content = readFn(candidate);
2966
3096
  if (content) return content;
2967
3097
  if (dir === ".") break;
2968
- dir = path4.dirname(dir);
3098
+ dir = path5.dirname(dir);
2969
3099
  }
2970
3100
  return null;
2971
3101
  }
@@ -4243,6 +4373,24 @@ var AdoWorkClient = class {
4243
4373
  { method: "POST", body: { text } }
4244
4374
  );
4245
4375
  }
4376
+ async addTags(collection, id, tags) {
4377
+ const item = await this.getWorkItem(collection, id);
4378
+ const existing = item.fields["System.Tags"] ?? "";
4379
+ const current = existing.split(";").map((t) => t.trim()).filter(Boolean);
4380
+ const lowerSet = new Set(current.map((t) => t.toLowerCase()));
4381
+ const toAdd = tags.filter((t) => !lowerSet.has(t.toLowerCase()));
4382
+ const merged = [...current, ...toAdd];
4383
+ const patch = [{ op: "add", path: "/fields/System.Tags", value: merged.join("; ") }];
4384
+ return this.updateWorkItem(collection, id, patch);
4385
+ }
4386
+ async removeTags(collection, id, tags) {
4387
+ const item = await this.getWorkItem(collection, id);
4388
+ const existing = item.fields["System.Tags"] ?? "";
4389
+ const lower = new Set(tags.map((t) => t.toLowerCase()));
4390
+ const remaining = existing.split(";").map((t) => t.trim()).filter((t) => t && !lower.has(t.toLowerCase()));
4391
+ const patch = [{ op: "add", path: "/fields/System.Tags", value: remaining.join("; ") }];
4392
+ return this.updateWorkItem(collection, id, patch);
4393
+ }
4246
4394
  async listWorkItemTypes(collection, project) {
4247
4395
  const result = await this.http.ado(
4248
4396
  `/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/wit/workitemtypes?api-version=${API6}`
@@ -4268,6 +4416,24 @@ var AdoWorkClient = class {
4268
4416
  );
4269
4417
  return result.value ?? [];
4270
4418
  }
4419
+ async listAttachments(collection, workItemId) {
4420
+ const item = await this.getWorkItem(collection, workItemId);
4421
+ return (item.relations ?? []).filter((r) => r.rel === "AttachedFile").map((r) => {
4422
+ const attrs = r.attributes ?? {};
4423
+ return {
4424
+ id: new URL(r.url).pathname.split("/").pop() ?? "",
4425
+ name: String(attrs["name"] ?? ""),
4426
+ url: r.url,
4427
+ comment: attrs["comment"] !== void 0 ? String(attrs["comment"]) : void 0,
4428
+ resourceSize: typeof attrs["resourceSize"] === "number" ? attrs["resourceSize"] : void 0,
4429
+ resourceCreatedDate: attrs["resourceCreatedDate"] !== void 0 ? String(attrs["resourceCreatedDate"]) : void 0,
4430
+ resourceModifiedDate: attrs["resourceModifiedDate"] !== void 0 ? String(attrs["resourceModifiedDate"]) : void 0
4431
+ };
4432
+ });
4433
+ }
4434
+ async downloadAttachment(absoluteUrl) {
4435
+ return this.http.adoBuffer(absoluteUrl);
4436
+ }
4271
4437
  };
4272
4438
 
4273
4439
  // src/services/ado/discovery.ts
@@ -4441,8 +4607,8 @@ function registerConfigCommands(program2) {
4441
4607
  if (cfg.ado.baseUrl) {
4442
4608
  try {
4443
4609
  const collection = cfg.defaults.ado?.collection;
4444
- const path6 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
4445
- await http.ado(path6);
4610
+ const path8 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
4611
+ await http.ado(path8);
4446
4612
  results.ado = { ok: true, message: "connected" };
4447
4613
  } catch (err) {
4448
4614
  results.ado = { ok: false, message: err instanceof Error ? err.message : String(err) };
@@ -4614,8 +4780,8 @@ function registerConfigCommands(program2) {
4614
4780
  } else {
4615
4781
  try {
4616
4782
  const collection = cfg.defaults.ado?.collection;
4617
- const path6 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
4618
- await http.ado(path6, { timeoutMs: 1e4 });
4783
+ const path8 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
4784
+ await http.ado(path8, { timeoutMs: 1e4 });
4619
4785
  results.ado = { status: "valid", message: "ok" };
4620
4786
  } catch (err) {
4621
4787
  results.ado = categorize2(err);
@@ -5777,6 +5943,8 @@ function registerAdoProjectCommands(ado) {
5777
5943
  }
5778
5944
 
5779
5945
  // src/services/ado/commands/work.ts
5946
+ import fs3 from "fs";
5947
+ import path6 from "path";
5780
5948
  function registerAdoWorkCommands(ado) {
5781
5949
  const work = ado.command("work").description("Azure DevOps work item operations");
5782
5950
  work.command("get").description("Get a work item by ID").requiredOption("--id <n>", "Work item ID").action(async (opts) => {
@@ -5917,6 +6085,55 @@ function registerAdoWorkCommands(ado) {
5917
6085
  fail(err, "ado", "work-add-comment", start);
5918
6086
  }
5919
6087
  });
6088
+ work.command("add-tag").description("Add one or more tags to a work item (non-destructive)").requiredOption("--id <n>", "Work item ID").requiredOption("--tags <tags>", "Semicolon- or comma-separated tags to add").action(async (opts) => {
6089
+ const start = Date.now();
6090
+ try {
6091
+ const { collection, workClient } = getAdoContext(ado);
6092
+ const tags = opts.tags.split(/[;,]/).map((t) => t.trim()).filter(Boolean);
6093
+ const data = await workClient.addTags(collection, parseInt(opts.id, 10), tags);
6094
+ success(data, "ado", "work-add-tag", start);
6095
+ } catch (err) {
6096
+ fail(err, "ado", "work-add-tag", start);
6097
+ }
6098
+ });
6099
+ work.command("remove-tag").description("Remove one or more tags from a work item (non-destructive)").requiredOption("--id <n>", "Work item ID").requiredOption("--tags <tags>", "Semicolon- or comma-separated tags to remove").action(async (opts) => {
6100
+ const start = Date.now();
6101
+ try {
6102
+ const { collection, workClient } = getAdoContext(ado);
6103
+ const tags = opts.tags.split(/[;,]/).map((t) => t.trim()).filter(Boolean);
6104
+ const data = await workClient.removeTags(collection, parseInt(opts.id, 10), tags);
6105
+ success(data, "ado", "work-remove-tag", start);
6106
+ } catch (err) {
6107
+ fail(err, "ado", "work-remove-tag", start);
6108
+ }
6109
+ });
6110
+ work.command("list-attachments").description("List attachments on a work item").requiredOption("--id <n>", "Work item ID").action(async (opts) => {
6111
+ const start = Date.now();
6112
+ try {
6113
+ const { collection, workClient } = getAdoContext(ado);
6114
+ const data = await workClient.listAttachments(collection, parseInt(opts.id, 10));
6115
+ success(data, "ado", "work-list-attachments", start);
6116
+ } catch (err) {
6117
+ fail(err, "ado", "work-list-attachments", start);
6118
+ }
6119
+ });
6120
+ work.command("download-attachment").description("Download a work item attachment to .pncli/ (or --dir)").requiredOption("--id <n>", "Work item ID").requiredOption("--attachment-id <guid>", "Attachment ID from work list-attachments output").option("--dir <path>", "Output directory (default: .pncli relative to cwd)").action(async (opts) => {
6121
+ const start = Date.now();
6122
+ try {
6123
+ const { collection, workClient } = getAdoContext(ado);
6124
+ const attachments = await workClient.listAttachments(collection, parseInt(opts.id, 10));
6125
+ const attachment = attachments.find((a) => a.id === opts.attachmentId);
6126
+ if (!attachment) throw new PncliError(`Attachment not found: ${opts.attachmentId}`, 1);
6127
+ const outDir = opts.dir ?? path6.join(process.cwd(), ".pncli");
6128
+ fs3.mkdirSync(outDir, { recursive: true });
6129
+ const outPath = path6.join(outDir, path6.basename(attachment.name));
6130
+ const buffer = await workClient.downloadAttachment(attachment.url);
6131
+ fs3.writeFileSync(outPath, buffer);
6132
+ success({ saved: outPath, name: attachment.name, size: buffer.length }, "ado", "work-download-attachment", start);
6133
+ } catch (err) {
6134
+ fail(err, "ado", "work-download-attachment", start);
6135
+ }
6136
+ });
5920
6137
  work.command("types").description("List work item types in the project").option("--discover", "Fetch from server (always true for this command)").option("--save", "Save discovered types to ~/.pncli/config.json").action(async (opts) => {
5921
6138
  const start = Date.now();
5922
6139
  try {
@@ -5925,10 +6142,10 @@ function registerAdoWorkCommands(ado) {
5925
6142
  const types = await discoverTypes(workClient, collection, project);
5926
6143
  if (opts.save) {
5927
6144
  const globalConfigPath = getGlobalConfigPath(globalOpts.config);
5928
- const fs4 = await import("fs");
5929
- const existing = JSON.parse(fs4.readFileSync(globalConfigPath, "utf8") || "{}");
6145
+ const fs5 = await import("fs");
6146
+ const existing = JSON.parse(fs5.readFileSync(globalConfigPath, "utf8") || "{}");
5930
6147
  existing.ado = { ...existing.ado ?? {}, discoveredTypes: types };
5931
- fs4.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
6148
+ fs5.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
5932
6149
  warn(`Saved ${types.length} types to ${globalConfigPath}`);
5933
6150
  }
5934
6151
  success(types, "ado", "work-types", start);
@@ -5957,11 +6174,11 @@ function registerAdoWorkCommands(ado) {
5957
6174
  ) : allFields;
5958
6175
  if (opts.save) {
5959
6176
  const globalConfigPath = getGlobalConfigPath(globalOpts.config);
5960
- const fs4 = await import("fs");
5961
- const existing = JSON.parse(fs4.readFileSync(globalConfigPath, "utf8") || "{}");
6177
+ const fs5 = await import("fs");
6178
+ const existing = JSON.parse(fs5.readFileSync(globalConfigPath, "utf8") || "{}");
5962
6179
  const aliases = buildDefaultAliases(allFields);
5963
6180
  existing.ado = { ...existing.ado ?? {}, discoveredFields: allFields, fieldAliases: aliases };
5964
- fs4.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
6181
+ fs5.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
5965
6182
  warn(`Saved ${allFields.length} fields and ${Object.keys(aliases).length} aliases to ${globalConfigPath}`);
5966
6183
  }
5967
6184
  success(fields, "ado", "work-fields", start);
@@ -6242,7 +6459,7 @@ function registerAdoRepoCommands(ado) {
6242
6459
  const config = lc({ configPath: globalOpts.config });
6243
6460
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
6244
6461
  const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
6245
- const { createHttpClient: createHttpClient2 } = await import("./http-5GDQYH62.js");
6462
+ const { createHttpClient: createHttpClient2 } = await import("./http-BNCZJQTD.js");
6246
6463
  const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
6247
6464
  const coreClient = new AdoCoreClient2(http);
6248
6465
  const me = await coreClient.getConnectionData(collection);
@@ -6643,18 +6860,18 @@ var ArtifactoryClient = class {
6643
6860
  }
6644
6861
  });
6645
6862
  }
6646
- async getStorageInfo(repo, path6) {
6647
- const cleanPath = path6.startsWith("/") ? path6.slice(1) : path6;
6863
+ async getStorageInfo(repo, path8) {
6864
+ const cleanPath = path8.startsWith("/") ? path8.slice(1) : path8;
6648
6865
  return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`);
6649
6866
  }
6650
- async getProperties(repo, path6) {
6651
- const cleanPath = path6.startsWith("/") ? path6.slice(1) : path6;
6867
+ async getProperties(repo, path8) {
6868
+ const cleanPath = path8.startsWith("/") ? path8.slice(1) : path8;
6652
6869
  return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
6653
6870
  params: { properties: "" }
6654
6871
  });
6655
6872
  }
6656
- async setProperties(repo, path6, properties, opts = {}) {
6657
- const cleanPath = path6.startsWith("/") ? path6.slice(1) : path6;
6873
+ async setProperties(repo, path8, properties, opts = {}) {
6874
+ const cleanPath = path8.startsWith("/") ? path8.slice(1) : path8;
6658
6875
  const propString = Object.entries(properties).map(([k, v]) => `${k}=${v}`).join(";");
6659
6876
  await this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
6660
6877
  method: "PUT",
@@ -6736,27 +6953,27 @@ function registerArtifactoryCommands(program2) {
6736
6953
  fail(err, "artifactory", "repos", start);
6737
6954
  }
6738
6955
  });
6739
- art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo, path6) => {
6956
+ art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo, path8) => {
6740
6957
  const start = Date.now();
6741
6958
  try {
6742
6959
  const client = getClient7(program2);
6743
- const data = await client.getStorageInfo(repo, path6);
6960
+ const data = await client.getStorageInfo(repo, path8);
6744
6961
  success(data, "artifactory", "artifact-info", start);
6745
6962
  } catch (err) {
6746
6963
  fail(err, "artifactory", "artifact-info", start);
6747
6964
  }
6748
6965
  });
6749
- art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo, path6) => {
6966
+ art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo, path8) => {
6750
6967
  const start = Date.now();
6751
6968
  try {
6752
6969
  const client = getClient7(program2);
6753
- const data = await client.getProperties(repo, path6);
6970
+ const data = await client.getProperties(repo, path8);
6754
6971
  success(data, "artifactory", "properties-get", start);
6755
6972
  } catch (err) {
6756
6973
  fail(err, "artifactory", "properties-get", start);
6757
6974
  }
6758
6975
  });
6759
- art.command("properties-set <repo> <path> [keyvals...]").description("Set custom properties on an artifact (format: key=value key=value...)").option("--recursive", "Apply recursively to folder contents", false).action(async (repo, path6, keyvals, opts) => {
6976
+ art.command("properties-set <repo> <path> [keyvals...]").description("Set custom properties on an artifact (format: key=value key=value...)").option("--recursive", "Apply recursively to folder contents", false).action(async (repo, path8, keyvals, opts) => {
6760
6977
  const start = Date.now();
6761
6978
  try {
6762
6979
  if (!keyvals.length) throw new PncliError("Provide at least one key=value pair");
@@ -6767,8 +6984,8 @@ function registerArtifactoryCommands(program2) {
6767
6984
  properties[kv.slice(0, eq)] = kv.slice(eq + 1);
6768
6985
  }
6769
6986
  const client = getClient7(program2);
6770
- await client.setProperties(repo, path6, properties, { recursive: opts.recursive });
6771
- success({ repo, path: path6, properties }, "artifactory", "properties-set", start);
6987
+ await client.setProperties(repo, path8, properties, { recursive: opts.recursive });
6988
+ success({ repo, path: path8, properties }, "artifactory", "properties-set", start);
6772
6989
  } catch (err) {
6773
6990
  fail(err, "artifactory", "properties-set", start);
6774
6991
  }
@@ -7372,8 +7589,8 @@ function registerSonatypeIqCommands(program2) {
7372
7589
  const startTime = Date.now();
7373
7590
  try {
7374
7591
  const http = getHttpClient(program2);
7375
- const path6 = opts.organizationId ? `/api/v2/policies/organization/${encodeURIComponent(opts.organizationId)}` : "/api/v2/policies";
7376
- const data = await http.sonatypeiq(path6);
7592
+ const path8 = opts.organizationId ? `/api/v2/policies/organization/${encodeURIComponent(opts.organizationId)}` : "/api/v2/policies";
7593
+ const data = await http.sonatypeiq(path8);
7377
7594
  success(data, "sonatypeiq", "policies list", startTime);
7378
7595
  } catch (err) {
7379
7596
  fail(err, "sonatypeiq", "policies list", startTime);
@@ -7382,8 +7599,8 @@ function registerSonatypeIqCommands(program2) {
7382
7599
  }
7383
7600
 
7384
7601
  // src/services/skills/commands.ts
7385
- import fs3 from "fs";
7386
- import path5 from "path";
7602
+ import fs4 from "fs";
7603
+ import path7 from "path";
7387
7604
  import os from "os";
7388
7605
  import { fileURLToPath } from "url";
7389
7606
  import { execFileSync as execFileSync3 } from "child_process";
@@ -7409,16 +7626,16 @@ function repoNameFromUrl(url) {
7409
7626
  }
7410
7627
  }
7411
7628
  function defaultMarketplacePath(url) {
7412
- return path5.join(os.homedir(), ".agents", "marketplaces", repoNameFromUrl(url));
7629
+ return path7.join(os.homedir(), ".agents", "marketplaces", repoNameFromUrl(url));
7413
7630
  }
7414
7631
  var AGENT_PATHS = {
7415
- "github-copilot": { project: ".agents/skills", user: path5.join(os.homedir(), ".copilot/skills") },
7416
- "claude-code": { project: ".claude/skills", user: path5.join(os.homedir(), ".claude/skills") }
7632
+ "github-copilot": { project: ".agents/skills", user: path7.join(os.homedir(), ".copilot/skills") },
7633
+ "claude-code": { project: ".claude/skills", user: path7.join(os.homedir(), ".claude/skills") }
7417
7634
  };
7418
7635
  function getBundledSkillsDir() {
7419
7636
  try {
7420
7637
  const thisFile = fileURLToPath(import.meta.url);
7421
- return path5.resolve(path5.dirname(thisFile), "..", "skills");
7638
+ return path7.resolve(path7.dirname(thisFile), "..", "skills");
7422
7639
  } catch {
7423
7640
  return "";
7424
7641
  }
@@ -7430,22 +7647,22 @@ function registerSkillsCommands(program2) {
7430
7647
  try {
7431
7648
  let targetDir;
7432
7649
  if (opts.target) {
7433
- targetDir = path5.resolve(opts.target);
7650
+ targetDir = path7.resolve(opts.target);
7434
7651
  } else {
7435
7652
  const agentConfig = AGENT_PATHS[opts.agent];
7436
7653
  if (!agentConfig) {
7437
7654
  throw new Error(`Unknown agent: "${opts.agent}". Use: ${Object.keys(AGENT_PATHS).join(" | ")}`);
7438
7655
  }
7439
7656
  const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
7440
- targetDir = path5.resolve(scopePath);
7657
+ targetDir = path7.resolve(scopePath);
7441
7658
  }
7442
- const resolvedTarget = path5.resolve(targetDir);
7659
+ const resolvedTarget = path7.resolve(targetDir);
7443
7660
  const bundledDir = getBundledSkillsDir();
7444
7661
  let skillDirs = [];
7445
7662
  if (bundledDir !== "") {
7446
7663
  try {
7447
- skillDirs = fs3.readdirSync(bundledDir).filter(
7448
- (name) => fs3.existsSync(path5.join(bundledDir, name, "SKILL.md"))
7664
+ skillDirs = fs4.readdirSync(bundledDir).filter(
7665
+ (name) => fs4.existsSync(path7.join(bundledDir, name, "SKILL.md"))
7449
7666
  );
7450
7667
  } catch {
7451
7668
  }
@@ -7456,25 +7673,25 @@ function registerSkillsCommands(program2) {
7456
7673
  const installed = [];
7457
7674
  const failed = [];
7458
7675
  for (const skillName of skillDirs) {
7459
- const existingDir = path5.resolve(targetDir, skillName);
7460
- if (!existingDir.startsWith(resolvedTarget + path5.sep)) continue;
7461
- if (fs3.existsSync(existingDir)) {
7462
- fs3.rmSync(existingDir, { recursive: true, force: true });
7676
+ const existingDir = path7.resolve(targetDir, skillName);
7677
+ if (!existingDir.startsWith(resolvedTarget + path7.sep)) continue;
7678
+ if (fs4.existsSync(existingDir)) {
7679
+ fs4.rmSync(existingDir, { recursive: true, force: true });
7463
7680
  }
7464
7681
  }
7465
7682
  warn(`Installing ${skillDirs.length} bundled skill(s) to ${targetDir}...`);
7466
7683
  for (const skillName of skillDirs) {
7467
- const skillDir = path5.resolve(targetDir, skillName);
7468
- if (!skillDir.startsWith(resolvedTarget + path5.sep)) {
7684
+ const skillDir = path7.resolve(targetDir, skillName);
7685
+ if (!skillDir.startsWith(resolvedTarget + path7.sep)) {
7469
7686
  failed.push(skillName);
7470
7687
  continue;
7471
7688
  }
7472
7689
  try {
7473
- const mdFiles = fs3.readdirSync(path5.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
7474
- fs3.mkdirSync(skillDir, { recursive: true });
7690
+ const mdFiles = fs4.readdirSync(path7.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
7691
+ fs4.mkdirSync(skillDir, { recursive: true });
7475
7692
  for (const mdFile of mdFiles) {
7476
- const content = fs3.readFileSync(path5.join(bundledDir, skillName, mdFile), "utf8");
7477
- fs3.writeFileSync(path5.join(skillDir, mdFile), content, "utf8");
7693
+ const content = fs4.readFileSync(path7.join(bundledDir, skillName, mdFile), "utf8");
7694
+ fs4.writeFileSync(path7.join(skillDir, mdFile), content, "utf8");
7478
7695
  }
7479
7696
  installed.push(skillName);
7480
7697
  } catch {
@@ -7503,25 +7720,25 @@ function registerSkillsCommands(program2) {
7503
7720
  try {
7504
7721
  let targetDir;
7505
7722
  if (opts.target) {
7506
- targetDir = path5.resolve(opts.target);
7723
+ targetDir = path7.resolve(opts.target);
7507
7724
  } else {
7508
7725
  const agentConfig = AGENT_PATHS[opts.agent];
7509
7726
  if (!agentConfig) {
7510
7727
  throw new Error(`Unknown agent: "${opts.agent}". Use: ${Object.keys(AGENT_PATHS).join(" | ")}`);
7511
7728
  }
7512
7729
  const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
7513
- targetDir = path5.resolve(scopePath);
7730
+ targetDir = path7.resolve(scopePath);
7514
7731
  }
7515
- if (!fs3.existsSync(targetDir)) {
7732
+ if (!fs4.existsSync(targetDir)) {
7516
7733
  success({ skills: [], message: `No skills directory found at ${targetDir}. Run: pncli skills install` }, "skills", "list", start);
7517
7734
  return;
7518
7735
  }
7519
- const skillDirs = fs3.readdirSync(targetDir).filter((name) => {
7520
- const skillPath = path5.join(targetDir, name, "SKILL.md");
7521
- return fs3.existsSync(skillPath);
7736
+ const skillDirs = fs4.readdirSync(targetDir).filter((name) => {
7737
+ const skillPath = path7.join(targetDir, name, "SKILL.md");
7738
+ return fs4.existsSync(skillPath);
7522
7739
  });
7523
7740
  const skillsList = skillDirs.map((name) => {
7524
- const content = fs3.readFileSync(path5.join(targetDir, name, "SKILL.md"), "utf8");
7741
+ const content = fs4.readFileSync(path7.join(targetDir, name, "SKILL.md"), "utf8");
7525
7742
  const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
7526
7743
  const data = {};
7527
7744
  const metadata = {};
@@ -7563,9 +7780,9 @@ function registerSkillsCommands(program2) {
7563
7780
  marketplace.command("setup").description("Clone a skills marketplace repo and register it in global config").argument("<url>", "Git clone URL of the marketplace repository").argument("[localPath]", "Local directory to clone into (default: ~/.agents/marketplaces/<repo-name>)").option("--branch <branch>", "Branch to clone (default: remote HEAD)").option("--token <token>", "HTTP access token for authenticated clone and pull (GitHub PAT or Bitbucket token)").action(async (url, localPath, opts) => {
7564
7781
  const start = Date.now();
7565
7782
  try {
7566
- const resolvedPath = path5.resolve(localPath ?? defaultMarketplacePath(url));
7567
- const hasGit = fs3.existsSync(path5.join(resolvedPath, ".git"));
7568
- if (fs3.existsSync(resolvedPath) && !hasGit && fs3.readdirSync(resolvedPath).length > 0) {
7783
+ const resolvedPath = path7.resolve(localPath ?? defaultMarketplacePath(url));
7784
+ const hasGit = fs4.existsSync(path7.join(resolvedPath, ".git"));
7785
+ if (fs4.existsSync(resolvedPath) && !hasGit && fs4.readdirSync(resolvedPath).length > 0) {
7569
7786
  throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
7570
7787
  }
7571
7788
  if (hasGit) {
@@ -7599,7 +7816,7 @@ function registerSkillsCommands(program2) {
7599
7816
  const configPath = getGlobalConfigPath();
7600
7817
  const globalConfig = loadJsonFile(configPath) ?? {};
7601
7818
  const marketplacePath = globalConfig.marketplace?.localPath;
7602
- if (!marketplacePath || !fs3.existsSync(marketplacePath)) {
7819
+ if (!marketplacePath || !fs4.existsSync(marketplacePath)) {
7603
7820
  throw new Error("Marketplace not configured. Run: pncli skills marketplace setup <url> <localPath>");
7604
7821
  }
7605
7822
  warn("Pulling latest marketplace content...");
@@ -7636,13 +7853,13 @@ function registerSkillsCommands(program2) {
7636
7853
  if (pluginChoices.length === 0) {
7637
7854
  throw new Error("No plugins found in marketplace. Check the marketplace repository structure.");
7638
7855
  }
7639
- const targetDir = opts.claude ? path5.join(os.homedir(), ".claude", "skills") : path5.join(os.homedir(), ".agents", "skills");
7856
+ const targetDir = opts.claude ? path7.join(os.homedir(), ".claude", "skills") : path7.join(os.homedir(), ".agents", "skills");
7640
7857
  if (plugin === "all") {
7641
7858
  const results = {};
7642
7859
  let totalInstalled = 0;
7643
7860
  for (const pluginChoice of pluginChoices) {
7644
7861
  const skillsSrc2 = resolveSkillsSrc(marketplacePath, pluginChoice.name);
7645
- if (!fs3.existsSync(skillsSrc2)) {
7862
+ if (!fs4.existsSync(skillsSrc2)) {
7646
7863
  results[pluginChoice.name] = { installed: [], failed: [] };
7647
7864
  warn(`No skills directory found for plugin "${pluginChoice.name}" \u2014 skipping.`);
7648
7865
  continue;
@@ -7650,6 +7867,9 @@ function registerSkillsCommands(program2) {
7650
7867
  const { installed: installed2, failed: failed2 } = copyPluginSkills(skillsSrc2, targetDir);
7651
7868
  results[pluginChoice.name] = { installed: installed2, failed: failed2 };
7652
7869
  totalInstalled += installed2.length;
7870
+ for (const skill of installed2) {
7871
+ warn(` ${skill}: ${path7.join(skillsSrc2, skill)} \u2192 ${path7.join(targetDir, skill)}`);
7872
+ }
7653
7873
  if (failed2.length > 0) {
7654
7874
  warn(`Skipped ${failed2.length} skill(s) with invalid names in "${pluginChoice.name}": ${failed2.join(", ")}`);
7655
7875
  }
@@ -7678,10 +7898,13 @@ function registerSkillsCommands(program2) {
7678
7898
  });
7679
7899
  }
7680
7900
  const skillsSrc = resolveSkillsSrc(marketplacePath, selectedPlugin);
7681
- if (!fs3.existsSync(skillsSrc)) {
7901
+ if (!fs4.existsSync(skillsSrc)) {
7682
7902
  throw new Error(`No skills directory found at ${skillsSrc}`);
7683
7903
  }
7684
7904
  const { installed, failed } = copyPluginSkills(skillsSrc, targetDir);
7905
+ for (const skill of installed) {
7906
+ warn(` ${skill}: ${path7.join(skillsSrc, skill)} \u2192 ${path7.join(targetDir, skill)}`);
7907
+ }
7685
7908
  if (failed.length > 0) {
7686
7909
  warn(`Skipped ${failed.length} skill(s) with invalid names: ${failed.join(", ")}`);
7687
7910
  }
@@ -7699,46 +7922,46 @@ function registerSkillsCommands(program2) {
7699
7922
  });
7700
7923
  }
7701
7924
  function resolvePluginChoices(marketplacePath) {
7702
- const marketplaceJsonPath = path5.join(marketplacePath, ".claude-plugin", "marketplace.json");
7703
- if (fs3.existsSync(marketplaceJsonPath)) {
7925
+ const marketplaceJsonPath = path7.join(marketplacePath, ".claude-plugin", "marketplace.json");
7926
+ if (fs4.existsSync(marketplaceJsonPath)) {
7704
7927
  try {
7705
- const meta = JSON.parse(fs3.readFileSync(marketplaceJsonPath, "utf8"));
7928
+ const meta = JSON.parse(fs4.readFileSync(marketplaceJsonPath, "utf8"));
7706
7929
  if (Array.isArray(meta.plugins) && meta.plugins.length > 0) {
7707
7930
  return meta.plugins.map((p) => ({ name: p.name, description: p.description ?? "" }));
7708
7931
  }
7709
7932
  } catch {
7710
7933
  }
7711
7934
  }
7712
- const pluginsDir = path5.join(marketplacePath, "plugins");
7713
- if (fs3.existsSync(pluginsDir)) {
7714
- return fs3.readdirSync(pluginsDir).filter((name) => fs3.statSync(path5.join(pluginsDir, name)).isDirectory()).map((name) => ({ name, description: "" }));
7935
+ const pluginsDir = path7.join(marketplacePath, "plugins");
7936
+ if (fs4.existsSync(pluginsDir)) {
7937
+ return fs4.readdirSync(pluginsDir).filter((name) => fs4.statSync(path7.join(pluginsDir, name)).isDirectory()).map((name) => ({ name, description: "" }));
7715
7938
  }
7716
7939
  return [];
7717
7940
  }
7718
7941
  function resolveSkillsSrc(marketplacePath, selectedPlugin) {
7719
- const pluginsBase = path5.resolve(marketplacePath, "plugins");
7720
- const skillsSrc = path5.resolve(pluginsBase, selectedPlugin, "skills");
7721
- if (!skillsSrc.startsWith(pluginsBase + path5.sep)) {
7942
+ const pluginsBase = path7.resolve(marketplacePath, "plugins");
7943
+ const skillsSrc = path7.resolve(pluginsBase, selectedPlugin, "skills");
7944
+ if (!skillsSrc.startsWith(pluginsBase + path7.sep)) {
7722
7945
  throw new Error(`Invalid plugin name: "${selectedPlugin}"`);
7723
7946
  }
7724
7947
  return skillsSrc;
7725
7948
  }
7726
7949
  function copyPluginSkills(skillsSrc, targetDir) {
7727
- fs3.mkdirSync(targetDir, { recursive: true });
7728
- const resolvedTarget = path5.resolve(targetDir);
7729
- const skillNames = fs3.readdirSync(skillsSrc).filter(
7730
- (name) => fs3.statSync(path5.join(skillsSrc, name)).isDirectory()
7950
+ fs4.mkdirSync(targetDir, { recursive: true });
7951
+ const resolvedTarget = path7.resolve(targetDir);
7952
+ const skillNames = fs4.readdirSync(skillsSrc).filter(
7953
+ (name) => fs4.statSync(path7.join(skillsSrc, name)).isDirectory()
7731
7954
  );
7732
7955
  const installed = [];
7733
7956
  const failed = [];
7734
7957
  for (const skillName of skillNames) {
7735
- const dest = path5.resolve(targetDir, skillName);
7736
- if (!dest.startsWith(resolvedTarget + path5.sep)) {
7958
+ const dest = path7.resolve(targetDir, skillName);
7959
+ if (!dest.startsWith(resolvedTarget + path7.sep)) {
7737
7960
  failed.push(skillName);
7738
7961
  continue;
7739
7962
  }
7740
- fs3.rmSync(dest, { recursive: true, force: true });
7741
- fs3.cpSync(path5.join(skillsSrc, skillName), dest, { recursive: true });
7963
+ fs4.rmSync(dest, { recursive: true, force: true });
7964
+ fs4.cpSync(path7.join(skillsSrc, skillName), dest, { recursive: true });
7742
7965
  installed.push(skillName);
7743
7966
  }
7744
7967
  return { installed, failed };