@kolatts/pncli 1.21.0 → 1.23.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
@@ -8,7 +8,7 @@ import {
8
8
  setRepoConfigValue,
9
9
  writeGlobalConfig,
10
10
  writeRepoConfig
11
- } from "./chunk-FXY262BR.js";
11
+ } from "./chunk-KRDKHGYS.js";
12
12
  import {
13
13
  ExitCode,
14
14
  PncliError,
@@ -20,7 +20,7 @@ import {
20
20
  success,
21
21
  warn,
22
22
  writeRawOutput
23
- } from "./chunk-PC4AHQCN.js";
23
+ } from "./chunk-XYHZMXJU.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) };
@@ -5762,6 +5804,16 @@ function registerConfigCommands(program2) {
5762
5804
  } else {
5763
5805
  results.dynatrace_platform = { ok: null, message: "not configured" };
5764
5806
  }
5807
+ if (cfg.logscale.baseUrl && cfg.logscale.token) {
5808
+ try {
5809
+ await http.logscale("/api/v1/repositories");
5810
+ results.logscale = { ok: true, message: "connected" };
5811
+ } catch (err) {
5812
+ results.logscale = { ok: false, message: err instanceof Error ? err.message : String(err) };
5813
+ }
5814
+ } else {
5815
+ results.logscale = { ok: null, message: "not configured" };
5816
+ }
5765
5817
  success(results, "config", "test", start);
5766
5818
  } catch (err) {
5767
5819
  fail(err, "config", "test", start);
@@ -5867,8 +5919,8 @@ function registerConfigCommands(program2) {
5867
5919
  } else {
5868
5920
  try {
5869
5921
  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 });
5922
+ const path9 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
5923
+ await http.ado(path9, { timeoutMs: 1e4 });
5872
5924
  results.ado = { status: "valid", message: "ok" };
5873
5925
  } catch (err) {
5874
5926
  results.ado = categorize2(err);
@@ -6006,11 +6058,23 @@ function registerConfigCommands(program2) {
6006
6058
  results.dynatrace_platform = categorize2(err);
6007
6059
  }
6008
6060
  }
6061
+ if (!cfg.logscale.token) {
6062
+ results.logscale = { status: "blank", message: "not configured" };
6063
+ } else if (!cfg.logscale.baseUrl) {
6064
+ results.logscale = { status: "error", message: "baseUrl not configured" };
6065
+ } else {
6066
+ try {
6067
+ await http.logscale("/api/v1/repositories", { timeoutMs: 1e4 });
6068
+ results.logscale = { status: "valid", message: "ok" };
6069
+ } catch (err) {
6070
+ results.logscale = categorize2(err);
6071
+ }
6072
+ }
6009
6073
  const statuses = Object.values(results).map((r) => r.status);
6010
6074
  if (statuses.includes("invalid")) process.exitCode = ExitCode.AUTH_ERROR;
6011
6075
  else if (statuses.includes("error")) process.exitCode = ExitCode.NETWORK_ERROR;
