@kolatts/pncli 1.21.0 → 1.22.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
@@ -20,7 +20,7 @@ import {
20
20
  success,
21
21
  warn,
22
22
  writeRawOutput
23
- } from "./chunk-PC4AHQCN.js";
23
+ } from "./chunk-PZWGOMIQ.js";
24
24
  import {
25
25
  AdoCoreClient
26
26
  } from "./chunk-VDL32PSG.js";
@@ -421,34 +421,34 @@ function parseAdoRemote(remoteUrl, adoBaseUrl) {
421
421
  } catch {
422
422
  baseHostname = adoBaseUrl.replace(/\/$/, "").replace(/^https?:\/\//, "").split(/[:/]/)[0].toLowerCase();
423
423
  }
424
- let path8 = remoteUrl;
424
+ let path9 = remoteUrl;
425
425
  let remoteHostname = "";
426
- const sshProtoMatch = path8.match(/^ssh:\/\/[^@]*@?([^/:]+)(?::\d+)?(.*)/);
426
+ const sshProtoMatch = path9.match(/^ssh:\/\/[^@]*@?([^/:]+)(?::\d+)?(.*)/);
427
427
  if (sshProtoMatch) {
428
428
  remoteHostname = sshProtoMatch[1].toLowerCase();
429
- path8 = sshProtoMatch[2];
429
+ path9 = sshProtoMatch[2];
430
430
  } else {
431
- const gitAtMatch = path8.match(/^git@([^:]+)(?::\d+)?:(.*)/);
431
+ const gitAtMatch = path9.match(/^git@([^:]+)(?::\d+)?:(.*)/);
432
432
  if (gitAtMatch) {
433
433
  remoteHostname = gitAtMatch[1].toLowerCase();
434
- path8 = "/" + gitAtMatch[2];
434
+ path9 = "/" + gitAtMatch[2];
435
435
  } else {
436
- const httpsMatch = path8.match(/^https?:\/\/([^/:]+)(?::\d+)?(.*)/);
436
+ const httpsMatch = path9.match(/^https?:\/\/([^/:]+)(?::\d+)?(.*)/);
437
437
  if (httpsMatch) {
438
438
  remoteHostname = httpsMatch[1].toLowerCase();
439
- path8 = httpsMatch[2];
439
+ path9 = httpsMatch[2];
440
440
  }
441
441
  }
442
442
  }
443
443
  if (!remoteHostname || remoteHostname !== baseHostname) return null;
444
- path8 = path8.replace(/\.git$/, "");
445
- const gitIdx = path8.indexOf("/_git/");
446
- const sshIdx = path8.indexOf("/_ssh/");
444
+ path9 = path9.replace(/\.git$/, "");
445
+ const gitIdx = path9.indexOf("/_git/");
446
+ const sshIdx = path9.indexOf("/_ssh/");
447
447
  const markerIdx = gitIdx !== -1 ? gitIdx : sshIdx;
448
448
  if (markerIdx === -1) return null;
449
- const repo = decodeURIComponent(path8.slice(markerIdx + 6));
449
+ const repo = decodeURIComponent(path9.slice(markerIdx + 6));
450
450
  if (!repo) return null;
451
- const before = path8.slice(1, markerIdx);
451
+ const before = path9.slice(1, markerIdx);
452
452
  const parts = before.split("/").filter(Boolean);
453
453
  if (parts.length < 2) return null;
454
454
  const project = decodeURIComponent(parts[parts.length - 1]);
@@ -1390,6 +1390,13 @@ function getDefaults(program2) {
1390
1390
  priority: config.defaults.jira?.priority
1391
1391
  };
1392
1392
  }
