@kolatts/pncli 1.20.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/copilot-instructions.md +135 -12
- package/dist/{chunk-PC4AHQCN.js → chunk-PZWGOMIQ.js} +24 -1
- package/dist/chunk-PZWGOMIQ.js.map +1 -0
- package/dist/cli.js +517 -161
- package/dist/cli.js.map +1 -1
- package/dist/{http-IXJA4TMS.js → http-52YHD3ZL.js} +2 -2
- package/package.json +2 -1
- package/skills/pncli/confluence.md +1 -1
- package/skills/pncli/marketplace.md +29 -0
- package/dist/chunk-PC4AHQCN.js.map +0 -1
- /package/dist/{http-IXJA4TMS.js.map → http-52YHD3ZL.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
success,
|
|
21
21
|
warn,
|
|
22
22
|
writeRawOutput
|
|
23
|
-
} from "./chunk-
|
|
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
|
|
424
|
+
let path9 = remoteUrl;
|
|
425
425
|
let remoteHostname = "";
|
|
426
|
-
const sshProtoMatch =
|
|
426
|
+
const sshProtoMatch = path9.match(/^ssh:\/\/[^@]*@?([^/:]+)(?::\d+)?(.*)/);
|
|
427
427
|
if (sshProtoMatch) {
|
|
428
428
|
remoteHostname = sshProtoMatch[1].toLowerCase();
|
|
429
|
-
|
|
429
|
+
path9 = sshProtoMatch[2];
|
|
430
430
|
} else {
|
|
431
|
-
const gitAtMatch =
|
|
431
|
+
const gitAtMatch = path9.match(/^git@([^:]+)(?::\d+)?:(.*)/);
|
|
432
432
|
if (gitAtMatch) {
|
|
433
433
|
remoteHostname = gitAtMatch[1].toLowerCase();
|
|
434
|
-
|
|
434
|
+
path9 = "/" + gitAtMatch[2];
|
|
435
435
|
} else {
|
|
436
|
-
const httpsMatch =
|
|
436
|
+
const httpsMatch = path9.match(/^https?:\/\/([^/:]+)(?::\d+)?(.*)/);
|
|
437
437
|
if (httpsMatch) {
|
|
438
438
|
remoteHostname = httpsMatch[1].toLowerCase();
|
|
439
|
-
|
|
439
|
+
path9 = httpsMatch[2];
|
|
440
440
|
}
|
|
441
441
|
}
|
|
442
442
|
}
|
|
443
443
|
if (!remoteHostname || remoteHostname !== baseHostname) return null;
|
|
444
|
-
|
|
445
|
-
const gitIdx =
|
|
446
|
-
const sshIdx =
|
|
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(
|
|
449
|
+
const repo = decodeURIComponent(path9.slice(markerIdx + 6));
|
|
450
450
|
if (!repo) return null;
|
|
451
|
-
const before =
|
|
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]);
|
|
@@ -744,6 +744,12 @@ var BitbucketClient = class {
|
|
|
744
744
|
const pr = await this.getPR(project, repo, prId);
|
|
745
745
|
return pr.reviewers;
|
|
746
746
|
}
|
|
747
|
+
async addReviewer(project, repo, prId, username) {
|
|
748
|
+
return this.http.bitbucket(
|
|
749
|
+
`${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/participants`,
|
|
750
|
+
{ method: "POST", body: { user: { name: username }, role: "REVIEWER" } }
|
|
751
|
+
);
|
|
752
|
+
}
|
|
747
753
|
async listBuilds(project, repo, prId) {
|
|
748
754
|
const commits = await this.http.bitbucket(
|
|
749
755
|
`${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/commits`,
|
|
@@ -1384,6 +1390,13 @@ function getDefaults(program2) {
|
|
|
1384
1390
|
priority: config.defaults.jira?.priority
|
|
1385
1391
|
};
|
|
1386
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
|
+
}
|
|
1387
1400
|
function registerJiraCommands(program2) {
|
|
1388
1401
|
const jira = program2.command("jira").description("Jira Data Cloud operations");
|
|
1389
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) => {
|
|
@@ -1533,12 +1546,13 @@ function registerJiraCommands(program2) {
|
|
|
1533
1546
|
fail(err, "jira", "list-comments", start);
|
|
1534
1547
|
}
|
|
1535
1548
|
});
|
|
1536
|
-
jira.command("search").description("Search Jira issues with JQL (consider --output-file for large results)").
|
|
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) => {
|
|
1537
1550
|
const start = Date.now();
|
|
1538
1551
|
try {
|
|
1539
1552
|
const { client, fieldMap, customFields } = getClientAndFields(program2);
|
|
1553
|
+
const jql = resolveJqlInput(opts.jql, opts.jqlFile);
|
|
1540
1554
|
const maxResults = opts.maxResults ? parseInt(opts.maxResults, 10) : void 0;
|
|
1541
|
-
const translatedJql = translateJql(
|
|
1555
|
+
const translatedJql = translateJql(jql, fieldMap);
|
|
1542
1556
|
const data = await client.search(translatedJql, maxResults, customFields);
|
|
1543
1557
|
const translatedIssues = data.issues.map((issue) => ({
|
|
1544
1558
|
...issue,
|
|
@@ -1986,6 +2000,16 @@ function registerBitbucketCommands(program2) {
|
|
|
1986
2000
|
fail(err, "bitbucket", "list-reviewers", start);
|
|
1987
2001
|
}
|
|
1988
2002
|
});
|
|
2003
|
+
bb.command("add-reviewer").description("Add a reviewer to a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--user <username>", "Username to add as a reviewer").action(async (opts) => {
|
|
2004
|
+
const start = Date.now();
|
|
2005
|
+
try {
|
|
2006
|
+
const { client, project, repo } = getClient2(bb);
|
|
2007
|
+
const data = await client.addReviewer(project, repo, parseInt(opts.pr, 10), opts.user);
|
|
2008
|
+
success(data, "bitbucket", "add-reviewer", start);
|
|
2009
|
+
} catch (err) {
|
|
2010
|
+
fail(err, "bitbucket", "add-reviewer", start);
|
|
2011
|
+
}
|
|
2012
|
+
});
|
|
1989
2013
|
bb.command("list-builds").description("List build statuses for a pull request").requiredOption("--pr <pr-id>", "Pull request ID").action(async (opts) => {
|
|
1990
2014
|
const start = Date.now();
|
|
1991
2015
|
try {
|
|
@@ -2200,8 +2224,8 @@ var GitHubClient = class {
|
|
|
2200
2224
|
);
|
|
2201
2225
|
}
|
|
2202
2226
|
async createRepo(opts) {
|
|
2203
|
-
const
|
|
2204
|
-
return this.http.github(
|
|
2227
|
+
const path9 = opts.org ? `/orgs/${opts.org}/repos` : "/user/repos";
|
|
2228
|
+
return this.http.github(path9, {
|
|
2205
2229
|
method: "POST",
|
|
2206
2230
|
body: {
|
|
2207
2231
|
name: opts.name,
|
|
@@ -2540,7 +2564,8 @@ function registerGitHubCommands(program2) {
|
|
|
2540
2564
|
}
|
|
2541
2565
|
|
|
2542
2566
|
// src/services/confluence/commands.ts
|
|
2543
|
-
import { existsSync as existsSync2 } from "fs";
|
|
2567
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2568
|
+
import path2 from "path";
|
|
2544
2569
|
|
|
2545
2570
|
// src/services/confluence/client.ts
|
|
2546
2571
|
import { readFileSync as readFileSync4 } from "fs";
|
|
@@ -2702,6 +2727,19 @@ var ConfluenceClient = class {
|
|
|
2702
2727
|
);
|
|
2703
2728
|
return result.results;
|
|
2704
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
|
+
}
|
|
2705
2743
|
async deleteAttachment(attachmentId) {
|
|
2706
2744
|
return this.http.confluence(`${API3}/content/${attachmentId}`, { method: "DELETE" });
|
|
2707
2745
|
}
|
|
@@ -2721,6 +2759,11 @@ var ConfluenceClient = class {
|
|
|
2721
2759
|
};
|
|
2722
2760
|
|
|
2723
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
|
+
}
|
|
2724
2767
|
function xmlParseHint(err, bodyContent, convertedFromMarkdown = false) {
|
|
2725
2768
|
if (!(err instanceof Error)) return null;
|
|
2726
2769
|
const m = err.message.match(/at \[row,col\]=\{(\d+),(\d+)\}/);
|
|
@@ -2970,6 +3013,21 @@ ${hint}`, status, url), "confluence", "update-page", start);
|
|
|
2970
3013
|
fail(err, "confluence", "upload-attachment", start);
|
|
2971
3014
|
}
|
|
2972
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
|
+
});
|
|
2973
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) => {
|
|
2974
3032
|
const start = Date.now();
|
|
2975
3033
|
try {
|
|
@@ -3533,23 +3591,23 @@ import { Option as Option2 } from "commander";
|
|
|
3533
3591
|
|
|
3534
3592
|
// src/services/deps/parsers/index.ts
|
|
3535
3593
|
import fs from "fs";
|
|
3536
|
-
import
|
|
3594
|
+
import path6 from "path";
|
|
3537
3595
|
import { execSync as execSync3, execFileSync as execFileSync2 } from "child_process";
|
|
3538
3596
|
|
|
3539
3597
|
// src/services/deps/parsers/npm.ts
|
|
3540
|
-
import
|
|
3598
|
+
import path3 from "path";
|
|
3541
3599
|
function findNpmManifests(files) {
|
|
3542
3600
|
const lockFiles = new Set(
|
|
3543
3601
|
files.filter((f) => {
|
|
3544
|
-
const base =
|
|
3602
|
+
const base = path3.basename(f);
|
|
3545
3603
|
return base === "package-lock.json" || base === "yarn.lock" || base === "pnpm-lock.yaml";
|
|
3546
3604
|
})
|
|
3547
3605
|
);
|
|
3548
3606
|
const manifests = [];
|
|
3549
3607
|
for (const file of files) {
|
|
3550
|
-
if (
|
|
3608
|
+
if (path3.basename(file) !== "package.json") continue;
|
|
3551
3609
|
if (file.split("/").includes("node_modules")) continue;
|
|
3552
|
-
const dir =
|
|
3610
|
+
const dir = path3.dirname(file);
|
|
3553
3611
|
let lockFile;
|
|
3554
3612
|
for (const lf of ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"]) {
|
|
3555
3613
|
const candidate = dir === "." ? lf : `${dir}/${lf}`;
|
|
@@ -3564,7 +3622,7 @@ function findNpmManifests(files) {
|
|
|
3564
3622
|
}
|
|
3565
3623
|
function parseNpmPackages(content, manifest, opts, lockContent) {
|
|
3566
3624
|
if (lockContent && manifest.lockFile) {
|
|
3567
|
-
const base =
|
|
3625
|
+
const base = path3.basename(manifest.lockFile);
|
|
3568
3626
|
if (base === "package-lock.json") {
|
|
3569
3627
|
const pkgs = parsePackageLock(lockContent, manifest.lockFile, opts);
|
|
3570
3628
|
if (pkgs.length > 0) return pkgs;
|
|
@@ -3712,26 +3770,26 @@ function parsePnpmLock(content, lockFilePath, opts, packageJsonContent) {
|
|
|
3712
3770
|
}
|
|
3713
3771
|
|
|
3714
3772
|
// src/services/deps/parsers/nuget.ts
|
|
3715
|
-
import
|
|
3773
|
+
import path4 from "path";
|
|
3716
3774
|
var PROJ_EXTENSIONS = /* @__PURE__ */ new Set([".csproj", ".fsproj", ".vbproj"]);
|
|
3717
3775
|
function findNugetManifests(files) {
|
|
3718
3776
|
const lockFiles = /* @__PURE__ */ new Map();
|
|
3719
3777
|
for (const f of files) {
|
|
3720
|
-
if (
|
|
3721
|
-
lockFiles.set(
|
|
3778
|
+
if (path4.basename(f) === "packages.lock.json") {
|
|
3779
|
+
lockFiles.set(path4.dirname(f), f);
|
|
3722
3780
|
}
|
|
3723
3781
|
}
|
|
3724
3782
|
const manifests = [];
|
|
3725
3783
|
for (const file of files) {
|
|
3726
|
-
const base =
|
|
3727
|
-
const ext =
|
|
3784
|
+
const base = path4.basename(file);
|
|
3785
|
+
const ext = path4.extname(file);
|
|
3728
3786
|
if (PROJ_EXTENSIONS.has(ext) || base === "packages.config") {
|
|
3729
|
-
const dir =
|
|
3787
|
+
const dir = path4.dirname(file);
|
|
3730
3788
|
const lockFile = lockFiles.get(dir);
|
|
3731
3789
|
manifests.push({ file, ecosystem: "nuget", lockFile });
|
|
3732
3790
|
}
|
|
3733
3791
|
if (base === "Directory.Packages.props") {
|
|
3734
|
-
const dir =
|
|
3792
|
+
const dir = path4.dirname(file);
|
|
3735
3793
|
manifests.push({ file, ecosystem: "nuget", lockFile: lockFiles.get(dir) });
|
|
3736
3794
|
}
|
|
3737
3795
|
}
|
|
@@ -3750,7 +3808,7 @@ function findNugetManifests(files) {
|
|
|
3750
3808
|
return deduped;
|
|
3751
3809
|
}
|
|
3752
3810
|
function parseNugetPackages(content, manifest, opts, lockContent, propsContent) {
|
|
3753
|
-
const base =
|
|
3811
|
+
const base = path4.basename(manifest.file);
|
|
3754
3812
|
if (lockContent) {
|
|
3755
3813
|
return parsePackagesLock(lockContent, manifest.lockFile ?? manifest.file, opts);
|
|
3756
3814
|
}
|
|
@@ -3871,14 +3929,14 @@ function parsePackagesLock(content, filePath, opts) {
|
|
|
3871
3929
|
}
|
|
3872
3930
|
|
|
3873
3931
|
// src/services/deps/parsers/maven.ts
|
|
3874
|
-
import
|
|
3932
|
+
import path5 from "path";
|
|
3875
3933
|
function findMavenManifests(files) {
|
|
3876
|
-
const lockFiles = new Set(files.filter((f) =>
|
|
3934
|
+
const lockFiles = new Set(files.filter((f) => path5.basename(f) === "gradle.lockfile"));
|
|
3877
3935
|
const manifests = [];
|
|
3878
3936
|
for (const file of files) {
|
|
3879
|
-
const base =
|
|
3937
|
+
const base = path5.basename(file);
|
|
3880
3938
|
if (base === "pom.xml" || base === "build.gradle" || base === "build.gradle.kts") {
|
|
3881
|
-
const dir =
|
|
3939
|
+
const dir = path5.dirname(file);
|
|
3882
3940
|
const lockCandidate = dir === "." ? "gradle.lockfile" : `${dir}/gradle.lockfile`;
|
|
3883
3941
|
const lockFile = lockFiles.has(lockCandidate) ? lockCandidate : void 0;
|
|
3884
3942
|
manifests.push({ file, ecosystem: "maven", lockFile });
|
|
@@ -3887,7 +3945,7 @@ function findMavenManifests(files) {
|
|
|
3887
3945
|
return manifests;
|
|
3888
3946
|
}
|
|
3889
3947
|
function parseMavenPackages(content, manifest, opts, lockContent) {
|
|
3890
|
-
const base =
|
|
3948
|
+
const base = path5.basename(manifest.file);
|
|
3891
3949
|
if (lockContent && manifest.lockFile) {
|
|
3892
3950
|
return parseGradleLock(lockContent, manifest.lockFile, opts);
|
|
3893
3951
|
}
|
|
@@ -4048,7 +4106,7 @@ function getRepoFiles(repoRoot) {
|
|
|
4048
4106
|
}
|
|
4049
4107
|
function readFile(repoRoot, relPath) {
|
|
4050
4108
|
try {
|
|
4051
|
-
return fs.readFileSync(
|
|
4109
|
+
return fs.readFileSync(path6.join(repoRoot, relPath), "utf8");
|
|
4052
4110
|
} catch {
|
|
4053
4111
|
return null;
|
|
4054
4112
|
}
|
|
@@ -4066,13 +4124,13 @@ function readFileAtRef(repoRoot, ref, relPath) {
|
|
|
4066
4124
|
}
|
|
4067
4125
|
}
|
|
4068
4126
|
function findPropsContent(manifestFile, readFn) {
|
|
4069
|
-
let dir =
|
|
4127
|
+
let dir = path6.dirname(manifestFile);
|
|
4070
4128
|
for (let depth = 0; depth < 10; depth++) {
|
|
4071
4129
|
const candidate = dir === "." ? "Directory.Packages.props" : `${dir}/Directory.Packages.props`;
|
|
4072
4130
|
const content = readFn(candidate);
|
|
4073
4131
|
if (content) return content;
|
|
4074
4132
|
if (dir === ".") break;
|
|
4075
|
-
dir =
|
|
4133
|
+
dir = path6.dirname(dir);
|
|
4076
4134
|
}
|
|
4077
4135
|
return null;
|
|
4078
4136
|
}
|
|
@@ -5514,7 +5572,7 @@ import chalk from "chalk";
|
|
|
5514
5572
|
import fs2 from "fs";
|
|
5515
5573
|
function registerConfigCommands(program2) {
|
|
5516
5574
|
const config = program2.command("config").description("Manage pncli configuration");
|
|
5517
|
-
config.command("init").description("
|
|
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) => {
|
|
5518
5576
|
const start = Date.now();
|
|
5519
5577
|
try {
|
|
5520
5578
|
if (opts.repo) {
|
|
@@ -5625,8 +5683,8 @@ function registerConfigCommands(program2) {
|
|
|
5625
5683
|
if (cfg.ado.baseUrl) {
|
|
5626
5684
|
try {
|
|
5627
5685
|
const collection = cfg.defaults.ado?.collection;
|
|
5628
|
-
const
|
|
5629
|
-
await http.ado(
|
|
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);
|
|
5630
5688
|
results.ado = { ok: true, message: "connected" };
|
|
5631
5689
|
} catch (err) {
|
|
5632
5690
|
results.ado = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
@@ -5851,8 +5909,8 @@ function registerConfigCommands(program2) {
|
|
|
5851
5909
|
} else {
|
|
5852
5910
|
try {
|
|
5853
5911
|
const collection = cfg.defaults.ado?.collection;
|
|
5854
|
-
const
|
|
5855
|
-
await http.ado(
|
|
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 });
|
|
5856
5914
|
results.ado = { status: "valid", message: "ok" };
|
|
5857
5915
|
} catch (err) {
|
|
5858
5916
|
results.ado = categorize2(err);
|
|
@@ -7247,7 +7305,7 @@ function registerAdoProjectCommands(ado) {
|
|
|
7247
7305
|
|
|
7248
7306
|
// src/services/ado/commands/work.ts
|
|
7249
7307
|
import fs3 from "fs";
|
|
7250
|
-
import
|
|
7308
|
+
import path7 from "path";
|
|
7251
7309
|
|
|
7252
7310
|
// src/services/ado/input-schema.ts
|
|
7253
7311
|
var ADO_WORK_INPUT_FILE_SCHEMA = {
|
|
@@ -7488,9 +7546,9 @@ function registerAdoWorkCommands(ado) {
|
|
|
7488
7546
|
const attachments = await workClient.listAttachments(collection, parseInt(opts.id, 10));
|
|
7489
7547
|
const attachment = attachments.find((a) => a.id === opts.attachmentId);
|
|
7490
7548
|
if (!attachment) throw new PncliError(`Attachment not found: ${opts.attachmentId}`, 1);
|
|
7491
|
-
const outDir = opts.dir ??
|
|
7549
|
+
const outDir = opts.dir ?? path7.join(process.cwd(), ".pncli");
|
|
7492
7550
|
fs3.mkdirSync(outDir, { recursive: true });
|
|
7493
|
-
const outPath =
|
|
7551
|
+
const outPath = path7.join(outDir, path7.basename(attachment.name));
|
|
7494
7552
|
const buffer = await workClient.downloadAttachment(attachment.url);
|
|
7495
7553
|
fs3.writeFileSync(outPath, buffer);
|
|
7496
7554
|
success({ saved: outPath, name: attachment.name, size: buffer.length }, "ado", "work-download-attachment", start);
|
|
@@ -7650,7 +7708,7 @@ function registerAdoRepoCommands(ado) {
|
|
|
7650
7708
|
fail(err, "ado", "repo-get-pr", start);
|
|
7651
7709
|
}
|
|
7652
7710
|
});
|
|
7653
|
-
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) => {
|
|
7654
7712
|
const start = Date.now();
|
|
7655
7713
|
try {
|
|
7656
7714
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
@@ -7660,14 +7718,15 @@ function registerAdoRepoCommands(ado) {
|
|
|
7660
7718
|
description: opts.description,
|
|
7661
7719
|
sourceRefName: `refs/heads/${opts.source}`,
|
|
7662
7720
|
targetRefName: `refs/heads/${opts.target}`,
|
|
7663
|
-
reviewers
|
|
7721
|
+
reviewers,
|
|
7722
|
+
...opts.draft !== void 0 ? { isDraft: opts.draft } : {}
|
|
7664
7723
|
});
|
|
7665
7724
|
success(data, "ado", "repo-create-pr", start);
|
|
7666
7725
|
} catch (err) {
|
|
7667
7726
|
fail(err, "ado", "repo-create-pr", start);
|
|
7668
7727
|
}
|
|
7669
7728
|
});
|
|
7670
|
-
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) => {
|
|
7671
7730
|
const start = Date.now();
|
|
7672
7731
|
try {
|
|
7673
7732
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
@@ -7676,6 +7735,7 @@ function registerAdoRepoCommands(ado) {
|
|
|
7676
7735
|
if (opts.title) body.title = opts.title;
|
|
7677
7736
|
if (opts.description !== void 0) body.description = opts.description;
|
|
7678
7737
|
if (reviewers) body.reviewers = reviewers;
|
|
7738
|
+
if (opts.draft !== void 0) body.isDraft = opts.draft === "true";
|
|
7679
7739
|
const data = await gitClient.updatePR(collection, project, repo2, parseInt(opts.id, 10), body);
|
|
7680
7740
|
success(data, "ado", "repo-update-pr", start);
|
|
7681
7741
|
} catch (err) {
|
|
@@ -7861,7 +7921,7 @@ function registerAdoRepoCommands(ado) {
|
|
|
7861
7921
|
const config = lc({ configPath: globalOpts.config });
|
|
7862
7922
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
7863
7923
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
7864
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
7924
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-52YHD3ZL.js");
|
|
7865
7925
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
7866
7926
|
const coreClient = new AdoCoreClient2(http);
|
|
7867
7927
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -8262,18 +8322,18 @@ var ArtifactoryClient = class {
|
|
|
8262
8322
|
}
|
|
8263
8323
|
});
|
|
8264
8324
|
}
|
|
8265
|
-
async getStorageInfo(repo,
|
|
8266
|
-
const cleanPath =
|
|
8325
|
+
async getStorageInfo(repo, path9) {
|
|
8326
|
+
const cleanPath = path9.startsWith("/") ? path9.slice(1) : path9;
|
|
8267
8327
|
return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`);
|
|
8268
8328
|
}
|
|
8269
|
-
async getProperties(repo,
|
|
8270
|
-
const cleanPath =
|
|
8329
|
+
async getProperties(repo, path9) {
|
|
8330
|
+
const cleanPath = path9.startsWith("/") ? path9.slice(1) : path9;
|
|
8271
8331
|
return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
|
|
8272
8332
|
params: { properties: "" }
|
|
8273
8333
|
});
|
|
8274
8334
|
}
|
|
8275
|
-
async setProperties(repo,
|
|
8276
|
-
const cleanPath =
|
|
8335
|
+
async setProperties(repo, path9, properties, opts = {}) {
|
|
8336
|
+
const cleanPath = path9.startsWith("/") ? path9.slice(1) : path9;
|
|
8277
8337
|
const propString = Object.entries(properties).map(([k, v]) => `${k}=${v}`).join(";");
|
|
8278
8338
|
await this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
|
|
8279
8339
|
method: "PUT",
|
|
@@ -8355,27 +8415,27 @@ function registerArtifactoryCommands(program2) {
|
|
|
8355
8415
|
fail(err, "artifactory", "repos", start);
|
|
8356
8416
|
}
|
|
8357
8417
|
});
|
|
8358
|
-
art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo,
|
|
8418
|
+
art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo, path9) => {
|
|
8359
8419
|
const start = Date.now();
|
|
8360
8420
|
try {
|
|
8361
8421
|
const client = getClient8(program2);
|
|
8362
|
-
const data = await client.getStorageInfo(repo,
|
|
8422
|
+
const data = await client.getStorageInfo(repo, path9);
|
|
8363
8423
|
success(data, "artifactory", "artifact-info", start);
|
|
8364
8424
|
} catch (err) {
|
|
8365
8425
|
fail(err, "artifactory", "artifact-info", start);
|
|
8366
8426
|
}
|
|
8367
8427
|
});
|
|
8368
|
-
art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo,
|
|
8428
|
+
art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo, path9) => {
|
|
8369
8429
|
const start = Date.now();
|
|
8370
8430
|
try {
|
|
8371
8431
|
const client = getClient8(program2);
|
|
8372
|
-
const data = await client.getProperties(repo,
|
|
8432
|
+
const data = await client.getProperties(repo, path9);
|
|
8373
8433
|
success(data, "artifactory", "properties-get", start);
|
|
8374
8434
|
} catch (err) {
|
|
8375
8435
|
fail(err, "artifactory", "properties-get", start);
|
|
8376
8436
|
}
|
|
8377
8437
|
});
|
|
8378
|
-
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,
|
|
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) => {
|
|
8379
8439
|
const start = Date.now();
|
|
8380
8440
|
try {
|
|
8381
8441
|
if (!keyvals.length) throw new PncliError("Provide at least one key=value pair");
|
|
@@ -8386,8 +8446,8 @@ function registerArtifactoryCommands(program2) {
|
|
|
8386
8446
|
properties[kv.slice(0, eq)] = kv.slice(eq + 1);
|
|
8387
8447
|
}
|
|
8388
8448
|
const client = getClient8(program2);
|
|
8389
|
-
await client.setProperties(repo,
|
|
8390
|
-
success({ repo, path:
|
|
8449
|
+
await client.setProperties(repo, path9, properties, { recursive: opts.recursive });
|
|
8450
|
+
success({ repo, path: path9, properties }, "artifactory", "properties-set", start);
|
|
8391
8451
|
} catch (err) {
|
|
8392
8452
|
fail(err, "artifactory", "properties-set", start);
|
|
8393
8453
|
}
|
|
@@ -8994,8 +9054,8 @@ function registerSonatypeIqCommands(program2) {
|
|
|
8994
9054
|
const startTime = Date.now();
|
|
8995
9055
|
try {
|
|
8996
9056
|
const http = getHttpClient(program2);
|
|
8997
|
-
const
|
|
8998
|
-
const data = await http.sonatypeiq(
|
|
9057
|
+
const path9 = opts.organizationId ? `/api/v2/policies/organization/${encodeURIComponent(opts.organizationId)}` : "/api/v2/policies";
|
|
9058
|
+
const data = await http.sonatypeiq(path9);
|
|
8999
9059
|
success(data, "sonatypeiq", "policies list", startTime);
|
|
9000
9060
|
} catch (err) {
|
|
9001
9061
|
fail(err, "sonatypeiq", "policies list", startTime);
|
|
@@ -9005,11 +9065,13 @@ function registerSonatypeIqCommands(program2) {
|
|
|
9005
9065
|
|
|
9006
9066
|
// src/services/skills/commands.ts
|
|
9007
9067
|
import fs4 from "fs";
|
|
9008
|
-
import
|
|
9068
|
+
import path8 from "path";
|
|
9009
9069
|
import os from "os";
|
|
9010
9070
|
import { fileURLToPath } from "url";
|
|
9011
9071
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
9012
9072
|
import select from "@inquirer/select";
|
|
9073
|
+
import checkbox, { Separator } from "@inquirer/checkbox";
|
|
9074
|
+
import input2 from "@inquirer/input";
|
|
9013
9075
|
var BACK = "__back__";
|
|
9014
9076
|
var ALL_MARKETPLACES = "__all_marketplaces__";
|
|
9015
9077
|
function injectTokenIntoUrl(url, token) {
|
|
@@ -9033,7 +9095,7 @@ function repoNameFromUrl(url) {
|
|
|
9033
9095
|
}
|
|
9034
9096
|
}
|
|
9035
9097
|
function defaultMarketplacePath(url) {
|
|
9036
|
-
return
|
|
9098
|
+
return path8.join(os.homedir(), ".agents", "marketplaces", repoNameFromUrl(url));
|
|
9037
9099
|
}
|
|
9038
9100
|
function marketplaceLabel(m) {
|
|
9039
9101
|
return m.name ?? repoNameFromUrl(m.repoUrl ?? "");
|
|
@@ -9047,8 +9109,8 @@ function assertInteractive(hint) {
|
|
|
9047
9109
|
}
|
|
9048
9110
|
}
|
|
9049
9111
|
var AGENT_PATHS = {
|
|
9050
|
-
"github-copilot": { project: ".agents/skills", user:
|
|
9051
|
-
"claude-code": { project: ".claude/skills", user:
|
|
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") }
|
|
9052
9114
|
};
|
|
9053
9115
|
function resolveAgentName(opts) {
|
|
9054
9116
|
return opts.claude ? "claude-code" : opts.agent ?? "github-copilot";
|
|
@@ -9060,18 +9122,25 @@ function resolveAgentPaths(agentName) {
|
|
|
9060
9122
|
}
|
|
9061
9123
|
return agentConfig;
|
|
9062
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
|
+
}
|
|
9063
9131
|
function getBundledSkillsDir() {
|
|
9064
9132
|
try {
|
|
9065
9133
|
const thisFile = fileURLToPath(import.meta.url);
|
|
9066
|
-
return
|
|
9134
|
+
return path8.resolve(path8.dirname(thisFile), "..", "skills");
|
|
9067
9135
|
} catch {
|
|
9068
9136
|
return "";
|
|
9069
9137
|
}
|
|
9070
9138
|
}
|
|
9071
9139
|
function getInstalledMetaPath(targetDir) {
|
|
9072
|
-
return
|
|
9140
|
+
return path8.join(targetDir, ".pncli-installed.json");
|
|
9073
9141
|
}
|
|
9074
9142
|
var SKILL_ORIGIN_FILENAME = "pncli-origin.json";
|
|
9143
|
+
var DISABLED_SUBDIR = ".pncli-disabled";
|
|
9075
9144
|
function readInstalledMeta(targetDir) {
|
|
9076
9145
|
const metaPath = getInstalledMetaPath(targetDir);
|
|
9077
9146
|
const raw = loadJsonFile(metaPath);
|
|
@@ -9079,7 +9148,7 @@ function readInstalledMeta(targetDir) {
|
|
|
9079
9148
|
return { version: 1, skills: {} };
|
|
9080
9149
|
}
|
|
9081
9150
|
function getSkillOriginPath(skillDir) {
|
|
9082
|
-
return
|
|
9151
|
+
return path8.join(skillDir, SKILL_ORIGIN_FILENAME);
|
|
9083
9152
|
}
|
|
9084
9153
|
function readSkillOrigin(skillDir) {
|
|
9085
9154
|
const originPath = getSkillOriginPath(skillDir);
|
|
@@ -9091,6 +9160,132 @@ function readSkillOrigin(skillDir) {
|
|
|
9091
9160
|
}
|
|
9092
9161
|
return null;
|
|
9093
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
|
+
}
|
|
9094
9289
|
function detectRepoBranch(repoPath) {
|
|
9095
9290
|
try {
|
|
9096
9291
|
const result = execFileSync3("git", ["-C", repoPath, "rev-parse", "--abbrev-ref", "HEAD"], {
|
|
@@ -9109,7 +9304,7 @@ function recordInstalledSkills(targetDir, skillNames, record) {
|
|
|
9109
9304
|
for (const skillName of skillNames) {
|
|
9110
9305
|
const fullRecord = { ...record, installedAt: now };
|
|
9111
9306
|
meta.skills[skillName] = fullRecord;
|
|
9112
|
-
const skillDir =
|
|
9307
|
+
const skillDir = path8.join(targetDir, skillName);
|
|
9113
9308
|
if (fs4.existsSync(skillDir)) {
|
|
9114
9309
|
const origin = { version: 1, ...fullRecord };
|
|
9115
9310
|
try {
|
|
@@ -9179,7 +9374,7 @@ function upsertMarketplace(all, entry) {
|
|
|
9179
9374
|
}
|
|
9180
9375
|
}
|
|
9181
9376
|
function cloneOrReuseMarketplace(url, resolvedPath, opts) {
|
|
9182
|
-
const hasGit = fs4.existsSync(
|
|
9377
|
+
const hasGit = fs4.existsSync(path8.join(resolvedPath, ".git"));
|
|
9183
9378
|
if (fs4.existsSync(resolvedPath) && !hasGit && fs4.readdirSync(resolvedPath).length > 0) {
|
|
9184
9379
|
throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
|
|
9185
9380
|
}
|
|
@@ -9247,7 +9442,7 @@ function installPluginsForMarketplace(marketplacePath, marketplaceName, repoUrl,
|
|
|
9247
9442
|
results[pluginName] = { installed, failed };
|
|
9248
9443
|
totalInstalled += installed.length;
|
|
9249
9444
|
for (const skill of installed) {
|
|
9250
|
-
warn(` ${skill}: ${
|
|
9445
|
+
warn(` ${skill}: ${path8.join(skillsSrc, skill)} \u2192 ${path8.join(targetDir, skill)}`);
|
|
9251
9446
|
}
|
|
9252
9447
|
if (failed.length > 0) {
|
|
9253
9448
|
warn(`Skipped ${failed.length} skill(s) with invalid names in "${pluginName}": ${failed.join(", ")}`);
|
|
@@ -9299,34 +9494,55 @@ function syncMarketplacePlugins(m, targetDir, force, pluginFilter) {
|
|
|
9299
9494
|
return { marketplace: marketplaceName, skipped: true, error: message };
|
|
9300
9495
|
}
|
|
9301
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
|
+
}
|
|
9302
9542
|
async function marketplaceAddAction(url, localPath, opts, commandName) {
|
|
9303
9543
|
const start = Date.now();
|
|
9304
9544
|
try {
|
|
9305
|
-
|
|
9306
|
-
const marketplaceName = opts.name ?? repoNameFromUrl(url);
|
|
9307
|
-
cloneOrReuseMarketplace(url, resolvedPath, opts);
|
|
9308
|
-
const configPath = getGlobalConfigPath();
|
|
9309
|
-
const { existing, all } = loadMarketplacesConfig(configPath);
|
|
9310
|
-
const entry = {
|
|
9311
|
-
name: marketplaceName,
|
|
9312
|
-
repoUrl: url,
|
|
9313
|
-
localPath: resolvedPath,
|
|
9314
|
-
...opts.token ? { token: opts.token } : {}
|
|
9315
|
-
};
|
|
9316
|
-
upsertMarketplace(all, entry);
|
|
9317
|
-
saveMarketplaces(configPath, existing, all);
|
|
9318
|
-
const targetDir = resolveAgentPaths(resolveAgentName(opts)).user;
|
|
9319
|
-
const { pluginResults, totalInstalled } = installAllPlugins(resolvedPath, marketplaceName, url, targetDir);
|
|
9320
|
-
success({
|
|
9321
|
-
name: marketplaceName,
|
|
9322
|
-
repoUrl: url,
|
|
9323
|
-
localPath: resolvedPath,
|
|
9324
|
-
branch: opts.branch ?? null,
|
|
9325
|
-
tokenConfigured: !!opts.token,
|
|
9326
|
-
plugins: pluginResults,
|
|
9327
|
-
total: totalInstalled,
|
|
9328
|
-
target: targetDir
|
|
9329
|
-
}, "skills", commandName, start);
|
|
9545
|
+
success(performMarketplaceAdd(url, localPath, opts), "skills", commandName, start);
|
|
9330
9546
|
} catch (err) {
|
|
9331
9547
|
fail(err, "skills", commandName, start);
|
|
9332
9548
|
}
|
|
@@ -9338,19 +9554,19 @@ function registerSkillsCommands(program2) {
|
|
|
9338
9554
|
try {
|
|
9339
9555
|
let targetDir;
|
|
9340
9556
|
if (opts.target) {
|
|
9341
|
-
targetDir =
|
|
9557
|
+
targetDir = path8.resolve(opts.target);
|
|
9342
9558
|
} else {
|
|
9343
9559
|
const agentConfig = resolveAgentPaths(opts.agent);
|
|
9344
9560
|
const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
|
|
9345
|
-
targetDir =
|
|
9561
|
+
targetDir = path8.resolve(scopePath);
|
|
9346
9562
|
}
|
|
9347
|
-
const resolvedTarget =
|
|
9563
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
9348
9564
|
const bundledDir = getBundledSkillsDir();
|
|
9349
9565
|
let skillDirs = [];
|
|
9350
9566
|
if (bundledDir !== "") {
|
|
9351
9567
|
try {
|
|
9352
9568
|
skillDirs = fs4.readdirSync(bundledDir).filter(
|
|
9353
|
-
(name) => fs4.existsSync(
|
|
9569
|
+
(name) => fs4.existsSync(path8.join(bundledDir, name, "SKILL.md"))
|
|
9354
9570
|
);
|
|
9355
9571
|
} catch {
|
|
9356
9572
|
}
|
|
@@ -9361,25 +9577,25 @@ function registerSkillsCommands(program2) {
|
|
|
9361
9577
|
const installed = [];
|
|
9362
9578
|
const failed = [];
|
|
9363
9579
|
for (const skillName of skillDirs) {
|
|
9364
|
-
const existingDir =
|
|
9365
|
-
if (!existingDir.startsWith(resolvedTarget +
|
|
9580
|
+
const existingDir = path8.resolve(targetDir, skillName);
|
|
9581
|
+
if (!existingDir.startsWith(resolvedTarget + path8.sep)) continue;
|
|
9366
9582
|
if (fs4.existsSync(existingDir)) {
|
|
9367
9583
|
fs4.rmSync(existingDir, { recursive: true, force: true });
|
|
9368
9584
|
}
|
|
9369
9585
|
}
|
|
9370
9586
|
warn(`Installing ${skillDirs.length} bundled skill(s) to ${targetDir}...`);
|
|
9371
9587
|
for (const skillName of skillDirs) {
|
|
9372
|
-
const skillDir =
|
|
9373
|
-
if (!skillDir.startsWith(resolvedTarget +
|
|
9588
|
+
const skillDir = path8.resolve(targetDir, skillName);
|
|
9589
|
+
if (!skillDir.startsWith(resolvedTarget + path8.sep)) {
|
|
9374
9590
|
failed.push(skillName);
|
|
9375
9591
|
continue;
|
|
9376
9592
|
}
|
|
9377
9593
|
try {
|
|
9378
|
-
const mdFiles = fs4.readdirSync(
|
|
9594
|
+
const mdFiles = fs4.readdirSync(path8.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
|
|
9379
9595
|
fs4.mkdirSync(skillDir, { recursive: true });
|
|
9380
9596
|
for (const mdFile of mdFiles) {
|
|
9381
|
-
const content = fs4.readFileSync(
|
|
9382
|
-
fs4.writeFileSync(
|
|
9597
|
+
const content = fs4.readFileSync(path8.join(bundledDir, skillName, mdFile), "utf8");
|
|
9598
|
+
fs4.writeFileSync(path8.join(skillDir, mdFile), content, "utf8");
|
|
9383
9599
|
}
|
|
9384
9600
|
installed.push(skillName);
|
|
9385
9601
|
} catch {
|
|
@@ -9409,11 +9625,11 @@ function registerSkillsCommands(program2) {
|
|
|
9409
9625
|
try {
|
|
9410
9626
|
let targetDir;
|
|
9411
9627
|
if (opts.target) {
|
|
9412
|
-
targetDir =
|
|
9628
|
+
targetDir = path8.resolve(opts.target);
|
|
9413
9629
|
} else {
|
|
9414
9630
|
const agentConfig = resolveAgentPaths(opts.agent);
|
|
9415
9631
|
const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
|
|
9416
|
-
targetDir =
|
|
9632
|
+
targetDir = path8.resolve(scopePath);
|
|
9417
9633
|
}
|
|
9418
9634
|
if (!fs4.existsSync(targetDir)) {
|
|
9419
9635
|
success({ skills: [], message: `No skills directory found at ${targetDir}. Run: pncli skills install` }, "skills", "list", start);
|
|
@@ -9422,11 +9638,11 @@ function registerSkillsCommands(program2) {
|
|
|
9422
9638
|
const meta = readInstalledMeta(targetDir);
|
|
9423
9639
|
const skillDirs = fs4.readdirSync(targetDir).filter((name) => {
|
|
9424
9640
|
if (name.startsWith(".")) return false;
|
|
9425
|
-
const skillPath =
|
|
9641
|
+
const skillPath = path8.join(targetDir, name, "SKILL.md");
|
|
9426
9642
|
return fs4.existsSync(skillPath);
|
|
9427
9643
|
});
|
|
9428
9644
|
const skillsList = skillDirs.map((name) => {
|
|
9429
|
-
const content = fs4.readFileSync(
|
|
9645
|
+
const content = fs4.readFileSync(path8.join(targetDir, name, "SKILL.md"), "utf8");
|
|
9430
9646
|
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
9431
9647
|
const data = {};
|
|
9432
9648
|
const metadata = {};
|
|
@@ -9470,15 +9686,15 @@ function registerSkillsCommands(program2) {
|
|
|
9470
9686
|
try {
|
|
9471
9687
|
let targetDir;
|
|
9472
9688
|
if (opts.target) {
|
|
9473
|
-
targetDir =
|
|
9689
|
+
targetDir = path8.resolve(opts.target);
|
|
9474
9690
|
} else {
|
|
9475
9691
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
9476
9692
|
const scopePath = opts.scope === "project" ? agentConfig.project : agentConfig.user;
|
|
9477
|
-
targetDir =
|
|
9693
|
+
targetDir = path8.resolve(scopePath);
|
|
9478
9694
|
}
|
|
9479
|
-
const resolvedTarget =
|
|
9480
|
-
const skillDir =
|
|
9481
|
-
if (!skillDir.startsWith(resolvedTarget +
|
|
9695
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
9696
|
+
const skillDir = path8.resolve(targetDir, name);
|
|
9697
|
+
if (!skillDir.startsWith(resolvedTarget + path8.sep)) {
|
|
9482
9698
|
throw new Error(`Invalid skill name: "${name}"`);
|
|
9483
9699
|
}
|
|
9484
9700
|
if (!fs4.existsSync(skillDir)) {
|
|
@@ -9552,27 +9768,12 @@ function registerSkillsCommands(program2) {
|
|
|
9552
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) => {
|
|
9553
9769
|
const start = Date.now();
|
|
9554
9770
|
try {
|
|
9555
|
-
|
|
9556
|
-
const { existing, all } = loadMarketplacesConfig(configPath);
|
|
9557
|
-
const idx = all.findIndex((m) => m.name === name || m.repoUrl === name);
|
|
9558
|
-
if (idx === -1) {
|
|
9559
|
-
throw new Error(`Marketplace "${name}" not found. Run: pncli skills marketplace list`);
|
|
9560
|
-
}
|
|
9561
|
-
const removed = all.splice(idx, 1)[0];
|
|
9562
|
-
saveMarketplaces(configPath, existing, all);
|
|
9563
|
-
success({
|
|
9564
|
-
removed: {
|
|
9565
|
-
name: marketplaceLabel(removed),
|
|
9566
|
-
repoUrl: removed.repoUrl,
|
|
9567
|
-
localPath: removed.localPath
|
|
9568
|
-
},
|
|
9569
|
-
remaining: all.length
|
|
9570
|
-
}, "skills", "marketplace-remove", start);
|
|
9771
|
+
success(performMarketplaceRemove(name), "skills", "marketplace-remove", start);
|
|
9571
9772
|
} catch (err) {
|
|
9572
9773
|
fail(err, "skills", "marketplace-remove", start);
|
|
9573
9774
|
}
|
|
9574
9775
|
});
|
|
9575
|
-
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) => {
|
|
9576
9777
|
const start = Date.now();
|
|
9577
9778
|
try {
|
|
9578
9779
|
const configPath = getGlobalConfigPath();
|
|
@@ -9685,13 +9886,13 @@ function registerSkillsCommands(program2) {
|
|
|
9685
9886
|
try {
|
|
9686
9887
|
let targetDir;
|
|
9687
9888
|
if (opts.target) {
|
|
9688
|
-
targetDir =
|
|
9889
|
+
targetDir = path8.resolve(opts.target);
|
|
9689
9890
|
} else {
|
|
9690
9891
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
9691
9892
|
const scopePath = (opts.scope ?? "user") === "project" ? agentConfig.project : agentConfig.user;
|
|
9692
|
-
targetDir =
|
|
9893
|
+
targetDir = path8.resolve(scopePath);
|
|
9693
9894
|
}
|
|
9694
|
-
const resolvedTarget =
|
|
9895
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
9695
9896
|
if (!fs4.existsSync(targetDir)) {
|
|
9696
9897
|
success({ removed: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-purge-plugin", start);
|
|
9697
9898
|
return;
|
|
@@ -9701,7 +9902,7 @@ function registerSkillsCommands(program2) {
|
|
|
9701
9902
|
const skipped = [];
|
|
9702
9903
|
const skillDirs = fs4.readdirSync(targetDir).filter((name) => {
|
|
9703
9904
|
if (name.startsWith(".")) return false;
|
|
9704
|
-
const p =
|
|
9905
|
+
const p = path8.join(targetDir, name);
|
|
9705
9906
|
try {
|
|
9706
9907
|
return fs4.statSync(p).isDirectory();
|
|
9707
9908
|
} catch {
|
|
@@ -9709,8 +9910,8 @@ function registerSkillsCommands(program2) {
|
|
|
9709
9910
|
}
|
|
9710
9911
|
});
|
|
9711
9912
|
for (const skillName of skillDirs) {
|
|
9712
|
-
const skillDir =
|
|
9713
|
-
if (!skillDir.startsWith(resolvedTarget +
|
|
9913
|
+
const skillDir = path8.resolve(targetDir, skillName);
|
|
9914
|
+
if (!skillDir.startsWith(resolvedTarget + path8.sep)) continue;
|
|
9714
9915
|
let record = meta.skills[skillName] ?? null;
|
|
9715
9916
|
if (!record) {
|
|
9716
9917
|
const perSkill = readSkillOrigin(skillDir);
|
|
@@ -9754,6 +9955,161 @@ function registerSkillsCommands(program2) {
|
|
|
9754
9955
|
fail(err, "skills", "marketplace-purge-plugin", start);
|
|
9755
9956
|
}
|
|
9756
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
|
+
});
|
|
9757
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) => {
|
|
9758
10114
|
const start = Date.now();
|
|
9759
10115
|
try {
|
|
@@ -9764,7 +10120,7 @@ function registerSkillsCommands(program2) {
|
|
|
9764
10120
|
}
|
|
9765
10121
|
const skillDirs = fs4.readdirSync(userDir).filter((name) => {
|
|
9766
10122
|
if (name.startsWith(".")) return false;
|
|
9767
|
-
const p =
|
|
10123
|
+
const p = path8.join(userDir, name);
|
|
9768
10124
|
try {
|
|
9769
10125
|
return fs4.statSync(p).isDirectory();
|
|
9770
10126
|
} catch {
|
|
@@ -9782,8 +10138,8 @@ Re-run with --force to confirm, or use \`pncli skills marketplace purge-plugin\`
|
|
|
9782
10138
|
);
|
|
9783
10139
|
}
|
|
9784
10140
|
for (const name of skillDirs) {
|
|
9785
|
-
const skillDir =
|
|
9786
|
-
if (!skillDir.startsWith(
|
|
10141
|
+
const skillDir = path8.resolve(userDir, name);
|
|
10142
|
+
if (!skillDir.startsWith(path8.resolve(userDir) + path8.sep)) continue;
|
|
9787
10143
|
fs4.rmSync(skillDir, { recursive: true, force: true });
|
|
9788
10144
|
}
|
|
9789
10145
|
const metaPath = getInstalledMetaPath(userDir);
|
|
@@ -9797,7 +10153,7 @@ Re-run with --force to confirm, or use \`pncli skills marketplace purge-plugin\`
|
|
|
9797
10153
|
});
|
|
9798
10154
|
}
|
|
9799
10155
|
function resolvePluginChoices(marketplacePath) {
|
|
9800
|
-
const marketplaceJsonPath =
|
|
10156
|
+
const marketplaceJsonPath = path8.join(marketplacePath, ".claude-plugin", "marketplace.json");
|
|
9801
10157
|
if (fs4.existsSync(marketplaceJsonPath)) {
|
|
9802
10158
|
try {
|
|
9803
10159
|
const meta = JSON.parse(fs4.readFileSync(marketplaceJsonPath, "utf8"));
|
|
@@ -9807,36 +10163,36 @@ function resolvePluginChoices(marketplacePath) {
|
|
|
9807
10163
|
} catch {
|
|
9808
10164
|
}
|
|
9809
10165
|
}
|
|
9810
|
-
const pluginsDir =
|
|
10166
|
+
const pluginsDir = path8.join(marketplacePath, "plugins");
|
|
9811
10167
|
if (fs4.existsSync(pluginsDir)) {
|
|
9812
|
-
return fs4.readdirSync(pluginsDir).filter((name) => fs4.statSync(
|
|
10168
|
+
return fs4.readdirSync(pluginsDir).filter((name) => fs4.statSync(path8.join(pluginsDir, name)).isDirectory()).map((name) => ({ name, description: "" }));
|
|
9813
10169
|
}
|
|
9814
10170
|
return [];
|
|
9815
10171
|
}
|
|
9816
10172
|
function resolveSkillsSrc(marketplacePath, selectedPlugin) {
|
|
9817
|
-
const pluginsBase =
|
|
9818
|
-
const skillsSrc =
|
|
9819
|
-
if (!skillsSrc.startsWith(pluginsBase +
|
|
10173
|
+
const pluginsBase = path8.resolve(marketplacePath, "plugins");
|
|
10174
|
+
const skillsSrc = path8.resolve(pluginsBase, selectedPlugin, "skills");
|
|
10175
|
+
if (!skillsSrc.startsWith(pluginsBase + path8.sep)) {
|
|
9820
10176
|
throw new Error(`Invalid plugin name: "${selectedPlugin}"`);
|
|
9821
10177
|
}
|
|
9822
10178
|
return skillsSrc;
|
|
9823
10179
|
}
|
|
9824
10180
|
function copyPluginSkills(skillsSrc, targetDir, meta) {
|
|
9825
10181
|
fs4.mkdirSync(targetDir, { recursive: true });
|
|
9826
|
-
const resolvedTarget =
|
|
10182
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
9827
10183
|
const skillNames = fs4.readdirSync(skillsSrc).filter(
|
|
9828
|
-
(name) => fs4.statSync(
|
|
10184
|
+
(name) => fs4.statSync(path8.join(skillsSrc, name)).isDirectory()
|
|
9829
10185
|
);
|
|
9830
10186
|
const installed = [];
|
|
9831
10187
|
const failed = [];
|
|
9832
10188
|
for (const skillName of skillNames) {
|
|
9833
|
-
const dest =
|
|
9834
|
-
if (!dest.startsWith(resolvedTarget +
|
|
10189
|
+
const dest = path8.resolve(targetDir, skillName);
|
|
10190
|
+
if (!dest.startsWith(resolvedTarget + path8.sep)) {
|
|
9835
10191
|
failed.push(skillName);
|
|
9836
10192
|
continue;
|
|
9837
10193
|
}
|
|
9838
10194
|
fs4.rmSync(dest, { recursive: true, force: true });
|
|
9839
|
-
fs4.cpSync(
|
|
10195
|
+
fs4.cpSync(path8.join(skillsSrc, skillName), dest, { recursive: true });
|
|
9840
10196
|
installed.push(skillName);
|
|
9841
10197
|
}
|
|
9842
10198
|
if (meta) {
|
|
@@ -9852,8 +10208,8 @@ function copyPluginSkills(skillsSrc, targetDir, meta) {
|
|
|
9852
10208
|
}
|
|
9853
10209
|
|
|
9854
10210
|
// src/services/jwt/commands.ts
|
|
9855
|
-
function base64urlDecode(
|
|
9856
|
-
const base64 =
|
|
10211
|
+
function base64urlDecode(input3) {
|
|
10212
|
+
const base64 = input3.replace(/-/g, "+").replace(/_/g, "/");
|
|
9857
10213
|
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
9858
10214
|
return Buffer.from(padded, "base64").toString("utf8");
|
|
9859
10215
|
}
|