6012
6076
  if (cmdOpts.output === "table") {
6013
- const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform"];
6077
+ const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform", "logscale"];
6014
6078
  const labelWidth = 14;
6015
6079
  const statusWidth = 9;
6016
6080
  for (const svc of services) {
@@ -6024,7 +6088,7 @@ function registerConfigCommands(program2) {
6024
6088
  process.stdout.write("\n");
6025
6089
  } else {
6026
6090
  if (opts.pretty) {
6027
- const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform"];
6091
+ const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform", "logscale"];
6028
6092
  const labelWidth = 14;
6029
6093
  const statusWidth = 9;
6030
6094
  for (const svc of services) {
@@ -6647,6 +6711,40 @@ async function initGlobalConfig(start) {
6647
6711
  }
6648
6712
  }
6649
6713
  }
6714
+ process.stderr.write("\n\u2500\u2500 LogScale \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
6715
+ const useLogscale = await confirm({
6716
+ message: "Configure LogScale (Falcon LogScale / Humio) for log queries?",
6717
+ default: false
6718
+ });
6719
+ let logscaleBaseUrl = "";
6720
+ let logscaleToken = "";
6721
+ if (useLogscale) {
6722
+ logscaleBaseUrl = await input({
6723
+ message: "LogScale base URL (e.g. https://logscale.imagile.dev):",
6724
+ default: ""
6725
+ });
6726
+ logscaleToken = await password({
6727
+ message: "LogScale personal access token:"
6728
+ });
6729
+ if (logscaleBaseUrl && logscaleToken) {
6730
+ process.stderr.write("\n Verifying connection...\n");
6731
+ try {
6732
+ const tempConfig = {
6733
+ ...loadConfig(),
6734
+ logscale: {
6735
+ baseUrl: normalizeBaseUrl(logscaleBaseUrl),
6736
+ token: logscaleToken
6737
+ }
6738
+ };
6739
+ const tempHttp = createHttpClient(tempConfig);
6740
+ await tempHttp.logscale("/api/v1/repositories");
6741
+ process.stderr.write(" Connected.\n");
6742
+ } catch (err) {
6743
+ warn(`Could not connect to LogScale: ${err instanceof Error ? err.message : String(err)}`);
6744
+ warn("Config will be saved anyway. Check your URL and token and re-run pncli config init or pncli config test.");
6745
+ }
6746
+ }
6747
+ }
6650
6748
  process.stderr.write("\n\u2500\u2500 Defaults \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
6651
6749
  const jiraProject = await input({
6652
6750
  message: "Default Jira project key (optional):",
@@ -6786,6 +6884,12 @@ async function initGlobalConfig(start) {
6786
6884
  platformToken: dynatracePlatformToken || void 0
6787
6885
  }
6788
6886
  } : {},
6887
+ ...useLogscale && logscaleBaseUrl ? {
6888
+ logscale: {
6889
+ baseUrl: normalizeBaseUrl(logscaleBaseUrl),
6890
+ token: logscaleToken || void 0
6891
+ }
6892
+ } : {},
6789
6893
  defaults: {
6790
6894
  jira: {
6791
6895
  project: jiraProject || void 0
@@ -7263,7 +7367,7 @@ function registerAdoProjectCommands(ado) {
7263
7367
 
7264
7368
  // src/services/ado/commands/work.ts
7265
7369
  import fs3 from "fs";
7266
- import path6 from "path";
7370
+ import path7 from "path";
7267
7371
 
7268
7372
  // src/services/ado/input-schema.ts
7269
7373
  var ADO_WORK_INPUT_FILE_SCHEMA = {
@@ -7504,9 +7608,9 @@ function registerAdoWorkCommands(ado) {
7504
7608
  const attachments = await workClient.listAttachments(collection, parseInt(opts.id, 10));
7505
7609
  const attachment = attachments.find((a) => a.id === opts.attachmentId);
7506
7610
  if (!attachment) throw new PncliError(`Attachment not found: ${opts.attachmentId}`, 1);
7507
- const outDir = opts.dir ?? path6.join(process.cwd(), ".pncli");
7611
+ const outDir = opts.dir ?? path7.join(process.cwd(), ".pncli");
7508
7612
  fs3.mkdirSync(outDir, { recursive: true });
7509
- const outPath = path6.join(outDir, path6.basename(attachment.name));
7613
+ const outPath = path7.join(outDir, path7.basename(attachment.name));
7510
7614
  const buffer = await workClient.downloadAttachment(attachment.url);
7511
7615
  fs3.writeFileSync(outPath, buffer);
7512
7616
  success({ saved: outPath, name: attachment.name, size: buffer.length }, "ado", "work-download-attachment", start);
@@ -7666,7 +7770,7 @@ function registerAdoRepoCommands(ado) {
7666
7770
  fail(err, "ado", "repo-get-pr", start);
7667
7771
  }
7668
7772
  });
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) => {
7773
+ 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
7774
  const start = Date.now();
7671
7775
  try {
7672
7776
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
@@ -7676,14 +7780,15 @@ function registerAdoRepoCommands(ado) {
7676
7780
  description: opts.description,
7677
7781
  sourceRefName: `refs/heads/${opts.source}`,
7678
7782
  targetRefName: `refs/heads/${opts.target}`,
7679
- reviewers
7783
+ reviewers,
7784
+ ...opts.draft !== void 0 ? { isDraft: opts.draft } : {}
7680
7785
  });
7681
7786
  success(data, "ado", "repo-create-pr", start);
7682
7787
  } catch (err) {
7683
7788
  fail(err, "ado", "repo-create-pr", start);
7684
7789
  }
7685
7790
  });
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) => {
7791
+ 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
7792
  const start = Date.now();
7688
7793
  try {
7689
7794
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
@@ -7692,6 +7797,7 @@ function registerAdoRepoCommands(ado) {
7692
7797
  if (opts.title) body.title = opts.title;
7693
7798
  if (opts.description !== void 0) body.description = opts.description;
7694
7799
  if (reviewers) body.reviewers = reviewers;
7800
+ if (opts.draft !== void 0) body.isDraft = opts.draft === "true";
7695
7801
  const data = await gitClient.updatePR(collection, project, repo2, parseInt(opts.id, 10), body);
7696
7802
  success(data, "ado", "repo-update-pr", start);
7697
7803
  } catch (err) {
@@ -7873,11 +7979,11 @@ function registerAdoRepoCommands(ado) {
7873
7979
  const start = Date.now();
7874
7980
  try {
7875
7981
  const globalOpts = ado.optsWithGlobals();
7876
- const { loadConfig: lc } = await import("./config-CPRXY5SN.js");
7982
+ const { loadConfig: lc } = await import("./config-QN5FFAZY.js");
7877
7983
  const config = lc({ configPath: globalOpts.config });
7878
7984
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
7879
7985
  const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
7880
- const { createHttpClient: createHttpClient2 } = await import("./http-IXJA4TMS.js");
7986
+ const { createHttpClient: createHttpClient2 } = await import("./http-MCIU2IV6.js");
7881
7987
  const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
7882
7988
  const coreClient = new AdoCoreClient2(http);
7883
7989
  const me = await coreClient.getConnectionData(collection);
@@ -8278,18 +8384,18 @@ var ArtifactoryClient = class {
8278
8384
  }
8279
8385
  });
8280
8386
  }
8281
- async getStorageInfo(repo, path8) {
8282
- const cleanPath = path8.startsWith("/") ? path8.slice(1) : path8;
8387
+ async getStorageInfo(repo, path9) {
8388
+ const cleanPath = path9.startsWith("/") ? path9.slice(1) : path9;
8283
8389
  return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`);
8284
8390
  }
8285
- async getProperties(repo, path8) {
8286
- const cleanPath = path8.startsWith("/") ? path8.slice(1) : path8;
8391
+ async getProperties(repo, path9) {
8392
+ const cleanPath = path9.startsWith("/") ? path9.slice(1) : path9;
8287
8393
  return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
8288
8394
  params: { properties: "" }
8289
8395
  });
8290
8396
  }
8291
- async setProperties(repo, path8, properties, opts = {}) {
8292
- const cleanPath = path8.startsWith("/") ? path8.slice(1) : path8;
8397
+ async setProperties(repo, path9, properties, opts = {}) {
8398
+ const cleanPath = path9.startsWith("/") ? path9.slice(1) : path9;
8293
8399
  const propString = Object.entries(properties).map(([k, v]) => `${k}=${v}`).join(";");
8294
8400
  await this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
8295
8401
  method: "PUT",
@@ -8371,27 +8477,27 @@ function registerArtifactoryCommands(program2) {
8371
8477
  fail(err, "artifactory", "repos", start);
8372
8478
  }
8373
8479
  });
8374
- art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo, path8) => {
8480
+ art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo, path9) => {
8375
8481
  const start = Date.now();
8376
8482
  try {
8377
8483
  const client = getClient8(program2);
8378
- const data = await client.getStorageInfo(repo, path8);
8484
+ const data = await client.getStorageInfo(repo, path9);
8379
8485
  success(data, "artifactory", "artifact-info", start);
8380
8486
  } catch (err) {
8381
8487
  fail(err, "artifactory", "artifact-info", start);
8382
8488
  }
8383
8489
  });
8384
- art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo, path8) => {
8490
+ art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo, path9) => {
8385
8491
  const start = Date.now();
8386
8492
  try {
8387
8493
  const client = getClient8(program2);
8388
- const data = await client.getProperties(repo, path8);
8494
+ const data = await client.getProperties(repo, path9);
8389
8495
  success(data, "artifactory", "properties-get", start);
8390
8496
  } catch (err) {
8391
8497
  fail(err, "artifactory", "properties-get", start);
8392
8498
  }
8393
8499
  });
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) => {
8500
+ 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
8501
  const start = Date.now();
8396
8502
  try {
8397
8503
  if (!keyvals.length) throw new PncliError("Provide at least one key=value pair");
@@ -8402,8 +8508,8 @@ function registerArtifactoryCommands(program2) {
8402
8508
  properties[kv.slice(0, eq)] = kv.slice(eq + 1);
8403
8509
  }
8404
8510
  const client = getClient8(program2);
8405
- await client.setProperties(repo, path8, properties, { recursive: opts.recursive });
8406
- success({ repo, path: path8, properties }, "artifactory", "properties-set", start);
8511
+ await client.setProperties(repo, path9, properties, { recursive: opts.recursive });
8512
+ success({ repo, path: path9, properties }, "artifactory", "properties-set", start);
8407
8513
  } catch (err) {
8408
8514
  fail(err, "artifactory", "properties-set", start);
8409
8515
  }
@@ -9010,8 +9116,8 @@ function registerSonatypeIqCommands(program2) {
9010
9116
  const startTime = Date.now();
9011
9117
  try {
9012
9118
  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);
9119
+ const path9 = opts.organizationId ? `/api/v2/policies/organization/${encodeURIComponent(opts.organizationId)}` : "/api/v2/policies";
9120
+ const data = await http.sonatypeiq(path9);
9015
9121
  success(data, "sonatypeiq", "policies list", startTime);
9016
9122
  } catch (err) {
9017
9123
  fail(err, "sonatypeiq", "policies list", startTime);
@@ -9021,11 +9127,13 @@ function registerSonatypeIqCommands(program2) {
9021
9127
 
9022
9128
  // src/services/skills/commands.ts
9023
9129
  import fs4 from "fs";
9024
- import path7 from "path";
9130
+ import path8 from "path";
9025
9131
  import os from "os";
9026
9132
  import { fileURLToPath } from "url";
9027
9133
  import { execFileSync as execFileSync3 } from "child_process";
9028
9134
  import select from "@inquirer/select";
9135
+ import checkbox, { Separator } from "@inquirer/checkbox";
9136
+ import input2 from "@inquirer/input";
9029
9137
  var BACK = "__back__";
9030
9138
  var ALL_MARKETPLACES = "__all_marketplaces__";
9031
9139
  function injectTokenIntoUrl(url, token) {
@@ -9049,7 +9157,7 @@ function repoNameFromUrl(url) {
9049
9157
  }
9050
9158
  }
9051
9159
  function defaultMarketplacePath(url) {
9052
- return path7.join(os.homedir(), ".agents", "marketplaces", repoNameFromUrl(url));
9160
+ return path8.join(os.homedir(), ".agents", "marketplaces", repoNameFromUrl(url));
9053
9161
  }
9054
9162
  function marketplaceLabel(m) {
9055
9163
  return m.name ?? repoNameFromUrl(m.repoUrl ?? "");
@@ -9063,8 +9171,8 @@ function assertInteractive(hint) {
9063
9171
  }
9064
9172
  }
9065
9173
  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") }
9174
+ "github-copilot": { project: ".agents/skills", user: path8.join(os.homedir(), ".agents/skills") },
9175
+ "claude-code": { project: ".claude/skills", user: path8.join(os.homedir(), ".claude/skills") }
9068
9176
  };
9069
9177
  function resolveAgentName(opts) {
9070
9178
  return opts.claude ? "claude-code" : opts.agent ?? "github-copilot";
@@ -9076,18 +9184,25 @@ function resolveAgentPaths(agentName) {
9076
9184
  }
9077
9185
  return agentConfig;
9078
9186
  }
9187
+ function resolveTargetDir(opts) {
9188
+ if (opts.target) return path8.resolve(opts.target);
9189
+ const agentConfig = resolveAgentPaths(resolveAgentName(opts));
9190
+ const scopePath = (opts.scope ?? "user") === "project" ? agentConfig.project : agentConfig.user;
9191
+ return path8.resolve(scopePath);
9192
+ }
9079
9193
  function getBundledSkillsDir() {
9080
9194
  try {
9081
9195
  const thisFile = fileURLToPath(import.meta.url);
9082
- return path7.resolve(path7.dirname(thisFile), "..", "skills");
9196
+ return path8.resolve(path8.dirname(thisFile), "..", "skills");
9083
9197
  } catch {
9084
9198
  return "";
9085
9199
  }
9086
9200
  }
9087
9201
  function getInstalledMetaPath(targetDir) {
9088
- return path7.join(targetDir, ".pncli-installed.json");
9202
+ return path8.join(targetDir, ".pncli-installed.json");
9089
9203
  }
9090
9204
  var SKILL_ORIGIN_FILENAME = "pncli-origin.json";
9205
+ var DISABLED_SUBDIR = ".pncli-disabled";
9091
9206
  function readInstalledMeta(targetDir) {
9092
9207
  const metaPath = getInstalledMetaPath(targetDir);
9093
9208
  const raw = loadJsonFile(metaPath);
@@ -9095,7 +9210,7 @@ function readInstalledMeta(targetDir) {
9095
9210
  return { version: 1, skills: {} };
9096
9211
  }
9097
9212
  function getSkillOriginPath(skillDir) {
9098
- return path7.join(skillDir, SKILL_ORIGIN_FILENAME);
9213
+ return path8.join(skillDir, SKILL_ORIGIN_FILENAME);
9099
9214
  }
9100
9215
  function readSkillOrigin(skillDir) {
9101
9216
  const originPath = getSkillOriginPath(skillDir);
@@ -9107,6 +9222,132 @@ function readSkillOrigin(skillDir) {
9107
9222
  }
9108
9223
  return null;
9109
9224
  }
9225
+ function matchesMarketplaceFilter(record, filter) {
9226
+ return !filter || record.marketplace === filter || record.installedFrom === filter;
9227
+ }
9228
+ function resolveSkillRecord(meta, targetDir, skillName) {
9229
+ const fromMeta = meta.skills[skillName];
9230
+ if (fromMeta) return fromMeta;
9231
+ const perSkill = readSkillOrigin(path8.join(targetDir, skillName));
9232
+ if (!perSkill) return null;
9233
+ return {
9234
+ source: perSkill.source,
9235
+ marketplace: perSkill.marketplace,
9236
+ plugin: perSkill.plugin,
9237
+ installedFrom: perSkill.installedFrom,
9238
+ branch: perSkill.branch,
9239
+ installedAt: perSkill.installedAt
9240
+ };
9241
+ }
9242
+ function listActiveSkillDirs(targetDir) {
9243
+ return fs4.readdirSync(targetDir).filter((name) => {
9244
+ if (name.startsWith(".")) return false;
9245
+ const p = path8.join(targetDir, name);
9246
+ try {
9247
+ return fs4.statSync(p).isDirectory();
9248
+ } catch {
9249
+ return false;
9250
+ }
9251
+ });
9252
+ }
9253
+ function disablePluginSkills(targetDir, plugin, marketplaceFilter) {
9254
+ const resolvedTarget = path8.resolve(targetDir);
9255
+ const stashDir = path8.join(targetDir, DISABLED_SUBDIR);
9256
+ const meta = readInstalledMeta(targetDir);
9257
+ const disabled = [];
9258
+ const alreadyDisabled = [];
9259
+ const skipped = [];
9260
+ for (const skillName of listActiveSkillDirs(targetDir)) {
9261
+ const skillDir = path8.resolve(targetDir, skillName);
9262
+ if (!skillDir.startsWith(resolvedTarget + path8.sep)) continue;
9263
+ const record = resolveSkillRecord(meta, targetDir, skillName);
9264
+ if (!record || record.source !== "marketplace") {
9265
+ skipped.push(skillName);
9266
+ continue;
9267
+ }
9268
+ if (!matchesMarketplaceFilter(record, marketplaceFilter) || record.plugin !== plugin) {
9269
+ skipped.push(skillName);
9270
+ continue;
9271
+ }
9272
+ const stashDest = path8.join(stashDir, skillName);
9273
+ fs4.mkdirSync(stashDir, { recursive: true });
9274
+ if (fs4.existsSync(stashDest)) fs4.rmSync(stashDest, { recursive: true, force: true });
9275
+ fs4.renameSync(skillDir, stashDest);
9276
+ delete meta.skills[skillName];
9277
+ meta.disabled = meta.disabled ?? {};
9278
+ meta.disabled[skillName] = record;
9279
+ disabled.push(skillName);
9280
+ }
9281
+ for (const [skillName, record] of Object.entries(meta.disabled ?? {})) {
9282
+ if (record.plugin === plugin && matchesMarketplaceFilter(record, marketplaceFilter) && !disabled.includes(skillName)) {
9283
+ alreadyDisabled.push(skillName);
9284
+ }
9285
+ }
9286
+ if (disabled.length > 0) {
9287
+ fs4.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
9288
+ }
9289
+ return { disabled, alreadyDisabled, skipped };
9290
+ }
9291
+ function enablePluginSkills(targetDir, plugin, marketplaceFilter) {
9292
+ const resolvedTarget = path8.resolve(targetDir);
9293
+ const stashDir = path8.join(targetDir, DISABLED_SUBDIR);
9294
+ const meta = readInstalledMeta(targetDir);
9295
+ const enabled = [];
9296
+ const stashMissing = [];
9297
+ const skipped = [];
9298
+ const disabledEntries = Object.entries(meta.disabled ?? {});
9299
+ for (const [skillName, record] of disabledEntries) {
9300
+ if (record.plugin !== plugin || !matchesMarketplaceFilter(record, marketplaceFilter)) {
9301
+ skipped.push(skillName);
9302
+ continue;
9303
+ }
9304
+ const dest = path8.resolve(targetDir, skillName);
9305
+ if (!dest.startsWith(resolvedTarget + path8.sep)) {
9306
+ skipped.push(skillName);
9307
+ continue;
9308
+ }
9309
+ const stashSrc = path8.join(stashDir, skillName);
9310
+ if (!fs4.existsSync(stashSrc)) {
9311
+ stashMissing.push(skillName);
9312
+ continue;
9313
+ }
9314
+ if (fs4.existsSync(dest)) fs4.rmSync(dest, { recursive: true, force: true });
9315
+ fs4.renameSync(stashSrc, dest);
9316
+ meta.skills[skillName] = record;
9317
+ delete meta.disabled[skillName];
9318
+ enabled.push(skillName);
9319
+ }
9320
+ if (fs4.existsSync(stashDir) && fs4.readdirSync(stashDir).length === 0) {
9321
+ fs4.rmdirSync(stashDir);
9322
+ }
9323
+ if (enabled.length > 0) {
9324
+ fs4.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
9325
+ }
9326
+ return { enabled, stashMissing, skipped, hadDisabled: disabledEntries.length > 0 };
9327
+ }
9328
+ function listPluginStates(targetDir) {
9329
+ const meta = readInstalledMeta(targetDir);
9330
+ const byKey = /* @__PURE__ */ new Map();
9331
+ const bucketFor = (record) => {
9332
+ if (record.source !== "marketplace" || !record.plugin) return null;
9333
+ const marketplaceName = record.marketplace ?? record.installedFrom;
9334
+ const key = `${marketplaceName ?? ""}\0${record.plugin}`;
9335
+ let state = byKey.get(key);
9336
+ if (!state) {
9337
+ state = { plugin: record.plugin, marketplace: marketplaceName, activeSkills: [], disabledSkills: [] };
9338
+ byKey.set(key, state);
9339
+ }
9340
+ return state;
9341
+ };
9342
+ for (const skillName of listActiveSkillDirs(targetDir)) {
9343
+ const record = resolveSkillRecord(meta, targetDir, skillName);
9344
+ if (record) bucketFor(record)?.activeSkills.push(skillName);
9345
+ }
9346
+ for (const [skillName, record] of Object.entries(meta.disabled ?? {})) {
9347
+ bucketFor(record)?.disabledSkills.push(skillName);
9348
+ }
9349
+ return [...byKey.values()].sort((a, b) => a.plugin.localeCompare(b.plugin));
9350
+ }
9110
9351
  function detectRepoBranch(repoPath) {
9111
9352
  try {
9112
9353
  const result = execFileSync3("git", ["-C", repoPath, "rev-parse", "--abbrev-ref", "HEAD"], {
@@ -9125,7 +9366,7 @@ function recordInstalledSkills(targetDir, skillNames, record) {
9125
9366
  for (const skillName of skillNames) {
9126
9367
  const fullRecord = { ...record, installedAt: now };
9127
9368
  meta.skills[skillName] = fullRecord;
9128
- const skillDir = path7.join(targetDir, skillName);
9369
+ const skillDir = path8.join(targetDir, skillName);
9129
9370
  if (fs4.existsSync(skillDir)) {
9130
9371
  const origin = { version: 1, ...fullRecord };
9131
9372
  try {
@@ -9195,7 +9436,7 @@ function upsertMarketplace(all, entry) {
9195
9436
  }
9196
9437
  }
9197
9438
  function cloneOrReuseMarketplace(url, resolvedPath, opts) {
9198
- const hasGit = fs4.existsSync(path7.join(resolvedPath, ".git"));
9439
+ const hasGit = fs4.existsSync(path8.join(resolvedPath, ".git"));
9199
9440
  if (fs4.existsSync(resolvedPath) && !hasGit && fs4.readdirSync(resolvedPath).length > 0) {
9200
9441
  throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
9201
9442
  }
@@ -9263,7 +9504,7 @@ function installPluginsForMarketplace(marketplacePath, marketplaceName, repoUrl,
9263
9504
  results[pluginName] = { installed, failed };
9264
9505
  totalInstalled += installed.length;
9265
9506
  for (const skill of installed) {
9266
- warn(` ${skill}: ${path7.join(skillsSrc, skill)} \u2192 ${path7.join(targetDir, skill)}`);
9507
+ warn(` ${skill}: ${path8.join(skillsSrc, skill)} \u2192 ${path8.join(targetDir, skill)}`);
9267
9508
  }
9268
9509
  if (failed.length > 0) {
9269
9510
  warn(`Skipped ${failed.length} skill(s) with invalid names in "${pluginName}": ${failed.join(", ")}`);
@@ -9315,34 +9556,55 @@ function syncMarketplacePlugins(m, targetDir, force, pluginFilter) {
9315
9556
  return { marketplace: marketplaceName, skipped: true, error: message };
9316
9557
  }
9317
9558
  }
9559
+ function performMarketplaceAdd(url, localPath, opts) {
9560
+ const resolvedPath = path8.resolve(localPath ?? defaultMarketplacePath(url));
9561
+ const marketplaceName = opts.name ?? repoNameFromUrl(url);
9562
+ cloneOrReuseMarketplace(url, resolvedPath, opts);
9563
+ const configPath = getGlobalConfigPath();
9564
+ const { existing, all } = loadMarketplacesConfig(configPath);
9565
+ const entry = {
9566
+ name: marketplaceName,
9567
+ repoUrl: url,
9568
+ localPath: resolvedPath,
9569
+ ...opts.token ? { token: opts.token } : {}
9570
+ };
9571
+ upsertMarketplace(all, entry);
9572
+ saveMarketplaces(configPath, existing, all);
9573
+ const targetDir = resolveAgentPaths(resolveAgentName(opts)).user;
9574
+ const { pluginResults, totalInstalled } = installAllPlugins(resolvedPath, marketplaceName, url, targetDir);
9575
+ return {
9576
+ name: marketplaceName,
9577
+ repoUrl: url,
9578
+ localPath: resolvedPath,
9579
+ branch: opts.branch ?? null,
9580
+ tokenConfigured: !!opts.token,
9581
+ plugins: pluginResults,
9582
+ total: totalInstalled,
9583
+ target: targetDir
9584
+ };
9585
+ }
9586
+ function performMarketplaceRemove(name) {
9587
+ const configPath = getGlobalConfigPath();
9588
+ const { existing, all } = loadMarketplacesConfig(configPath);
9589
+ const idx = all.findIndex((m) => m.name === name || m.repoUrl === name);
9590
+ if (idx === -1) {
9591
+ throw new Error(`Marketplace "${name}" not found. Run: pncli skills marketplace list`);
9592
+ }
9593
+ const removed = all.splice(idx, 1)[0];
9594
+ saveMarketplaces(configPath, existing, all);
9595
+ return {
9596
+ removed: {
9597
+ name: marketplaceLabel(removed),
9598
+ repoUrl: removed.repoUrl,
9599
+ localPath: removed.localPath
9600
+ },
9601
+ remaining: all.length
9602
+ };
9603
+ }
9318
9604
  async function marketplaceAddAction(url, localPath, opts, commandName) {
9319
9605
  const start = Date.now();
9320
9606
  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);
9607
+ success(performMarketplaceAdd(url, localPath, opts), "skills", commandName, start);
9346
9608
  } catch (err) {
9347
9609
  fail(err, "skills", commandName, start);
9348
9610
  }
@@ -9354,19 +9616,19 @@ function registerSkillsCommands(program2) {
9354
9616
  try {
9355
9617
  let targetDir;
9356
9618
  if (opts.target) {
9357
- targetDir = path7.resolve(opts.target);
9619
+ targetDir = path8.resolve(opts.target);
9358
9620
  } else {
9359
9621
  const agentConfig = resolveAgentPaths(opts.agent);
9360
9622
  const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
9361
- targetDir = path7.resolve(scopePath);
9623
+ targetDir = path8.resolve(scopePath);
9362
9624
  }
9363
- const resolvedTarget = path7.resolve(targetDir);
9625
+ const resolvedTarget = path8.resolve(targetDir);
9364
9626
  const bundledDir = getBundledSkillsDir();
9365
9627
  let skillDirs = [];
9366
9628
  if (bundledDir !== "") {
9367
9629
  try {
9368
9630
  skillDirs = fs4.readdirSync(bundledDir).filter(
9369
- (name) => fs4.existsSync(path7.join(bundledDir, name, "SKILL.md"))
9631
+ (name) => fs4.existsSync(path8.join(bundledDir, name, "SKILL.md"))
9370
9632
  );
9371
9633
  } catch {
9372
9634
  }
@@ -9377,25 +9639,25 @@ function registerSkillsCommands(program2) {
9377
9639
  const installed = [];
9378
9640
  const failed = [];
9379
9641
  for (const skillName of skillDirs) {
9380
- const existingDir = path7.resolve(targetDir, skillName);
9381
- if (!existingDir.startsWith(resolvedTarget + path7.sep)) continue;
9642
+ const existingDir = path8.resolve(targetDir, skillName);
9643
+ if (!existingDir.startsWith(resolvedTarget + path8.sep)) continue;
9382
9644
  if (fs4.existsSync(existingDir)) {
9383
9645
  fs4.rmSync(existingDir, { recursive: true, force: true });
9384
9646
  }
9385
9647
  }
9386
9648
  warn(`Installing ${skillDirs.length} bundled skill(s) to ${targetDir}...`);
9387
9649
  for (const skillName of skillDirs) {
9388
- const skillDir = path7.resolve(targetDir, skillName);
9389
- if (!skillDir.startsWith(resolvedTarget + path7.sep)) {
9650
+ const skillDir = path8.resolve(targetDir, skillName);
9651
+ if (!skillDir.startsWith(resolvedTarget + path8.sep)) {
9390
9652
  failed.push(skillName);
9391
9653
  continue;
9392
9654
  }
9393
9655
  try {
9394
- const mdFiles = fs4.readdirSync(path7.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
9656
+ const mdFiles = fs4.readdirSync(path8.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
9395
9657
  fs4.mkdirSync(skillDir, { recursive: true });
9396
9658
  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");
9659
+ const content = fs4.readFileSync(path8.join(bundledDir, skillName, mdFile), "utf8");
9660
+ fs4.writeFileSync(path8.join(skillDir, mdFile), content, "utf8");
9399
9661
  }
9400
9662
  installed.push(skillName);
9401
9663
  } catch {
@@ -9425,11 +9687,11 @@ function registerSkillsCommands(program2) {
9425
9687
  try {
9426
9688
  let targetDir;
9427
9689
  if (opts.target) {
9428
- targetDir = path7.resolve(opts.target);
9690
+ targetDir = path8.resolve(opts.target);
9429
9691
  } else {
9430
9692
  const agentConfig = resolveAgentPaths(opts.agent);
9431
9693
  const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
9432
- targetDir = path7.resolve(scopePath);
9694
+ targetDir = path8.resolve(scopePath);
9433
9695
  }
9434
9696
  if (!fs4.existsSync(targetDir)) {
9435
9697
  success({ skills: [], message: `No skills directory found at ${targetDir}. Run: pncli skills install` }, "skills", "list", start);
@@ -9438,11 +9700,11 @@ function registerSkillsCommands(program2) {
9438
9700
  const meta = readInstalledMeta(targetDir);
9439
9701
  const skillDirs = fs4.readdirSync(targetDir).filter((name) => {
9440
9702
  if (name.startsWith(".")) return false;
9441
- const skillPath = path7.join(targetDir, name, "SKILL.md");
9703
+ const skillPath = path8.join(targetDir, name, "SKILL.md");
9442
9704
  return fs4.existsSync(skillPath);
9443
9705
  });
9444
9706
  const skillsList = skillDirs.map((name) => {
9445
- const content = fs4.readFileSync(path7.join(targetDir, name, "SKILL.md"), "utf8");
9707
+ const content = fs4.readFileSync(path8.join(targetDir, name, "SKILL.md"), "utf8");
9446
9708
  const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
9447
9709
  const data = {};
9448
9710
  const metadata = {};
@@ -9486,15 +9748,15 @@ function registerSkillsCommands(program2) {
9486
9748
  try {
9487
9749
  let targetDir;
9488
9750
  if (opts.target) {
9489
- targetDir = path7.resolve(opts.target);
9751
+ targetDir = path8.resolve(opts.target);
9490
9752
  } else {
9491
9753
  const agentConfig = resolveAgentPaths(resolveAgentName(opts));
9492
9754
  const scopePath = opts.scope === "project" ? agentConfig.project : agentConfig.user;
9493
- targetDir = path7.resolve(scopePath);
9755
+ targetDir = path8.resolve(scopePath);
9494
9756
  }
9495
- const resolvedTarget = path7.resolve(targetDir);
9496
- const skillDir = path7.resolve(targetDir, name);
9497
- if (!skillDir.startsWith(resolvedTarget + path7.sep)) {
9757
+ const resolvedTarget = path8.resolve(targetDir);
9758
+ const skillDir = path8.resolve(targetDir, name);
9759
+ if (!skillDir.startsWith(resolvedTarget + path8.sep)) {
9498
9760
  throw new Error(`Invalid skill name: "${name}"`);
9499
9761
  }
9500
9762
  if (!fs4.existsSync(skillDir)) {
@@ -9568,27 +9830,12 @@ function registerSkillsCommands(program2) {
9568
9830
  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
9831
  const start = Date.now();
9570
9832
  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);
9833
+ success(performMarketplaceRemove(name), "skills", "marketplace-remove", start);
9587
9834
  } catch (err) {
9588
9835
  fail(err, "skills", "marketplace-remove", start);
9589
9836
  }
9590
9837
  });
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) => {
9838
+ 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
9839
  const start = Date.now();
9593
9840
  try {
9594
9841
  const configPath = getGlobalConfigPath();
@@ -9701,13 +9948,13 @@ function registerSkillsCommands(program2) {
9701
9948
  try {
9702
9949
  let targetDir;
9703
9950
  if (opts.target) {
9704
- targetDir = path7.resolve(opts.target);
9951
+ targetDir = path8.resolve(opts.target);
9705
9952
  } else {
9706
9953
  const agentConfig = resolveAgentPaths(resolveAgentName(opts));
9707
9954
  const scopePath = (opts.scope ?? "user") === "project" ? agentConfig.project : agentConfig.user;
9708
- targetDir = path7.resolve(scopePath);
9955
+ targetDir = path8.resolve(scopePath);
9709
9956
  }
9710
- const resolvedTarget = path7.resolve(targetDir);
9957
+ const resolvedTarget = path8.resolve(targetDir);
9711
9958
  if (!fs4.existsSync(targetDir)) {
9712
9959
  success({ removed: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-purge-plugin", start);
9713
9960
  return;
@@ -9717,7 +9964,7 @@ function registerSkillsCommands(program2) {
9717
9964
  const skipped = [];
9718
9965
  const skillDirs = fs4.readdirSync(targetDir).filter((name) => {
9719
9966
  if (name.startsWith(".")) return false;
9720
- const p = path7.join(targetDir, name);
9967
+ const p = path8.join(targetDir, name);
9721
9968
  try {
9722
9969
  return fs4.statSync(p).isDirectory();
9723
9970
  } catch {
@@ -9725,8 +9972,8 @@ function registerSkillsCommands(program2) {
9725
9972
  }
9726
9973
  });
9727
9974
  for (const skillName of skillDirs) {
9728
- const skillDir = path7.resolve(targetDir, skillName);
9729
- if (!skillDir.startsWith(resolvedTarget + path7.sep)) continue;
9975
+ const skillDir = path8.resolve(targetDir, skillName);
9976
+ if (!skillDir.startsWith(resolvedTarget + path8.sep)) continue;
9730
9977
  let record = meta.skills[skillName] ?? null;
9731
9978
  if (!record) {
9732
9979
  const perSkill = readSkillOrigin(skillDir);
@@ -9770,6 +10017,161 @@ function registerSkillsCommands(program2) {
9770
10017
  fail(err, "skills", "marketplace-purge-plugin", start);
9771
10018
  }
9772
10019
  });
10020
+ 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) => {
10021
+ const start = Date.now();
10022
+ try {
10023
+ const targetDir = resolveTargetDir(opts);
10024
+ if (!fs4.existsSync(targetDir)) {
10025
+ success({ disabled: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-disable", start);
10026
+ return;
10027
+ }
10028
+ const result = disablePluginSkills(targetDir, plugin, opts.marketplace);
10029
+ success({
10030
+ disabled: result.disabled,
10031
+ alreadyDisabled: result.alreadyDisabled,
10032
+ skipped: result.skipped.length,
10033
+ total: result.disabled.length,
10034
+ target: targetDir,
10035
+ plugin,
10036
+ stash: path8.join(targetDir, DISABLED_SUBDIR),
10037
+ ...opts.marketplace ? { marketplace: opts.marketplace } : {}
10038
+ }, "skills", "marketplace-disable", start);
10039
+ } catch (err) {
10040
+ fail(err, "skills", "marketplace-disable", start);
10041
+ }
10042
+ });
10043
+ 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) => {
10044
+ const start = Date.now();
10045
+ try {
10046
+ const targetDir = resolveTargetDir(opts);
10047
+ if (!fs4.existsSync(targetDir)) {
10048
+ success({ enabled: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-enable", start);
10049
+ return;
10050
+ }
10051
+ const result = enablePluginSkills(targetDir, plugin, opts.marketplace);
10052
+ if (!result.hadDisabled) {
10053
+ success({ enabled: [], total: 0, target: targetDir, plugin, message: `No disabled skills found for plugin "${plugin}".` }, "skills", "marketplace-enable", start);
10054
+ return;
10055
+ }
10056
+ success({
10057
+ enabled: result.enabled,
10058
+ skipped: result.skipped.length,
10059
+ total: result.enabled.length,
10060
+ target: targetDir,
10061
+ plugin,
10062
+ ...result.stashMissing.length > 0 ? {
10063
+ stashMissing: result.stashMissing,
10064
+ 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.`
10065
+ } : {},
10066
+ ...opts.marketplace ? { marketplace: opts.marketplace } : {}
10067
+ }, "skills", "marketplace-enable", start);
10068
+ } catch (err) {
10069
+ fail(err, "skills", "marketplace-enable", start);
10070
+ }
10071
+ });
10072
+ 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) => {
10073
+ const start = Date.now();
10074
+ try {
10075
+ assertInteractive("Use `pncli skills marketplace enable|disable <plugin>`, `add <url>`, and `remove <name>` to manage non-interactively.");
10076
+ const targetDir = resolveTargetDir(opts);
10077
+ const enabledPlugins = [];
10078
+ const disabledPlugins = [];
10079
+ const stashMissing = [];
10080
+ const addedMarketplaces = [];
10081
+ const removedMarketplaces = [];
10082
+ for (; ; ) {
10083
+ const states = fs4.existsSync(targetDir) ? listPluginStates(targetDir) : [];
10084
+ const registered = loadMarketplaces(getGlobalConfigPath());
10085
+ const action = await select({
10086
+ message: `Manage skills marketplaces (target: ${targetDir}):`,
10087
+ choices: [
10088
+ ...states.length > 0 ? [{ value: "toggle", name: `Toggle plugins on/off (${states.length} installed)` }] : [],
10089
+ { value: "add", name: "Add a marketplace" },
10090
+ ...registered.length > 0 ? [{ value: "remove", name: `Remove a marketplace (${registered.length} registered)` }] : [],
10091
+ { value: "done", name: "Done" }
10092
+ ]
10093
+ });
10094
+ if (action === "done") break;
10095
+ if (action === "toggle") {
10096
+ const sorted = [...states].sort((a, b) => (a.marketplace ?? "").localeCompare(b.marketplace ?? "") || a.plugin.localeCompare(b.plugin));
10097
+ const choices = [];
10098
+ let lastMarketplace = null;
10099
+ sorted.forEach((state, index) => {
10100
+ const marketplaceName = state.marketplace ?? "(unknown marketplace)";
10101
+ if (marketplaceName !== lastMarketplace) {
10102
+ choices.push(new Separator(`\u2500\u2500 ${marketplaceName} \u2500\u2500`));
10103
+ lastMarketplace = marketplaceName;
10104
+ }
10105
+ const active = state.activeSkills.length;
10106
+ const stashed = state.disabledSkills.length;
10107
+ const skillCount = stashed === 0 ? `${active} skill${active === 1 ? "" : "s"}` : active === 0 ? `${stashed} skill${stashed === 1 ? "" : "s"}, disabled` : `${active} active, ${stashed} disabled`;
10108
+ choices.push({ name: `${state.plugin} (${skillCount})`, value: index, checked: active > 0 });
10109
+ });
10110
+ const checkedIndexes = await checkbox({
10111
+ message: "Toggle plugins (space toggles, enter applies):",
10112
+ choices,
10113
+ pageSize: 15
10114
+ });
10115
+ const wanted = new Set(checkedIndexes);
10116
+ sorted.forEach((state, index) => {
10117
+ if (wanted.has(index)) {
10118
+ if (state.disabledSkills.length === 0) return;
10119
+ const result = enablePluginSkills(targetDir, state.plugin, state.marketplace);
10120
+ stashMissing.push(...result.stashMissing);
10121
+ if (result.enabled.length > 0) {
10122
+ enabledPlugins.push({ plugin: state.plugin, marketplace: state.marketplace, skills: result.enabled });
10123
+ }
10124
+ } else {
10125
+ if (state.activeSkills.length === 0) return;
10126
+ const result = disablePluginSkills(targetDir, state.plugin, state.marketplace);
10127
+ if (result.disabled.length > 0) {
10128
+ disabledPlugins.push({ plugin: state.plugin, marketplace: state.marketplace, skills: result.disabled });
10129
+ }
10130
+ }
10131
+ });
10132
+ } else if (action === "add") {
10133
+ const url = (await input2({
10134
+ message: "Git clone URL of the marketplace repository:",
10135
+ validate: (v) => v.trim().length > 0 || "URL is required"
10136
+ })).trim();
10137
+ const name = (await input2({
10138
+ message: "Marketplace name:",
10139
+ default: repoNameFromUrl(url)
10140
+ })).trim();
10141
+ addedMarketplaces.push(performMarketplaceAdd(url, void 0, {
10142
+ name: name || void 0,
10143
+ agent: opts.agent,
10144
+ claude: opts.claude
10145
+ }));
10146
+ } else if (action === "remove") {
10147
+ const chosen = await select({
10148
+ message: "Remove which marketplace? (the local clone is kept on disk)",
10149
+ choices: [
10150
+ // Index-based values: labels alone aren't guaranteed unique (two marketplaces can share a derived name).
10151
+ ...registered.map((m, i) => ({ value: String(i), name: `${marketplaceLabel(m)} \u2014 ${m.repoUrl ?? ""}` })),
10152
+ { value: BACK, name: "\u2190 Back" }
10153
+ ]
10154
+ });
10155
+ if (chosen === BACK) continue;
10156
+ const target = registered[Number(chosen)];
10157
+ removedMarketplaces.push(performMarketplaceRemove(target.repoUrl ?? marketplaceLabel(target)));
10158
+ }
10159
+ }
10160
+ success({
10161
+ enabled: enabledPlugins,
10162
+ disabled: disabledPlugins,
10163
+ addedMarketplaces,
10164
+ removedMarketplaces,
10165
+ target: targetDir,
10166
+ ...stashMissing.length > 0 ? {
10167
+ stashMissing,
10168
+ 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."
10169
+ } : {}
10170
+ }, "skills", "marketplace-manage", start);
10171
+ } catch (err) {
10172
+ fail(err, "skills", "marketplace-manage", start);
10173
+ }
10174
+ });
9773
10175
  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
10176
  const start = Date.now();
9775
10177
  try {
@@ -9780,7 +10182,7 @@ function registerSkillsCommands(program2) {
9780
10182
  }
9781
10183
  const skillDirs = fs4.readdirSync(userDir).filter((name) => {
9782
10184
  if (name.startsWith(".")) return false;
9783
- const p = path7.join(userDir, name);
10185
+ const p = path8.join(userDir, name);
9784
10186
  try {
9785
10187
  return fs4.statSync(p).isDirectory();
9786
10188
  } catch {
@@ -9798,8 +10200,8 @@ Re-run with --force to confirm, or use \`pncli skills marketplace purge-plugin\`
9798
10200
  );
9799
10201
  }
9800
10202
  for (const name of skillDirs) {
9801
- const skillDir = path7.resolve(userDir, name);
9802
- if (!skillDir.startsWith(path7.resolve(userDir) + path7.sep)) continue;
10203
+ const skillDir = path8.resolve(userDir, name);
10204
+ if (!skillDir.startsWith(path8.resolve(userDir) + path8.sep)) continue;
9803
10205
  fs4.rmSync(skillDir, { recursive: true, force: true });
9804
10206
  }
9805
10207
  const metaPath = getInstalledMetaPath(userDir);
@@ -9813,7 +10215,7 @@ Re-run with --force to confirm, or use \`pncli skills marketplace purge-plugin\`
9813
10215
  });
9814
10216
  }
9815
10217
  function resolvePluginChoices(marketplacePath) {
9816
- const marketplaceJsonPath = path7.join(marketplacePath, ".claude-plugin", "marketplace.json");
10218
+ const marketplaceJsonPath = path8.join(marketplacePath, ".claude-plugin", "marketplace.json");
9817
10219
  if (fs4.existsSync(marketplaceJsonPath)) {
9818
10220
  try {
9819
10221
  const meta = JSON.parse(fs4.readFileSync(marketplaceJsonPath, "utf8"));
@@ -9823,36 +10225,36 @@ function resolvePluginChoices(marketplacePath) {
9823
10225
  } catch {
9824
10226
  }
9825
10227
  }
9826
- const pluginsDir = path7.join(marketplacePath, "plugins");
10228
+ const pluginsDir = path8.join(marketplacePath, "plugins");
9827
10229
  if (fs4.existsSync(pluginsDir)) {
9828
- return fs4.readdirSync(pluginsDir).filter((name) => fs4.statSync(path7.join(pluginsDir, name)).isDirectory()).map((name) => ({ name, description: "" }));
10230
+ return fs4.readdirSync(pluginsDir).filter((name) => fs4.statSync(path8.join(pluginsDir, name)).isDirectory()).map((name) => ({ name, description: "" }));
9829
10231
  }
9830
10232
  return [];
9831
10233
  }
9832
10234
  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)) {
10235
+ const pluginsBase = path8.resolve(marketplacePath, "plugins");
10236
+ const skillsSrc = path8.resolve(pluginsBase, selectedPlugin, "skills");
10237
+ if (!skillsSrc.startsWith(pluginsBase + path8.sep)) {
9836
10238
  throw new Error(`Invalid plugin name: "${selectedPlugin}"`);
9837
10239
  }
9838
10240
  return skillsSrc;
9839
10241
  }
9840
10242
  function copyPluginSkills(skillsSrc, targetDir, meta) {
9841
10243
  fs4.mkdirSync(targetDir, { recursive: true });
9842
- const resolvedTarget = path7.resolve(targetDir);
10244
+ const resolvedTarget = path8.resolve(targetDir);
9843
10245
  const skillNames = fs4.readdirSync(skillsSrc).filter(
9844
- (name) => fs4.statSync(path7.join(skillsSrc, name)).isDirectory()
10246
+ (name) => fs4.statSync(path8.join(skillsSrc, name)).isDirectory()
9845
10247
  );
9846
10248
  const installed = [];
9847
10249
  const failed = [];
9848
10250
  for (const skillName of skillNames) {
9849
- const dest = path7.resolve(targetDir, skillName);
9850
- if (!dest.startsWith(resolvedTarget + path7.sep)) {
10251
+ const dest = path8.resolve(targetDir, skillName);
10252
+ if (!dest.startsWith(resolvedTarget + path8.sep)) {
9851
10253
  failed.push(skillName);
9852
10254
  continue;
9853
10255
  }
9854
10256
  fs4.rmSync(dest, { recursive: true, force: true });
9855
- fs4.cpSync(path7.join(skillsSrc, skillName), dest, { recursive: true });
10257
+ fs4.cpSync(path8.join(skillsSrc, skillName), dest, { recursive: true });
9856
10258
  installed.push(skillName);
9857
10259
  }
9858
10260
  if (meta) {
@@ -9868,8 +10270,8 @@ function copyPluginSkills(skillsSrc, targetDir, meta) {
9868
10270
  }
9869
10271
 
9870
10272
  // src/services/jwt/commands.ts
9871
- function base64urlDecode(input2) {
9872
- const base64 = input2.replace(/-/g, "+").replace(/_/g, "/");
10273
+ function base64urlDecode(input3) {
10274
+ const base64 = input3.replace(/-/g, "+").replace(/_/g, "/");
9873
10275
  const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
9874
10276
  return Buffer.from(padded, "base64").toString("utf8");
9875
10277
  }
@@ -10287,6 +10689,65 @@ function registerDynatraceCommands(program2) {
10287
10689
  });
10288
10690
  }
10289
10691
 
10692
+ // src/services/logscale/commands.ts
10693
+ function getHttp4(program2) {
10694
+ const opts = program2.optsWithGlobals();
10695
+ return createHttpClient(
10696
+ loadConfig({ configPath: opts.config }),
10697
+ Boolean(opts.dryRun)
10698
+ );
10699
+ }
10700
+ function registerLogscaleCommands(program2) {
10701
+ const logscale = program2.command("logscale").description("LogScale log query operations");
10702
+ const repositories = logscale.command("repositories").description("Repository operations");
10703
+ repositories.command("list").description("List available repositories").action(async () => {
10704
+ const start = Date.now();
10705
+ try {
10706
+ const data = await getHttp4(program2).logscale("/api/v1/repositories");
10707
+ success({ count: data.length, repositories: data }, "logscale", "repositories list", start);
10708
+ } catch (err) {
10709
+ fail(err, "logscale", "repositories list", start);
10710
+ }
10711
+ });
10712
+ logscale.command("query").description("Execute a LogScale Query Language (LQL) search against a repository").requiredOption("--repository <name>", "Repository name to query").requiredOption("--query <lql>", 'LogScale Query Language expression (e.g. "error" or "level=error | count()")').option("--start <time>", "Start time (ISO 8601 or relative such as 1h or 24h)", "1h").option("--end <time>", "End time (ISO 8601 or relative)", "now").option("--limit <n>", "Maximum number of events to return", "200").option("--timeout-ms <ms>", "Client-side HTTP timeout in milliseconds", "30000").action(async (opts) => {
10713
+ const ts = Date.now();
10714
+ try {
10715
+ const body = {
10716
+ queryString: opts.query,
10717
+ start: opts.start,
10718
+ end: opts.end,
10719
+ limit: parseInt(opts.limit, 10),
10720
+ showQueryEventDistribution: false,
10721
+ isLive: false
10722
+ };
10723
+ const data = await getHttp4(program2).logscale(
10724
+ `/api/v1/repositories/${encodeURIComponent(opts.repository)}/query`,
10725
+ {
10726
+ method: "POST",
10727
+ body,
10728
+ timeoutMs: parseInt(opts.timeoutMs, 10)
10729
+ }
10730
+ );
10731
+ success(
10732
+ {
10733
+ repository: opts.repository,
10734
+ done: data.done,
10735
+ cancelled: data.cancelled,
10736
+ eventCount: data.events.length,
10737
+ events: data.events,
10738
+ warnings: data.warnings ?? [],
10739
+ metaData: data.metaData
10740
+ },
10741
+ "logscale",
10742
+ "query",
10743
+ ts
10744
+ );
10745
+ } catch (err) {
10746
+ fail(err, "logscale", "query", ts);
10747
+ }
10748
+ });
10749
+ }
10750
+
10290
10751
  // src/cli.ts
10291
10752
  if (!process.env.PNCLI_VERIFY_TLS) {
10292
10753
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
@@ -10331,6 +10792,7 @@ registerSkillsCommands(program);
10331
10792
  registerJwtCommands(program);
10332
10793
  registerOpenShiftCommands(program);
10333
10794
  registerDynatraceCommands(program);
10795
+ registerLogscaleCommands(program);
10334
10796
  program.addHelpText("after", `
10335
10797
  Services:
10336
10798
  git Local git operations (status, diff, log, branch)
@@ -10351,6 +10813,7 @@ Services:
10351
10813
  sonatypeiq Sonatype IQ Server (applications, organizations, policies)
10352
10814
  openshift OpenShift / Kubernetes (pods, events, logs, metrics)
10353
10815
  dynatrace Dynatrace (services, entities, problems, traces, Kubernetes workloads)
10816
+ logscale LogScale (log query, repositories)
10354
10817
  config Manage pncli configuration
10355
10818
  skills Download and manage Claude Code skills
10356
10819
  jwt JWT token utilities (decode header and payload)