1393
+ function resolveJqlInput(jql, jqlFile) {
1394
+ const raw = resolveTextInput(jql, jqlFile, "jql");
1395
+ if (raw === void 0) throw new PncliError("Must specify --jql or --jql-file", 1);
1396
+ const trimmed = raw.trim();
1397
+ if (!trimmed) throw new PncliError("JQL query is empty", 1);
1398
+ return trimmed;
1399
+ }
1393
1400
  function registerJiraCommands(program2) {
1394
1401
  const jira = program2.command("jira").description("Jira Data Cloud operations");
1395
1402
  jira.command("get-issue").description("Get a Jira issue by key").requiredOption("--key <issue-key>", "Issue key (e.g. PROJ-123)").action(async (opts) => {
@@ -1539,12 +1546,13 @@ function registerJiraCommands(program2) {
1539
1546
  fail(err, "jira", "list-comments", start);
1540
1547
  }
1541
1548
  });
1542
- jira.command("search").description("Search Jira issues with JQL (consider --output-file for large results)").requiredOption("--jql <query>", "JQL query string").option("--max-results <n>", "Maximum number of results").action(async (opts) => {
1549
+ jira.command("search").description("Search Jira issues with JQL (consider --output-file for large results)").option("--jql <query>", "JQL query string").option("--jql-file <path>", "Path to a file containing the JQL query ('-' = stdin)").option("--max-results <n>", "Maximum number of results").action(async (opts) => {
1543
1550
  const start = Date.now();
1544
1551
  try {
1545
1552
  const { client, fieldMap, customFields } = getClientAndFields(program2);
1553
+ const jql = resolveJqlInput(opts.jql, opts.jqlFile);
1546
1554
  const maxResults = opts.maxResults ? parseInt(opts.maxResults, 10) : void 0;
1547
- const translatedJql = translateJql(opts.jql, fieldMap);
1555
+ const translatedJql = translateJql(jql, fieldMap);
1548
1556
  const data = await client.search(translatedJql, maxResults, customFields);
1549
1557
  const translatedIssues = data.issues.map((issue) => ({
1550
1558
  ...issue,
@@ -2216,8 +2224,8 @@ var GitHubClient = class {
2216
2224
  );
2217
2225
  }
2218
2226
  async createRepo(opts) {
2219
- const path8 = opts.org ? `/orgs/${opts.org}/repos` : "/user/repos";
2220
- return this.http.github(path8, {
2227
+ const path9 = opts.org ? `/orgs/${opts.org}/repos` : "/user/repos";
2228
+ return this.http.github(path9, {
2221
2229
  method: "POST",
2222
2230
  body: {
2223
2231
  name: opts.name,
@@ -2556,7 +2564,8 @@ function registerGitHubCommands(program2) {
2556
2564
  }
2557
2565
 
2558
2566
  // src/services/confluence/commands.ts
2559
- import { existsSync as existsSync2 } from "fs";
2567
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
2568
+ import path2 from "path";
2560
2569
 
2561
2570
  // src/services/confluence/client.ts
2562
2571
  import { readFileSync as readFileSync4 } from "fs";
@@ -2718,6 +2727,19 @@ var ConfluenceClient = class {
2718
2727
  );
2719
2728
  return result.results;
2720
2729
  }
2730
+ async getAttachment(attachmentId) {
2731
+ return this.http.confluence(`${API3}/content/${attachmentId}`);
2732
+ }
2733
+ async downloadAttachment(attachment) {
2734
+ const downloadPath = attachment._links.download;
2735
+ if (!downloadPath) {
2736
+ throw new PncliError(
2737
+ `Content ${attachment.id} has no download link \u2014 is it an attachment? (title: ${attachment.title})`,
2738
+ 1
2739
+ );
2740
+ }
2741
+ return this.http.confluenceBuffer(downloadPath);
2742
+ }
2721
2743
  async deleteAttachment(attachmentId) {
2722
2744
  return this.http.confluence(`${API3}/content/${attachmentId}`, { method: "DELETE" });
2723
2745
  }
@@ -2737,6 +2759,11 @@ var ConfluenceClient = class {
2737
2759
  };
2738
2760
 
2739
2761
  // src/services/confluence/commands.ts
2762
+ function resolveAttachmentPath(outDir, title) {
2763
+ const segments = title.split(/[\\/]/).filter((s) => s !== "" && s !== "." && s !== "..");
2764
+ const name = segments.pop() ?? "attachment";
2765
+ return path2.join(outDir, name);
2766
+ }
2740
2767
  function xmlParseHint(err, bodyContent, convertedFromMarkdown = false) {
2741
2768
  if (!(err instanceof Error)) return null;
2742
2769
  const m = err.message.match(/at \[row,col\]=\{(\d+),(\d+)\}/);
@@ -2986,6 +3013,21 @@ ${hint}`, status, url), "confluence", "update-page", start);
2986
3013
  fail(err, "confluence", "upload-attachment", start);
2987
3014
  }
2988
3015
  });
3016
+ confluence.command("download-attachment").description("Download a Confluence attachment to .pncli/ (or --dir)").requiredOption("--id <attachment-id>", "Attachment content ID").option("--dir <path>", "Output directory (default: .pncli relative to cwd)").action(async (opts) => {
3017
+ const start = Date.now();
3018
+ try {
3019
+ const client = getClient4(program2);
3020
+ const attachment = await client.getAttachment(opts.id);
3021
+ const outDir = opts.dir ?? path2.join(process.cwd(), ".pncli");
3022
+ mkdirSync2(outDir, { recursive: true });
3023
+ const outPath = resolveAttachmentPath(outDir, attachment.title);
3024
+ const buffer = await client.downloadAttachment(attachment);
3025
+ writeFileSync2(outPath, buffer);
3026
+ success({ saved: outPath, filename: attachment.title, size: buffer.length }, "confluence", "download-attachment", start);
3027
+ } catch (err) {
3028
+ fail(err, "confluence", "download-attachment", start);
3029
+ }
3030
+ });
2989
3031
  confluence.command("delete-attachment").description("Delete an attachment from Confluence by its content ID (moves it to the trash; does not permanently purge it)").requiredOption("--id <attachment-id>", "Attachment content ID").action(async (opts) => {
2990
3032
  const start = Date.now();
2991
3033
  try {
@@ -3549,23 +3591,23 @@ import { Option as Option2 } from "commander";
3549
3591
 
3550
3592
  // src/services/deps/parsers/index.ts
3551
3593
  import fs from "fs";
3552
- import path5 from "path";
3594
+ import path6 from "path";
3553
3595
  import { execSync as execSync3, execFileSync as execFileSync2 } from "child_process";
3554
3596
 
3555
3597
  // src/services/deps/parsers/npm.ts
3556
- import path2 from "path";
3598
+ import path3 from "path";
3557
3599
  function findNpmManifests(files) {
3558
3600
  const lockFiles = new Set(
3559
3601
  files.filter((f) => {
3560
- const base = path2.basename(f);
3602
+ const base = path3.basename(f);
3561
3603
  return base === "package-lock.json" || base === "yarn.lock" || base === "pnpm-lock.yaml";
3562
3604
  })
3563
3605
  );
3564
3606
  const manifests = [];
3565
3607
  for (const file of files) {
3566
- if (path2.basename(file) !== "package.json") continue;
3608
+ if (path3.basename(file) !== "package.json") continue;
3567
3609
  if (file.split("/").includes("node_modules")) continue;
3568
- const dir = path2.dirname(file);
3610
+ const dir = path3.dirname(file);
3569
3611
  let lockFile;
3570
3612
  for (const lf of ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"]) {
3571
3613
  const candidate = dir === "." ? lf : `${dir}/${lf}`;
@@ -3580,7 +3622,7 @@ function findNpmManifests(files) {
3580
3622
  }
3581
3623
  function parseNpmPackages(content, manifest, opts, lockContent) {
3582
3624
  if (lockContent && manifest.lockFile) {
3583
- const base = path2.basename(manifest.lockFile);
3625
+ const base = path3.basename(manifest.lockFile);
3584
3626
  if (base === "package-lock.json") {
3585
3627
  const pkgs = parsePackageLock(lockContent, manifest.lockFile, opts);
3586
3628
  if (pkgs.length > 0) return pkgs;
@@ -3728,26 +3770,26 @@ function parsePnpmLock(content, lockFilePath, opts, packageJsonContent) {
3728
3770
  }
3729
3771
 
3730
3772
  // src/services/deps/parsers/nuget.ts
3731
- import path3 from "path";
3773
+ import path4 from "path";
3732
3774
  var PROJ_EXTENSIONS = /* @__PURE__ */ new Set([".csproj", ".fsproj", ".vbproj"]);
3733
3775
  function findNugetManifests(files) {
3734
3776
  const lockFiles = /* @__PURE__ */ new Map();
3735
3777
  for (const f of files) {
3736
- if (path3.basename(f) === "packages.lock.json") {
3737
- lockFiles.set(path3.dirname(f), f);
3778
+ if (path4.basename(f) === "packages.lock.json") {
3779
+ lockFiles.set(path4.dirname(f), f);
3738
3780
  }
3739
3781
  }
3740
3782
  const manifests = [];
3741
3783
  for (const file of files) {
3742
- const base = path3.basename(file);
3743
- const ext = path3.extname(file);
3784
+ const base = path4.basename(file);
3785
+ const ext = path4.extname(file);
3744
3786
  if (PROJ_EXTENSIONS.has(ext) || base === "packages.config") {
3745
- const dir = path3.dirname(file);
3787
+ const dir = path4.dirname(file);
3746
3788
  const lockFile = lockFiles.get(dir);
3747
3789
  manifests.push({ file, ecosystem: "nuget", lockFile });
3748
3790
  }
3749
3791
  if (base === "Directory.Packages.props") {
3750
- const dir = path3.dirname(file);
3792
+ const dir = path4.dirname(file);
3751
3793
  manifests.push({ file, ecosystem: "nuget", lockFile: lockFiles.get(dir) });
3752
3794
  }
3753
3795
  }
@@ -3766,7 +3808,7 @@ function findNugetManifests(files) {
3766
3808
  return deduped;
3767
3809
  }
3768
3810
  function parseNugetPackages(content, manifest, opts, lockContent, propsContent) {
3769
- const base = path3.basename(manifest.file);
3811
+ const base = path4.basename(manifest.file);
3770
3812
  if (lockContent) {
3771
3813
  return parsePackagesLock(lockContent, manifest.lockFile ?? manifest.file, opts);
3772
3814
  }
@@ -3887,14 +3929,14 @@ function parsePackagesLock(content, filePath, opts) {
3887
3929
  }
3888
3930
 
3889
3931
  // src/services/deps/parsers/maven.ts
3890
- import path4 from "path";
3932
+ import path5 from "path";
3891
3933
  function findMavenManifests(files) {
3892
- const lockFiles = new Set(files.filter((f) => path4.basename(f) === "gradle.lockfile"));
3934
+ const lockFiles = new Set(files.filter((f) => path5.basename(f) === "gradle.lockfile"));
3893
3935
  const manifests = [];
3894
3936
  for (const file of files) {
3895
- const base = path4.basename(file);
3937
+ const base = path5.basename(file);
3896
3938
  if (base === "pom.xml" || base === "build.gradle" || base === "build.gradle.kts") {
3897
- const dir = path4.dirname(file);
3939
+ const dir = path5.dirname(file);
3898
3940
  const lockCandidate = dir === "." ? "gradle.lockfile" : `${dir}/gradle.lockfile`;
3899
3941
  const lockFile = lockFiles.has(lockCandidate) ? lockCandidate : void 0;
3900
3942
  manifests.push({ file, ecosystem: "maven", lockFile });
@@ -3903,7 +3945,7 @@ function findMavenManifests(files) {
3903
3945
  return manifests;
3904
3946
  }
3905
3947
  function parseMavenPackages(content, manifest, opts, lockContent) {
3906
- const base = path4.basename(manifest.file);
3948
+ const base = path5.basename(manifest.file);
3907
3949
  if (lockContent && manifest.lockFile) {
3908
3950
  return parseGradleLock(lockContent, manifest.lockFile, opts);
3909
3951
  }
@@ -4064,7 +4106,7 @@ function getRepoFiles(repoRoot) {
4064
4106
  }
4065
4107
  function readFile(repoRoot, relPath) {
4066
4108
  try {
4067
- return fs.readFileSync(path5.join(repoRoot, relPath), "utf8");
4109
+ return fs.readFileSync(path6.join(repoRoot, relPath), "utf8");
4068
4110
  } catch {
4069
4111
  return null;
4070
4112
  }
@@ -4082,13 +4124,13 @@ function readFileAtRef(repoRoot, ref, relPath) {
4082
4124
  }
4083
4125
  }
4084
4126
  function findPropsContent(manifestFile, readFn) {
4085
- let dir = path5.dirname(manifestFile);
4127
+ let dir = path6.dirname(manifestFile);
4086
4128
  for (let depth = 0; depth < 10; depth++) {
4087
4129
  const candidate = dir === "." ? "Directory.Packages.props" : `${dir}/Directory.Packages.props`;
4088
4130
  const content = readFn(candidate);
4089
4131
  if (content) return content;
4090
4132
  if (dir === ".") break;
4091
- dir = path5.dirname(dir);
4133
+ dir = path6.dirname(dir);
4092
4134
  }
4093
4135
  return null;
4094
4136
  }
@@ -5530,7 +5572,7 @@ import chalk from "chalk";
5530
5572
  import fs2 from "fs";
5531
5573
  function registerConfigCommands(program2) {
5532
5574
  const config = program2.command("config").description("Manage pncli configuration");
5533
- config.command("init").description("Interactive setup wizard").option("--repo", "Write repo config (.pncli.json) instead of global config").action(async (opts) => {
5575
+ config.command("init").description("Setup wizard for service credentials (interactive)").option("--repo", "Write repo config (.pncli.json) instead of global config").action(async (opts) => {
5534
5576
  const start = Date.now();
5535
5577
  try {
5536
5578
  if (opts.repo) {
@@ -5641,8 +5683,8 @@ function registerConfigCommands(program2) {
5641
5683
  if (cfg.ado.baseUrl) {
5642
5684
  try {
5643
5685
  const collection = cfg.defaults.ado?.collection;
5644
- const path8 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
5645
- await http.ado(path8);
5686
+ const path9 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
5687
+ await http.ado(path9);
5646
5688
  results.ado = { ok: true, message: "connected" };
5647
5689
  } catch (err) {
5648
5690
  results.ado = { ok: false, message: err instanceof Error ? err.message : String(err) };
@@ -5867,8 +5909,8 @@ function registerConfigCommands(program2) {
5867
5909
  } else {
5868
5910
  try {
5869
5911
  const collection = cfg.defaults.ado?.collection;
5870
- const path8 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
5871
- await http.ado(path8, { timeoutMs: 1e4 });
5912
+ const path9 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
5913
+ await http.ado(path9, { timeoutMs: 1e4 });
5872
5914
  results.ado = { status: "valid", message: "ok" };
5873
5915
  } catch (err) {
5874
5916
  results.ado = categorize2(err);
@@ -7263,7 +7305,7 @@ function registerAdoProjectCommands(ado) {
7263
7305
 
7264
7306
  // src/services/ado/commands/work.ts
7265
7307
  import fs3 from "fs";
7266
- import path6 from "path";
7308
+ import path7 from "path";
7267
7309
 
7268
7310
  // src/services/ado/input-schema.ts
7269
7311
  var ADO_WORK_INPUT_FILE_SCHEMA = {
@@ -7504,9 +7546,9 @@ function registerAdoWorkCommands(ado) {
7504
7546
  const attachments = await workClient.listAttachments(collection, parseInt(opts.id, 10));
7505
7547
  const attachment = attachments.find((a) => a.id === opts.attachmentId);
7506
7548
  if (!attachment) throw new PncliError(`Attachment not found: ${opts.attachmentId}`, 1);
7507
- const outDir = opts.dir ?? path6.join(process.cwd(), ".pncli");
7549
+ const outDir = opts.dir ?? path7.join(process.cwd(), ".pncli");
7508
7550
  fs3.mkdirSync(outDir, { recursive: true });
7509
- const outPath = path6.join(outDir, path6.basename(attachment.name));
7551
+ const outPath = path7.join(outDir, path7.basename(attachment.name));
7510
7552
  const buffer = await workClient.downloadAttachment(attachment.url);
7511
7553
  fs3.writeFileSync(outPath, buffer);
7512
7554
  success({ saved: outPath, name: attachment.name, size: buffer.length }, "ado", "work-download-attachment", start);
@@ -7666,7 +7708,7 @@ function registerAdoRepoCommands(ado) {
7666
7708
  fail(err, "ado", "repo-get-pr", start);
7667
7709
  }
7668
7710
  });
7669
- repo.command("create-pr").description("Create a pull request").requiredOption("--title <title>", "PR title").requiredOption("--source <branch>", "Source branch").option("--target <branch>", "Target branch (default: main)", "main").option("--description <text>", "PR description").option("--reviewers <ids>", "Comma-separated reviewer IDs or display names").action(async (opts) => {
7711
+ repo.command("create-pr").description("Create a pull request").requiredOption("--title <title>", "PR title").requiredOption("--source <branch>", "Source branch").option("--target <branch>", "Target branch (default: main)", "main").option("--description <text>", "PR description").option("--reviewers <ids>", "Comma-separated reviewer IDs or display names").option("--draft", "Create as a draft pull request").action(async (opts) => {
7670
7712
  const start = Date.now();
7671
7713
  try {
7672
7714
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
@@ -7676,14 +7718,15 @@ function registerAdoRepoCommands(ado) {
7676
7718
  description: opts.description,
7677
7719
  sourceRefName: `refs/heads/${opts.source}`,
7678
7720
  targetRefName: `refs/heads/${opts.target}`,
7679
- reviewers
7721
+ reviewers,
7722
+ ...opts.draft !== void 0 ? { isDraft: opts.draft } : {}
7680
7723
  });
7681
7724
  success(data, "ado", "repo-create-pr", start);
7682
7725
  } catch (err) {
7683
7726
  fail(err, "ado", "repo-create-pr", start);
7684
7727
  }
7685
7728
  });
7686
- repo.command("update-pr").description("Update a pull request").requiredOption("--id <n>", "Pull request ID").option("--title <title>", "New title").option("--description <text>", "New description").option("--reviewers <ids>", "Comma-separated reviewer IDs").action(async (opts) => {
7729
+ repo.command("update-pr").description("Update a pull request").requiredOption("--id <n>", "Pull request ID").option("--title <title>", "New title").option("--description <text>", "New description").option("--reviewers <ids>", "Comma-separated reviewer IDs").option("--draft <bool>", "Set draft status: true|false").action(async (opts) => {
7687
7730
  const start = Date.now();
7688
7731
  try {
7689
7732
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
@@ -7692,6 +7735,7 @@ function registerAdoRepoCommands(ado) {
7692
7735
  if (opts.title) body.title = opts.title;
7693
7736
  if (opts.description !== void 0) body.description = opts.description;
7694
7737
  if (reviewers) body.reviewers = reviewers;
7738
+ if (opts.draft !== void 0) body.isDraft = opts.draft === "true";
7695
7739
  const data = await gitClient.updatePR(collection, project, repo2, parseInt(opts.id, 10), body);
7696
7740
  success(data, "ado", "repo-update-pr", start);
7697
7741
  } catch (err) {
@@ -7877,7 +7921,7 @@ function registerAdoRepoCommands(ado) {
7877
7921
  const config = lc({ configPath: globalOpts.config });
7878
7922
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
7879
7923
  const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
7880
- const { createHttpClient: createHttpClient2 } = await import("./http-IXJA4TMS.js");
7924
+ const { createHttpClient: createHttpClient2 } = await import("./http-52YHD3ZL.js");
7881
7925
  const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
7882
7926
  const coreClient = new AdoCoreClient2(http);
7883
7927
  const me = await coreClient.getConnectionData(collection);
@@ -8278,18 +8322,18 @@ var ArtifactoryClient = class {
8278
8322
  }
8279
8323
  });
8280
8324
  }
8281
- async getStorageInfo(repo, path8) {
8282
- const cleanPath = path8.startsWith("/") ? path8.slice(1) : path8;
8325
+ async getStorageInfo(repo, path9) {
8326
+ const cleanPath = path9.startsWith("/") ? path9.slice(1) : path9;
8283
8327
  return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`);
8284
8328
  }
8285
- async getProperties(repo, path8) {
8286
- const cleanPath = path8.startsWith("/") ? path8.slice(1) : path8;
8329
+ async getProperties(repo, path9) {
8330
+ const cleanPath = path9.startsWith("/") ? path9.slice(1) : path9;
8287
8331
  return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
8288
8332
  params: { properties: "" }
8289
8333
  });
8290
8334
  }
8291
- async setProperties(repo, path8, properties, opts = {}) {
8292
- const cleanPath = path8.startsWith("/") ? path8.slice(1) : path8;
8335
+ async setProperties(repo, path9, properties, opts = {}) {
8336
+ const cleanPath = path9.startsWith("/") ? path9.slice(1) : path9;
8293
8337
  const propString = Object.entries(properties).map(([k, v]) => `${k}=${v}`).join(";");
8294
8338
  await this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
8295
8339
  method: "PUT",
@@ -8371,27 +8415,27 @@ function registerArtifactoryCommands(program2) {
8371
8415
  fail(err, "artifactory", "repos", start);
8372
8416
  }
8373
8417
  });
8374
- art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo, path8) => {
8418
+ art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo, path9) => {
8375
8419
  const start = Date.now();
8376
8420
  try {
8377
8421
  const client = getClient8(program2);
8378
- const data = await client.getStorageInfo(repo, path8);
8422
+ const data = await client.getStorageInfo(repo, path9);
8379
8423
  success(data, "artifactory", "artifact-info", start);
8380
8424
  } catch (err) {
8381
8425
  fail(err, "artifactory", "artifact-info", start);
8382
8426
  }
8383
8427
  });
8384
- art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo, path8) => {
8428
+ art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo, path9) => {
8385
8429
  const start = Date.now();
8386
8430
  try {
8387
8431
  const client = getClient8(program2);
8388
- const data = await client.getProperties(repo, path8);
8432
+ const data = await client.getProperties(repo, path9);
8389
8433
  success(data, "artifactory", "properties-get", start);
8390
8434
  } catch (err) {
8391
8435
  fail(err, "artifactory", "properties-get", start);
8392
8436
  }
8393
8437
  });
8394
- 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) => {
8438
+ 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, path9, keyvals, opts) => {
8395
8439
  const start = Date.now();
8396
8440
  try {
8397
8441
  if (!keyvals.length) throw new PncliError("Provide at least one key=value pair");
@@ -8402,8 +8446,8 @@ function registerArtifactoryCommands(program2) {
8402
8446
  properties[kv.slice(0, eq)] = kv.slice(eq + 1);
8403
8447
  }
8404
8448
  const client = getClient8(program2);
8405
- await client.setProperties(repo, path8, properties, { recursive: opts.recursive });
8406
- success({ repo, path: path8, properties }, "artifactory", "properties-set", start);
8449
+ await client.setProperties(repo, path9, properties, { recursive: opts.recursive });
8450
+ success({ repo, path: path9, properties }, "artifactory", "properties-set", start);
8407
8451
  } catch (err) {
8408
8452
  fail(err, "artifactory", "properties-set", start);
8409
8453
  }
@@ -9010,8 +9054,8 @@ function registerSonatypeIqCommands(program2) {
9010
9054
  const startTime = Date.now();
9011
9055
  try {
9012
9056
  const http = getHttpClient(program2);
9013
- const path8 = opts.organizationId ? `/api/v2/policies/organization/${encodeURIComponent(opts.organizationId)}` : "/api/v2/policies";
9014
- const data = await http.sonatypeiq(path8);
9057
+ const path9 = opts.organizationId ? `/api/v2/policies/organization/${encodeURIComponent(opts.organizationId)}` : "/api/v2/policies";
9058
+ const data = await http.sonatypeiq(path9);
9015
9059
  success(data, "sonatypeiq", "policies list", startTime);
9016
9060
  } catch (err) {
9017
9061
  fail(err, "sonatypeiq", "policies list", startTime);
@@ -9021,11 +9065,13 @@ function registerSonatypeIqCommands(program2) {
9021
9065
 
9022
9066
  // src/services/skills/commands.ts
9023
9067
  import fs4 from "fs";
9024
- import path7 from "path";
9068
+ import path8 from "path";
9025
9069
  import os from "os";
9026
9070
  import { fileURLToPath } from "url";
9027
9071
  import { execFileSync as execFileSync3 } from "child_process";
9028
9072
  import select from "@inquirer/select";
9073
+ import checkbox, { Separator } from "@inquirer/checkbox";
9074
+ import input2 from "@inquirer/input";
9029
9075
  var BACK = "__back__";
9030
9076
  var ALL_MARKETPLACES = "__all_marketplaces__";
9031
9077
  function injectTokenIntoUrl(url, token) {
@@ -9049,7 +9095,7 @@ function repoNameFromUrl(url) {
9049
9095
  }
9050
9096
  }
9051
9097
  function defaultMarketplacePath(url) {
9052
- return path7.join(os.homedir(), ".agents", "marketplaces", repoNameFromUrl(url));
9098
+ return path8.join(os.homedir(), ".agents", "marketplaces", repoNameFromUrl(url));
9053
9099
  }
9054
9100
  function marketplaceLabel(m) {
9055
9101
  return m.name ?? repoNameFromUrl(m.repoUrl ?? "");
@@ -9063,8 +9109,8 @@ function assertInteractive(hint) {
9063
9109
  }
9064
9110
  }
9065
9111
  var AGENT_PATHS = {
9066
- "github-copilot": { project: ".agents/skills", user: path7.join(os.homedir(), ".agents/skills") },
9067
- "claude-code": { project: ".claude/skills", user: path7.join(os.homedir(), ".claude/skills") }
9112
+ "github-copilot": { project: ".agents/skills", user: path8.join(os.homedir(), ".agents/skills") },
9113
+ "claude-code": { project: ".claude/skills", user: path8.join(os.homedir(), ".claude/skills") }
9068
9114
  };
9069
9115
  function resolveAgentName(opts) {
9070
9116
  return opts.claude ? "claude-code" : opts.agent ?? "github-copilot";
@@ -9076,18 +9122,25 @@ function resolveAgentPaths(agentName) {
9076
9122
  }
9077
9123
  return agentConfig;
9078
9124
  }
9125
+ function resolveTargetDir(opts) {
9126
+ if (opts.target) return path8.resolve(opts.target);
9127
+ const agentConfig = resolveAgentPaths(resolveAgentName(opts));
9128
+ const scopePath = (opts.scope ?? "user") === "project" ? agentConfig.project : agentConfig.user;
9129
+ return path8.resolve(scopePath);
9130
+ }
9079
9131
  function getBundledSkillsDir() {
9080
9132
  try {
9081
9133
  const thisFile = fileURLToPath(import.meta.url);
9082
- return path7.resolve(path7.dirname(thisFile), "..", "skills");
9134
+ return path8.resolve(path8.dirname(thisFile), "..", "skills");
9083
9135
  } catch {
9084
9136
  return "";
9085
9137
  }
9086
9138
  }
9087
9139
  function getInstalledMetaPath(targetDir) {
9088
- return path7.join(targetDir, ".pncli-installed.json");
9140
+ return path8.join(targetDir, ".pncli-installed.json");
9089
9141
  }
9090
9142
  var SKILL_ORIGIN_FILENAME = "pncli-origin.json";
9143
+ var DISABLED_SUBDIR = ".pncli-disabled";
9091
9144
  function readInstalledMeta(targetDir) {
9092
9145
  const metaPath = getInstalledMetaPath(targetDir);
9093
9146
  const raw = loadJsonFile(metaPath);
@@ -9095,7 +9148,7 @@ function readInstalledMeta(targetDir) {
9095
9148
  return { version: 1, skills: {} };
9096
9149
  }
9097
9150
  function getSkillOriginPath(skillDir) {
9098
- return path7.join(skillDir, SKILL_ORIGIN_FILENAME);
9151
+ return path8.join(skillDir, SKILL_ORIGIN_FILENAME);
9099
9152
  }
9100
9153
  function readSkillOrigin(skillDir) {
9101
9154
  const originPath = getSkillOriginPath(skillDir);
@@ -9107,6 +9160,132 @@ function readSkillOrigin(skillDir) {
9107
9160
  }
9108
9161
  return null;
9109
9162
  }
9163
+ function matchesMarketplaceFilter(record, filter) {
9164
+ return !filter || record.marketplace === filter || record.installedFrom === filter;
9165
+ }
9166
+ function resolveSkillRecord(meta, targetDir, skillName) {
9167
+ const fromMeta = meta.skills[skillName];
9168
+ if (fromMeta) return fromMeta;
9169
+ const perSkill = readSkillOrigin(path8.join(targetDir, skillName));
9170
+ if (!perSkill) return null;
9171
+ return {
9172
+ source: perSkill.source,
9173
+ marketplace: perSkill.marketplace,
9174
+ plugin: perSkill.plugin,
9175
+ installedFrom: perSkill.installedFrom,
9176
+ branch: perSkill.branch,
9177
+ installedAt: perSkill.installedAt
9178
+ };
9179
+ }
9180
+ function listActiveSkillDirs(targetDir) {
9181
+ return fs4.readdirSync(targetDir).filter((name) => {
9182
+ if (name.startsWith(".")) return false;
9183
+ const p = path8.join(targetDir, name);
9184
+ try {
9185
+ return fs4.statSync(p).isDirectory();
9186
+ } catch {
9187
+ return false;
9188
+ }
9189
+ });
9190
+ }
9191
+ function disablePluginSkills(targetDir, plugin, marketplaceFilter) {
9192
+ const resolvedTarget = path8.resolve(targetDir);
9193
+ const stashDir = path8.join(targetDir, DISABLED_SUBDIR);
9194
+ const meta = readInstalledMeta(targetDir);
9195
+ const disabled = [];
9196
+ const alreadyDisabled = [];
9197
+ const skipped = [];
9198
+ for (const skillName of listActiveSkillDirs(targetDir)) {
9199
+ const skillDir = path8.resolve(targetDir, skillName);
9200
+ if (!skillDir.startsWith(resolvedTarget + path8.sep)) continue;
9201
+ const record = resolveSkillRecord(meta, targetDir, skillName);
9202
+ if (!record || record.source !== "marketplace") {
9203
+ skipped.push(skillName);
9204
+ continue;
9205
+ }
9206
+ if (!matchesMarketplaceFilter(record, marketplaceFilter) || record.plugin !== plugin) {
9207
+ skipped.push(skillName);
9208
+ continue;
9209
+ }
9210
+ const stashDest = path8.join(stashDir, skillName);
9211
+ fs4.mkdirSync(stashDir, { recursive: true });
9212
+ if (fs4.existsSync(stashDest)) fs4.rmSync(stashDest, { recursive: true, force: true });
9213
+ fs4.renameSync(skillDir, stashDest);
9214
+ delete meta.skills[skillName];
9215
+ meta.disabled = meta.disabled ?? {};
9216
+ meta.disabled[skillName] = record;
9217
+ disabled.push(skillName);
9218
+ }
9219
+ for (const [skillName, record] of Object.entries(meta.disabled ?? {})) {
9220
+ if (record.plugin === plugin && matchesMarketplaceFilter(record, marketplaceFilter) && !disabled.includes(skillName)) {
9221
+ alreadyDisabled.push(skillName);
9222
+ }
9223
+ }
9224
+ if (disabled.length > 0) {
9225
+ fs4.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
9226
+ }
9227
+ return { disabled, alreadyDisabled, skipped };
9228
+ }
9229
+ function enablePluginSkills(targetDir, plugin, marketplaceFilter) {
9230
+ const resolvedTarget = path8.resolve(targetDir);
9231
+ const stashDir = path8.join(targetDir, DISABLED_SUBDIR);
9232
+ const meta = readInstalledMeta(targetDir);
9233
+ const enabled = [];
9234
+ const stashMissing = [];
9235
+ const skipped = [];
9236
+ const disabledEntries = Object.entries(meta.disabled ?? {});
9237
+ for (const [skillName, record] of disabledEntries) {
9238
+ if (record.plugin !== plugin || !matchesMarketplaceFilter(record, marketplaceFilter)) {
9239
+ skipped.push(skillName);
9240
+ continue;
9241
+ }
9242
+ const dest = path8.resolve(targetDir, skillName);
9243
+ if (!dest.startsWith(resolvedTarget + path8.sep)) {
9244
+ skipped.push(skillName);
9245
+ continue;
9246
+ }
9247
+ const stashSrc = path8.join(stashDir, skillName);
9248
+ if (!fs4.existsSync(stashSrc)) {
9249
+ stashMissing.push(skillName);
9250
+ continue;
9251
+ }
9252
+ if (fs4.existsSync(dest)) fs4.rmSync(dest, { recursive: true, force: true });
9253
+ fs4.renameSync(stashSrc, dest);
9254
+ meta.skills[skillName] = record;
9255
+ delete meta.disabled[skillName];
9256
+ enabled.push(skillName);
9257
+ }
9258
+ if (fs4.existsSync(stashDir) && fs4.readdirSync(stashDir).length === 0) {
9259
+ fs4.rmdirSync(stashDir);
9260
+ }
9261
+ if (enabled.length > 0) {
9262
+ fs4.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
9263
+ }
9264
+ return { enabled, stashMissing, skipped, hadDisabled: disabledEntries.length > 0 };
9265
+ }
9266
+ function listPluginStates(targetDir) {
9267
+ const meta = readInstalledMeta(targetDir);
9268
+ const byKey = /* @__PURE__ */ new Map();
9269
+ const bucketFor = (record) => {
9270
+ if (record.source !== "marketplace" || !record.plugin) return null;
9271
+ const marketplaceName = record.marketplace ?? record.installedFrom;
9272
+ const key = `${marketplaceName ?? ""}\0${record.plugin}`;
9273
+ let state = byKey.get(key);
9274
+ if (!state) {
9275
+ state = { plugin: record.plugin, marketplace: marketplaceName, activeSkills: [], disabledSkills: [] };
9276
+ byKey.set(key, state);
9277
+ }
9278
+ return state;
9279
+ };
9280
+ for (const skillName of listActiveSkillDirs(targetDir)) {
9281
+ const record = resolveSkillRecord(meta, targetDir, skillName);
9282
+ if (record) bucketFor(record)?.activeSkills.push(skillName);
9283
+ }
9284
+ for (const [skillName, record] of Object.entries(meta.disabled ?? {})) {
9285
+ bucketFor(record)?.disabledSkills.push(skillName);
9286
+ }
9287
+ return [...byKey.values()].sort((a, b) => a.plugin.localeCompare(b.plugin));
9288
+ }
9110
9289
  function detectRepoBranch(repoPath) {
9111
9290
  try {
9112
9291
  const result = execFileSync3("git", ["-C", repoPath, "rev-parse", "--abbrev-ref", "HEAD"], {
@@ -9125,7 +9304,7 @@ function recordInstalledSkills(targetDir, skillNames, record) {
9125
9304
  for (const skillName of skillNames) {
9126
9305
  const fullRecord = { ...record, installedAt: now };
9127
9306
  meta.skills[skillName] = fullRecord;
9128
- const skillDir = path7.join(targetDir, skillName);
9307
+ const skillDir = path8.join(targetDir, skillName);
9129
9308
  if (fs4.existsSync(skillDir)) {
9130
9309
  const origin = { version: 1, ...fullRecord };
9131
9310
  try {
@@ -9195,7 +9374,7 @@ function upsertMarketplace(all, entry) {
9195
9374
  }
9196
9375
  }
9197
9376
  function cloneOrReuseMarketplace(url, resolvedPath, opts) {
9198
- const hasGit = fs4.existsSync(path7.join(resolvedPath, ".git"));
9377
+ const hasGit = fs4.existsSync(path8.join(resolvedPath, ".git"));
9199
9378
  if (fs4.existsSync(resolvedPath) && !hasGit && fs4.readdirSync(resolvedPath).length > 0) {
9200
9379
  throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
9201
9380
  }
@@ -9263,7 +9442,7 @@ function installPluginsForMarketplace(marketplacePath, marketplaceName, repoUrl,
9263
9442
  results[pluginName] = { installed, failed };
9264
9443
  totalInstalled += installed.length;
9265
9444
  for (const skill of installed) {
9266
- warn(` ${skill}: ${path7.join(skillsSrc, skill)} \u2192 ${path7.join(targetDir, skill)}`);
9445
+ warn(` ${skill}: ${path8.join(skillsSrc, skill)} \u2192 ${path8.join(targetDir, skill)}`);
9267
9446
  }
9268
9447
  if (failed.length > 0) {
9269
9448
  warn(`Skipped ${failed.length} skill(s) with invalid names in "${pluginName}": ${failed.join(", ")}`);
@@ -9315,34 +9494,55 @@ function syncMarketplacePlugins(m, targetDir, force, pluginFilter) {
9315
9494
  return { marketplace: marketplaceName, skipped: true, error: message };
9316
9495
  }
9317
9496
  }
9497
+ function performMarketplaceAdd(url, localPath, opts) {
9498
+ const resolvedPath = path8.resolve(localPath ?? defaultMarketplacePath(url));
9499
+ const marketplaceName = opts.name ?? repoNameFromUrl(url);
9500
+ cloneOrReuseMarketplace(url, resolvedPath, opts);
9501
+ const configPath = getGlobalConfigPath();
9502
+ const { existing, all } = loadMarketplacesConfig(configPath);
9503
+ const entry = {
9504
+ name: marketplaceName,
9505
+ repoUrl: url,
9506
+ localPath: resolvedPath,
9507
+ ...opts.token ? { token: opts.token } : {}
9508
+ };
9509
+ upsertMarketplace(all, entry);
9510
+ saveMarketplaces(configPath, existing, all);
9511
+ const targetDir = resolveAgentPaths(resolveAgentName(opts)).user;
9512
+ const { pluginResults, totalInstalled } = installAllPlugins(resolvedPath, marketplaceName, url, targetDir);
9513
+ return {
9514
+ name: marketplaceName,
9515
+ repoUrl: url,
9516
+ localPath: resolvedPath,
9517
+ branch: opts.branch ?? null,
9518
+ tokenConfigured: !!opts.token,
9519
+ plugins: pluginResults,
9520
+ total: totalInstalled,
9521
+ target: targetDir
9522
+ };
9523
+ }
9524
+ function performMarketplaceRemove(name) {
9525
+ const configPath = getGlobalConfigPath();
9526
+ const { existing, all } = loadMarketplacesConfig(configPath);
9527
+ const idx = all.findIndex((m) => m.name === name || m.repoUrl === name);
9528
+ if (idx === -1) {
9529
+ throw new Error(`Marketplace "${name}" not found. Run: pncli skills marketplace list`);
9530
+ }
9531
+ const removed = all.splice(idx, 1)[0];
9532
+ saveMarketplaces(configPath, existing, all);
9533
+ return {
9534
+ removed: {
9535
+ name: marketplaceLabel(removed),
9536
+ repoUrl: removed.repoUrl,
9537
+ localPath: removed.localPath
9538
+ },
9539
+ remaining: all.length
9540
+ };
9541
+ }
9318
9542
  async function marketplaceAddAction(url, localPath, opts, commandName) {
9319
9543
  const start = Date.now();
9320
9544
  try {
9321
- const resolvedPath = path7.resolve(localPath ?? defaultMarketplacePath(url));
9322
- const marketplaceName = opts.name ?? repoNameFromUrl(url);
9323
- cloneOrReuseMarketplace(url, resolvedPath, opts);
9324
- const configPath = getGlobalConfigPath();
9325
- const { existing, all } = loadMarketplacesConfig(configPath);
9326
- const entry = {
9327
- name: marketplaceName,
9328
- repoUrl: url,
9329
- localPath: resolvedPath,
9330
- ...opts.token ? { token: opts.token } : {}
9331
- };
9332
- upsertMarketplace(all, entry);
9333
- saveMarketplaces(configPath, existing, all);
9334
- const targetDir = resolveAgentPaths(resolveAgentName(opts)).user;
9335
- const { pluginResults, totalInstalled } = installAllPlugins(resolvedPath, marketplaceName, url, targetDir);
9336
- success({
9337
- name: marketplaceName,
9338
- repoUrl: url,
9339
- localPath: resolvedPath,
9340
- branch: opts.branch ?? null,
9341
- tokenConfigured: !!opts.token,
9342
- plugins: pluginResults,
9343
- total: totalInstalled,
9344
- target: targetDir
9345
- }, "skills", commandName, start);
9545
+ success(performMarketplaceAdd(url, localPath, opts), "skills", commandName, start);
9346
9546
  } catch (err) {
9347
9547
  fail(err, "skills", commandName, start);
9348
9548
  }
@@ -9354,19 +9554,19 @@ function registerSkillsCommands(program2) {
9354
9554
  try {
9355
9555
  let targetDir;
9356
9556
  if (opts.target) {
9357
- targetDir = path7.resolve(opts.target);
9557
+ targetDir = path8.resolve(opts.target);
9358
9558
  } else {
9359
9559
  const agentConfig = resolveAgentPaths(opts.agent);
9360
9560
  const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
9361
- targetDir = path7.resolve(scopePath);
9561
+ targetDir = path8.resolve(scopePath);
9362
9562
  }
9363
- const resolvedTarget = path7.resolve(targetDir);
9563
+ const resolvedTarget = path8.resolve(targetDir);
9364
9564
  const bundledDir = getBundledSkillsDir();
9365
9565
  let skillDirs = [];
9366
9566
  if (bundledDir !== "") {
9367
9567
  try {
9368
9568
  skillDirs = fs4.readdirSync(bundledDir).filter(
9369
- (name) => fs4.existsSync(path7.join(bundledDir, name, "SKILL.md"))
9569
+ (name) => fs4.existsSync(path8.join(bundledDir, name, "SKILL.md"))
9370
9570
  );
9371
9571
  } catch {
9372
9572
  }
@@ -9377,25 +9577,25 @@ function registerSkillsCommands(program2) {
9377
9577
  const installed = [];
9378
9578
  const failed = [];
9379
9579
  for (const skillName of skillDirs) {
9380
- const existingDir = path7.resolve(targetDir, skillName);
9381
- if (!existingDir.startsWith(resolvedTarget + path7.sep)) continue;
9580
+ const existingDir = path8.resolve(targetDir, skillName);
9581
+ if (!existingDir.startsWith(resolvedTarget + path8.sep)) continue;
9382
9582
  if (fs4.existsSync(existingDir)) {
9383
9583
  fs4.rmSync(existingDir, { recursive: true, force: true });
9384
9584
  }
9385
9585
  }
9386
9586
  warn(`Installing ${skillDirs.length} bundled skill(s) to ${targetDir}...`);
9387
9587
  for (const skillName of skillDirs) {
9388
- const skillDir = path7.resolve(targetDir, skillName);
9389
- if (!skillDir.startsWith(resolvedTarget + path7.sep)) {
9588
+ const skillDir = path8.resolve(targetDir, skillName);
9589
+ if (!skillDir.startsWith(resolvedTarget + path8.sep)) {
9390
9590
  failed.push(skillName);
9391
9591
  continue;
9392
9592
  }
9393
9593
  try {
9394
- const mdFiles = fs4.readdirSync(path7.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
9594
+ const mdFiles = fs4.readdirSync(path8.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
9395
9595
  fs4.mkdirSync(skillDir, { recursive: true });
9396
9596
  for (const mdFile of mdFiles) {
9397
- const content = fs4.readFileSync(path7.join(bundledDir, skillName, mdFile), "utf8");
9398
- fs4.writeFileSync(path7.join(skillDir, mdFile), content, "utf8");
9597
+ const content = fs4.readFileSync(path8.join(bundledDir, skillName, mdFile), "utf8");
9598
+ fs4.writeFileSync(path8.join(skillDir, mdFile), content, "utf8");
9399
9599
  }
9400
9600
  installed.push(skillName);
9401
9601
  } catch {
@@ -9425,11 +9625,11 @@ function registerSkillsCommands(program2) {
9425
9625
  try {
9426
9626
  let targetDir;
9427
9627
  if (opts.target) {
9428
- targetDir = path7.resolve(opts.target);
9628
+ targetDir = path8.resolve(opts.target);
9429
9629
  } else {
9430
9630
  const agentConfig = resolveAgentPaths(opts.agent);
9431
9631
  const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
9432
- targetDir = path7.resolve(scopePath);
9632
+ targetDir = path8.resolve(scopePath);
9433
9633
  }
9434
9634
  if (!fs4.existsSync(targetDir)) {
9435
9635
  success({ skills: [], message: `No skills directory found at ${targetDir}. Run: pncli skills install` }, "skills", "list", start);
@@ -9438,11 +9638,11 @@ function registerSkillsCommands(program2) {
9438
9638
  const meta = readInstalledMeta(targetDir);
9439
9639
  const skillDirs = fs4.readdirSync(targetDir).filter((name) => {
9440
9640
  if (name.startsWith(".")) return false;
9441
- const skillPath = path7.join(targetDir, name, "SKILL.md");
9641
+ const skillPath = path8.join(targetDir, name, "SKILL.md");
9442
9642
  return fs4.existsSync(skillPath);
9443
9643
  });
9444
9644
  const skillsList = skillDirs.map((name) => {
9445
- const content = fs4.readFileSync(path7.join(targetDir, name, "SKILL.md"), "utf8");
9645
+ const content = fs4.readFileSync(path8.join(targetDir, name, "SKILL.md"), "utf8");
9446
9646
  const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
9447
9647
  const data = {};
9448
9648
  const metadata = {};
@@ -9486,15 +9686,15 @@ function registerSkillsCommands(program2) {
9486
9686
  try {
9487
9687
  let targetDir;
9488
9688
  if (opts.target) {
9489
- targetDir = path7.resolve(opts.target);
9689
+ targetDir = path8.resolve(opts.target);
9490
9690
  } else {
9491
9691
  const agentConfig = resolveAgentPaths(resolveAgentName(opts));
9492
9692
  const scopePath = opts.scope === "project" ? agentConfig.project : agentConfig.user;
9493
- targetDir = path7.resolve(scopePath);
9693
+ targetDir = path8.resolve(scopePath);
9494
9694
  }
9495
- const resolvedTarget = path7.resolve(targetDir);
9496
- const skillDir = path7.resolve(targetDir, name);
9497
- if (!skillDir.startsWith(resolvedTarget + path7.sep)) {
9695
+ const resolvedTarget = path8.resolve(targetDir);
9696
+ const skillDir = path8.resolve(targetDir, name);
9697
+ if (!skillDir.startsWith(resolvedTarget + path8.sep)) {
9498
9698
  throw new Error(`Invalid skill name: "${name}"`);
9499
9699
  }
9500
9700
  if (!fs4.existsSync(skillDir)) {
@@ -9568,27 +9768,12 @@ function registerSkillsCommands(program2) {
9568
9768
  marketplace.command("remove").description("Remove a registered marketplace from the config (does not delete the local clone)").argument("<name>", "Name of the marketplace to remove").action((name) => {
9569
9769
  const start = Date.now();
9570
9770
  try {
9571
- const configPath = getGlobalConfigPath();
9572
- const { existing, all } = loadMarketplacesConfig(configPath);
9573
- const idx = all.findIndex((m) => m.name === name || m.repoUrl === name);
9574
- if (idx === -1) {
9575
- throw new Error(`Marketplace "${name}" not found. Run: pncli skills marketplace list`);
9576
- }
9577
- const removed = all.splice(idx, 1)[0];
9578
- saveMarketplaces(configPath, existing, all);
9579
- success({
9580
- removed: {
9581
- name: marketplaceLabel(removed),
9582
- repoUrl: removed.repoUrl,
9583
- localPath: removed.localPath
9584
- },
9585
- remaining: all.length
9586
- }, "skills", "marketplace-remove", start);
9771
+ success(performMarketplaceRemove(name), "skills", "marketplace-remove", start);
9587
9772
  } catch (err) {
9588
9773
  fail(err, "skills", "marketplace-remove", start);
9589
9774
  }
9590
9775
  });
9591
- marketplace.command("sync").description("Pull latest marketplace content and install plugin skills").argument("[plugin]", 'Plugin name to install, or "all" to install every plugin (skips interactive selection)').option("--marketplace <name>", 'Marketplace name to sync, or "all" to sync every registered marketplace (skips interactive selection)').option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--force", 'Reinstall even if a marketplace has no new changes (applies to single-plugin and "all" installs alike)').action(async (plugin, opts) => {
9776
+ marketplace.command("sync").description("Pull latest marketplace content and install plugin skills (interactive picker when no plugin is given)").argument("[plugin]", 'Plugin name to install, or "all" to install every plugin (skips interactive selection)').option("--marketplace <name>", 'Marketplace name to sync, or "all" to sync every registered marketplace (skips interactive selection)').option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--force", 'Reinstall even if a marketplace has no new changes (applies to single-plugin and "all" installs alike)').action(async (plugin, opts) => {
9592
9777
  const start = Date.now();
9593
9778
  try {
9594
9779
  const configPath = getGlobalConfigPath();
@@ -9701,13 +9886,13 @@ function registerSkillsCommands(program2) {
9701
9886
  try {
9702
9887
  let targetDir;
9703
9888
  if (opts.target) {
9704
- targetDir = path7.resolve(opts.target);
9889
+ targetDir = path8.resolve(opts.target);
9705
9890
  } else {
9706
9891
  const agentConfig = resolveAgentPaths(resolveAgentName(opts));
9707
9892
  const scopePath = (opts.scope ?? "user") === "project" ? agentConfig.project : agentConfig.user;
9708
- targetDir = path7.resolve(scopePath);
9893
+ targetDir = path8.resolve(scopePath);
9709
9894
  }
9710
- const resolvedTarget = path7.resolve(targetDir);
9895
+ const resolvedTarget = path8.resolve(targetDir);
9711
9896
  if (!fs4.existsSync(targetDir)) {
9712
9897
  success({ removed: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-purge-plugin", start);
9713
9898
  return;
@@ -9717,7 +9902,7 @@ function registerSkillsCommands(program2) {
9717
9902
  const skipped = [];
9718
9903
  const skillDirs = fs4.readdirSync(targetDir).filter((name) => {
9719
9904
  if (name.startsWith(".")) return false;
9720
- const p = path7.join(targetDir, name);
9905
+ const p = path8.join(targetDir, name);
9721
9906
  try {
9722
9907
  return fs4.statSync(p).isDirectory();
9723
9908
  } catch {
@@ -9725,8 +9910,8 @@ function registerSkillsCommands(program2) {
9725
9910
  }
9726
9911
  });
9727
9912
  for (const skillName of skillDirs) {
9728
- const skillDir = path7.resolve(targetDir, skillName);
9729
- if (!skillDir.startsWith(resolvedTarget + path7.sep)) continue;
9913
+ const skillDir = path8.resolve(targetDir, skillName);
9914
+ if (!skillDir.startsWith(resolvedTarget + path8.sep)) continue;
9730
9915
  let record = meta.skills[skillName] ?? null;
9731
9916
  if (!record) {
9732
9917
  const perSkill = readSkillOrigin(skillDir);
@@ -9770,6 +9955,161 @@ function registerSkillsCommands(program2) {
9770
9955
  fail(err, "skills", "marketplace-purge-plugin", start);
9771
9956
  }
9772
9957
  });
9958
+ marketplace.command("disable").description("Temporarily deactivate a plugin's skills without deleting them (re-enable with `marketplace enable`)").argument("<plugin>", "Plugin name to disable").option("--marketplace <name>", "Restrict to skills from this marketplace (by name or repo URL)").option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--scope <scope>", "Installation scope: project | user (default: user)").option("--target <dir>", "Override skills directory").action((plugin, opts) => {
9959
+ const start = Date.now();
9960
+ try {
9961
+ const targetDir = resolveTargetDir(opts);
9962
+ if (!fs4.existsSync(targetDir)) {
9963
+ success({ disabled: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-disable", start);
9964
+ return;
9965
+ }
9966
+ const result = disablePluginSkills(targetDir, plugin, opts.marketplace);
9967
+ success({
9968
+ disabled: result.disabled,
9969
+ alreadyDisabled: result.alreadyDisabled,
9970
+ skipped: result.skipped.length,
9971
+ total: result.disabled.length,
9972
+ target: targetDir,
9973
+ plugin,
9974
+ stash: path8.join(targetDir, DISABLED_SUBDIR),
9975
+ ...opts.marketplace ? { marketplace: opts.marketplace } : {}
9976
+ }, "skills", "marketplace-disable", start);
9977
+ } catch (err) {
9978
+ fail(err, "skills", "marketplace-disable", start);
9979
+ }
9980
+ });
9981
+ marketplace.command("enable").description("Re-activate a previously disabled plugin's skills").argument("<plugin>", "Plugin name to enable").option("--marketplace <name>", "Restrict to skills from this marketplace (by name or repo URL)").option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--scope <scope>", "Installation scope: project | user (default: user)").option("--target <dir>", "Override skills directory").action((plugin, opts) => {
9982
+ const start = Date.now();
9983
+ try {
9984
+ const targetDir = resolveTargetDir(opts);
9985
+ if (!fs4.existsSync(targetDir)) {
9986
+ success({ enabled: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-enable", start);
9987
+ return;
9988
+ }
9989
+ const result = enablePluginSkills(targetDir, plugin, opts.marketplace);
9990
+ if (!result.hadDisabled) {
9991
+ success({ enabled: [], total: 0, target: targetDir, plugin, message: `No disabled skills found for plugin "${plugin}".` }, "skills", "marketplace-enable", start);
9992
+ return;
9993
+ }
9994
+ success({
9995
+ enabled: result.enabled,
9996
+ skipped: result.skipped.length,
9997
+ total: result.enabled.length,
9998
+ target: targetDir,
9999
+ plugin,
10000
+ ...result.stashMissing.length > 0 ? {
10001
+ stashMissing: result.stashMissing,
10002
+ warning: `Skills marked disabled but missing from the stash were left as-is. Re-install the plugin (pncli skills marketplace sync ${plugin}) to restore them.`
10003
+ } : {},
10004
+ ...opts.marketplace ? { marketplace: opts.marketplace } : {}
10005
+ }, "skills", "marketplace-enable", start);
10006
+ } catch (err) {
10007
+ fail(err, "skills", "marketplace-enable", start);
10008
+ }
10009
+ });
10010
+ marketplace.command("manage").description("Manage marketplaces and plugins: toggle plugins on/off, add or remove marketplaces (interactive)").option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--scope <scope>", "Installation scope: project | user (default: user)").option("--target <dir>", "Override skills directory").action(async (opts) => {
10011
+ const start = Date.now();
10012
+ try {
10013
+ assertInteractive("Use `pncli skills marketplace enable|disable <plugin>`, `add <url>`, and `remove <name>` to manage non-interactively.");
10014
+ const targetDir = resolveTargetDir(opts);
10015
+ const enabledPlugins = [];
10016
+ const disabledPlugins = [];
10017
+ const stashMissing = [];
10018
+ const addedMarketplaces = [];
10019
+ const removedMarketplaces = [];
10020
+ for (; ; ) {
10021
+ const states = fs4.existsSync(targetDir) ? listPluginStates(targetDir) : [];
10022
+ const registered = loadMarketplaces(getGlobalConfigPath());
10023
+ const action = await select({
10024
+ message: `Manage skills marketplaces (target: ${targetDir}):`,
10025
+ choices: [
10026
+ ...states.length > 0 ? [{ value: "toggle", name: `Toggle plugins on/off (${states.length} installed)` }] : [],
10027
+ { value: "add", name: "Add a marketplace" },
10028
+ ...registered.length > 0 ? [{ value: "remove", name: `Remove a marketplace (${registered.length} registered)` }] : [],
10029
+ { value: "done", name: "Done" }
10030
+ ]
10031
+ });
10032
+ if (action === "done") break;
10033
+ if (action === "toggle") {
10034
+ const sorted = [...states].sort((a, b) => (a.marketplace ?? "").localeCompare(b.marketplace ?? "") || a.plugin.localeCompare(b.plugin));
10035
+ const choices = [];
10036
+ let lastMarketplace = null;
10037
+ sorted.forEach((state, index) => {
10038
+ const marketplaceName = state.marketplace ?? "(unknown marketplace)";
10039
+ if (marketplaceName !== lastMarketplace) {
10040
+ choices.push(new Separator(`\u2500\u2500 ${marketplaceName} \u2500\u2500`));
10041
+ lastMarketplace = marketplaceName;
10042
+ }
10043
+ const active = state.activeSkills.length;
10044
+ const stashed = state.disabledSkills.length;
10045
+ const skillCount = stashed === 0 ? `${active} skill${active === 1 ? "" : "s"}` : active === 0 ? `${stashed} skill${stashed === 1 ? "" : "s"}, disabled` : `${active} active, ${stashed} disabled`;
10046
+ choices.push({ name: `${state.plugin} (${skillCount})`, value: index, checked: active > 0 });
10047
+ });
10048
+ const checkedIndexes = await checkbox({
10049
+ message: "Toggle plugins (space toggles, enter applies):",
10050
+ choices,
10051
+ pageSize: 15
10052
+ });
10053
+ const wanted = new Set(checkedIndexes);
10054
+ sorted.forEach((state, index) => {
10055
+ if (wanted.has(index)) {
10056
+ if (state.disabledSkills.length === 0) return;
10057
+ const result = enablePluginSkills(targetDir, state.plugin, state.marketplace);
10058
+ stashMissing.push(...result.stashMissing);
10059
+ if (result.enabled.length > 0) {
10060
+ enabledPlugins.push({ plugin: state.plugin, marketplace: state.marketplace, skills: result.enabled });
10061
+ }
10062
+ } else {
10063
+ if (state.activeSkills.length === 0) return;
10064
+ const result = disablePluginSkills(targetDir, state.plugin, state.marketplace);
10065
+ if (result.disabled.length > 0) {
10066
+ disabledPlugins.push({ plugin: state.plugin, marketplace: state.marketplace, skills: result.disabled });
10067
+ }
10068
+ }
10069
+ });
10070
+ } else if (action === "add") {
10071
+ const url = (await input2({
10072
+ message: "Git clone URL of the marketplace repository:",
10073
+ validate: (v) => v.trim().length > 0 || "URL is required"
10074
+ })).trim();
10075
+ const name = (await input2({
10076
+ message: "Marketplace name:",
10077
+ default: repoNameFromUrl(url)
10078
+ })).trim();
10079
+ addedMarketplaces.push(performMarketplaceAdd(url, void 0, {
10080
+ name: name || void 0,
10081
+ agent: opts.agent,
10082
+ claude: opts.claude
10083
+ }));
10084
+ } else if (action === "remove") {
10085
+ const chosen = await select({
10086
+ message: "Remove which marketplace? (the local clone is kept on disk)",
10087
+ choices: [
10088
+ // Index-based values: labels alone aren't guaranteed unique (two marketplaces can share a derived name).
10089
+ ...registered.map((m, i) => ({ value: String(i), name: `${marketplaceLabel(m)} \u2014 ${m.repoUrl ?? ""}` })),
10090
+ { value: BACK, name: "\u2190 Back" }
10091
+ ]
10092
+ });
10093
+ if (chosen === BACK) continue;
10094
+ const target = registered[Number(chosen)];
10095
+ removedMarketplaces.push(performMarketplaceRemove(target.repoUrl ?? marketplaceLabel(target)));
10096
+ }
10097
+ }
10098
+ success({
10099
+ enabled: enabledPlugins,
10100
+ disabled: disabledPlugins,
10101
+ addedMarketplaces,
10102
+ removedMarketplaces,
10103
+ target: targetDir,
10104
+ ...stashMissing.length > 0 ? {
10105
+ stashMissing,
10106
+ warning: "Skills marked disabled but missing from the stash were left as-is. Re-install their plugin (pncli skills marketplace sync <plugin>) to restore them."
10107
+ } : {}
10108
+ }, "skills", "marketplace-manage", start);
10109
+ } catch (err) {
10110
+ fail(err, "skills", "marketplace-manage", start);
10111
+ }
10112
+ });
9773
10113
  skills.command("purge-user").description("Remove all skills from the user-level skills folder for the target agent").option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--force", "Skip confirmation \u2014 remove all skills without prompting").action((opts) => {
9774
10114
  const start = Date.now();
9775
10115
  try {
@@ -9780,7 +10120,7 @@ function registerSkillsCommands(program2) {
9780
10120
  }
9781
10121
  const skillDirs = fs4.readdirSync(userDir).filter((name) => {
9782
10122
  if (name.startsWith(".")) return false;
9783
- const p = path7.join(userDir, name);
10123
+ const p = path8.join(userDir, name);
9784
10124
  try {
9785
10125
  return fs4.statSync(p).isDirectory();
9786
10126
  } catch {
@@ -9798,8 +10138,8 @@ Re-run with --force to confirm, or use \`pncli skills marketplace purge-plugin\`
9798
10138
  );
9799
10139
  }
9800
10140
  for (const name of skillDirs) {
9801
- const skillDir = path7.resolve(userDir, name);
9802
- if (!skillDir.startsWith(path7.resolve(userDir) + path7.sep)) continue;
10141
+ const skillDir = path8.resolve(userDir, name);
10142
+ if (!skillDir.startsWith(path8.resolve(userDir) + path8.sep)) continue;
9803
10143
  fs4.rmSync(skillDir, { recursive: true, force: true });
9804
10144
  }
9805
10145
  const metaPath = getInstalledMetaPath(userDir);
@@ -9813,7 +10153,7 @@ Re-run with --force to confirm, or use \`pncli skills marketplace purge-plugin\`
9813
10153
  });
9814
10154
  }
9815
10155
  function resolvePluginChoices(marketplacePath) {
9816
- const marketplaceJsonPath = path7.join(marketplacePath, ".claude-plugin", "marketplace.json");
10156
+ const marketplaceJsonPath = path8.join(marketplacePath, ".claude-plugin", "marketplace.json");
9817
10157
  if (fs4.existsSync(marketplaceJsonPath)) {
9818
10158
  try {
9819
10159
  const meta = JSON.parse(fs4.readFileSync(marketplaceJsonPath, "utf8"));
@@ -9823,36 +10163,36 @@ function resolvePluginChoices(marketplacePath) {
9823
10163
  } catch {
9824
10164
  }
9825
10165
  }
9826
- const pluginsDir = path7.join(marketplacePath, "plugins");
10166
+ const pluginsDir = path8.join(marketplacePath, "plugins");
9827
10167
  if (fs4.existsSync(pluginsDir)) {
9828
- return fs4.readdirSync(pluginsDir).filter((name) => fs4.statSync(path7.join(pluginsDir, name)).isDirectory()).map((name) => ({ name, description: "" }));
10168
+ return fs4.readdirSync(pluginsDir).filter((name) => fs4.statSync(path8.join(pluginsDir, name)).isDirectory()).map((name) => ({ name, description: "" }));
9829
10169
  }
9830
10170
  return [];
9831
10171
  }
9832
10172
  function resolveSkillsSrc(marketplacePath, selectedPlugin) {
9833
- const pluginsBase = path7.resolve(marketplacePath, "plugins");
9834
- const skillsSrc = path7.resolve(pluginsBase, selectedPlugin, "skills");
9835
- if (!skillsSrc.startsWith(pluginsBase + path7.sep)) {
10173
+ const pluginsBase = path8.resolve(marketplacePath, "plugins");
10174
+ const skillsSrc = path8.resolve(pluginsBase, selectedPlugin, "skills");
10175
+ if (!skillsSrc.startsWith(pluginsBase + path8.sep)) {
9836
10176
  throw new Error(`Invalid plugin name: "${selectedPlugin}"`);
9837
10177
  }
9838
10178
  return skillsSrc;
9839
10179
  }
9840
10180
  function copyPluginSkills(skillsSrc, targetDir, meta) {
9841
10181
  fs4.mkdirSync(targetDir, { recursive: true });
9842
- const resolvedTarget = path7.resolve(targetDir);
10182
+ const resolvedTarget = path8.resolve(targetDir);
9843
10183
  const skillNames = fs4.readdirSync(skillsSrc).filter(
9844
- (name) => fs4.statSync(path7.join(skillsSrc, name)).isDirectory()
10184
+ (name) => fs4.statSync(path8.join(skillsSrc, name)).isDirectory()
9845
10185
  );
9846
10186
  const installed = [];
9847
10187
  const failed = [];
9848
10188
  for (const skillName of skillNames) {
9849
- const dest = path7.resolve(targetDir, skillName);
9850
- if (!dest.startsWith(resolvedTarget + path7.sep)) {
10189
+ const dest = path8.resolve(targetDir, skillName);
10190
+ if (!dest.startsWith(resolvedTarget + path8.sep)) {
9851
10191
  failed.push(skillName);
9852
10192
  continue;
9853
10193
  }
9854
10194
  fs4.rmSync(dest, { recursive: true, force: true });
9855
- fs4.cpSync(path7.join(skillsSrc, skillName), dest, { recursive: true });
10195
+ fs4.cpSync(path8.join(skillsSrc, skillName), dest, { recursive: true });
9856
10196
  installed.push(skillName);
9857
10197
  }
9858
10198
  if (meta) {
@@ -9868,8 +10208,8 @@ function copyPluginSkills(skillsSrc, targetDir, meta) {
9868
10208
  }
9869
10209
 
9870
10210
  // src/services/jwt/commands.ts
9871
- function base64urlDecode(input2) {
9872
- const base64 = input2.replace(/-/g, "+").replace(/_/g, "/");
10211
+ function base64urlDecode(input3) {
10212
+ const base64 = input3.replace(/-/g, "+").replace(/_/g, "/");
9873
10213
  const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
9874
10214
  return Buffer.from(padded, "base64").toString("utf8");
9875
10215
  }