@kolatts/pncli 1.21.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +501 -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]);
|
|
@@ -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)").
|
|
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(
|
|
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
|
|
2220
|
-
return this.http.github(
|
|
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
|
|
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
|
|
3598
|
+
import path3 from "path";
|
|
3557
3599
|
function findNpmManifests(files) {
|
|
3558
3600
|
const lockFiles = new Set(
|
|
3559
3601
|
files.filter((f) => {
|
|
3560
|
-
const base =
|
|
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 (
|
|
3608
|
+
if (path3.basename(file) !== "package.json") continue;
|
|
3567
3609
|
if (file.split("/").includes("node_modules")) continue;
|
|
3568
|
-
const dir =
|
|
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 =
|
|
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
|
|
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 (
|
|
3737
|
-
lockFiles.set(
|
|
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 =
|
|
3743
|
-
const ext =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
3932
|
+
import path5 from "path";
|
|
3891
3933
|
function findMavenManifests(files) {
|
|
3892
|
-
const lockFiles = new Set(files.filter((f) =>
|
|
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 =
|
|
3937
|
+
const base = path5.basename(file);
|
|
3896
3938
|
if (base === "pom.xml" || base === "build.gradle" || base === "build.gradle.kts") {
|
|
3897
|
-
const dir =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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("
|
|
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
|
|
5645
|
-
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);
|
|
5646
5688
|
results.ado = { ok: true, message: "connected" };
|
|
5647
5689
|
} catch (err) {
|
|
5648
5690
|
results.ado = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
@@ -5867,8 +5909,8 @@ function registerConfigCommands(program2) {
|
|
|
5867
5909
|
} else {
|
|
5868
5910
|
try {
|
|
5869
5911
|
const collection = cfg.defaults.ado?.collection;
|
|
5870
|
-
const
|
|
5871
|
-
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 });
|
|
5872
5914
|
results.ado = { status: "valid", message: "ok" };
|
|
5873
5915
|
} catch (err) {
|
|
5874
5916
|
results.ado = categorize2(err);
|
|
@@ -7263,7 +7305,7 @@ function registerAdoProjectCommands(ado) {
|
|
|
7263
7305
|
|
|
7264
7306
|
// src/services/ado/commands/work.ts
|
|
7265
7307
|
import fs3 from "fs";
|
|
7266
|
-
import
|
|
7308
|
+
import path7 from "path";
|
|
7267
7309
|
|
|
7268
7310
|
// src/services/ado/input-schema.ts
|
|
7269
7311
|
var ADO_WORK_INPUT_FILE_SCHEMA = {
|
|
@@ -7504,9 +7546,9 @@ function registerAdoWorkCommands(ado) {
|
|
|
7504
7546
|
const attachments = await workClient.listAttachments(collection, parseInt(opts.id, 10));
|
|
7505
7547
|
const attachment = attachments.find((a) => a.id === opts.attachmentId);
|
|
7506
7548
|
if (!attachment) throw new PncliError(`Attachment not found: ${opts.attachmentId}`, 1);
|
|
7507
|
-
const outDir = opts.dir ??
|
|
7549
|
+
const outDir = opts.dir ?? path7.join(process.cwd(), ".pncli");
|
|
7508
7550
|
fs3.mkdirSync(outDir, { recursive: true });
|
|
7509
|
-
const outPath =
|
|
7551
|
+
const outPath = path7.join(outDir, path7.basename(attachment.name));
|
|
7510
7552
|
const buffer = await workClient.downloadAttachment(attachment.url);
|
|
7511
7553
|
fs3.writeFileSync(outPath, buffer);
|
|
7512
7554
|
success({ saved: outPath, name: attachment.name, size: buffer.length }, "ado", "work-download-attachment", start);
|
|
@@ -7666,7 +7708,7 @@ function registerAdoRepoCommands(ado) {
|
|
|
7666
7708
|
fail(err, "ado", "repo-get-pr", start);
|
|
7667
7709
|
}
|
|
7668
7710
|
});
|
|
7669
|
-
repo.command("create-pr").description("Create a pull request").requiredOption("--title <title>", "PR title").requiredOption("--source <branch>", "Source branch").option("--target <branch>", "Target branch (default: main)", "main").option("--description <text>", "PR description").option("--reviewers <ids>", "Comma-separated reviewer IDs or display names").action(async (opts) => {
|
|
7711
|
+
repo.command("create-pr").description("Create a pull request").requiredOption("--title <title>", "PR title").requiredOption("--source <branch>", "Source branch").option("--target <branch>", "Target branch (default: main)", "main").option("--description <text>", "PR description").option("--reviewers <ids>", "Comma-separated reviewer IDs or display names").option("--draft", "Create as a draft pull request").action(async (opts) => {
|
|
7670
7712
|
const start = Date.now();
|
|
7671
7713
|
try {
|
|
7672
7714
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
@@ -7676,14 +7718,15 @@ function registerAdoRepoCommands(ado) {
|
|
|
7676
7718
|
description: opts.description,
|
|
7677
7719
|
sourceRefName: `refs/heads/${opts.source}`,
|
|
7678
7720
|
targetRefName: `refs/heads/${opts.target}`,
|
|
7679
|
-
reviewers
|
|
7721
|
+
reviewers,
|
|
7722
|
+
...opts.draft !== void 0 ? { isDraft: opts.draft } : {}
|
|
7680
7723
|
});
|
|
7681
7724
|
success(data, "ado", "repo-create-pr", start);
|
|
7682
7725
|
} catch (err) {
|
|
7683
7726
|
fail(err, "ado", "repo-create-pr", start);
|
|
7684
7727
|
}
|
|
7685
7728
|
});
|
|
7686
|
-
repo.command("update-pr").description("Update a pull request").requiredOption("--id <n>", "Pull request ID").option("--title <title>", "New title").option("--description <text>", "New description").option("--reviewers <ids>", "Comma-separated reviewer IDs").action(async (opts) => {
|
|
7729
|
+
repo.command("update-pr").description("Update a pull request").requiredOption("--id <n>", "Pull request ID").option("--title <title>", "New title").option("--description <text>", "New description").option("--reviewers <ids>", "Comma-separated reviewer IDs").option("--draft <bool>", "Set draft status: true|false").action(async (opts) => {
|
|
7687
7730
|
const start = Date.now();
|
|
7688
7731
|
try {
|
|
7689
7732
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
@@ -7692,6 +7735,7 @@ function registerAdoRepoCommands(ado) {
|
|
|
7692
7735
|
if (opts.title) body.title = opts.title;
|
|
7693
7736
|
if (opts.description !== void 0) body.description = opts.description;
|
|
7694
7737
|
if (reviewers) body.reviewers = reviewers;
|
|
7738
|
+
if (opts.draft !== void 0) body.isDraft = opts.draft === "true";
|
|
7695
7739
|
const data = await gitClient.updatePR(collection, project, repo2, parseInt(opts.id, 10), body);
|
|
7696
7740
|
success(data, "ado", "repo-update-pr", start);
|
|
7697
7741
|
} catch (err) {
|
|
@@ -7877,7 +7921,7 @@ function registerAdoRepoCommands(ado) {
|
|
|
7877
7921
|
const config = lc({ configPath: globalOpts.config });
|
|
7878
7922
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
7879
7923
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
7880
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
7924
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-52YHD3ZL.js");
|
|
7881
7925
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
7882
7926
|
const coreClient = new AdoCoreClient2(http);
|
|
7883
7927
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -8278,18 +8322,18 @@ var ArtifactoryClient = class {
|
|
|
8278
8322
|
}
|
|
8279
8323
|
});
|
|
8280
8324
|
}
|
|
8281
|
-
async getStorageInfo(repo,
|
|
8282
|
-
const cleanPath =
|
|
8325
|
+
async getStorageInfo(repo, path9) {
|
|
8326
|
+
const cleanPath = path9.startsWith("/") ? path9.slice(1) : path9;
|
|
8283
8327
|
return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`);
|
|
8284
8328
|
}
|
|
8285
|
-
async getProperties(repo,
|
|
8286
|
-
const cleanPath =
|
|
8329
|
+
async getProperties(repo, path9) {
|
|
8330
|
+
const cleanPath = path9.startsWith("/") ? path9.slice(1) : path9;
|
|
8287
8331
|
return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
|
|
8288
8332
|
params: { properties: "" }
|
|
8289
8333
|
});
|
|
8290
8334
|
}
|
|
8291
|
-
async setProperties(repo,
|
|
8292
|
-
const cleanPath =
|
|
8335
|
+
async setProperties(repo, path9, properties, opts = {}) {
|
|
8336
|
+
const cleanPath = path9.startsWith("/") ? path9.slice(1) : path9;
|
|
8293
8337
|
const propString = Object.entries(properties).map(([k, v]) => `${k}=${v}`).join(";");
|
|
8294
8338
|
await this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
|
|
8295
8339
|
method: "PUT",
|
|
@@ -8371,27 +8415,27 @@ function registerArtifactoryCommands(program2) {
|
|
|
8371
8415
|
fail(err, "artifactory", "repos", start);
|
|
8372
8416
|
}
|
|
8373
8417
|
});
|
|
8374
|
-
art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo,
|
|
8418
|
+
art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo, path9) => {
|
|
8375
8419
|
const start = Date.now();
|
|
8376
8420
|
try {
|
|
8377
8421
|
const client = getClient8(program2);
|
|
8378
|
-
const data = await client.getStorageInfo(repo,
|
|
8422
|
+
const data = await client.getStorageInfo(repo, path9);
|
|
8379
8423
|
success(data, "artifactory", "artifact-info", start);
|
|
8380
8424
|
} catch (err) {
|
|
8381
8425
|
fail(err, "artifactory", "artifact-info", start);
|
|
8382
8426
|
}
|
|
8383
8427
|
});
|
|
8384
|
-
art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo,
|
|
8428
|
+
art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo, path9) => {
|
|
8385
8429
|
const start = Date.now();
|
|
8386
8430
|
try {
|
|
8387
8431
|
const client = getClient8(program2);
|
|
8388
|
-
const data = await client.getProperties(repo,
|
|
8432
|
+
const data = await client.getProperties(repo, path9);
|
|
8389
8433
|
success(data, "artifactory", "properties-get", start);
|
|
8390
8434
|
} catch (err) {
|
|
8391
8435
|
fail(err, "artifactory", "properties-get", start);
|
|
8392
8436
|
}
|
|
8393
8437
|
});
|
|
8394
|
-
art.command("properties-set <repo> <path> [keyvals...]").description("Set custom properties on an artifact (format: key=value key=value...)").option("--recursive", "Apply recursively to folder contents", false).action(async (repo,
|
|
8438
|
+
art.command("properties-set <repo> <path> [keyvals...]").description("Set custom properties on an artifact (format: key=value key=value...)").option("--recursive", "Apply recursively to folder contents", false).action(async (repo, path9, keyvals, opts) => {
|
|
8395
8439
|
const start = Date.now();
|
|
8396
8440
|
try {
|
|
8397
8441
|
if (!keyvals.length) throw new PncliError("Provide at least one key=value pair");
|
|
@@ -8402,8 +8446,8 @@ function registerArtifactoryCommands(program2) {
|
|
|
8402
8446
|
properties[kv.slice(0, eq)] = kv.slice(eq + 1);
|
|
8403
8447
|
}
|
|
8404
8448
|
const client = getClient8(program2);
|
|
8405
|
-
await client.setProperties(repo,
|
|
8406
|
-
success({ repo, path:
|
|
8449
|
+
await client.setProperties(repo, path9, properties, { recursive: opts.recursive });
|
|
8450
|
+
success({ repo, path: path9, properties }, "artifactory", "properties-set", start);
|
|
8407
8451
|
} catch (err) {
|
|
8408
8452
|
fail(err, "artifactory", "properties-set", start);
|
|
8409
8453
|
}
|
|
@@ -9010,8 +9054,8 @@ function registerSonatypeIqCommands(program2) {
|
|
|
9010
9054
|
const startTime = Date.now();
|
|
9011
9055
|
try {
|
|
9012
9056
|
const http = getHttpClient(program2);
|
|
9013
|
-
const
|
|
9014
|
-
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);
|
|
9015
9059
|
success(data, "sonatypeiq", "policies list", startTime);
|
|
9016
9060
|
} catch (err) {
|
|
9017
9061
|
fail(err, "sonatypeiq", "policies list", startTime);
|
|
@@ -9021,11 +9065,13 @@ function registerSonatypeIqCommands(program2) {
|
|
|
9021
9065
|
|
|
9022
9066
|
// src/services/skills/commands.ts
|
|
9023
9067
|
import fs4 from "fs";
|
|
9024
|
-
import
|
|
9068
|
+
import path8 from "path";
|
|
9025
9069
|
import os from "os";
|
|
9026
9070
|
import { fileURLToPath } from "url";
|
|
9027
9071
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
9028
9072
|
import select from "@inquirer/select";
|
|
9073
|
+
import checkbox, { Separator } from "@inquirer/checkbox";
|
|
9074
|
+
import input2 from "@inquirer/input";
|
|
9029
9075
|
var BACK = "__back__";
|
|
9030
9076
|
var ALL_MARKETPLACES = "__all_marketplaces__";
|
|
9031
9077
|
function injectTokenIntoUrl(url, token) {
|
|
@@ -9049,7 +9095,7 @@ function repoNameFromUrl(url) {
|
|
|
9049
9095
|
}
|
|
9050
9096
|
}
|
|
9051
9097
|
function defaultMarketplacePath(url) {
|
|
9052
|
-
return
|
|
9098
|
+
return path8.join(os.homedir(), ".agents", "marketplaces", repoNameFromUrl(url));
|
|
9053
9099
|
}
|
|
9054
9100
|
function marketplaceLabel(m) {
|
|
9055
9101
|
return m.name ?? repoNameFromUrl(m.repoUrl ?? "");
|
|
@@ -9063,8 +9109,8 @@ function assertInteractive(hint) {
|
|
|
9063
9109
|
}
|
|
9064
9110
|
}
|
|
9065
9111
|
var AGENT_PATHS = {
|
|
9066
|
-
"github-copilot": { project: ".agents/skills", user:
|
|
9067
|
-
"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") }
|
|
9068
9114
|
};
|
|
9069
9115
|
function resolveAgentName(opts) {
|
|
9070
9116
|
return opts.claude ? "claude-code" : opts.agent ?? "github-copilot";
|
|
@@ -9076,18 +9122,25 @@ function resolveAgentPaths(agentName) {
|
|
|
9076
9122
|
}
|
|
9077
9123
|
return agentConfig;
|
|
9078
9124
|
}
|
|
9125
|
+
function resolveTargetDir(opts) {
|
|
9126
|
+
if (opts.target) return path8.resolve(opts.target);
|
|
9127
|
+
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
9128
|
+
const scopePath = (opts.scope ?? "user") === "project" ? agentConfig.project : agentConfig.user;
|
|
9129
|
+
return path8.resolve(scopePath);
|
|
9130
|
+
}
|
|
9079
9131
|
function getBundledSkillsDir() {
|
|
9080
9132
|
try {
|
|
9081
9133
|
const thisFile = fileURLToPath(import.meta.url);
|
|
9082
|
-
return
|
|
9134
|
+
return path8.resolve(path8.dirname(thisFile), "..", "skills");
|
|
9083
9135
|
} catch {
|
|
9084
9136
|
return "";
|
|
9085
9137
|
}
|
|
9086
9138
|
}
|
|
9087
9139
|
function getInstalledMetaPath(targetDir) {
|
|
9088
|
-
return
|
|
9140
|
+
return path8.join(targetDir, ".pncli-installed.json");
|
|
9089
9141
|
}
|
|
9090
9142
|
var SKILL_ORIGIN_FILENAME = "pncli-origin.json";
|
|
9143
|
+
var DISABLED_SUBDIR = ".pncli-disabled";
|
|
9091
9144
|
function readInstalledMeta(targetDir) {
|
|
9092
9145
|
const metaPath = getInstalledMetaPath(targetDir);
|
|
9093
9146
|
const raw = loadJsonFile(metaPath);
|
|
@@ -9095,7 +9148,7 @@ function readInstalledMeta(targetDir) {
|
|
|
9095
9148
|
return { version: 1, skills: {} };
|
|
9096
9149
|
}
|
|
9097
9150
|
function getSkillOriginPath(skillDir) {
|
|
9098
|
-
return
|
|
9151
|
+
return path8.join(skillDir, SKILL_ORIGIN_FILENAME);
|
|
9099
9152
|
}
|
|
9100
9153
|
function readSkillOrigin(skillDir) {
|
|
9101
9154
|
const originPath = getSkillOriginPath(skillDir);
|
|
@@ -9107,6 +9160,132 @@ function readSkillOrigin(skillDir) {
|
|
|
9107
9160
|
}
|
|
9108
9161
|
return null;
|
|
9109
9162
|
}
|
|
9163
|
+
function matchesMarketplaceFilter(record, filter) {
|
|
9164
|
+
return !filter || record.marketplace === filter || record.installedFrom === filter;
|
|
9165
|
+
}
|
|
9166
|
+
function resolveSkillRecord(meta, targetDir, skillName) {
|
|
9167
|
+
const fromMeta = meta.skills[skillName];
|
|
9168
|
+
if (fromMeta) return fromMeta;
|
|
9169
|
+
const perSkill = readSkillOrigin(path8.join(targetDir, skillName));
|
|
9170
|
+
if (!perSkill) return null;
|
|
9171
|
+
return {
|
|
9172
|
+
source: perSkill.source,
|
|
9173
|
+
marketplace: perSkill.marketplace,
|
|
9174
|
+
plugin: perSkill.plugin,
|
|
9175
|
+
installedFrom: perSkill.installedFrom,
|
|
9176
|
+
branch: perSkill.branch,
|
|
9177
|
+
installedAt: perSkill.installedAt
|
|
9178
|
+
};
|
|
9179
|
+
}
|
|
9180
|
+
function listActiveSkillDirs(targetDir) {
|
|
9181
|
+
return fs4.readdirSync(targetDir).filter((name) => {
|
|
9182
|
+
if (name.startsWith(".")) return false;
|
|
9183
|
+
const p = path8.join(targetDir, name);
|
|
9184
|
+
try {
|
|
9185
|
+
return fs4.statSync(p).isDirectory();
|
|
9186
|
+
} catch {
|
|
9187
|
+
return false;
|
|
9188
|
+
}
|
|
9189
|
+
});
|
|
9190
|
+
}
|
|
9191
|
+
function disablePluginSkills(targetDir, plugin, marketplaceFilter) {
|
|
9192
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
9193
|
+
const stashDir = path8.join(targetDir, DISABLED_SUBDIR);
|
|
9194
|
+
const meta = readInstalledMeta(targetDir);
|
|
9195
|
+
const disabled = [];
|
|
9196
|
+
const alreadyDisabled = [];
|
|
9197
|
+
const skipped = [];
|
|
9198
|
+
for (const skillName of listActiveSkillDirs(targetDir)) {
|
|
9199
|
+
const skillDir = path8.resolve(targetDir, skillName);
|
|
9200
|
+
if (!skillDir.startsWith(resolvedTarget + path8.sep)) continue;
|
|
9201
|
+
const record = resolveSkillRecord(meta, targetDir, skillName);
|
|
9202
|
+
if (!record || record.source !== "marketplace") {
|
|
9203
|
+
skipped.push(skillName);
|
|
9204
|
+
continue;
|
|
9205
|
+
}
|
|
9206
|
+
if (!matchesMarketplaceFilter(record, marketplaceFilter) || record.plugin !== plugin) {
|
|
9207
|
+
skipped.push(skillName);
|
|
9208
|
+
continue;
|
|
9209
|
+
}
|
|
9210
|
+
const stashDest = path8.join(stashDir, skillName);
|
|
9211
|
+
fs4.mkdirSync(stashDir, { recursive: true });
|
|
9212
|
+
if (fs4.existsSync(stashDest)) fs4.rmSync(stashDest, { recursive: true, force: true });
|
|
9213
|
+
fs4.renameSync(skillDir, stashDest);
|
|
9214
|
+
delete meta.skills[skillName];
|
|
9215
|
+
meta.disabled = meta.disabled ?? {};
|
|
9216
|
+
meta.disabled[skillName] = record;
|
|
9217
|
+
disabled.push(skillName);
|
|
9218
|
+
}
|
|
9219
|
+
for (const [skillName, record] of Object.entries(meta.disabled ?? {})) {
|
|
9220
|
+
if (record.plugin === plugin && matchesMarketplaceFilter(record, marketplaceFilter) && !disabled.includes(skillName)) {
|
|
9221
|
+
alreadyDisabled.push(skillName);
|
|
9222
|
+
}
|
|
9223
|
+
}
|
|
9224
|
+
if (disabled.length > 0) {
|
|
9225
|
+
fs4.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
9226
|
+
}
|
|
9227
|
+
return { disabled, alreadyDisabled, skipped };
|
|
9228
|
+
}
|
|
9229
|
+
function enablePluginSkills(targetDir, plugin, marketplaceFilter) {
|
|
9230
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
9231
|
+
const stashDir = path8.join(targetDir, DISABLED_SUBDIR);
|
|
9232
|
+
const meta = readInstalledMeta(targetDir);
|
|
9233
|
+
const enabled = [];
|
|
9234
|
+
const stashMissing = [];
|
|
9235
|
+
const skipped = [];
|
|
9236
|
+
const disabledEntries = Object.entries(meta.disabled ?? {});
|
|
9237
|
+
for (const [skillName, record] of disabledEntries) {
|
|
9238
|
+
if (record.plugin !== plugin || !matchesMarketplaceFilter(record, marketplaceFilter)) {
|
|
9239
|
+
skipped.push(skillName);
|
|
9240
|
+
continue;
|
|
9241
|
+
}
|
|
9242
|
+
const dest = path8.resolve(targetDir, skillName);
|
|
9243
|
+
if (!dest.startsWith(resolvedTarget + path8.sep)) {
|
|
9244
|
+
skipped.push(skillName);
|
|
9245
|
+
continue;
|
|
9246
|
+
}
|
|
9247
|
+
const stashSrc = path8.join(stashDir, skillName);
|
|
9248
|
+
if (!fs4.existsSync(stashSrc)) {
|
|
9249
|
+
stashMissing.push(skillName);
|
|
9250
|
+
continue;
|
|
9251
|
+
}
|
|
9252
|
+
if (fs4.existsSync(dest)) fs4.rmSync(dest, { recursive: true, force: true });
|
|
9253
|
+
fs4.renameSync(stashSrc, dest);
|
|
9254
|
+
meta.skills[skillName] = record;
|
|
9255
|
+
delete meta.disabled[skillName];
|
|
9256
|
+
enabled.push(skillName);
|
|
9257
|
+
}
|
|
9258
|
+
if (fs4.existsSync(stashDir) && fs4.readdirSync(stashDir).length === 0) {
|
|
9259
|
+
fs4.rmdirSync(stashDir);
|
|
9260
|
+
}
|
|
9261
|
+
if (enabled.length > 0) {
|
|
9262
|
+
fs4.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
9263
|
+
}
|
|
9264
|
+
return { enabled, stashMissing, skipped, hadDisabled: disabledEntries.length > 0 };
|
|
9265
|
+
}
|
|
9266
|
+
function listPluginStates(targetDir) {
|
|
9267
|
+
const meta = readInstalledMeta(targetDir);
|
|
9268
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
9269
|
+
const bucketFor = (record) => {
|
|
9270
|
+
if (record.source !== "marketplace" || !record.plugin) return null;
|
|
9271
|
+
const marketplaceName = record.marketplace ?? record.installedFrom;
|
|
9272
|
+
const key = `${marketplaceName ?? ""}\0${record.plugin}`;
|
|
9273
|
+
let state = byKey.get(key);
|
|
9274
|
+
if (!state) {
|
|
9275
|
+
state = { plugin: record.plugin, marketplace: marketplaceName, activeSkills: [], disabledSkills: [] };
|
|
9276
|
+
byKey.set(key, state);
|
|
9277
|
+
}
|
|
9278
|
+
return state;
|
|
9279
|
+
};
|
|
9280
|
+
for (const skillName of listActiveSkillDirs(targetDir)) {
|
|
9281
|
+
const record = resolveSkillRecord(meta, targetDir, skillName);
|
|
9282
|
+
if (record) bucketFor(record)?.activeSkills.push(skillName);
|
|
9283
|
+
}
|
|
9284
|
+
for (const [skillName, record] of Object.entries(meta.disabled ?? {})) {
|
|
9285
|
+
bucketFor(record)?.disabledSkills.push(skillName);
|
|
9286
|
+
}
|
|
9287
|
+
return [...byKey.values()].sort((a, b) => a.plugin.localeCompare(b.plugin));
|
|
9288
|
+
}
|
|
9110
9289
|
function detectRepoBranch(repoPath) {
|
|
9111
9290
|
try {
|
|
9112
9291
|
const result = execFileSync3("git", ["-C", repoPath, "rev-parse", "--abbrev-ref", "HEAD"], {
|
|
@@ -9125,7 +9304,7 @@ function recordInstalledSkills(targetDir, skillNames, record) {
|
|
|
9125
9304
|
for (const skillName of skillNames) {
|
|
9126
9305
|
const fullRecord = { ...record, installedAt: now };
|
|
9127
9306
|
meta.skills[skillName] = fullRecord;
|
|
9128
|
-
const skillDir =
|
|
9307
|
+
const skillDir = path8.join(targetDir, skillName);
|
|
9129
9308
|
if (fs4.existsSync(skillDir)) {
|
|
9130
9309
|
const origin = { version: 1, ...fullRecord };
|
|
9131
9310
|
try {
|
|
@@ -9195,7 +9374,7 @@ function upsertMarketplace(all, entry) {
|
|
|
9195
9374
|
}
|
|
9196
9375
|
}
|
|
9197
9376
|
function cloneOrReuseMarketplace(url, resolvedPath, opts) {
|
|
9198
|
-
const hasGit = fs4.existsSync(
|
|
9377
|
+
const hasGit = fs4.existsSync(path8.join(resolvedPath, ".git"));
|
|
9199
9378
|
if (fs4.existsSync(resolvedPath) && !hasGit && fs4.readdirSync(resolvedPath).length > 0) {
|
|
9200
9379
|
throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
|
|
9201
9380
|
}
|
|
@@ -9263,7 +9442,7 @@ function installPluginsForMarketplace(marketplacePath, marketplaceName, repoUrl,
|
|
|
9263
9442
|
results[pluginName] = { installed, failed };
|
|
9264
9443
|
totalInstalled += installed.length;
|
|
9265
9444
|
for (const skill of installed) {
|
|
9266
|
-
warn(` ${skill}: ${
|
|
9445
|
+
warn(` ${skill}: ${path8.join(skillsSrc, skill)} \u2192 ${path8.join(targetDir, skill)}`);
|
|
9267
9446
|
}
|
|
9268
9447
|
if (failed.length > 0) {
|
|
9269
9448
|
warn(`Skipped ${failed.length} skill(s) with invalid names in "${pluginName}": ${failed.join(", ")}`);
|
|
@@ -9315,34 +9494,55 @@ function syncMarketplacePlugins(m, targetDir, force, pluginFilter) {
|
|
|
9315
9494
|
return { marketplace: marketplaceName, skipped: true, error: message };
|
|
9316
9495
|
}
|
|
9317
9496
|
}
|
|
9497
|
+
function performMarketplaceAdd(url, localPath, opts) {
|
|
9498
|
+
const resolvedPath = path8.resolve(localPath ?? defaultMarketplacePath(url));
|
|
9499
|
+
const marketplaceName = opts.name ?? repoNameFromUrl(url);
|
|
9500
|
+
cloneOrReuseMarketplace(url, resolvedPath, opts);
|
|
9501
|
+
const configPath = getGlobalConfigPath();
|
|
9502
|
+
const { existing, all } = loadMarketplacesConfig(configPath);
|
|
9503
|
+
const entry = {
|
|
9504
|
+
name: marketplaceName,
|
|
9505
|
+
repoUrl: url,
|
|
9506
|
+
localPath: resolvedPath,
|
|
9507
|
+
...opts.token ? { token: opts.token } : {}
|
|
9508
|
+
};
|
|
9509
|
+
upsertMarketplace(all, entry);
|
|
9510
|
+
saveMarketplaces(configPath, existing, all);
|
|
9511
|
+
const targetDir = resolveAgentPaths(resolveAgentName(opts)).user;
|
|
9512
|
+
const { pluginResults, totalInstalled } = installAllPlugins(resolvedPath, marketplaceName, url, targetDir);
|
|
9513
|
+
return {
|
|
9514
|
+
name: marketplaceName,
|
|
9515
|
+
repoUrl: url,
|
|
9516
|
+
localPath: resolvedPath,
|
|
9517
|
+
branch: opts.branch ?? null,
|
|
9518
|
+
tokenConfigured: !!opts.token,
|
|
9519
|
+
plugins: pluginResults,
|
|
9520
|
+
total: totalInstalled,
|
|
9521
|
+
target: targetDir
|
|
9522
|
+
};
|
|
9523
|
+
}
|
|
9524
|
+
function performMarketplaceRemove(name) {
|
|
9525
|
+
const configPath = getGlobalConfigPath();
|
|
9526
|
+
const { existing, all } = loadMarketplacesConfig(configPath);
|
|
9527
|
+
const idx = all.findIndex((m) => m.name === name || m.repoUrl === name);
|
|
9528
|
+
if (idx === -1) {
|
|
9529
|
+
throw new Error(`Marketplace "${name}" not found. Run: pncli skills marketplace list`);
|
|
9530
|
+
}
|
|
9531
|
+
const removed = all.splice(idx, 1)[0];
|
|
9532
|
+
saveMarketplaces(configPath, existing, all);
|
|
9533
|
+
return {
|
|
9534
|
+
removed: {
|
|
9535
|
+
name: marketplaceLabel(removed),
|
|
9536
|
+
repoUrl: removed.repoUrl,
|
|
9537
|
+
localPath: removed.localPath
|
|
9538
|
+
},
|
|
9539
|
+
remaining: all.length
|
|
9540
|
+
};
|
|
9541
|
+
}
|
|
9318
9542
|
async function marketplaceAddAction(url, localPath, opts, commandName) {
|
|
9319
9543
|
const start = Date.now();
|
|
9320
9544
|
try {
|
|
9321
|
-
|
|
9322
|
-
const marketplaceName = opts.name ?? repoNameFromUrl(url);
|
|
9323
|
-
cloneOrReuseMarketplace(url, resolvedPath, opts);
|
|
9324
|
-
const configPath = getGlobalConfigPath();
|
|
9325
|
-
const { existing, all } = loadMarketplacesConfig(configPath);
|
|
9326
|
-
const entry = {
|
|
9327
|
-
name: marketplaceName,
|
|
9328
|
-
repoUrl: url,
|
|
9329
|
-
localPath: resolvedPath,
|
|
9330
|
-
...opts.token ? { token: opts.token } : {}
|
|
9331
|
-
};
|
|
9332
|
-
upsertMarketplace(all, entry);
|
|
9333
|
-
saveMarketplaces(configPath, existing, all);
|
|
9334
|
-
const targetDir = resolveAgentPaths(resolveAgentName(opts)).user;
|
|
9335
|
-
const { pluginResults, totalInstalled } = installAllPlugins(resolvedPath, marketplaceName, url, targetDir);
|
|
9336
|
-
success({
|
|
9337
|
-
name: marketplaceName,
|
|
9338
|
-
repoUrl: url,
|
|
9339
|
-
localPath: resolvedPath,
|
|
9340
|
-
branch: opts.branch ?? null,
|
|
9341
|
-
tokenConfigured: !!opts.token,
|
|
9342
|
-
plugins: pluginResults,
|
|
9343
|
-
total: totalInstalled,
|
|
9344
|
-
target: targetDir
|
|
9345
|
-
}, "skills", commandName, start);
|
|
9545
|
+
success(performMarketplaceAdd(url, localPath, opts), "skills", commandName, start);
|
|
9346
9546
|
} catch (err) {
|
|
9347
9547
|
fail(err, "skills", commandName, start);
|
|
9348
9548
|
}
|
|
@@ -9354,19 +9554,19 @@ function registerSkillsCommands(program2) {
|
|
|
9354
9554
|
try {
|
|
9355
9555
|
let targetDir;
|
|
9356
9556
|
if (opts.target) {
|
|
9357
|
-
targetDir =
|
|
9557
|
+
targetDir = path8.resolve(opts.target);
|
|
9358
9558
|
} else {
|
|
9359
9559
|
const agentConfig = resolveAgentPaths(opts.agent);
|
|
9360
9560
|
const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
|
|
9361
|
-
targetDir =
|
|
9561
|
+
targetDir = path8.resolve(scopePath);
|
|
9362
9562
|
}
|
|
9363
|
-
const resolvedTarget =
|
|
9563
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
9364
9564
|
const bundledDir = getBundledSkillsDir();
|
|
9365
9565
|
let skillDirs = [];
|
|
9366
9566
|
if (bundledDir !== "") {
|
|
9367
9567
|
try {
|
|
9368
9568
|
skillDirs = fs4.readdirSync(bundledDir).filter(
|
|
9369
|
-
(name) => fs4.existsSync(
|
|
9569
|
+
(name) => fs4.existsSync(path8.join(bundledDir, name, "SKILL.md"))
|
|
9370
9570
|
);
|
|
9371
9571
|
} catch {
|
|
9372
9572
|
}
|
|
@@ -9377,25 +9577,25 @@ function registerSkillsCommands(program2) {
|
|
|
9377
9577
|
const installed = [];
|
|
9378
9578
|
const failed = [];
|
|
9379
9579
|
for (const skillName of skillDirs) {
|
|
9380
|
-
const existingDir =
|
|
9381
|
-
if (!existingDir.startsWith(resolvedTarget +
|
|
9580
|
+
const existingDir = path8.resolve(targetDir, skillName);
|
|
9581
|
+
if (!existingDir.startsWith(resolvedTarget + path8.sep)) continue;
|
|
9382
9582
|
if (fs4.existsSync(existingDir)) {
|
|
9383
9583
|
fs4.rmSync(existingDir, { recursive: true, force: true });
|
|
9384
9584
|
}
|
|
9385
9585
|
}
|
|
9386
9586
|
warn(`Installing ${skillDirs.length} bundled skill(s) to ${targetDir}...`);
|
|
9387
9587
|
for (const skillName of skillDirs) {
|
|
9388
|
-
const skillDir =
|
|
9389
|
-
if (!skillDir.startsWith(resolvedTarget +
|
|
9588
|
+
const skillDir = path8.resolve(targetDir, skillName);
|
|
9589
|
+
if (!skillDir.startsWith(resolvedTarget + path8.sep)) {
|
|
9390
9590
|
failed.push(skillName);
|
|
9391
9591
|
continue;
|
|
9392
9592
|
}
|
|
9393
9593
|
try {
|
|
9394
|
-
const mdFiles = fs4.readdirSync(
|
|
9594
|
+
const mdFiles = fs4.readdirSync(path8.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
|
|
9395
9595
|
fs4.mkdirSync(skillDir, { recursive: true });
|
|
9396
9596
|
for (const mdFile of mdFiles) {
|
|
9397
|
-
const content = fs4.readFileSync(
|
|
9398
|
-
fs4.writeFileSync(
|
|
9597
|
+
const content = fs4.readFileSync(path8.join(bundledDir, skillName, mdFile), "utf8");
|
|
9598
|
+
fs4.writeFileSync(path8.join(skillDir, mdFile), content, "utf8");
|
|
9399
9599
|
}
|
|
9400
9600
|
installed.push(skillName);
|
|
9401
9601
|
} catch {
|
|
@@ -9425,11 +9625,11 @@ function registerSkillsCommands(program2) {
|
|
|
9425
9625
|
try {
|
|
9426
9626
|
let targetDir;
|
|
9427
9627
|
if (opts.target) {
|
|
9428
|
-
targetDir =
|
|
9628
|
+
targetDir = path8.resolve(opts.target);
|
|
9429
9629
|
} else {
|
|
9430
9630
|
const agentConfig = resolveAgentPaths(opts.agent);
|
|
9431
9631
|
const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
|
|
9432
|
-
targetDir =
|
|
9632
|
+
targetDir = path8.resolve(scopePath);
|
|
9433
9633
|
}
|
|
9434
9634
|
if (!fs4.existsSync(targetDir)) {
|
|
9435
9635
|
success({ skills: [], message: `No skills directory found at ${targetDir}. Run: pncli skills install` }, "skills", "list", start);
|
|
@@ -9438,11 +9638,11 @@ function registerSkillsCommands(program2) {
|
|
|
9438
9638
|
const meta = readInstalledMeta(targetDir);
|
|
9439
9639
|
const skillDirs = fs4.readdirSync(targetDir).filter((name) => {
|
|
9440
9640
|
if (name.startsWith(".")) return false;
|
|
9441
|
-
const skillPath =
|
|
9641
|
+
const skillPath = path8.join(targetDir, name, "SKILL.md");
|
|
9442
9642
|
return fs4.existsSync(skillPath);
|
|
9443
9643
|
});
|
|
9444
9644
|
const skillsList = skillDirs.map((name) => {
|
|
9445
|
-
const content = fs4.readFileSync(
|
|
9645
|
+
const content = fs4.readFileSync(path8.join(targetDir, name, "SKILL.md"), "utf8");
|
|
9446
9646
|
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
9447
9647
|
const data = {};
|
|
9448
9648
|
const metadata = {};
|
|
@@ -9486,15 +9686,15 @@ function registerSkillsCommands(program2) {
|
|
|
9486
9686
|
try {
|
|
9487
9687
|
let targetDir;
|
|
9488
9688
|
if (opts.target) {
|
|
9489
|
-
targetDir =
|
|
9689
|
+
targetDir = path8.resolve(opts.target);
|
|
9490
9690
|
} else {
|
|
9491
9691
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
9492
9692
|
const scopePath = opts.scope === "project" ? agentConfig.project : agentConfig.user;
|
|
9493
|
-
targetDir =
|
|
9693
|
+
targetDir = path8.resolve(scopePath);
|
|
9494
9694
|
}
|
|
9495
|
-
const resolvedTarget =
|
|
9496
|
-
const skillDir =
|
|
9497
|
-
if (!skillDir.startsWith(resolvedTarget +
|
|
9695
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
9696
|
+
const skillDir = path8.resolve(targetDir, name);
|
|
9697
|
+
if (!skillDir.startsWith(resolvedTarget + path8.sep)) {
|
|
9498
9698
|
throw new Error(`Invalid skill name: "${name}"`);
|
|
9499
9699
|
}
|
|
9500
9700
|
if (!fs4.existsSync(skillDir)) {
|
|
@@ -9568,27 +9768,12 @@ function registerSkillsCommands(program2) {
|
|
|
9568
9768
|
marketplace.command("remove").description("Remove a registered marketplace from the config (does not delete the local clone)").argument("<name>", "Name of the marketplace to remove").action((name) => {
|
|
9569
9769
|
const start = Date.now();
|
|
9570
9770
|
try {
|
|
9571
|
-
|
|
9572
|
-
const { existing, all } = loadMarketplacesConfig(configPath);
|
|
9573
|
-
const idx = all.findIndex((m) => m.name === name || m.repoUrl === name);
|
|
9574
|
-
if (idx === -1) {
|
|
9575
|
-
throw new Error(`Marketplace "${name}" not found. Run: pncli skills marketplace list`);
|
|
9576
|
-
}
|
|
9577
|
-
const removed = all.splice(idx, 1)[0];
|
|
9578
|
-
saveMarketplaces(configPath, existing, all);
|
|
9579
|
-
success({
|
|
9580
|
-
removed: {
|
|
9581
|
-
name: marketplaceLabel(removed),
|
|
9582
|
-
repoUrl: removed.repoUrl,
|
|
9583
|
-
localPath: removed.localPath
|
|
9584
|
-
},
|
|
9585
|
-
remaining: all.length
|
|
9586
|
-
}, "skills", "marketplace-remove", start);
|
|
9771
|
+
success(performMarketplaceRemove(name), "skills", "marketplace-remove", start);
|
|
9587
9772
|
} catch (err) {
|
|
9588
9773
|
fail(err, "skills", "marketplace-remove", start);
|
|
9589
9774
|
}
|
|
9590
9775
|
});
|
|
9591
|
-
marketplace.command("sync").description("Pull latest marketplace content and install plugin skills").argument("[plugin]", 'Plugin name to install, or "all" to install every plugin (skips interactive selection)').option("--marketplace <name>", 'Marketplace name to sync, or "all" to sync every registered marketplace (skips interactive selection)').option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--force", 'Reinstall even if a marketplace has no new changes (applies to single-plugin and "all" installs alike)').action(async (plugin, opts) => {
|
|
9776
|
+
marketplace.command("sync").description("Pull latest marketplace content and install plugin skills (interactive picker when no plugin is given)").argument("[plugin]", 'Plugin name to install, or "all" to install every plugin (skips interactive selection)').option("--marketplace <name>", 'Marketplace name to sync, or "all" to sync every registered marketplace (skips interactive selection)').option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--force", 'Reinstall even if a marketplace has no new changes (applies to single-plugin and "all" installs alike)').action(async (plugin, opts) => {
|
|
9592
9777
|
const start = Date.now();
|
|
9593
9778
|
try {
|
|
9594
9779
|
const configPath = getGlobalConfigPath();
|
|
@@ -9701,13 +9886,13 @@ function registerSkillsCommands(program2) {
|
|
|
9701
9886
|
try {
|
|
9702
9887
|
let targetDir;
|
|
9703
9888
|
if (opts.target) {
|
|
9704
|
-
targetDir =
|
|
9889
|
+
targetDir = path8.resolve(opts.target);
|
|
9705
9890
|
} else {
|
|
9706
9891
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
9707
9892
|
const scopePath = (opts.scope ?? "user") === "project" ? agentConfig.project : agentConfig.user;
|
|
9708
|
-
targetDir =
|
|
9893
|
+
targetDir = path8.resolve(scopePath);
|
|
9709
9894
|
}
|
|
9710
|
-
const resolvedTarget =
|
|
9895
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
9711
9896
|
if (!fs4.existsSync(targetDir)) {
|
|
9712
9897
|
success({ removed: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-purge-plugin", start);
|
|
9713
9898
|
return;
|
|
@@ -9717,7 +9902,7 @@ function registerSkillsCommands(program2) {
|
|
|
9717
9902
|
const skipped = [];
|
|
9718
9903
|
const skillDirs = fs4.readdirSync(targetDir).filter((name) => {
|
|
9719
9904
|
if (name.startsWith(".")) return false;
|
|
9720
|
-
const p =
|
|
9905
|
+
const p = path8.join(targetDir, name);
|
|
9721
9906
|
try {
|
|
9722
9907
|
return fs4.statSync(p).isDirectory();
|
|
9723
9908
|
} catch {
|
|
@@ -9725,8 +9910,8 @@ function registerSkillsCommands(program2) {
|
|
|
9725
9910
|
}
|
|
9726
9911
|
});
|
|
9727
9912
|
for (const skillName of skillDirs) {
|
|
9728
|
-
const skillDir =
|
|
9729
|
-
if (!skillDir.startsWith(resolvedTarget +
|
|
9913
|
+
const skillDir = path8.resolve(targetDir, skillName);
|
|
9914
|
+
if (!skillDir.startsWith(resolvedTarget + path8.sep)) continue;
|
|
9730
9915
|
let record = meta.skills[skillName] ?? null;
|
|
9731
9916
|
if (!record) {
|
|
9732
9917
|
const perSkill = readSkillOrigin(skillDir);
|
|
@@ -9770,6 +9955,161 @@ function registerSkillsCommands(program2) {
|
|
|
9770
9955
|
fail(err, "skills", "marketplace-purge-plugin", start);
|
|
9771
9956
|
}
|
|
9772
9957
|
});
|
|
9958
|
+
marketplace.command("disable").description("Temporarily deactivate a plugin's skills without deleting them (re-enable with `marketplace enable`)").argument("<plugin>", "Plugin name to disable").option("--marketplace <name>", "Restrict to skills from this marketplace (by name or repo URL)").option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--scope <scope>", "Installation scope: project | user (default: user)").option("--target <dir>", "Override skills directory").action((plugin, opts) => {
|
|
9959
|
+
const start = Date.now();
|
|
9960
|
+
try {
|
|
9961
|
+
const targetDir = resolveTargetDir(opts);
|
|
9962
|
+
if (!fs4.existsSync(targetDir)) {
|
|
9963
|
+
success({ disabled: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-disable", start);
|
|
9964
|
+
return;
|
|
9965
|
+
}
|
|
9966
|
+
const result = disablePluginSkills(targetDir, plugin, opts.marketplace);
|
|
9967
|
+
success({
|
|
9968
|
+
disabled: result.disabled,
|
|
9969
|
+
alreadyDisabled: result.alreadyDisabled,
|
|
9970
|
+
skipped: result.skipped.length,
|
|
9971
|
+
total: result.disabled.length,
|
|
9972
|
+
target: targetDir,
|
|
9973
|
+
plugin,
|
|
9974
|
+
stash: path8.join(targetDir, DISABLED_SUBDIR),
|
|
9975
|
+
...opts.marketplace ? { marketplace: opts.marketplace } : {}
|
|
9976
|
+
}, "skills", "marketplace-disable", start);
|
|
9977
|
+
} catch (err) {
|
|
9978
|
+
fail(err, "skills", "marketplace-disable", start);
|
|
9979
|
+
}
|
|
9980
|
+
});
|
|
9981
|
+
marketplace.command("enable").description("Re-activate a previously disabled plugin's skills").argument("<plugin>", "Plugin name to enable").option("--marketplace <name>", "Restrict to skills from this marketplace (by name or repo URL)").option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--scope <scope>", "Installation scope: project | user (default: user)").option("--target <dir>", "Override skills directory").action((plugin, opts) => {
|
|
9982
|
+
const start = Date.now();
|
|
9983
|
+
try {
|
|
9984
|
+
const targetDir = resolveTargetDir(opts);
|
|
9985
|
+
if (!fs4.existsSync(targetDir)) {
|
|
9986
|
+
success({ enabled: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-enable", start);
|
|
9987
|
+
return;
|
|
9988
|
+
}
|
|
9989
|
+
const result = enablePluginSkills(targetDir, plugin, opts.marketplace);
|
|
9990
|
+
if (!result.hadDisabled) {
|
|
9991
|
+
success({ enabled: [], total: 0, target: targetDir, plugin, message: `No disabled skills found for plugin "${plugin}".` }, "skills", "marketplace-enable", start);
|
|
9992
|
+
return;
|
|
9993
|
+
}
|
|
9994
|
+
success({
|
|
9995
|
+
enabled: result.enabled,
|
|
9996
|
+
skipped: result.skipped.length,
|
|
9997
|
+
total: result.enabled.length,
|
|
9998
|
+
target: targetDir,
|
|
9999
|
+
plugin,
|
|
10000
|
+
...result.stashMissing.length > 0 ? {
|
|
10001
|
+
stashMissing: result.stashMissing,
|
|
10002
|
+
warning: `Skills marked disabled but missing from the stash were left as-is. Re-install the plugin (pncli skills marketplace sync ${plugin}) to restore them.`
|
|
10003
|
+
} : {},
|
|
10004
|
+
...opts.marketplace ? { marketplace: opts.marketplace } : {}
|
|
10005
|
+
}, "skills", "marketplace-enable", start);
|
|
10006
|
+
} catch (err) {
|
|
10007
|
+
fail(err, "skills", "marketplace-enable", start);
|
|
10008
|
+
}
|
|
10009
|
+
});
|
|
10010
|
+
marketplace.command("manage").description("Manage marketplaces and plugins: toggle plugins on/off, add or remove marketplaces (interactive)").option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--scope <scope>", "Installation scope: project | user (default: user)").option("--target <dir>", "Override skills directory").action(async (opts) => {
|
|
10011
|
+
const start = Date.now();
|
|
10012
|
+
try {
|
|
10013
|
+
assertInteractive("Use `pncli skills marketplace enable|disable <plugin>`, `add <url>`, and `remove <name>` to manage non-interactively.");
|
|
10014
|
+
const targetDir = resolveTargetDir(opts);
|
|
10015
|
+
const enabledPlugins = [];
|
|
10016
|
+
const disabledPlugins = [];
|
|
10017
|
+
const stashMissing = [];
|
|
10018
|
+
const addedMarketplaces = [];
|
|
10019
|
+
const removedMarketplaces = [];
|
|
10020
|
+
for (; ; ) {
|
|
10021
|
+
const states = fs4.existsSync(targetDir) ? listPluginStates(targetDir) : [];
|
|
10022
|
+
const registered = loadMarketplaces(getGlobalConfigPath());
|
|
10023
|
+
const action = await select({
|
|
10024
|
+
message: `Manage skills marketplaces (target: ${targetDir}):`,
|
|
10025
|
+
choices: [
|
|
10026
|
+
...states.length > 0 ? [{ value: "toggle", name: `Toggle plugins on/off (${states.length} installed)` }] : [],
|
|
10027
|
+
{ value: "add", name: "Add a marketplace" },
|
|
10028
|
+
...registered.length > 0 ? [{ value: "remove", name: `Remove a marketplace (${registered.length} registered)` }] : [],
|
|
10029
|
+
{ value: "done", name: "Done" }
|
|
10030
|
+
]
|
|
10031
|
+
});
|
|
10032
|
+
if (action === "done") break;
|
|
10033
|
+
if (action === "toggle") {
|
|
10034
|
+
const sorted = [...states].sort((a, b) => (a.marketplace ?? "").localeCompare(b.marketplace ?? "") || a.plugin.localeCompare(b.plugin));
|
|
10035
|
+
const choices = [];
|
|
10036
|
+
let lastMarketplace = null;
|
|
10037
|
+
sorted.forEach((state, index) => {
|
|
10038
|
+
const marketplaceName = state.marketplace ?? "(unknown marketplace)";
|
|
10039
|
+
if (marketplaceName !== lastMarketplace) {
|
|
10040
|
+
choices.push(new Separator(`\u2500\u2500 ${marketplaceName} \u2500\u2500`));
|
|
10041
|
+
lastMarketplace = marketplaceName;
|
|
10042
|
+
}
|
|
10043
|
+
const active = state.activeSkills.length;
|
|
10044
|
+
const stashed = state.disabledSkills.length;
|
|
10045
|
+
const skillCount = stashed === 0 ? `${active} skill${active === 1 ? "" : "s"}` : active === 0 ? `${stashed} skill${stashed === 1 ? "" : "s"}, disabled` : `${active} active, ${stashed} disabled`;
|
|
10046
|
+
choices.push({ name: `${state.plugin} (${skillCount})`, value: index, checked: active > 0 });
|
|
10047
|
+
});
|
|
10048
|
+
const checkedIndexes = await checkbox({
|
|
10049
|
+
message: "Toggle plugins (space toggles, enter applies):",
|
|
10050
|
+
choices,
|
|
10051
|
+
pageSize: 15
|
|
10052
|
+
});
|
|
10053
|
+
const wanted = new Set(checkedIndexes);
|
|
10054
|
+
sorted.forEach((state, index) => {
|
|
10055
|
+
if (wanted.has(index)) {
|
|
10056
|
+
if (state.disabledSkills.length === 0) return;
|
|
10057
|
+
const result = enablePluginSkills(targetDir, state.plugin, state.marketplace);
|
|
10058
|
+
stashMissing.push(...result.stashMissing);
|
|
10059
|
+
if (result.enabled.length > 0) {
|
|
10060
|
+
enabledPlugins.push({ plugin: state.plugin, marketplace: state.marketplace, skills: result.enabled });
|
|
10061
|
+
}
|
|
10062
|
+
} else {
|
|
10063
|
+
if (state.activeSkills.length === 0) return;
|
|
10064
|
+
const result = disablePluginSkills(targetDir, state.plugin, state.marketplace);
|
|
10065
|
+
if (result.disabled.length > 0) {
|
|
10066
|
+
disabledPlugins.push({ plugin: state.plugin, marketplace: state.marketplace, skills: result.disabled });
|
|
10067
|
+
}
|
|
10068
|
+
}
|
|
10069
|
+
});
|
|
10070
|
+
} else if (action === "add") {
|
|
10071
|
+
const url = (await input2({
|
|
10072
|
+
message: "Git clone URL of the marketplace repository:",
|
|
10073
|
+
validate: (v) => v.trim().length > 0 || "URL is required"
|
|
10074
|
+
})).trim();
|
|
10075
|
+
const name = (await input2({
|
|
10076
|
+
message: "Marketplace name:",
|
|
10077
|
+
default: repoNameFromUrl(url)
|
|
10078
|
+
})).trim();
|
|
10079
|
+
addedMarketplaces.push(performMarketplaceAdd(url, void 0, {
|
|
10080
|
+
name: name || void 0,
|
|
10081
|
+
agent: opts.agent,
|
|
10082
|
+
claude: opts.claude
|
|
10083
|
+
}));
|
|
10084
|
+
} else if (action === "remove") {
|
|
10085
|
+
const chosen = await select({
|
|
10086
|
+
message: "Remove which marketplace? (the local clone is kept on disk)",
|
|
10087
|
+
choices: [
|
|
10088
|
+
// Index-based values: labels alone aren't guaranteed unique (two marketplaces can share a derived name).
|
|
10089
|
+
...registered.map((m, i) => ({ value: String(i), name: `${marketplaceLabel(m)} \u2014 ${m.repoUrl ?? ""}` })),
|
|
10090
|
+
{ value: BACK, name: "\u2190 Back" }
|
|
10091
|
+
]
|
|
10092
|
+
});
|
|
10093
|
+
if (chosen === BACK) continue;
|
|
10094
|
+
const target = registered[Number(chosen)];
|
|
10095
|
+
removedMarketplaces.push(performMarketplaceRemove(target.repoUrl ?? marketplaceLabel(target)));
|
|
10096
|
+
}
|
|
10097
|
+
}
|
|
10098
|
+
success({
|
|
10099
|
+
enabled: enabledPlugins,
|
|
10100
|
+
disabled: disabledPlugins,
|
|
10101
|
+
addedMarketplaces,
|
|
10102
|
+
removedMarketplaces,
|
|
10103
|
+
target: targetDir,
|
|
10104
|
+
...stashMissing.length > 0 ? {
|
|
10105
|
+
stashMissing,
|
|
10106
|
+
warning: "Skills marked disabled but missing from the stash were left as-is. Re-install their plugin (pncli skills marketplace sync <plugin>) to restore them."
|
|
10107
|
+
} : {}
|
|
10108
|
+
}, "skills", "marketplace-manage", start);
|
|
10109
|
+
} catch (err) {
|
|
10110
|
+
fail(err, "skills", "marketplace-manage", start);
|
|
10111
|
+
}
|
|
10112
|
+
});
|
|
9773
10113
|
skills.command("purge-user").description("Remove all skills from the user-level skills folder for the target agent").option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--force", "Skip confirmation \u2014 remove all skills without prompting").action((opts) => {
|
|
9774
10114
|
const start = Date.now();
|
|
9775
10115
|
try {
|
|
@@ -9780,7 +10120,7 @@ function registerSkillsCommands(program2) {
|
|
|
9780
10120
|
}
|
|
9781
10121
|
const skillDirs = fs4.readdirSync(userDir).filter((name) => {
|
|
9782
10122
|
if (name.startsWith(".")) return false;
|
|
9783
|
-
const p =
|
|
10123
|
+
const p = path8.join(userDir, name);
|
|
9784
10124
|
try {
|
|
9785
10125
|
return fs4.statSync(p).isDirectory();
|
|
9786
10126
|
} catch {
|
|
@@ -9798,8 +10138,8 @@ Re-run with --force to confirm, or use \`pncli skills marketplace purge-plugin\`
|
|
|
9798
10138
|
);
|
|
9799
10139
|
}
|
|
9800
10140
|
for (const name of skillDirs) {
|
|
9801
|
-
const skillDir =
|
|
9802
|
-
if (!skillDir.startsWith(
|
|
10141
|
+
const skillDir = path8.resolve(userDir, name);
|
|
10142
|
+
if (!skillDir.startsWith(path8.resolve(userDir) + path8.sep)) continue;
|
|
9803
10143
|
fs4.rmSync(skillDir, { recursive: true, force: true });
|
|
9804
10144
|
}
|
|
9805
10145
|
const metaPath = getInstalledMetaPath(userDir);
|
|
@@ -9813,7 +10153,7 @@ Re-run with --force to confirm, or use \`pncli skills marketplace purge-plugin\`
|
|
|
9813
10153
|
});
|
|
9814
10154
|
}
|
|
9815
10155
|
function resolvePluginChoices(marketplacePath) {
|
|
9816
|
-
const marketplaceJsonPath =
|
|
10156
|
+
const marketplaceJsonPath = path8.join(marketplacePath, ".claude-plugin", "marketplace.json");
|
|
9817
10157
|
if (fs4.existsSync(marketplaceJsonPath)) {
|
|
9818
10158
|
try {
|
|
9819
10159
|
const meta = JSON.parse(fs4.readFileSync(marketplaceJsonPath, "utf8"));
|
|
@@ -9823,36 +10163,36 @@ function resolvePluginChoices(marketplacePath) {
|
|
|
9823
10163
|
} catch {
|
|
9824
10164
|
}
|
|
9825
10165
|
}
|
|
9826
|
-
const pluginsDir =
|
|
10166
|
+
const pluginsDir = path8.join(marketplacePath, "plugins");
|
|
9827
10167
|
if (fs4.existsSync(pluginsDir)) {
|
|
9828
|
-
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: "" }));
|
|
9829
10169
|
}
|
|
9830
10170
|
return [];
|
|
9831
10171
|
}
|
|
9832
10172
|
function resolveSkillsSrc(marketplacePath, selectedPlugin) {
|
|
9833
|
-
const pluginsBase =
|
|
9834
|
-
const skillsSrc =
|
|
9835
|
-
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)) {
|
|
9836
10176
|
throw new Error(`Invalid plugin name: "${selectedPlugin}"`);
|
|
9837
10177
|
}
|
|
9838
10178
|
return skillsSrc;
|
|
9839
10179
|
}
|
|
9840
10180
|
function copyPluginSkills(skillsSrc, targetDir, meta) {
|
|
9841
10181
|
fs4.mkdirSync(targetDir, { recursive: true });
|
|
9842
|
-
const resolvedTarget =
|
|
10182
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
9843
10183
|
const skillNames = fs4.readdirSync(skillsSrc).filter(
|
|
9844
|
-
(name) => fs4.statSync(
|
|
10184
|
+
(name) => fs4.statSync(path8.join(skillsSrc, name)).isDirectory()
|
|
9845
10185
|
);
|
|
9846
10186
|
const installed = [];
|
|
9847
10187
|
const failed = [];
|
|
9848
10188
|
for (const skillName of skillNames) {
|
|
9849
|
-
const dest =
|
|
9850
|
-
if (!dest.startsWith(resolvedTarget +
|
|
10189
|
+
const dest = path8.resolve(targetDir, skillName);
|
|
10190
|
+
if (!dest.startsWith(resolvedTarget + path8.sep)) {
|
|
9851
10191
|
failed.push(skillName);
|
|
9852
10192
|
continue;
|
|
9853
10193
|
}
|
|
9854
10194
|
fs4.rmSync(dest, { recursive: true, force: true });
|
|
9855
|
-
fs4.cpSync(
|
|
10195
|
+
fs4.cpSync(path8.join(skillsSrc, skillName), dest, { recursive: true });
|
|
9856
10196
|
installed.push(skillName);
|
|
9857
10197
|
}
|
|
9858
10198
|
if (meta) {
|
|
@@ -9868,8 +10208,8 @@ function copyPluginSkills(skillsSrc, targetDir, meta) {
|
|
|
9868
10208
|
}
|
|
9869
10209
|
|
|
9870
10210
|
// src/services/jwt/commands.ts
|
|
9871
|
-
function base64urlDecode(
|
|
9872
|
-
const base64 =
|
|
10211
|
+
function base64urlDecode(input3) {
|
|
10212
|
+
const base64 = input3.replace(/-/g, "+").replace(/_/g, "/");
|
|
9873
10213
|
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
9874
10214
|
return Buffer.from(padded, "base64").toString("utf8");
|
|
9875
10215
|
}
|