@kolatts/pncli 5.1.0 → 5.3.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/README.md +20 -3
- package/dist/{chunk-FAMJPKO4.js → chunk-2J2HAFKO.js} +2 -2
- package/dist/{chunk-FAMJPKO4.js.map → chunk-2J2HAFKO.js.map} +1 -1
- package/dist/cli.js +703 -356
- package/dist/cli.js.map +1 -1
- package/dist/{http-TAINU2LF.js → http-V7PZD7LE.js} +2 -2
- package/package.json +2 -2
- package/skills/pncli/SKILL.md +2 -2
- package/skills/pncli/marketplace.md +67 -12
- /package/dist/{http-TAINU2LF.js.map → http-V7PZD7LE.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
createHttpClient,
|
|
16
16
|
validateAlationAccessToken
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-2J2HAFKO.js";
|
|
18
18
|
import {
|
|
19
19
|
ExitCode,
|
|
20
20
|
fail,
|
|
@@ -447,34 +447,34 @@ function parseAdoRemote(remoteUrl, adoBaseUrl) {
|
|
|
447
447
|
} catch {
|
|
448
448
|
baseHostname = adoBaseUrl.replace(/\/$/, "").replace(/^https?:\/\//, "").split(/[:/]/)[0].toLowerCase();
|
|
449
449
|
}
|
|
450
|
-
let
|
|
450
|
+
let path11 = remoteUrl;
|
|
451
451
|
let remoteHostname = "";
|
|
452
|
-
const sshProtoMatch =
|
|
452
|
+
const sshProtoMatch = path11.match(/^ssh:\/\/[^@]*@?([^/:]+)(?::\d+)?(.*)/);
|
|
453
453
|
if (sshProtoMatch) {
|
|
454
454
|
remoteHostname = sshProtoMatch[1].toLowerCase();
|
|
455
|
-
|
|
455
|
+
path11 = sshProtoMatch[2];
|
|
456
456
|
} else {
|
|
457
|
-
const gitAtMatch =
|
|
457
|
+
const gitAtMatch = path11.match(/^git@([^:]+)(?::\d+)?:(.*)/);
|
|
458
458
|
if (gitAtMatch) {
|
|
459
459
|
remoteHostname = gitAtMatch[1].toLowerCase();
|
|
460
|
-
|
|
460
|
+
path11 = "/" + gitAtMatch[2];
|
|
461
461
|
} else {
|
|
462
|
-
const httpsMatch =
|
|
462
|
+
const httpsMatch = path11.match(/^https?:\/\/([^/:]+)(?::\d+)?(.*)/);
|
|
463
463
|
if (httpsMatch) {
|
|
464
464
|
remoteHostname = httpsMatch[1].toLowerCase();
|
|
465
|
-
|
|
465
|
+
path11 = httpsMatch[2];
|
|
466
466
|
}
|
|
467
467
|
}
|
|
468
468
|
}
|
|
469
469
|
if (!remoteHostname || remoteHostname !== baseHostname) return null;
|
|
470
|
-
|
|
471
|
-
const gitIdx =
|
|
472
|
-
const sshIdx =
|
|
470
|
+
path11 = path11.replace(/\.git$/, "");
|
|
471
|
+
const gitIdx = path11.indexOf("/_git/");
|
|
472
|
+
const sshIdx = path11.indexOf("/_ssh/");
|
|
473
473
|
const markerIdx = gitIdx !== -1 ? gitIdx : sshIdx;
|
|
474
474
|
if (markerIdx === -1) return null;
|
|
475
|
-
const repo = decodeURIComponent(
|
|
475
|
+
const repo = decodeURIComponent(path11.slice(markerIdx + 6));
|
|
476
476
|
if (!repo) return null;
|
|
477
|
-
const before =
|
|
477
|
+
const before = path11.slice(1, markerIdx);
|
|
478
478
|
const parts = before.split("/").filter(Boolean);
|
|
479
479
|
if (parts.length < 2) return null;
|
|
480
480
|
const project = decodeURIComponent(parts[parts.length - 1]);
|
|
@@ -587,30 +587,30 @@ var BitbucketClient = class {
|
|
|
587
587
|
);
|
|
588
588
|
}
|
|
589
589
|
async createPR(opts) {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
id: `refs/heads/${opts.target}`,
|
|
606
|
-
repository: {
|
|
607
|
-
slug: opts.repo,
|
|
608
|
-
project: { key: opts.project }
|
|
609
|
-
}
|
|
610
|
-
},
|
|
611
|
-
reviewers: (opts.reviewers ?? []).map((name) => ({ user: { name } }))
|
|
590
|
+
const body = {
|
|
591
|
+
title: opts.title,
|
|
592
|
+
description: opts.description,
|
|
593
|
+
fromRef: {
|
|
594
|
+
id: `refs/heads/${opts.source}`,
|
|
595
|
+
repository: {
|
|
596
|
+
slug: opts.repo,
|
|
597
|
+
project: { key: opts.project }
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
toRef: {
|
|
601
|
+
id: `refs/heads/${opts.target}`,
|
|
602
|
+
repository: {
|
|
603
|
+
slug: opts.repo,
|
|
604
|
+
project: { key: opts.project }
|
|
612
605
|
}
|
|
613
606
|
}
|
|
607
|
+
};
|
|
608
|
+
if (opts.reviewers && opts.reviewers.length > 0) {
|
|
609
|
+
body.reviewers = opts.reviewers.map((name) => ({ user: { name } }));
|
|
610
|
+
}
|
|
611
|
+
return this.http.bitbucket(
|
|
612
|
+
`${API}/projects/${opts.project}/repos/${opts.repo}/pull-requests`,
|
|
613
|
+
{ method: "POST", body }
|
|
614
614
|
);
|
|
615
615
|
}
|
|
616
616
|
async updatePR(opts) {
|
|
@@ -1861,7 +1861,7 @@ function registerBitbucketCommands(program2) {
|
|
|
1861
1861
|
const { client, project, repo } = getClient2(bb, { project: opts.project, repo: opts.repo });
|
|
1862
1862
|
const config = loadConfig({ configPath: bb.optsWithGlobals().config });
|
|
1863
1863
|
const target = opts.target ?? config.defaults.bitbucket?.targetBranch ?? "main";
|
|
1864
|
-
const reviewers = opts.reviewers ? opts.reviewers.split(",").map((s) => s.trim()) :
|
|
1864
|
+
const reviewers = opts.reviewers ? opts.reviewers.split(",").map((s) => s.trim()) : void 0;
|
|
1865
1865
|
const data = await client.createPR({ project, repo, title: opts.title, source: opts.source, target, description: opts.description, reviewers });
|
|
1866
1866
|
success(data, "bitbucket", "create-pr", start);
|
|
1867
1867
|
} catch (err) {
|
|
@@ -1885,7 +1885,7 @@ function registerBitbucketCommands(program2) {
|
|
|
1885
1885
|
throw new PncliError("--repos must contain at least one repository slug", 1);
|
|
1886
1886
|
}
|
|
1887
1887
|
const title = opts.title ?? `Promote ${opts.from} to ${opts.to}`;
|
|
1888
|
-
const reviewers = opts.reviewers ? opts.reviewers.split(",").map((s) => s.trim()) :
|
|
1888
|
+
const reviewers = opts.reviewers ? opts.reviewers.split(",").map((s) => s.trim()) : void 0;
|
|
1889
1889
|
const results = [];
|
|
1890
1890
|
let created = 0;
|
|
1891
1891
|
let nothingToPromote = 0;
|
|
@@ -2001,21 +2001,21 @@ function registerBitbucketCommands(program2) {
|
|
|
2001
2001
|
fail(err, "bitbucket", "reply-comment", start);
|
|
2002
2002
|
}
|
|
2003
2003
|
});
|
|
2004
|
-
bb.command("resolve-comment").description("Resolve a comment on a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--comment-id <id>", "Comment ID").option("--version <n>", "Comment version", "0").action(async (opts) => {
|
|
2004
|
+
bb.command("resolve-comment").description("Resolve a comment on a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--comment-id <id>", "Comment ID").option("--comment-version <n>", "Comment version, as reported by list-comments", "0").action(async (opts) => {
|
|
2005
2005
|
const start = Date.now();
|
|
2006
2006
|
try {
|
|
2007
2007
|
const { client, project, repo } = getClient2(bb);
|
|
2008
|
-
await client.resolveComment(project, repo, parseInt(opts.pr, 10), parseInt(opts.commentId, 10), parseInt(opts.
|
|
2008
|
+
await client.resolveComment(project, repo, parseInt(opts.pr, 10), parseInt(opts.commentId, 10), parseInt(opts.commentVersion ?? "0", 10));
|
|
2009
2009
|
success({ resolved: true }, "bitbucket", "resolve-comment", start);
|
|
2010
2010
|
} catch (err) {
|
|
2011
2011
|
fail(err, "bitbucket", "resolve-comment", start);
|
|
2012
2012
|
}
|
|
2013
2013
|
});
|
|
2014
|
-
bb.command("delete-comment").description("Delete a comment on a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--comment-id <id>", "Comment ID").option("--version <n>", "Comment version", "0").action(async (opts) => {
|
|
2014
|
+
bb.command("delete-comment").description("Delete a comment on a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--comment-id <id>", "Comment ID").option("--comment-version <n>", "Comment version, as reported by list-comments", "0").action(async (opts) => {
|
|
2015
2015
|
const start = Date.now();
|
|
2016
2016
|
try {
|
|
2017
2017
|
const { client, project, repo } = getClient2(bb);
|
|
2018
|
-
await client.deleteComment(project, repo, parseInt(opts.pr, 10), parseInt(opts.commentId, 10), parseInt(opts.
|
|
2018
|
+
await client.deleteComment(project, repo, parseInt(opts.pr, 10), parseInt(opts.commentId, 10), parseInt(opts.commentVersion ?? "0", 10));
|
|
2019
2019
|
success({ deleted: true }, "bitbucket", "delete-comment", start);
|
|
2020
2020
|
} catch (err) {
|
|
2021
2021
|
fail(err, "bitbucket", "delete-comment", start);
|
|
@@ -2306,8 +2306,8 @@ var GitHubClient = class {
|
|
|
2306
2306
|
);
|
|
2307
2307
|
}
|
|
2308
2308
|
async createRepo(opts) {
|
|
2309
|
-
const
|
|
2310
|
-
return this.http.github(
|
|
2309
|
+
const path11 = opts.org ? `/orgs/${opts.org}/repos` : "/user/repos";
|
|
2310
|
+
return this.http.github(path11, {
|
|
2311
2311
|
method: "POST",
|
|
2312
2312
|
body: {
|
|
2313
2313
|
name: opts.name,
|
|
@@ -6272,8 +6272,8 @@ async function runCredentialChecks(cfg, http) {
|
|
|
6272
6272
|
} else {
|
|
6273
6273
|
try {
|
|
6274
6274
|
const collection = cfg.defaults.ado?.collection;
|
|
6275
|
-
const
|
|
6276
|
-
await http.ado(
|
|
6275
|
+
const path11 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
|
|
6276
|
+
await http.ado(path11, { timeoutMs: 1e4 });
|
|
6277
6277
|
results.ado = { status: "valid", message: "ok" };
|
|
6278
6278
|
} catch (err) {
|
|
6279
6279
|
results.ado = categorize(err);
|
|
@@ -6511,9 +6511,9 @@ async function runCredentialChecks(cfg, http) {
|
|
|
6511
6511
|
}
|
|
6512
6512
|
|
|
6513
6513
|
// src/services/skills/commands.ts
|
|
6514
|
-
import
|
|
6515
|
-
import
|
|
6516
|
-
import
|
|
6514
|
+
import fs3 from "fs";
|
|
6515
|
+
import path8 from "path";
|
|
6516
|
+
import os2 from "os";
|
|
6517
6517
|
import { fileURLToPath } from "url";
|
|
6518
6518
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
6519
6519
|
import select from "@inquirer/select";
|
|
@@ -6540,6 +6540,170 @@ function getPncliVersion() {
|
|
|
6540
6540
|
return cached;
|
|
6541
6541
|
}
|
|
6542
6542
|
|
|
6543
|
+
// src/services/skills/instructions.ts
|
|
6544
|
+
import fs2 from "fs";
|
|
6545
|
+
import path7 from "path";
|
|
6546
|
+
import os from "os";
|
|
6547
|
+
var INSTRUCTIONS_SUBDIR = "instructions";
|
|
6548
|
+
var INSTRUCTION_SOURCE_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
6549
|
+
var AGENT_INSTRUCTION_SPECS = {
|
|
6550
|
+
"codex": {
|
|
6551
|
+
file: () => path7.join(process.env.CODEX_HOME || path7.join(os.homedir(), ".codex"), "AGENTS.md"),
|
|
6552
|
+
sources: ["AGENTS.md", "CLAUDE.md"]
|
|
6553
|
+
},
|
|
6554
|
+
"github-copilot": {
|
|
6555
|
+
file: () => path7.join(process.env.COPILOT_HOME || path7.join(os.homedir(), ".copilot"), "copilot-instructions.md"),
|
|
6556
|
+
sources: ["AGENTS.md", "CLAUDE.md"]
|
|
6557
|
+
},
|
|
6558
|
+
"claude-code": {
|
|
6559
|
+
file: () => path7.join(process.env.CLAUDE_CONFIG_DIR || path7.join(os.homedir(), ".claude"), "CLAUDE.md"),
|
|
6560
|
+
sources: ["CLAUDE.md", "AGENTS.md"]
|
|
6561
|
+
}
|
|
6562
|
+
};
|
|
6563
|
+
var INSTRUCTION_AGENTS = Object.keys(AGENT_INSTRUCTION_SPECS);
|
|
6564
|
+
function agentInstructionsFile(agent) {
|
|
6565
|
+
const spec = AGENT_INSTRUCTION_SPECS[agent];
|
|
6566
|
+
if (!spec) throw new Error(`Unknown agent: "${agent}". Use: ${INSTRUCTION_AGENTS.join(" | ")}`);
|
|
6567
|
+
return path7.resolve(spec.file());
|
|
6568
|
+
}
|
|
6569
|
+
function findMarketplaceInstructions(marketplacePath) {
|
|
6570
|
+
const dir = path7.join(marketplacePath, INSTRUCTIONS_SUBDIR);
|
|
6571
|
+
return INSTRUCTION_SOURCE_FILES.map((file) => ({ file, path: path7.join(dir, file) })).filter((entry) => {
|
|
6572
|
+
try {
|
|
6573
|
+
return fs2.statSync(entry.path).isFile();
|
|
6574
|
+
} catch {
|
|
6575
|
+
return false;
|
|
6576
|
+
}
|
|
6577
|
+
});
|
|
6578
|
+
}
|
|
6579
|
+
function pickInstructionSource(agent, shipped) {
|
|
6580
|
+
const spec = AGENT_INSTRUCTION_SPECS[agent];
|
|
6581
|
+
if (!spec) throw new Error(`Unknown agent: "${agent}". Use: ${INSTRUCTION_AGENTS.join(" | ")}`);
|
|
6582
|
+
for (const wanted of spec.sources) {
|
|
6583
|
+
const match = shipped.find((s) => s.file === wanted);
|
|
6584
|
+
if (match) return match;
|
|
6585
|
+
}
|
|
6586
|
+
return null;
|
|
6587
|
+
}
|
|
6588
|
+
var MARKER_PREFIX = "pncli:instructions";
|
|
6589
|
+
function escapeRegExp(s) {
|
|
6590
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6591
|
+
}
|
|
6592
|
+
function assertSafeName(name) {
|
|
6593
|
+
if (!name.trim() || name.includes("-->") || name.includes('"') || /[\r\n]/.test(name)) {
|
|
6594
|
+
throw new Error(`Marketplace name "${name}" cannot be used as an instructions block marker.`);
|
|
6595
|
+
}
|
|
6596
|
+
}
|
|
6597
|
+
function beginMarker(marketplaceName) {
|
|
6598
|
+
assertSafeName(marketplaceName);
|
|
6599
|
+
return `<!-- ${MARKER_PREFIX} marketplace="${marketplaceName}" begin -->`;
|
|
6600
|
+
}
|
|
6601
|
+
function endMarker(marketplaceName) {
|
|
6602
|
+
assertSafeName(marketplaceName);
|
|
6603
|
+
return `<!-- ${MARKER_PREFIX} marketplace="${marketplaceName}" end -->`;
|
|
6604
|
+
}
|
|
6605
|
+
function blockPattern(marketplaceName) {
|
|
6606
|
+
const begin = escapeRegExp(beginMarker(marketplaceName));
|
|
6607
|
+
const end = escapeRegExp(endMarker(marketplaceName));
|
|
6608
|
+
return new RegExp(`${begin}\\r?\\n[\\s\\S]*?${end}`);
|
|
6609
|
+
}
|
|
6610
|
+
function renderManagedBlock(marketplaceName, body, sourceFile) {
|
|
6611
|
+
const trimmed = body.replace(/\r\n/g, "\n").trim();
|
|
6612
|
+
return [
|
|
6613
|
+
beginMarker(marketplaceName),
|
|
6614
|
+
`<!-- Managed by pncli from ${sourceFile} in marketplace "${marketplaceName}". Edits here are overwritten on the next sync; remove with: pncli skills marketplace instructions remove ${marketplaceName} -->`,
|
|
6615
|
+
trimmed,
|
|
6616
|
+
endMarker(marketplaceName)
|
|
6617
|
+
].join("\n");
|
|
6618
|
+
}
|
|
6619
|
+
function detectEol(content) {
|
|
6620
|
+
return /\r\n/.test(content) ? "\r\n" : "\n";
|
|
6621
|
+
}
|
|
6622
|
+
function upsertManagedBlock(existing, marketplaceName, block) {
|
|
6623
|
+
const eol = detectEol(existing);
|
|
6624
|
+
const rendered = block.replace(/\n/g, eol);
|
|
6625
|
+
const pattern = blockPattern(marketplaceName);
|
|
6626
|
+
const match = pattern.exec(existing);
|
|
6627
|
+
if (match) {
|
|
6628
|
+
if (match[0] === rendered) return { content: existing, action: "unchanged" };
|
|
6629
|
+
return { content: existing.slice(0, match.index) + rendered + existing.slice(match.index + match[0].length), action: "updated" };
|
|
6630
|
+
}
|
|
6631
|
+
const separator = existing.length === 0 ? "" : eol;
|
|
6632
|
+
return { content: `${existing}${separator}${rendered}${eol}`, action: "added" };
|
|
6633
|
+
}
|
|
6634
|
+
function removeManagedBlock(existing, marketplaceName) {
|
|
6635
|
+
const match = blockPattern(marketplaceName).exec(existing);
|
|
6636
|
+
if (!match) return { content: existing, removed: false };
|
|
6637
|
+
const eol = detectEol(existing);
|
|
6638
|
+
let start = match.index;
|
|
6639
|
+
let end = start + match[0].length;
|
|
6640
|
+
if (existing.startsWith(eol, end)) end += eol.length;
|
|
6641
|
+
if (start > 0 && existing.slice(0, start).endsWith(eol)) start -= eol.length;
|
|
6642
|
+
else if (start === 0 && existing.startsWith(eol, end)) end += eol.length;
|
|
6643
|
+
return { content: existing.slice(0, start) + existing.slice(end), removed: true };
|
|
6644
|
+
}
|
|
6645
|
+
function listManagedBlocks(content) {
|
|
6646
|
+
const re = new RegExp(`<!-- ${escapeRegExp(MARKER_PREFIX)} marketplace="([^"]*)" begin -->`, "g");
|
|
6647
|
+
return Array.from(content.matchAll(re)).map((m) => m[1]);
|
|
6648
|
+
}
|
|
6649
|
+
function readFileOrEmpty(file) {
|
|
6650
|
+
try {
|
|
6651
|
+
return fs2.readFileSync(file, "utf8");
|
|
6652
|
+
} catch {
|
|
6653
|
+
return "";
|
|
6654
|
+
}
|
|
6655
|
+
}
|
|
6656
|
+
function applyMarketplaceInstructions(marketplacePath, marketplaceName, agents) {
|
|
6657
|
+
const shipped = findMarketplaceInstructions(marketplacePath);
|
|
6658
|
+
return agents.map((agent) => {
|
|
6659
|
+
const file = agentInstructionsFile(agent);
|
|
6660
|
+
const source = pickInstructionSource(agent, shipped);
|
|
6661
|
+
if (!source) return { agent, file, source: null, action: "skipped" };
|
|
6662
|
+
const body = fs2.readFileSync(source.path, "utf8");
|
|
6663
|
+
const block = renderManagedBlock(marketplaceName, body, `${INSTRUCTIONS_SUBDIR}/${source.file}`);
|
|
6664
|
+
const { content, action } = upsertManagedBlock(readFileOrEmpty(file), marketplaceName, block);
|
|
6665
|
+
if (action !== "unchanged") {
|
|
6666
|
+
fs2.mkdirSync(path7.dirname(file), { recursive: true });
|
|
6667
|
+
fs2.writeFileSync(file, content, "utf8");
|
|
6668
|
+
}
|
|
6669
|
+
return { agent, file, source: `${INSTRUCTIONS_SUBDIR}/${source.file}`, action };
|
|
6670
|
+
});
|
|
6671
|
+
}
|
|
6672
|
+
function removeMarketplaceInstructions(marketplaceName, agents) {
|
|
6673
|
+
return agents.map((agent) => {
|
|
6674
|
+
const file = agentInstructionsFile(agent);
|
|
6675
|
+
const existing = readFileOrEmpty(file);
|
|
6676
|
+
const { content, removed } = removeManagedBlock(existing, marketplaceName);
|
|
6677
|
+
if (removed) fs2.writeFileSync(file, content, "utf8");
|
|
6678
|
+
return { agent, file, removed };
|
|
6679
|
+
});
|
|
6680
|
+
}
|
|
6681
|
+
function marketplaceInstructionsStatus(marketplacePath, marketplaceName, agents) {
|
|
6682
|
+
const shipped = marketplacePath && fs2.existsSync(marketplacePath) ? findMarketplaceInstructions(marketplacePath) : [];
|
|
6683
|
+
return agents.map((agent) => {
|
|
6684
|
+
const file = agentInstructionsFile(agent);
|
|
6685
|
+
const fileExists = fs2.existsSync(file);
|
|
6686
|
+
const existing = fileExists ? readFileOrEmpty(file) : "";
|
|
6687
|
+
const installed = listManagedBlocks(existing).includes(marketplaceName);
|
|
6688
|
+
const source = pickInstructionSource(agent, shipped);
|
|
6689
|
+
let upToDate = null;
|
|
6690
|
+
if (installed && source) {
|
|
6691
|
+
const block = renderManagedBlock(marketplaceName, fs2.readFileSync(source.path, "utf8"), `${INSTRUCTIONS_SUBDIR}/${source.file}`);
|
|
6692
|
+
upToDate = upsertManagedBlock(existing, marketplaceName, block).action === "unchanged";
|
|
6693
|
+
} else if (installed) {
|
|
6694
|
+
upToDate = false;
|
|
6695
|
+
}
|
|
6696
|
+
return {
|
|
6697
|
+
agent,
|
|
6698
|
+
file,
|
|
6699
|
+
fileExists,
|
|
6700
|
+
source: source ? `${INSTRUCTIONS_SUBDIR}/${source.file}` : null,
|
|
6701
|
+
installed,
|
|
6702
|
+
upToDate
|
|
6703
|
+
};
|
|
6704
|
+
});
|
|
6705
|
+
}
|
|
6706
|
+
|
|
6543
6707
|
// src/services/skills/commands.ts
|
|
6544
6708
|
var BACK = "__back__";
|
|
6545
6709
|
var ALL_MARKETPLACES = "__all_marketplaces__";
|
|
@@ -6564,7 +6728,7 @@ function repoNameFromUrl(url) {
|
|
|
6564
6728
|
}
|
|
6565
6729
|
}
|
|
6566
6730
|
function defaultMarketplacePath(url) {
|
|
6567
|
-
return
|
|
6731
|
+
return path8.join(os2.homedir(), ".agents", "marketplaces", repoNameFromUrl(url));
|
|
6568
6732
|
}
|
|
6569
6733
|
function marketplaceLabel(m) {
|
|
6570
6734
|
return m.name ?? repoNameFromUrl(m.repoUrl ?? "");
|
|
@@ -6619,9 +6783,9 @@ function assertInteractive(hint) {
|
|
|
6619
6783
|
}
|
|
6620
6784
|
}
|
|
6621
6785
|
var AGENT_PATHS = {
|
|
6622
|
-
"codex": { project: ".agents/skills", user:
|
|
6623
|
-
"github-copilot": { project: ".github/skills", user:
|
|
6624
|
-
"claude-code": { project: ".claude/skills", user:
|
|
6786
|
+
"codex": { project: ".agents/skills", user: path8.join(os2.homedir(), ".agents/skills") },
|
|
6787
|
+
"github-copilot": { project: ".github/skills", user: path8.join(os2.homedir(), ".copilot/skills") },
|
|
6788
|
+
"claude-code": { project: ".claude/skills", user: path8.join(os2.homedir(), ".claude/skills") }
|
|
6625
6789
|
};
|
|
6626
6790
|
var DEFAULT_AGENT = "codex";
|
|
6627
6791
|
var AGENT_CHOICES = Object.keys(AGENT_PATHS).join(" | ");
|
|
@@ -6648,23 +6812,23 @@ function findGitRoot(from = process.cwd()) {
|
|
|
6648
6812
|
encoding: "utf8",
|
|
6649
6813
|
stdio: ["pipe", "pipe", "pipe"]
|
|
6650
6814
|
}).trim();
|
|
6651
|
-
return root ?
|
|
6815
|
+
return root ? path8.resolve(root) : null;
|
|
6652
6816
|
} catch {
|
|
6653
6817
|
return null;
|
|
6654
6818
|
}
|
|
6655
6819
|
}
|
|
6656
6820
|
function resolveProjectPath(relativePath) {
|
|
6657
6821
|
const root = findGitRoot();
|
|
6658
|
-
return root ?
|
|
6822
|
+
return root ? path8.resolve(root, relativePath) : path8.resolve(relativePath);
|
|
6659
6823
|
}
|
|
6660
6824
|
function resolveScopedPath(agentPaths, scope) {
|
|
6661
|
-
return scope === "project" ? resolveProjectPath(agentPaths.project) :
|
|
6825
|
+
return scope === "project" ? resolveProjectPath(agentPaths.project) : path8.resolve(agentPaths.user);
|
|
6662
6826
|
}
|
|
6663
6827
|
function hasInstalledPncliSkill() {
|
|
6664
6828
|
try {
|
|
6665
6829
|
for (const agentPaths of Object.values(AGENT_PATHS)) {
|
|
6666
6830
|
for (const scope of ["project", "user"]) {
|
|
6667
|
-
if (
|
|
6831
|
+
if (fs3.existsSync(path8.join(resolveScopedPath(agentPaths, scope), "pncli", "SKILL.md"))) {
|
|
6668
6832
|
return true;
|
|
6669
6833
|
}
|
|
6670
6834
|
}
|
|
@@ -6674,20 +6838,30 @@ function hasInstalledPncliSkill() {
|
|
|
6674
6838
|
return false;
|
|
6675
6839
|
}
|
|
6676
6840
|
function resolveTargetDir(opts) {
|
|
6677
|
-
if (opts.target) return
|
|
6841
|
+
if (opts.target) return path8.resolve(opts.target);
|
|
6678
6842
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
6679
6843
|
return resolveScopedPath(agentConfig, opts.scope ?? "user");
|
|
6680
6844
|
}
|
|
6845
|
+
function resolveInstallTargets(opts, scope = "user") {
|
|
6846
|
+
if (opts.allAgents) {
|
|
6847
|
+
if (opts.claude || opts.agent) {
|
|
6848
|
+
throw new Error("--all-agents cannot be combined with --claude or --agent");
|
|
6849
|
+
}
|
|
6850
|
+
return Object.entries(AGENT_PATHS).map(([agent2, paths]) => ({ agent: agent2, target: resolveScopedPath(paths, scope) }));
|
|
6851
|
+
}
|
|
6852
|
+
const agent = resolveAgentName(opts);
|
|
6853
|
+
return [{ agent, target: resolveScopedPath(resolveAgentPaths(agent), scope) }];
|
|
6854
|
+
}
|
|
6681
6855
|
function getBundledSkillsDir() {
|
|
6682
6856
|
try {
|
|
6683
6857
|
const thisFile = fileURLToPath(import.meta.url);
|
|
6684
|
-
return
|
|
6858
|
+
return path8.resolve(path8.dirname(thisFile), "..", "skills");
|
|
6685
6859
|
} catch {
|
|
6686
6860
|
return "";
|
|
6687
6861
|
}
|
|
6688
6862
|
}
|
|
6689
6863
|
function getInstalledMetaPath(targetDir) {
|
|
6690
|
-
return
|
|
6864
|
+
return path8.join(targetDir, ".pncli-installed.json");
|
|
6691
6865
|
}
|
|
6692
6866
|
var SKILL_ORIGIN_FILENAME = "pncli-origin.json";
|
|
6693
6867
|
var DISABLED_SUBDIR = ".pncli-disabled";
|
|
@@ -6698,13 +6872,13 @@ function readInstalledMeta(targetDir) {
|
|
|
6698
6872
|
return { version: 1, skills: {} };
|
|
6699
6873
|
}
|
|
6700
6874
|
function getSkillOriginPath(skillDir) {
|
|
6701
|
-
return
|
|
6875
|
+
return path8.join(skillDir, SKILL_ORIGIN_FILENAME);
|
|
6702
6876
|
}
|
|
6703
6877
|
function readSkillOrigin(skillDir) {
|
|
6704
6878
|
const originPath = getSkillOriginPath(skillDir);
|
|
6705
|
-
if (!
|
|
6879
|
+
if (!fs3.existsSync(originPath)) return null;
|
|
6706
6880
|
try {
|
|
6707
|
-
const raw = JSON.parse(
|
|
6881
|
+
const raw = JSON.parse(fs3.readFileSync(originPath, "utf8"));
|
|
6708
6882
|
if (raw && raw.version === 1 && raw.source) return raw;
|
|
6709
6883
|
} catch {
|
|
6710
6884
|
}
|
|
@@ -6716,7 +6890,7 @@ function matchesMarketplaceFilter(record, filter) {
|
|
|
6716
6890
|
function resolveSkillRecord(meta, targetDir, skillName) {
|
|
6717
6891
|
const fromMeta = meta.skills[skillName];
|
|
6718
6892
|
if (fromMeta) return fromMeta;
|
|
6719
|
-
const perSkill = readSkillOrigin(
|
|
6893
|
+
const perSkill = readSkillOrigin(path8.join(targetDir, skillName));
|
|
6720
6894
|
if (!perSkill) return null;
|
|
6721
6895
|
return {
|
|
6722
6896
|
source: perSkill.source,
|
|
@@ -6729,28 +6903,28 @@ function resolveSkillRecord(meta, targetDir, skillName) {
|
|
|
6729
6903
|
};
|
|
6730
6904
|
}
|
|
6731
6905
|
function listActiveSkillDirs(targetDir) {
|
|
6732
|
-
if (!
|
|
6733
|
-
return
|
|
6906
|
+
if (!fs3.existsSync(targetDir)) return [];
|
|
6907
|
+
return fs3.readdirSync(targetDir).filter((name) => {
|
|
6734
6908
|
if (name.startsWith(".")) return false;
|
|
6735
|
-
const dir =
|
|
6909
|
+
const dir = path8.join(targetDir, name);
|
|
6736
6910
|
try {
|
|
6737
|
-
if (!
|
|
6911
|
+
if (!fs3.statSync(dir).isDirectory()) return false;
|
|
6738
6912
|
} catch {
|
|
6739
6913
|
return false;
|
|
6740
6914
|
}
|
|
6741
|
-
return
|
|
6915
|
+
return fs3.existsSync(path8.join(dir, "SKILL.md"));
|
|
6742
6916
|
});
|
|
6743
6917
|
}
|
|
6744
6918
|
function disablePluginSkills(targetDir, plugin, marketplaceFilter) {
|
|
6745
|
-
const resolvedTarget =
|
|
6746
|
-
const stashDir =
|
|
6919
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
6920
|
+
const stashDir = path8.join(targetDir, DISABLED_SUBDIR);
|
|
6747
6921
|
const meta = readInstalledMeta(targetDir);
|
|
6748
6922
|
const disabled = [];
|
|
6749
6923
|
const alreadyDisabled = [];
|
|
6750
6924
|
const skipped = [];
|
|
6751
6925
|
for (const skillName of listActiveSkillDirs(targetDir)) {
|
|
6752
|
-
const skillDir =
|
|
6753
|
-
if (!skillDir.startsWith(resolvedTarget +
|
|
6926
|
+
const skillDir = path8.resolve(targetDir, skillName);
|
|
6927
|
+
if (!skillDir.startsWith(resolvedTarget + path8.sep)) continue;
|
|
6754
6928
|
const record = resolveSkillRecord(meta, targetDir, skillName);
|
|
6755
6929
|
if (!record || record.source !== "marketplace") {
|
|
6756
6930
|
skipped.push(skillName);
|
|
@@ -6760,10 +6934,10 @@ function disablePluginSkills(targetDir, plugin, marketplaceFilter) {
|
|
|
6760
6934
|
skipped.push(skillName);
|
|
6761
6935
|
continue;
|
|
6762
6936
|
}
|
|
6763
|
-
const stashDest =
|
|
6764
|
-
|
|
6765
|
-
if (
|
|
6766
|
-
|
|
6937
|
+
const stashDest = path8.join(stashDir, skillName);
|
|
6938
|
+
fs3.mkdirSync(stashDir, { recursive: true });
|
|
6939
|
+
if (fs3.existsSync(stashDest)) fs3.rmSync(stashDest, { recursive: true, force: true });
|
|
6940
|
+
fs3.renameSync(skillDir, stashDest);
|
|
6767
6941
|
delete meta.skills[skillName];
|
|
6768
6942
|
meta.disabled = meta.disabled ?? {};
|
|
6769
6943
|
meta.disabled[skillName] = record;
|
|
@@ -6775,13 +6949,13 @@ function disablePluginSkills(targetDir, plugin, marketplaceFilter) {
|
|
|
6775
6949
|
}
|
|
6776
6950
|
}
|
|
6777
6951
|
if (disabled.length > 0) {
|
|
6778
|
-
|
|
6952
|
+
fs3.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
6779
6953
|
}
|
|
6780
6954
|
return { disabled, alreadyDisabled, skipped };
|
|
6781
6955
|
}
|
|
6782
6956
|
function enablePluginSkills(targetDir, plugin, marketplaceFilter) {
|
|
6783
|
-
const resolvedTarget =
|
|
6784
|
-
const stashDir =
|
|
6957
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
6958
|
+
const stashDir = path8.join(targetDir, DISABLED_SUBDIR);
|
|
6785
6959
|
const meta = readInstalledMeta(targetDir);
|
|
6786
6960
|
const enabled = [];
|
|
6787
6961
|
const stashMissing = [];
|
|
@@ -6792,27 +6966,27 @@ function enablePluginSkills(targetDir, plugin, marketplaceFilter) {
|
|
|
6792
6966
|
skipped.push(skillName);
|
|
6793
6967
|
continue;
|
|
6794
6968
|
}
|
|
6795
|
-
const dest =
|
|
6796
|
-
if (!dest.startsWith(resolvedTarget +
|
|
6969
|
+
const dest = path8.resolve(targetDir, skillName);
|
|
6970
|
+
if (!dest.startsWith(resolvedTarget + path8.sep)) {
|
|
6797
6971
|
skipped.push(skillName);
|
|
6798
6972
|
continue;
|
|
6799
6973
|
}
|
|
6800
|
-
const stashSrc =
|
|
6801
|
-
if (!
|
|
6974
|
+
const stashSrc = path8.join(stashDir, skillName);
|
|
6975
|
+
if (!fs3.existsSync(stashSrc)) {
|
|
6802
6976
|
stashMissing.push(skillName);
|
|
6803
6977
|
continue;
|
|
6804
6978
|
}
|
|
6805
|
-
if (
|
|
6806
|
-
|
|
6979
|
+
if (fs3.existsSync(dest)) fs3.rmSync(dest, { recursive: true, force: true });
|
|
6980
|
+
fs3.renameSync(stashSrc, dest);
|
|
6807
6981
|
meta.skills[skillName] = record;
|
|
6808
6982
|
delete meta.disabled[skillName];
|
|
6809
6983
|
enabled.push(skillName);
|
|
6810
6984
|
}
|
|
6811
|
-
if (
|
|
6812
|
-
|
|
6985
|
+
if (fs3.existsSync(stashDir) && fs3.readdirSync(stashDir).length === 0) {
|
|
6986
|
+
fs3.rmdirSync(stashDir);
|
|
6813
6987
|
}
|
|
6814
6988
|
if (enabled.length > 0) {
|
|
6815
|
-
|
|
6989
|
+
fs3.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
6816
6990
|
}
|
|
6817
6991
|
return { enabled, stashMissing, skipped, hadDisabled: disabledEntries.length > 0 };
|
|
6818
6992
|
}
|
|
@@ -6878,11 +7052,11 @@ function getInstalledPluginsForMarketplace(targetDir, marketplaceName, repoUrl)
|
|
|
6878
7052
|
function readCustomTargets(globalConfig) {
|
|
6879
7053
|
const raw = globalConfig.skillsTargets;
|
|
6880
7054
|
if (!Array.isArray(raw)) return [];
|
|
6881
|
-
return [...new Set(raw.filter((t) => typeof t === "string" && t.length > 0).map((t) =>
|
|
7055
|
+
return [...new Set(raw.filter((t) => typeof t === "string" && t.length > 0).map((t) => path8.resolve(t)))];
|
|
6882
7056
|
}
|
|
6883
7057
|
function rememberCustomTarget(configPath, targetDir) {
|
|
6884
|
-
const resolved =
|
|
6885
|
-
const builtIn = Object.values(AGENT_PATHS).flatMap((a) => [
|
|
7058
|
+
const resolved = path8.resolve(targetDir);
|
|
7059
|
+
const builtIn = Object.values(AGENT_PATHS).flatMap((a) => [path8.resolve(a.user), resolveProjectPath(a.project)]);
|
|
6886
7060
|
if (builtIn.includes(resolved)) return false;
|
|
6887
7061
|
const existing = loadJsonFile(configPath) ?? {};
|
|
6888
7062
|
const targets = readCustomTargets(existing);
|
|
@@ -6891,7 +7065,7 @@ function rememberCustomTarget(configPath, targetDir) {
|
|
|
6891
7065
|
return true;
|
|
6892
7066
|
}
|
|
6893
7067
|
function forgetCustomTarget(configPath, targetDir) {
|
|
6894
|
-
const resolved =
|
|
7068
|
+
const resolved = path8.resolve(targetDir);
|
|
6895
7069
|
const existing = loadJsonFile(configPath) ?? {};
|
|
6896
7070
|
const targets = readCustomTargets(existing);
|
|
6897
7071
|
if (!targets.includes(resolved)) return false;
|
|
@@ -6899,12 +7073,12 @@ function forgetCustomTarget(configPath, targetDir) {
|
|
|
6899
7073
|
return true;
|
|
6900
7074
|
}
|
|
6901
7075
|
function summarizeLocation(agent, scope, targetDir) {
|
|
6902
|
-
const resolved =
|
|
7076
|
+
const resolved = path8.resolve(targetDir);
|
|
6903
7077
|
const base = {
|
|
6904
7078
|
agent,
|
|
6905
7079
|
scope,
|
|
6906
7080
|
path: resolved,
|
|
6907
|
-
exists:
|
|
7081
|
+
exists: fs3.existsSync(resolved),
|
|
6908
7082
|
totalSkills: 0,
|
|
6909
7083
|
marketplaceSkills: 0,
|
|
6910
7084
|
bundledSkills: 0,
|
|
@@ -6923,8 +7097,8 @@ function summarizeLocation(agent, scope, targetDir) {
|
|
|
6923
7097
|
}
|
|
6924
7098
|
const disabledNames = Object.keys(meta.disabled ?? {});
|
|
6925
7099
|
base.disabledSkills = disabledNames.length;
|
|
6926
|
-
const stashDir =
|
|
6927
|
-
base.disabledStashMissing = disabledNames.filter((name) => !
|
|
7100
|
+
const stashDir = path8.join(resolved, DISABLED_SUBDIR);
|
|
7101
|
+
base.disabledStashMissing = disabledNames.filter((name) => !fs3.existsSync(path8.join(stashDir, name)));
|
|
6928
7102
|
return base;
|
|
6929
7103
|
}
|
|
6930
7104
|
function listKnownLocations(globalConfig) {
|
|
@@ -6949,7 +7123,7 @@ function collectSkillStatus(globalConfig, locations) {
|
|
|
6949
7123
|
const { byName, byUrl } = buildMarketplaceIndex(getAllMarketplaces(globalConfig));
|
|
6950
7124
|
const remoteCache = /* @__PURE__ */ new Map();
|
|
6951
7125
|
const remoteFor = (localPath) => {
|
|
6952
|
-
if (!localPath || !
|
|
7126
|
+
if (!localPath || !fs3.existsSync(localPath)) return null;
|
|
6953
7127
|
if (!remoteCache.has(localPath)) remoteCache.set(localPath, getRepoRemoteUrl(localPath));
|
|
6954
7128
|
return remoteCache.get(localPath) ?? null;
|
|
6955
7129
|
};
|
|
@@ -6962,7 +7136,7 @@ function collectSkillStatus(globalConfig, locations) {
|
|
|
6962
7136
|
const registered = (recordedName ? byName.get(recordedName) : void 0) ?? (record?.installedFrom ? byUrl.get(record.installedFrom) : void 0) ?? null;
|
|
6963
7137
|
records.push({
|
|
6964
7138
|
skill: skillName,
|
|
6965
|
-
path: enabled ?
|
|
7139
|
+
path: enabled ? path8.join(location.path, skillName) : path8.join(location.path, DISABLED_SUBDIR, skillName),
|
|
6966
7140
|
agent: location.agent,
|
|
6967
7141
|
scope: location.scope,
|
|
6968
7142
|
location: location.path,
|
|
@@ -6995,16 +7169,16 @@ function recordInstalledSkills(targetDir, skillNames, record) {
|
|
|
6995
7169
|
for (const skillName of skillNames) {
|
|
6996
7170
|
const fullRecord = { pncliVersion: getPncliVersion(), ...record, installedAt: now };
|
|
6997
7171
|
meta.skills[skillName] = fullRecord;
|
|
6998
|
-
const skillDir =
|
|
6999
|
-
if (
|
|
7172
|
+
const skillDir = path8.join(targetDir, skillName);
|
|
7173
|
+
if (fs3.existsSync(skillDir)) {
|
|
7000
7174
|
const origin = { version: 1, ...fullRecord };
|
|
7001
7175
|
try {
|
|
7002
|
-
|
|
7176
|
+
fs3.writeFileSync(getSkillOriginPath(skillDir), JSON.stringify(origin, null, 2), "utf8");
|
|
7003
7177
|
} catch {
|
|
7004
7178
|
}
|
|
7005
7179
|
}
|
|
7006
7180
|
}
|
|
7007
|
-
|
|
7181
|
+
fs3.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
7008
7182
|
}
|
|
7009
7183
|
function findStaleBundledSkills(targetDir) {
|
|
7010
7184
|
const currentVersion = getPncliVersion();
|
|
@@ -7080,8 +7254,8 @@ function upsertMarketplace(all, entry) {
|
|
|
7080
7254
|
}
|
|
7081
7255
|
}
|
|
7082
7256
|
function cloneOrReuseMarketplace(url, resolvedPath, opts, marketplaceName) {
|
|
7083
|
-
const hasGit =
|
|
7084
|
-
if (
|
|
7257
|
+
const hasGit = fs3.existsSync(path8.join(resolvedPath, ".git"));
|
|
7258
|
+
if (fs3.existsSync(resolvedPath) && !hasGit && fs3.readdirSync(resolvedPath).length > 0) {
|
|
7085
7259
|
throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
|
|
7086
7260
|
}
|
|
7087
7261
|
if (hasGit) {
|
|
@@ -7138,7 +7312,7 @@ function installPluginsForMarketplace(marketplacePath, marketplaceName, repoUrl,
|
|
|
7138
7312
|
const branch = detectRepoBranch(marketplacePath);
|
|
7139
7313
|
for (const pluginName of pluginNames) {
|
|
7140
7314
|
const skillsSrc = resolveSkillsSrc(marketplacePath, pluginName);
|
|
7141
|
-
if (!
|
|
7315
|
+
if (!fs3.existsSync(skillsSrc)) {
|
|
7142
7316
|
results[pluginName] = { installed: [], failed: [] };
|
|
7143
7317
|
warn(`No skills directory found for plugin "${pluginName}" in "${marketplaceName}" \u2014 skipping.`);
|
|
7144
7318
|
continue;
|
|
@@ -7152,61 +7326,108 @@ function installPluginsForMarketplace(marketplacePath, marketplaceName, repoUrl,
|
|
|
7152
7326
|
results[pluginName] = { installed, failed };
|
|
7153
7327
|
totalInstalled += installed.length;
|
|
7154
7328
|
for (const skill of installed) {
|
|
7155
|
-
|
|
7329
|
+
log(` ${skill}: ${path8.join(skillsSrc, skill)} \u2192 ${path8.join(targetDir, skill)}`);
|
|
7156
7330
|
}
|
|
7157
7331
|
if (failed.length > 0) {
|
|
7158
7332
|
warn(`Skipped ${failed.length} skill(s) with invalid names in "${pluginName}": ${failed.join(", ")}`);
|
|
7159
7333
|
}
|
|
7160
7334
|
}
|
|
7335
|
+
warn(`Installed ${totalInstalled} skill(s) from ${pluginNames.length} plugin(s) in "${marketplaceName}" to ${targetDir}`);
|
|
7161
7336
|
return { results, totalInstalled };
|
|
7162
7337
|
}
|
|
7163
|
-
function
|
|
7338
|
+
function installMarketplaceToTarget(m, marketplacePath, pluginChoices, pluginFilter, target, opts) {
|
|
7339
|
+
const marketplaceName = marketplaceLabel(m);
|
|
7340
|
+
const base = { agent: target.agent, target: target.target };
|
|
7341
|
+
const installedHere = getInstalledPluginsForMarketplace(target.target, marketplaceName, m.repoUrl).filter((name) => pluginChoices.some((p) => p.name === name));
|
|
7342
|
+
let requested;
|
|
7343
|
+
if (pluginFilter === "all") {
|
|
7344
|
+
if (opts.installedOnly) {
|
|
7345
|
+
if (installedHere.length === 0) {
|
|
7346
|
+
warn(`No installed plugins from "${marketplaceName}" in ${target.target} \u2014 skipping. Run marketplace sync without --installed-only to install plugins.`);
|
|
7347
|
+
return { ...base, plugins: {}, total: 0, skipped: true, installedOnly: true, message: "No installed plugins found \u2014 nothing to sync." };
|
|
7348
|
+
}
|
|
7349
|
+
requested = installedHere;
|
|
7350
|
+
} else {
|
|
7351
|
+
requested = pluginChoices.map((p) => p.name);
|
|
7352
|
+
}
|
|
7353
|
+
} else {
|
|
7354
|
+
requested = [pluginFilter];
|
|
7355
|
+
}
|
|
7356
|
+
const missing = requested.filter((name) => !installedHere.includes(name));
|
|
7357
|
+
if (!opts.updated && !opts.force) {
|
|
7358
|
+
if (missing.length === 0) {
|
|
7359
|
+
return { ...base, plugins: {}, total: 0, skipped: true, message: "No changes detected \u2014 skipping install. Use --force to reinstall anyway." };
|
|
7360
|
+
}
|
|
7361
|
+
requested = missing;
|
|
7362
|
+
}
|
|
7363
|
+
const { results, totalInstalled } = installPluginsForMarketplace(marketplacePath, marketplaceName, m.repoUrl, requested, target.target);
|
|
7364
|
+
return { ...base, plugins: results, total: totalInstalled };
|
|
7365
|
+
}
|
|
7366
|
+
function applyInstructionsForTargets(m, marketplacePath, targets, enabled) {
|
|
7367
|
+
if (!enabled || findMarketplaceInstructions(marketplacePath).length === 0) return void 0;
|
|
7368
|
+
const results = applyMarketplaceInstructions(marketplacePath, marketplaceLabel(m), targets.map((t) => t.agent));
|
|
7369
|
+
for (const r of results) {
|
|
7370
|
+
if (r.action === "added" || r.action === "updated") warn(`Instructions ${r.action} in ${r.file} (from ${r.source})`);
|
|
7371
|
+
}
|
|
7372
|
+
return results;
|
|
7373
|
+
}
|
|
7374
|
+
function shapeSyncResult(marketplaceName, updated, pluginFilter, targetResults, extras) {
|
|
7375
|
+
const tail = {
|
|
7376
|
+
marketplaceUpdated: updated,
|
|
7377
|
+
...extras.installedOnly ? { installedOnly: true } : {},
|
|
7378
|
+
...extras.instructions ? { instructions: extras.instructions } : {}
|
|
7379
|
+
};
|
|
7380
|
+
if (targetResults.length === 1) {
|
|
7381
|
+
const r = targetResults[0];
|
|
7382
|
+
if (r.skipped) {
|
|
7383
|
+
return { marketplace: marketplaceName, updated: false, skipped: true, target: r.target, message: r.message, ...tail };
|
|
7384
|
+
}
|
|
7385
|
+
if (pluginFilter === "all") {
|
|
7386
|
+
return { marketplace: marketplaceName, plugins: r.plugins, total: r.total, target: r.target, ...tail };
|
|
7387
|
+
}
|
|
7388
|
+
const single = r.plugins[pluginFilter] ?? { installed: [], failed: [] };
|
|
7389
|
+
return { marketplace: marketplaceName, plugin: pluginFilter, installed: single.installed, failed: single.failed, total: single.installed.length, target: r.target, ...tail };
|
|
7390
|
+
}
|
|
7391
|
+
return {
|
|
7392
|
+
marketplace: marketplaceName,
|
|
7393
|
+
...pluginFilter === "all" ? {} : { plugin: pluginFilter },
|
|
7394
|
+
targets: targetResults,
|
|
7395
|
+
total: targetResults.reduce((sum, r) => sum + r.total, 0),
|
|
7396
|
+
...tail
|
|
7397
|
+
};
|
|
7398
|
+
}
|
|
7399
|
+
function installAllPlugins(m, resolvedPath, targets) {
|
|
7164
7400
|
const pluginChoices = resolvePluginChoices(resolvedPath);
|
|
7165
7401
|
if (pluginChoices.length === 0) {
|
|
7166
7402
|
warn("No plugins found in marketplace. Check the marketplace repository structure.");
|
|
7167
|
-
return {
|
|
7403
|
+
return targets.map((t) => ({ ...t, plugins: {}, total: 0 }));
|
|
7168
7404
|
}
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7405
|
+
return targets.map((t) => installMarketplaceToTarget(m, resolvedPath, pluginChoices, "all", t, { updated: true, force: true, installedOnly: false }));
|
|
7406
|
+
}
|
|
7407
|
+
function isBareMarketplaceSync(plugin, opts) {
|
|
7408
|
+
return plugin === void 0 && !opts.marketplace && !opts.force;
|
|
7172
7409
|
}
|
|
7173
|
-
function syncMarketplacePlugins(m,
|
|
7410
|
+
function syncMarketplacePlugins(m, targets, pluginFilter, opts) {
|
|
7174
7411
|
const marketplaceName = marketplaceLabel(m);
|
|
7175
7412
|
try {
|
|
7176
7413
|
const marketplacePath = m.localPath;
|
|
7177
|
-
if (!marketplacePath || !
|
|
7414
|
+
if (!marketplacePath || !fs3.existsSync(marketplacePath)) {
|
|
7178
7415
|
warn(`Marketplace "${marketplaceName}" local path not found \u2014 skipping.`);
|
|
7179
7416
|
return { marketplace: marketplaceName, skipped: true, message: "Local path not found." };
|
|
7180
7417
|
}
|
|
7181
7418
|
const { updated } = pullMarketplace(marketplacePath, m.repoUrl, m.token, marketplaceName);
|
|
7182
|
-
if (!updated && !force) {
|
|
7183
|
-
return { marketplace: marketplaceName, marketplaceUpdated: false, skipped: true, message: "No changes detected \u2014 skipping install. Use --force to reinstall anyway." };
|
|
7184
|
-
}
|
|
7185
7419
|
const pluginChoices = resolvePluginChoices(marketplacePath);
|
|
7186
7420
|
if (pluginChoices.length === 0) {
|
|
7187
7421
|
warn(`No plugins found in marketplace "${marketplaceName}" \u2014 skipping.`);
|
|
7188
7422
|
return { marketplace: marketplaceName, skipped: true, message: "No plugins found." };
|
|
7189
7423
|
}
|
|
7190
|
-
|
|
7191
|
-
if (pluginFilter === "all") {
|
|
7192
|
-
if (installedOnly) {
|
|
7193
|
-
const installed = getInstalledPluginsForMarketplace(targetDir, marketplaceName, m.repoUrl).filter((name) => pluginChoices.some((p) => p.name === name));
|
|
7194
|
-
if (installed.length === 0) {
|
|
7195
|
-
warn(`No installed plugins found for "${marketplaceName}" \u2014 skipping. Run marketplace sync without --installed-only to install plugins.`);
|
|
7196
|
-
return { marketplace: marketplaceName, skipped: true, installedOnly: true, message: "No installed plugins found \u2014 nothing to sync." };
|
|
7197
|
-
}
|
|
7198
|
-
pluginNames = installed;
|
|
7199
|
-
} else {
|
|
7200
|
-
pluginNames = pluginChoices.map((p) => p.name);
|
|
7201
|
-
}
|
|
7202
|
-
} else if (pluginChoices.some((p) => p.name === pluginFilter)) {
|
|
7203
|
-
pluginNames = [pluginFilter];
|
|
7204
|
-
} else {
|
|
7424
|
+
if (pluginFilter !== "all" && !pluginChoices.some((p) => p.name === pluginFilter)) {
|
|
7205
7425
|
warn(`Plugin "${pluginFilter}" not found in "${marketplaceName}" \u2014 skipping.`);
|
|
7206
7426
|
return { marketplace: marketplaceName, skipped: true, message: `Plugin "${pluginFilter}" not found.` };
|
|
7207
7427
|
}
|
|
7208
|
-
const
|
|
7209
|
-
|
|
7428
|
+
const targetResults = targets.map((t) => installMarketplaceToTarget(m, marketplacePath, pluginChoices, pluginFilter, t, { updated, ...opts }));
|
|
7429
|
+
const instructions = applyInstructionsForTargets(m, marketplacePath, targets, opts.instructions);
|
|
7430
|
+
return shapeSyncResult(marketplaceName, updated, pluginFilter, targetResults, { installedOnly: opts.installedOnly, instructions });
|
|
7210
7431
|
} catch (err) {
|
|
7211
7432
|
const message = err instanceof Error ? err.message : String(err);
|
|
7212
7433
|
warn(`Marketplace "${marketplaceName}" failed \u2014 skipping. ${message}`);
|
|
@@ -7214,7 +7435,7 @@ function syncMarketplacePlugins(m, targetDir, force, pluginFilter, installedOnly
|
|
|
7214
7435
|
}
|
|
7215
7436
|
}
|
|
7216
7437
|
function performMarketplaceAdd(url, localPath, opts) {
|
|
7217
|
-
const resolvedPath =
|
|
7438
|
+
const resolvedPath = path8.resolve(localPath ?? defaultMarketplacePath(url));
|
|
7218
7439
|
const marketplaceName = opts.name ?? repoNameFromUrl(url);
|
|
7219
7440
|
cloneOrReuseMarketplace(url, resolvedPath, opts, marketplaceName);
|
|
7220
7441
|
const configPath = getGlobalConfigPath();
|
|
@@ -7227,17 +7448,18 @@ function performMarketplaceAdd(url, localPath, opts) {
|
|
|
7227
7448
|
};
|
|
7228
7449
|
upsertMarketplace(all, entry);
|
|
7229
7450
|
saveMarketplaces(configPath, existing, all);
|
|
7230
|
-
const
|
|
7231
|
-
const
|
|
7451
|
+
const targets = resolveInstallTargets(opts);
|
|
7452
|
+
const targetResults = installAllPlugins(entry, resolvedPath, targets);
|
|
7453
|
+
const instructions = applyInstructionsForTargets(entry, resolvedPath, targets, opts.instructions !== false);
|
|
7232
7454
|
return {
|
|
7233
7455
|
name: marketplaceName,
|
|
7234
7456
|
repoUrl: url,
|
|
7235
7457
|
localPath: resolvedPath,
|
|
7236
7458
|
branch: opts.branch ?? null,
|
|
7237
7459
|
tokenConfigured: !!opts.token,
|
|
7238
|
-
|
|
7239
|
-
total:
|
|
7240
|
-
|
|
7460
|
+
// One target keeps the flat shape earlier versions emitted; several nest under `targets`.
|
|
7461
|
+
...targetResults.length === 1 ? { plugins: targetResults[0].plugins, total: targetResults[0].total, target: targetResults[0].target } : { targets: targetResults, total: targetResults.reduce((sum, r) => sum + r.total, 0) },
|
|
7462
|
+
...instructions ? { instructions } : {}
|
|
7241
7463
|
};
|
|
7242
7464
|
}
|
|
7243
7465
|
function performMarketplaceRemove(name) {
|
|
@@ -7271,8 +7493,8 @@ function listBundledSkillDirs() {
|
|
|
7271
7493
|
let skillDirs = [];
|
|
7272
7494
|
if (bundledDir !== "") {
|
|
7273
7495
|
try {
|
|
7274
|
-
skillDirs =
|
|
7275
|
-
(name) =>
|
|
7496
|
+
skillDirs = fs3.readdirSync(bundledDir).filter(
|
|
7497
|
+
(name) => fs3.existsSync(path8.join(bundledDir, name, "SKILL.md"))
|
|
7276
7498
|
);
|
|
7277
7499
|
} catch {
|
|
7278
7500
|
}
|
|
@@ -7283,37 +7505,37 @@ function listBundledSkillDirs() {
|
|
|
7283
7505
|
return skillDirs;
|
|
7284
7506
|
}
|
|
7285
7507
|
function installBundledSkillsTo(targetDir, skillDirs) {
|
|
7286
|
-
const resolvedTarget =
|
|
7508
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
7287
7509
|
const bundledDir = getBundledSkillsDir();
|
|
7288
7510
|
const installed = [];
|
|
7289
7511
|
const failed = [];
|
|
7290
7512
|
for (const skillName of skillDirs) {
|
|
7291
|
-
const existingDir =
|
|
7292
|
-
if (!existingDir.startsWith(resolvedTarget +
|
|
7293
|
-
if (
|
|
7294
|
-
|
|
7513
|
+
const existingDir = path8.resolve(targetDir, skillName);
|
|
7514
|
+
if (!existingDir.startsWith(resolvedTarget + path8.sep)) continue;
|
|
7515
|
+
if (fs3.existsSync(existingDir)) {
|
|
7516
|
+
fs3.rmSync(existingDir, { recursive: true, force: true });
|
|
7295
7517
|
}
|
|
7296
7518
|
}
|
|
7297
|
-
|
|
7519
|
+
log(`Installing ${skillDirs.length} bundled skill(s) to ${targetDir}...`);
|
|
7298
7520
|
for (const skillName of skillDirs) {
|
|
7299
|
-
const skillDir =
|
|
7300
|
-
if (!skillDir.startsWith(resolvedTarget +
|
|
7521
|
+
const skillDir = path8.resolve(targetDir, skillName);
|
|
7522
|
+
if (!skillDir.startsWith(resolvedTarget + path8.sep)) {
|
|
7301
7523
|
failed.push(skillName);
|
|
7302
7524
|
continue;
|
|
7303
7525
|
}
|
|
7304
7526
|
try {
|
|
7305
|
-
const mdFiles =
|
|
7306
|
-
|
|
7527
|
+
const mdFiles = fs3.readdirSync(path8.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
|
|
7528
|
+
fs3.mkdirSync(skillDir, { recursive: true });
|
|
7307
7529
|
for (const mdFile of mdFiles) {
|
|
7308
|
-
const content =
|
|
7309
|
-
|
|
7530
|
+
const content = fs3.readFileSync(path8.join(bundledDir, skillName, mdFile), "utf8");
|
|
7531
|
+
fs3.writeFileSync(path8.join(skillDir, mdFile), content, "utf8");
|
|
7310
7532
|
}
|
|
7311
7533
|
installed.push(skillName);
|
|
7312
7534
|
} catch {
|
|
7313
7535
|
failed.push(skillName);
|
|
7314
7536
|
}
|
|
7315
7537
|
}
|
|
7316
|
-
warn(`Installed ${installed.length} skill(s) to ${targetDir}`);
|
|
7538
|
+
warn(`Installed ${installed.length} bundled skill(s) to ${targetDir}`);
|
|
7317
7539
|
if (failed.length > 0) {
|
|
7318
7540
|
warn(`Failed to install: ${failed.join(", ")}`);
|
|
7319
7541
|
}
|
|
@@ -7321,8 +7543,28 @@ function installBundledSkillsTo(targetDir, skillDirs) {
|
|
|
7321
7543
|
return { installed, failed };
|
|
7322
7544
|
}
|
|
7323
7545
|
function registerSkillsCommands(program2) {
|
|
7324
|
-
const skills = program2.command("skills").description(`
|
|
7325
|
-
skills.
|
|
7546
|
+
const skills = program2.command("skills").description(`Install bundled pncli skills and org plugins from git marketplaces (${AGENT_CHOICES})`);
|
|
7547
|
+
skills.addHelpText("after", `
|
|
7548
|
+
Where skills go:
|
|
7549
|
+
--agent codex (default) \u2192 .agents/skills (project) or ~/.agents/skills (user); read by Codex and GitHub Copilot
|
|
7550
|
+
--agent github-copilot \u2192 .github/skills or ~/.copilot/skills
|
|
7551
|
+
--agent claude-code \u2192 .claude/skills or ~/.claude/skills (--claude is a shorthand)
|
|
7552
|
+
--all-agents \u2192 every host above in one run
|
|
7553
|
+
|
|
7554
|
+
Bundled skills (ship with pncli, refresh after upgrading):
|
|
7555
|
+
pncli skills install --all-agents # into this repo, every agent host
|
|
7556
|
+
pncli skills install --all-agents --scope user # for every repo on this machine
|
|
7557
|
+
|
|
7558
|
+
Org plugins (skills and AGENTS.md/CLAUDE.md from a git-hosted marketplace):
|
|
7559
|
+
pncli skills marketplace add <git-url> --all-agents # register, clone, install everything
|
|
7560
|
+
pncli skills marketplace sync --marketplace all --all-agents
|
|
7561
|
+
pncli skills marketplace manage # interactive: toggle plugins, add/remove marketplaces
|
|
7562
|
+
pncli skills marketplace --help # the full plugin workflow
|
|
7563
|
+
|
|
7564
|
+
Check what is installed:
|
|
7565
|
+
pncli skills status | pncli skills locations | pncli doctor
|
|
7566
|
+
`);
|
|
7567
|
+
skills.command("install").description("Install the bundled pncli skills (project scope by default; --scope user for every repo)").option("--agent <agent>", `Target agent host: ${AGENT_CHOICES}`, DEFAULT_AGENT).option("--scope <scope>", "Installation scope: project | user", "project").option("--claude", "Shorthand for --agent claude-code").option("--all-agents", "Install to every supported agent host in one run").option("--target <dir>", "Override install directory (ignores --agent and --scope)").action((opts, cmd) => {
|
|
7326
7568
|
const start = Date.now();
|
|
7327
7569
|
try {
|
|
7328
7570
|
const agentExplicit = cmd.getOptionValueSource("agent") === "cli";
|
|
@@ -7332,10 +7574,9 @@ function registerSkillsCommands(program2) {
|
|
|
7332
7574
|
const scope = opts.scope === "user" ? "user" : "project";
|
|
7333
7575
|
const skillDirs = listBundledSkillDirs();
|
|
7334
7576
|
if (opts.allAgents) {
|
|
7335
|
-
const targets =
|
|
7336
|
-
const
|
|
7337
|
-
|
|
7338
|
-
return { agent, target: targetDir2, installed: installed2, failed: failed2, total: installed2.length };
|
|
7577
|
+
const targets = resolveInstallTargets({ allAgents: true }, scope).map(({ agent, target }) => {
|
|
7578
|
+
const { installed: installed2, failed: failed2 } = installBundledSkillsTo(target, skillDirs);
|
|
7579
|
+
return { agent, target, installed: installed2, failed: failed2, total: installed2.length };
|
|
7339
7580
|
});
|
|
7340
7581
|
success({
|
|
7341
7582
|
targets,
|
|
@@ -7346,7 +7587,7 @@ function registerSkillsCommands(program2) {
|
|
|
7346
7587
|
return;
|
|
7347
7588
|
}
|
|
7348
7589
|
const agentName = resolveAgentName(opts);
|
|
7349
|
-
const targetDir = opts.target ?
|
|
7590
|
+
const targetDir = opts.target ? path8.resolve(opts.target) : resolveScopedPath(resolveAgentPaths(agentName), scope);
|
|
7350
7591
|
const { installed, failed } = installBundledSkillsTo(targetDir, skillDirs);
|
|
7351
7592
|
let tracked = false;
|
|
7352
7593
|
if (opts.target && installed.length > 0) {
|
|
@@ -7375,12 +7616,12 @@ function registerSkillsCommands(program2) {
|
|
|
7375
7616
|
try {
|
|
7376
7617
|
let targetDir;
|
|
7377
7618
|
if (opts.target) {
|
|
7378
|
-
targetDir =
|
|
7619
|
+
targetDir = path8.resolve(opts.target);
|
|
7379
7620
|
} else {
|
|
7380
7621
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
7381
7622
|
targetDir = resolveScopedPath(agentConfig, opts.scope === "user" ? "user" : "project");
|
|
7382
7623
|
}
|
|
7383
|
-
if (!
|
|
7624
|
+
if (!fs3.existsSync(targetDir)) {
|
|
7384
7625
|
success({
|
|
7385
7626
|
skills: [],
|
|
7386
7627
|
total: 0,
|
|
@@ -7391,13 +7632,13 @@ function registerSkillsCommands(program2) {
|
|
|
7391
7632
|
return;
|
|
7392
7633
|
}
|
|
7393
7634
|
const meta = readInstalledMeta(targetDir);
|
|
7394
|
-
const skillDirs =
|
|
7635
|
+
const skillDirs = fs3.readdirSync(targetDir).filter((name) => {
|
|
7395
7636
|
if (name.startsWith(".")) return false;
|
|
7396
|
-
const skillPath =
|
|
7397
|
-
return
|
|
7637
|
+
const skillPath = path8.join(targetDir, name, "SKILL.md");
|
|
7638
|
+
return fs3.existsSync(skillPath);
|
|
7398
7639
|
});
|
|
7399
7640
|
const skillsList = skillDirs.map((name) => {
|
|
7400
|
-
const content =
|
|
7641
|
+
const content = fs3.readFileSync(path8.join(targetDir, name, "SKILL.md"), "utf8");
|
|
7401
7642
|
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
7402
7643
|
const data = {};
|
|
7403
7644
|
const metadata = {};
|
|
@@ -7450,24 +7691,24 @@ function registerSkillsCommands(program2) {
|
|
|
7450
7691
|
try {
|
|
7451
7692
|
let targetDir;
|
|
7452
7693
|
if (opts.target) {
|
|
7453
|
-
targetDir =
|
|
7694
|
+
targetDir = path8.resolve(opts.target);
|
|
7454
7695
|
} else {
|
|
7455
7696
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
7456
7697
|
targetDir = resolveScopedPath(agentConfig, opts.scope === "project" ? "project" : "user");
|
|
7457
7698
|
}
|
|
7458
|
-
const resolvedTarget =
|
|
7459
|
-
const skillDir =
|
|
7460
|
-
if (!skillDir.startsWith(resolvedTarget +
|
|
7699
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
7700
|
+
const skillDir = path8.resolve(targetDir, name);
|
|
7701
|
+
if (!skillDir.startsWith(resolvedTarget + path8.sep)) {
|
|
7461
7702
|
throw new Error(`Invalid skill name: "${name}"`);
|
|
7462
7703
|
}
|
|
7463
|
-
if (!
|
|
7704
|
+
if (!fs3.existsSync(skillDir)) {
|
|
7464
7705
|
throw new Error(`Skill "${name}" not found at ${skillDir}`);
|
|
7465
7706
|
}
|
|
7466
7707
|
const meta = readInstalledMeta(targetDir);
|
|
7467
7708
|
const record = meta.skills[name];
|
|
7468
|
-
|
|
7709
|
+
fs3.rmSync(skillDir, { recursive: true, force: true });
|
|
7469
7710
|
delete meta.skills[name];
|
|
7470
|
-
|
|
7711
|
+
fs3.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
7471
7712
|
success({
|
|
7472
7713
|
uninstalled: name,
|
|
7473
7714
|
target: targetDir,
|
|
@@ -7538,7 +7779,7 @@ function registerSkillsCommands(program2) {
|
|
|
7538
7779
|
const start = Date.now();
|
|
7539
7780
|
try {
|
|
7540
7781
|
const configPath = getGlobalConfigPath();
|
|
7541
|
-
const resolved =
|
|
7782
|
+
const resolved = path8.resolve(dir);
|
|
7542
7783
|
const forgotten = forgetCustomTarget(configPath, resolved);
|
|
7543
7784
|
if (!forgotten) {
|
|
7544
7785
|
warn(`${resolved} is not a tracked custom target \u2014 nothing to forget.`);
|
|
@@ -7552,16 +7793,28 @@ function registerSkillsCommands(program2) {
|
|
|
7552
7793
|
fail(err, "skills", "forget-target", start);
|
|
7553
7794
|
}
|
|
7554
7795
|
});
|
|
7555
|
-
const marketplace = skills.command("marketplace").description("
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7796
|
+
const marketplace = skills.command("marketplace").description("Org plugins from git-hosted marketplaces: add, sync, enable/disable, and shipped AGENTS.md/CLAUDE.md");
|
|
7797
|
+
marketplace.addHelpText("after", `
|
|
7798
|
+
Workflow:
|
|
7799
|
+
pncli skills marketplace add <git-url> --all-agents # register + clone + install every plugin
|
|
7800
|
+
pncli skills marketplace sync --marketplace all --all-agents # pull and refresh everything installed
|
|
7801
|
+
pncli skills marketplace plugins <name> # browse a marketplace without installing
|
|
7802
|
+
pncli skills marketplace manage # interactive hub (toggle, add, remove)
|
|
7803
|
+
pncli skills marketplace disable <plugin> / enable <plugin> # switch a plugin off without deleting it
|
|
7804
|
+
|
|
7805
|
+
Shipped instructions:
|
|
7806
|
+
A marketplace may ship instructions/AGENTS.md and/or instructions/CLAUDE.md. add and sync merge
|
|
7807
|
+
them into each agent's user-level file (~/.codex/AGENTS.md, ~/.copilot/copilot-instructions.md,
|
|
7808
|
+
~/.claude/CLAUDE.md) as a marked block that never touches your own content.
|
|
7809
|
+
pncli skills marketplace instructions list | install | remove <name>
|
|
7810
|
+
|
|
7811
|
+
Plugin skills always install at user scope. --agent picks the host (default: ${DEFAULT_AGENT}); --all-agents covers all of them.
|
|
7812
|
+
`);
|
|
7813
|
+
marketplace.command("add").alias("setup").description("Register a new marketplace, clone it, install all its plugins, and apply its shipped AGENTS.md / CLAUDE.md (`setup` is an alias)").argument("<url>", "Git clone URL of the marketplace repository").argument("[localPath]", "Local directory to clone into (default: ~/.agents/marketplaces/<repo-name>)").option("--name <name>", "Human-readable name for this marketplace (default: derived from URL)").option("--branch <branch>", "Branch to clone (default: remote HEAD)").option("--token <token>", "HTTP access token for authenticated clone and pull (GitHub PAT or Bitbucket token)").option("--agent <agent>", `Target agent host for plugin install: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).option("--claude", "Shorthand for --agent claude-code").option("--all-agents", "Install to every supported agent host in one run").option("--no-instructions", "Do not apply the shipped AGENTS.md / CLAUDE.md to user-level instructions files").action((url, localPath, opts) => {
|
|
7814
|
+
const marketplaceIdx = process.argv.indexOf("marketplace");
|
|
7815
|
+
const invokedAs = marketplaceIdx !== -1 && process.argv[marketplaceIdx + 1] === "setup" ? "marketplace-setup" : "marketplace-add";
|
|
7816
|
+
return marketplaceAddAction(url, localPath, opts, invokedAs);
|
|
7817
|
+
});
|
|
7565
7818
|
marketplace.command("list").description("List all registered marketplaces").action(() => {
|
|
7566
7819
|
const start = Date.now();
|
|
7567
7820
|
try {
|
|
@@ -7573,7 +7826,7 @@ function registerSkillsCommands(program2) {
|
|
|
7573
7826
|
repoUrl: m.repoUrl,
|
|
7574
7827
|
localPath: m.localPath,
|
|
7575
7828
|
tokenConfigured: !!m.token,
|
|
7576
|
-
upstreamRemote: m.localPath &&
|
|
7829
|
+
upstreamRemote: m.localPath && fs3.existsSync(m.localPath) ? getRepoRemoteUrl(m.localPath) : null
|
|
7577
7830
|
})),
|
|
7578
7831
|
total: all.length
|
|
7579
7832
|
}, "skills", "marketplace-list", start);
|
|
@@ -7590,7 +7843,7 @@ function registerSkillsCommands(program2) {
|
|
|
7590
7843
|
if (!found) {
|
|
7591
7844
|
throw new Error(`Marketplace "${name}" not found. Run: pncli skills marketplace list`);
|
|
7592
7845
|
}
|
|
7593
|
-
if (!found.localPath || !
|
|
7846
|
+
if (!found.localPath || !fs3.existsSync(found.localPath)) {
|
|
7594
7847
|
throw new Error(`Marketplace "${marketplaceLabel(found)}" local path not found at ${found.localPath ?? "(not set)"}. Run: pncli skills marketplace add <url>`);
|
|
7595
7848
|
}
|
|
7596
7849
|
const plugins = resolvePluginChoices(found.localPath);
|
|
@@ -7611,7 +7864,7 @@ function registerSkillsCommands(program2) {
|
|
|
7611
7864
|
fail(err, "skills", "marketplace-remove", start);
|
|
7612
7865
|
}
|
|
7613
7866
|
});
|
|
7614
|
-
marketplace.command("sync").description("
|
|
7867
|
+
marketplace.command("sync").description("With no arguments, refreshes every already-installed plugin from every marketplace (same as --marketplace all --installed-only). Pass --force, a plugin, or --marketplace to get the interactive picker instead.").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: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).option("--claude", "Shorthand for --agent claude-code").option("--all-agents", "Install to every supported agent host in one run").option("--force", 'Reinstall even if a marketplace has no new changes (applies to single-plugin and "all" installs alike)').option("--installed-only", "Only sync plugins that are already installed \u2014 skip plugins newly added to the marketplace").option("--no-instructions", "Do not apply the shipped AGENTS.md / CLAUDE.md to user-level instructions files").action(async (plugin, opts) => {
|
|
7615
7868
|
const start = Date.now();
|
|
7616
7869
|
try {
|
|
7617
7870
|
const configPath = getGlobalConfigPath();
|
|
@@ -7619,12 +7872,13 @@ function registerSkillsCommands(program2) {
|
|
|
7619
7872
|
if (allMarketplaces.length === 0) {
|
|
7620
7873
|
throw new Error("No marketplaces configured. Run: pncli skills marketplace add <url>");
|
|
7621
7874
|
}
|
|
7622
|
-
const
|
|
7623
|
-
const
|
|
7624
|
-
const
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7875
|
+
const bareSync = isBareMarketplaceSync(plugin, opts);
|
|
7876
|
+
const targets = resolveInstallTargets(opts);
|
|
7877
|
+
const syncOpts = { force: opts.force ?? false, installedOnly: bareSync ? true : opts.installedOnly ?? false, instructions: opts.instructions !== false };
|
|
7878
|
+
const targetSummary = targets.length === 1 ? { target: targets[0].target } : { targets: targets.map((t) => t.target) };
|
|
7879
|
+
if (opts.marketplace === "all" || bareSync) {
|
|
7880
|
+
const results = allMarketplaces.map((m) => syncMarketplacePlugins(m, targets, plugin ?? "all", syncOpts));
|
|
7881
|
+
success({ allMarketplaces: true, marketplaces: results, ...targetSummary }, "skills", "marketplace-sync", start);
|
|
7628
7882
|
return;
|
|
7629
7883
|
}
|
|
7630
7884
|
let selectedMarketplace;
|
|
@@ -7647,12 +7901,12 @@ function registerSkillsCommands(program2) {
|
|
|
7647
7901
|
choices: [
|
|
7648
7902
|
{ value: ALL_MARKETPLACES, name: "All marketplaces \u2014 sync every plugin from every marketplace" },
|
|
7649
7903
|
// Index-based values: labels alone aren't guaranteed unique (two marketplaces can share a derived name).
|
|
7650
|
-
...allMarketplaces.map((
|
|
7904
|
+
...allMarketplaces.map((m2, i) => ({ value: String(i), name: `${marketplaceLabel(m2)} \u2014 ${m2.repoUrl ?? ""}` }))
|
|
7651
7905
|
]
|
|
7652
7906
|
});
|
|
7653
7907
|
if (chosen === ALL_MARKETPLACES) {
|
|
7654
|
-
const
|
|
7655
|
-
success({ allMarketplaces: true, marketplaces:
|
|
7908
|
+
const results = allMarketplaces.map((m2) => syncMarketplacePlugins(m2, targets, selectedPlugin ?? "all", syncOpts));
|
|
7909
|
+
success({ allMarketplaces: true, marketplaces: results, ...targetSummary }, "skills", "marketplace-sync", start);
|
|
7656
7910
|
return;
|
|
7657
7911
|
}
|
|
7658
7912
|
selectedMarketplace = allMarketplaces[Number(chosen)];
|
|
@@ -7662,7 +7916,7 @@ function registerSkillsCommands(program2) {
|
|
|
7662
7916
|
}
|
|
7663
7917
|
const marketplaceName = marketplaceLabel(selectedMarketplace);
|
|
7664
7918
|
const marketplacePath = selectedMarketplace.localPath;
|
|
7665
|
-
if (!marketplacePath || !
|
|
7919
|
+
if (!marketplacePath || !fs3.existsSync(marketplacePath)) {
|
|
7666
7920
|
throw new Error(`Marketplace "${marketplaceName}" local path not found at ${marketplacePath ?? "(not set)"}. Run: pncli skills marketplace add <url>`);
|
|
7667
7921
|
}
|
|
7668
7922
|
const { updated } = pullMarketplace(marketplacePath, selectedMarketplace.repoUrl, selectedMarketplace.token, marketplaceName);
|
|
@@ -7688,55 +7942,94 @@ function registerSkillsCommands(program2) {
|
|
|
7688
7942
|
} else if (selectedPlugin !== "all" && !pluginChoices.some((p) => p.name === selectedPlugin)) {
|
|
7689
7943
|
throw new Error(`Plugin "${selectedPlugin}" not found in "${marketplaceName}". Available: ${pluginChoices.map((p) => p.name).join(", ")}`);
|
|
7690
7944
|
}
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7945
|
+
const m = selectedMarketplace;
|
|
7946
|
+
const targetResults = targets.map((t) => installMarketplaceToTarget(m, marketplacePath, pluginChoices, selectedPlugin, t, { updated, force: syncOpts.force, installedOnly: syncOpts.installedOnly }));
|
|
7947
|
+
const instructions2 = applyInstructionsForTargets(m, marketplacePath, targets, syncOpts.instructions);
|
|
7948
|
+
success(
|
|
7949
|
+
shapeSyncResult(marketplaceName, updated, selectedPlugin, targetResults, { installedOnly: syncOpts.installedOnly, instructions: instructions2 }),
|
|
7950
|
+
"skills",
|
|
7951
|
+
"marketplace-sync",
|
|
7952
|
+
start
|
|
7953
|
+
);
|
|
7954
|
+
return;
|
|
7955
|
+
}
|
|
7956
|
+
} catch (err) {
|
|
7957
|
+
fail(err, "skills", "marketplace-sync", start);
|
|
7958
|
+
}
|
|
7959
|
+
});
|
|
7960
|
+
const instructions = marketplace.command("instructions").description("Manage the AGENTS.md / CLAUDE.md a marketplace ships (merged as a marked block into each agent's user-level instructions file)");
|
|
7961
|
+
function resolveInstructionAgents(opts) {
|
|
7962
|
+
return resolveInstallTargets(opts).map((t) => t.agent);
|
|
7963
|
+
}
|
|
7964
|
+
function findMarketplaceOrThrow(all, name) {
|
|
7965
|
+
const found = all.find((m) => m.name === name || m.repoUrl === name);
|
|
7966
|
+
if (!found) throw new Error(`Marketplace "${name}" not found. Run: pncli skills marketplace list`);
|
|
7967
|
+
return found;
|
|
7968
|
+
}
|
|
7969
|
+
instructions.command("list").description("Show which marketplaces ship instructions and whether each agent's user-level file has them installed and current").option("--marketplace <name>", "Only report this marketplace (name or clone URL)").action((opts) => {
|
|
7970
|
+
const start = Date.now();
|
|
7971
|
+
try {
|
|
7972
|
+
const all = loadMarketplaces(getGlobalConfigPath());
|
|
7973
|
+
const selected = opts.marketplace ? [findMarketplaceOrThrow(all, opts.marketplace)] : all;
|
|
7974
|
+
const agents = Object.keys(AGENT_PATHS);
|
|
7975
|
+
const marketplaces = selected.map((m) => {
|
|
7976
|
+
const shipped = m.localPath && fs3.existsSync(m.localPath) ? findMarketplaceInstructions(m.localPath).map((s) => s.file) : [];
|
|
7977
|
+
return {
|
|
7978
|
+
marketplace: marketplaceLabel(m),
|
|
7979
|
+
localPath: m.localPath ?? null,
|
|
7980
|
+
ships: shipped,
|
|
7981
|
+
agents: marketplaceInstructionsStatus(m.localPath, marketplaceLabel(m), agents)
|
|
7982
|
+
};
|
|
7983
|
+
});
|
|
7984
|
+
success({
|
|
7985
|
+
files: Object.fromEntries(agents.map((a) => [a, agentInstructionsFile(a)])),
|
|
7986
|
+
marketplaces,
|
|
7987
|
+
hint: marketplaces.some((m) => m.ships.length > 0) ? "Apply with: pncli skills marketplace instructions install --all-agents" : "No registered marketplace ships an instructions/AGENTS.md or instructions/CLAUDE.md."
|
|
7988
|
+
}, "skills", "marketplace-instructions-list", start);
|
|
7989
|
+
} catch (err) {
|
|
7990
|
+
fail(err, "skills", "marketplace-instructions-list", start);
|
|
7991
|
+
}
|
|
7992
|
+
});
|
|
7993
|
+
instructions.command("install").description("Apply (or refresh) the shipped instructions of one or every marketplace into the agent's user-level file").option("--marketplace <name>", "Marketplace to apply (name or clone URL); default: every registered marketplace that ships instructions").option("--agent <agent>", `Target agent host: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).option("--claude", "Shorthand for --agent claude-code").option("--all-agents", "Apply to every supported agent host in one run").action((opts) => {
|
|
7994
|
+
const start = Date.now();
|
|
7995
|
+
try {
|
|
7996
|
+
const all = loadMarketplaces(getGlobalConfigPath());
|
|
7997
|
+
if (all.length === 0) throw new Error("No marketplaces configured. Run: pncli skills marketplace add <url>");
|
|
7998
|
+
const agents = resolveInstructionAgents(opts);
|
|
7999
|
+
const selected = opts.marketplace ? [findMarketplaceOrThrow(all, opts.marketplace)] : all;
|
|
8000
|
+
const results = selected.map((m) => {
|
|
8001
|
+
const name = marketplaceLabel(m);
|
|
8002
|
+
if (!m.localPath || !fs3.existsSync(m.localPath)) {
|
|
8003
|
+
return { marketplace: name, skipped: true, message: "Local path not found. Run: pncli skills marketplace add <url>" };
|
|
7700
8004
|
}
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
if (installedOnly) {
|
|
7704
|
-
const installed = getInstalledPluginsForMarketplace(targetDir, marketplaceName, selectedMarketplace.repoUrl).filter((name) => pluginChoices.some((p) => p.name === name));
|
|
7705
|
-
if (installed.length === 0) {
|
|
7706
|
-
success({
|
|
7707
|
-
marketplace: marketplaceName,
|
|
7708
|
-
skipped: true,
|
|
7709
|
-
installedOnly: true,
|
|
7710
|
-
message: `No installed plugins found for "${marketplaceName}" \u2014 run marketplace sync without --installed-only to install plugins.`
|
|
7711
|
-
}, "skills", "marketplace-sync", start);
|
|
7712
|
-
return;
|
|
7713
|
-
}
|
|
7714
|
-
pluginNames = installed;
|
|
7715
|
-
} else {
|
|
7716
|
-
pluginNames = pluginChoices.map((p) => p.name);
|
|
7717
|
-
}
|
|
7718
|
-
} else {
|
|
7719
|
-
pluginNames = [selectedPlugin];
|
|
8005
|
+
if (findMarketplaceInstructions(m.localPath).length === 0) {
|
|
8006
|
+
return { marketplace: name, skipped: true, message: `Ships no instructions/AGENTS.md or instructions/CLAUDE.md.` };
|
|
7720
8007
|
}
|
|
7721
|
-
const
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
} else {
|
|
7725
|
-
const single = results[selectedPlugin] ?? { installed: [], failed: [] };
|
|
7726
|
-
success({
|
|
7727
|
-
marketplace: marketplaceName,
|
|
7728
|
-
plugin: selectedPlugin,
|
|
7729
|
-
installed: single.installed,
|
|
7730
|
-
failed: single.failed,
|
|
7731
|
-
total: single.installed.length,
|
|
7732
|
-
target: targetDir,
|
|
7733
|
-
marketplaceUpdated: updated
|
|
7734
|
-
}, "skills", "marketplace-sync", start);
|
|
8008
|
+
const applied = applyMarketplaceInstructions(m.localPath, name, agents);
|
|
8009
|
+
for (const r of applied) {
|
|
8010
|
+
if (r.action === "added" || r.action === "updated") warn(`Instructions ${r.action} in ${r.file} (from ${r.source})`);
|
|
7735
8011
|
}
|
|
7736
|
-
return;
|
|
8012
|
+
return { marketplace: name, agents: applied };
|
|
8013
|
+
});
|
|
8014
|
+
if (opts.marketplace && "skipped" in results[0]) throw new Error(`Marketplace "${results[0].marketplace}": ${results[0].message}`);
|
|
8015
|
+
success({ marketplaces: results, agents }, "skills", "marketplace-instructions-install", start);
|
|
8016
|
+
} catch (err) {
|
|
8017
|
+
fail(err, "skills", "marketplace-instructions-install", start);
|
|
8018
|
+
}
|
|
8019
|
+
});
|
|
8020
|
+
instructions.command("remove").description("Strip a marketplace's instructions block from the agent's user-level file (everything else in the file is kept)").argument("<marketplace>", "Marketplace name (or clone URL). Works even after the marketplace has been removed from config.").option("--agent <agent>", `Target agent host: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).option("--claude", "Shorthand for --agent claude-code").option("--all-agents", "Remove from every supported agent host in one run").action((marketplaceName, opts) => {
|
|
8021
|
+
const start = Date.now();
|
|
8022
|
+
try {
|
|
8023
|
+
const agents = resolveInstructionAgents(opts);
|
|
8024
|
+
const registered = loadMarketplaces(getGlobalConfigPath()).find((m) => m.name === marketplaceName || m.repoUrl === marketplaceName);
|
|
8025
|
+
const name = registered ? marketplaceLabel(registered) : marketplaceName;
|
|
8026
|
+
const results = removeMarketplaceInstructions(name, agents);
|
|
8027
|
+
for (const r of results) {
|
|
8028
|
+
if (r.removed) warn(`Instructions block for "${name}" removed from ${r.file}`);
|
|
7737
8029
|
}
|
|
8030
|
+
success({ marketplace: name, agents: results, removed: results.filter((r) => r.removed).length }, "skills", "marketplace-instructions-remove", start);
|
|
7738
8031
|
} catch (err) {
|
|
7739
|
-
fail(err, "skills", "marketplace-
|
|
8032
|
+
fail(err, "skills", "marketplace-instructions-remove", start);
|
|
7740
8033
|
}
|
|
7741
8034
|
});
|
|
7742
8035
|
marketplace.command("purge-plugin").description("Remove all skills installed from a specific plugin (or all plugins in a marketplace)").argument("<plugin>", 'Plugin name to purge, or "all" to remove every skill from the marketplace').option("--marketplace <name>", "Restrict purge to skills from this marketplace (by name or repo URL)").option("--agent <agent>", `Target agent host: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).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) => {
|
|
@@ -7744,31 +8037,31 @@ function registerSkillsCommands(program2) {
|
|
|
7744
8037
|
try {
|
|
7745
8038
|
let targetDir;
|
|
7746
8039
|
if (opts.target) {
|
|
7747
|
-
targetDir =
|
|
8040
|
+
targetDir = path8.resolve(opts.target);
|
|
7748
8041
|
} else {
|
|
7749
8042
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
7750
8043
|
targetDir = resolveScopedPath(agentConfig, opts.scope ?? "user");
|
|
7751
8044
|
}
|
|
7752
|
-
const resolvedTarget =
|
|
7753
|
-
if (!
|
|
8045
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
8046
|
+
if (!fs3.existsSync(targetDir)) {
|
|
7754
8047
|
success({ removed: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-purge-plugin", start);
|
|
7755
8048
|
return;
|
|
7756
8049
|
}
|
|
7757
8050
|
const meta = readInstalledMeta(targetDir);
|
|
7758
8051
|
const removed = [];
|
|
7759
8052
|
const skipped = [];
|
|
7760
|
-
const skillDirs =
|
|
8053
|
+
const skillDirs = fs3.readdirSync(targetDir).filter((name) => {
|
|
7761
8054
|
if (name.startsWith(".")) return false;
|
|
7762
|
-
const p =
|
|
8055
|
+
const p = path8.join(targetDir, name);
|
|
7763
8056
|
try {
|
|
7764
|
-
return
|
|
8057
|
+
return fs3.statSync(p).isDirectory();
|
|
7765
8058
|
} catch {
|
|
7766
8059
|
return false;
|
|
7767
8060
|
}
|
|
7768
8061
|
});
|
|
7769
8062
|
for (const skillName of skillDirs) {
|
|
7770
|
-
const skillDir =
|
|
7771
|
-
if (!skillDir.startsWith(resolvedTarget +
|
|
8063
|
+
const skillDir = path8.resolve(targetDir, skillName);
|
|
8064
|
+
if (!skillDir.startsWith(resolvedTarget + path8.sep)) continue;
|
|
7772
8065
|
let record = meta.skills[skillName] ?? null;
|
|
7773
8066
|
if (!record) {
|
|
7774
8067
|
const perSkill = readSkillOrigin(skillDir);
|
|
@@ -7793,12 +8086,12 @@ function registerSkillsCommands(program2) {
|
|
|
7793
8086
|
skipped.push(skillName);
|
|
7794
8087
|
continue;
|
|
7795
8088
|
}
|
|
7796
|
-
|
|
8089
|
+
fs3.rmSync(skillDir, { recursive: true, force: true });
|
|
7797
8090
|
delete meta.skills[skillName];
|
|
7798
8091
|
removed.push(skillName);
|
|
7799
8092
|
}
|
|
7800
8093
|
if (removed.length > 0) {
|
|
7801
|
-
|
|
8094
|
+
fs3.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
7802
8095
|
}
|
|
7803
8096
|
success({
|
|
7804
8097
|
removed,
|
|
@@ -7816,7 +8109,7 @@ function registerSkillsCommands(program2) {
|
|
|
7816
8109
|
const start = Date.now();
|
|
7817
8110
|
try {
|
|
7818
8111
|
const targetDir = resolveTargetDir(opts);
|
|
7819
|
-
if (!
|
|
8112
|
+
if (!fs3.existsSync(targetDir)) {
|
|
7820
8113
|
success({ disabled: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-disable", start);
|
|
7821
8114
|
return;
|
|
7822
8115
|
}
|
|
@@ -7828,7 +8121,7 @@ function registerSkillsCommands(program2) {
|
|
|
7828
8121
|
total: result.disabled.length,
|
|
7829
8122
|
target: targetDir,
|
|
7830
8123
|
plugin,
|
|
7831
|
-
stash:
|
|
8124
|
+
stash: path8.join(targetDir, DISABLED_SUBDIR),
|
|
7832
8125
|
...opts.marketplace ? { marketplace: opts.marketplace } : {}
|
|
7833
8126
|
}, "skills", "marketplace-disable", start);
|
|
7834
8127
|
} catch (err) {
|
|
@@ -7839,7 +8132,7 @@ function registerSkillsCommands(program2) {
|
|
|
7839
8132
|
const start = Date.now();
|
|
7840
8133
|
try {
|
|
7841
8134
|
const targetDir = resolveTargetDir(opts);
|
|
7842
|
-
if (!
|
|
8135
|
+
if (!fs3.existsSync(targetDir)) {
|
|
7843
8136
|
success({ enabled: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-enable", start);
|
|
7844
8137
|
return;
|
|
7845
8138
|
}
|
|
@@ -7864,29 +8157,48 @@ function registerSkillsCommands(program2) {
|
|
|
7864
8157
|
fail(err, "skills", "marketplace-enable", start);
|
|
7865
8158
|
}
|
|
7866
8159
|
});
|
|
7867
|
-
marketplace.command("manage").description("Manage marketplaces and plugins: toggle plugins on/off, add or remove marketplaces (interactive)").option("--agent <agent>", `Target agent host: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).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) => {
|
|
8160
|
+
marketplace.command("manage").description("Manage marketplaces and plugins: toggle plugins on/off, sync every marketplace, apply shipped AGENTS.md / CLAUDE.md, add or remove marketplaces (interactive)").option("--agent <agent>", `Target agent host: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).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) => {
|
|
7868
8161
|
const start = Date.now();
|
|
7869
8162
|
try {
|
|
7870
|
-
assertInteractive("Use `pncli skills marketplace enable|disable <plugin>`, `add <url>`, and `remove <name>` to manage non-interactively.");
|
|
8163
|
+
assertInteractive("Use `pncli skills marketplace enable|disable <plugin>`, `sync --marketplace all`, `instructions install`, `add <url>`, and `remove <name>` to manage non-interactively.");
|
|
7871
8164
|
const targetDir = resolveTargetDir(opts);
|
|
7872
8165
|
const enabledPlugins = [];
|
|
7873
8166
|
const disabledPlugins = [];
|
|
7874
8167
|
const stashMissing = [];
|
|
7875
8168
|
const addedMarketplaces = [];
|
|
7876
8169
|
const removedMarketplaces = [];
|
|
8170
|
+
const syncedMarketplaces = /* @__PURE__ */ new Map();
|
|
8171
|
+
const appliedInstructions = /* @__PURE__ */ new Map();
|
|
8172
|
+
const instructionAgents = opts.target ? [] : [resolveAgentName(opts)];
|
|
7877
8173
|
for (; ; ) {
|
|
7878
|
-
const states =
|
|
8174
|
+
const states = fs3.existsSync(targetDir) ? listPluginStates(targetDir) : [];
|
|
7879
8175
|
const registered = loadMarketplaces(getGlobalConfigPath());
|
|
8176
|
+
const shipping = registered.filter((m) => m.localPath && fs3.existsSync(m.localPath) && findMarketplaceInstructions(m.localPath).length > 0);
|
|
7880
8177
|
const action = await select({
|
|
7881
8178
|
message: `Manage skills marketplaces (target: ${targetDir}):`,
|
|
7882
8179
|
choices: [
|
|
7883
8180
|
...states.length > 0 ? [{ value: "toggle", name: `Toggle plugins on/off (${states.length} installed)` }] : [],
|
|
8181
|
+
...registered.length > 0 ? [{ value: "sync", name: `Sync every marketplace (${registered.length} registered)` }] : [],
|
|
8182
|
+
...shipping.length > 0 && instructionAgents.length > 0 ? [{ value: "instructions", name: `Apply shipped AGENTS.md / CLAUDE.md (${shipping.length} marketplace(s) ship them)` }] : [],
|
|
7884
8183
|
{ value: "add", name: "Add a marketplace" },
|
|
7885
8184
|
...registered.length > 0 ? [{ value: "remove", name: `Remove a marketplace (${registered.length} registered)` }] : [],
|
|
7886
8185
|
{ value: "done", name: "Done" }
|
|
7887
8186
|
]
|
|
7888
8187
|
});
|
|
7889
8188
|
if (action === "done") break;
|
|
8189
|
+
if (action === "sync") {
|
|
8190
|
+
const syncTargets = [{ agent: opts.target ? "custom" : resolveAgentName(opts), target: targetDir }];
|
|
8191
|
+
for (const m of registered) {
|
|
8192
|
+
syncedMarketplaces.set(marketplaceLabel(m), syncMarketplacePlugins(m, syncTargets, "all", { force: false, installedOnly: false, instructions: instructionAgents.length > 0 }));
|
|
8193
|
+
}
|
|
8194
|
+
continue;
|
|
8195
|
+
}
|
|
8196
|
+
if (action === "instructions") {
|
|
8197
|
+
for (const m of shipping) {
|
|
8198
|
+
appliedInstructions.set(marketplaceLabel(m), { marketplace: marketplaceLabel(m), agents: applyMarketplaceInstructions(m.localPath, marketplaceLabel(m), instructionAgents) });
|
|
8199
|
+
}
|
|
8200
|
+
continue;
|
|
8201
|
+
}
|
|
7890
8202
|
if (action === "toggle") {
|
|
7891
8203
|
const sorted = [...states].sort((a, b) => (a.marketplace ?? "").localeCompare(b.marketplace ?? "") || a.plugin.localeCompare(b.plugin));
|
|
7892
8204
|
const choices = [];
|
|
@@ -7936,7 +8248,9 @@ function registerSkillsCommands(program2) {
|
|
|
7936
8248
|
addedMarketplaces.push(performMarketplaceAdd(url, void 0, {
|
|
7937
8249
|
name: name || void 0,
|
|
7938
8250
|
agent: opts.agent,
|
|
7939
|
-
claude: opts.claude
|
|
8251
|
+
claude: opts.claude,
|
|
8252
|
+
// A custom --target has no agent host to map instructions onto.
|
|
8253
|
+
instructions: instructionAgents.length > 0
|
|
7940
8254
|
}));
|
|
7941
8255
|
} else if (action === "remove") {
|
|
7942
8256
|
const chosen = await select({
|
|
@@ -7957,6 +8271,8 @@ function registerSkillsCommands(program2) {
|
|
|
7957
8271
|
disabled: disabledPlugins,
|
|
7958
8272
|
addedMarketplaces,
|
|
7959
8273
|
removedMarketplaces,
|
|
8274
|
+
syncedMarketplaces: [...syncedMarketplaces.values()],
|
|
8275
|
+
appliedInstructions: [...appliedInstructions.values()],
|
|
7960
8276
|
target: targetDir,
|
|
7961
8277
|
...stashMissing.length > 0 ? {
|
|
7962
8278
|
stashMissing,
|
|
@@ -7971,15 +8287,15 @@ function registerSkillsCommands(program2) {
|
|
|
7971
8287
|
const start = Date.now();
|
|
7972
8288
|
try {
|
|
7973
8289
|
const userDir = resolveAgentPaths(resolveAgentName(opts)).user;
|
|
7974
|
-
if (!
|
|
8290
|
+
if (!fs3.existsSync(userDir)) {
|
|
7975
8291
|
success({ removed: 0, target: userDir, message: "No user-level skills directory found \u2014 nothing to purge." }, "skills", "purge-user", start);
|
|
7976
8292
|
return;
|
|
7977
8293
|
}
|
|
7978
|
-
const skillDirs =
|
|
8294
|
+
const skillDirs = fs3.readdirSync(userDir).filter((name) => {
|
|
7979
8295
|
if (name.startsWith(".")) return false;
|
|
7980
|
-
const p =
|
|
8296
|
+
const p = path8.join(userDir, name);
|
|
7981
8297
|
try {
|
|
7982
|
-
return
|
|
8298
|
+
return fs3.statSync(p).isDirectory();
|
|
7983
8299
|
} catch {
|
|
7984
8300
|
return false;
|
|
7985
8301
|
}
|
|
@@ -7995,13 +8311,13 @@ Re-run with --force to confirm, or use \`pncli skills marketplace purge-plugin\`
|
|
|
7995
8311
|
);
|
|
7996
8312
|
}
|
|
7997
8313
|
for (const name of skillDirs) {
|
|
7998
|
-
const skillDir =
|
|
7999
|
-
if (!skillDir.startsWith(
|
|
8000
|
-
|
|
8314
|
+
const skillDir = path8.resolve(userDir, name);
|
|
8315
|
+
if (!skillDir.startsWith(path8.resolve(userDir) + path8.sep)) continue;
|
|
8316
|
+
fs3.rmSync(skillDir, { recursive: true, force: true });
|
|
8001
8317
|
}
|
|
8002
8318
|
const metaPath = getInstalledMetaPath(userDir);
|
|
8003
|
-
if (
|
|
8004
|
-
|
|
8319
|
+
if (fs3.existsSync(metaPath)) {
|
|
8320
|
+
fs3.writeFileSync(metaPath, JSON.stringify({ version: 1, skills: {} }, null, 2), "utf8");
|
|
8005
8321
|
}
|
|
8006
8322
|
success({ removed: skillDirs.length, skills: skillDirs, target: userDir }, "skills", "purge-user", start);
|
|
8007
8323
|
} catch (err) {
|
|
@@ -8010,46 +8326,46 @@ Re-run with --force to confirm, or use \`pncli skills marketplace purge-plugin\`
|
|
|
8010
8326
|
});
|
|
8011
8327
|
}
|
|
8012
8328
|
function resolvePluginChoices(marketplacePath) {
|
|
8013
|
-
const marketplaceJsonPath =
|
|
8014
|
-
if (
|
|
8329
|
+
const marketplaceJsonPath = path8.join(marketplacePath, ".claude-plugin", "marketplace.json");
|
|
8330
|
+
if (fs3.existsSync(marketplaceJsonPath)) {
|
|
8015
8331
|
try {
|
|
8016
|
-
const meta = JSON.parse(
|
|
8332
|
+
const meta = JSON.parse(fs3.readFileSync(marketplaceJsonPath, "utf8"));
|
|
8017
8333
|
if (Array.isArray(meta.plugins) && meta.plugins.length > 0) {
|
|
8018
8334
|
return meta.plugins.map((p) => ({ name: p.name, description: p.description ?? "" }));
|
|
8019
8335
|
}
|
|
8020
8336
|
} catch {
|
|
8021
8337
|
}
|
|
8022
8338
|
}
|
|
8023
|
-
const pluginsDir =
|
|
8024
|
-
if (
|
|
8025
|
-
return
|
|
8339
|
+
const pluginsDir = path8.join(marketplacePath, "plugins");
|
|
8340
|
+
if (fs3.existsSync(pluginsDir)) {
|
|
8341
|
+
return fs3.readdirSync(pluginsDir).filter((name) => fs3.statSync(path8.join(pluginsDir, name)).isDirectory()).map((name) => ({ name, description: "" }));
|
|
8026
8342
|
}
|
|
8027
8343
|
return [];
|
|
8028
8344
|
}
|
|
8029
8345
|
function resolveSkillsSrc(marketplacePath, selectedPlugin) {
|
|
8030
|
-
const pluginsBase =
|
|
8031
|
-
const skillsSrc =
|
|
8032
|
-
if (!skillsSrc.startsWith(pluginsBase +
|
|
8346
|
+
const pluginsBase = path8.resolve(marketplacePath, "plugins");
|
|
8347
|
+
const skillsSrc = path8.resolve(pluginsBase, selectedPlugin, "skills");
|
|
8348
|
+
if (!skillsSrc.startsWith(pluginsBase + path8.sep)) {
|
|
8033
8349
|
throw new Error(`Invalid plugin name: "${selectedPlugin}"`);
|
|
8034
8350
|
}
|
|
8035
8351
|
return skillsSrc;
|
|
8036
8352
|
}
|
|
8037
8353
|
function copyPluginSkills(skillsSrc, targetDir, meta) {
|
|
8038
|
-
|
|
8039
|
-
const resolvedTarget =
|
|
8040
|
-
const skillNames =
|
|
8041
|
-
(name) =>
|
|
8354
|
+
fs3.mkdirSync(targetDir, { recursive: true });
|
|
8355
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
8356
|
+
const skillNames = fs3.readdirSync(skillsSrc).filter(
|
|
8357
|
+
(name) => fs3.statSync(path8.join(skillsSrc, name)).isDirectory()
|
|
8042
8358
|
);
|
|
8043
8359
|
const installed = [];
|
|
8044
8360
|
const failed = [];
|
|
8045
8361
|
for (const skillName of skillNames) {
|
|
8046
|
-
const dest =
|
|
8047
|
-
if (!dest.startsWith(resolvedTarget +
|
|
8362
|
+
const dest = path8.resolve(targetDir, skillName);
|
|
8363
|
+
if (!dest.startsWith(resolvedTarget + path8.sep)) {
|
|
8048
8364
|
failed.push(skillName);
|
|
8049
8365
|
continue;
|
|
8050
8366
|
}
|
|
8051
|
-
|
|
8052
|
-
|
|
8367
|
+
fs3.rmSync(dest, { recursive: true, force: true });
|
|
8368
|
+
fs3.cpSync(path8.join(skillsSrc, skillName), dest, { recursive: true });
|
|
8053
8369
|
installed.push(skillName);
|
|
8054
8370
|
}
|
|
8055
8371
|
if (meta) {
|
|
@@ -8066,7 +8382,7 @@ function copyPluginSkills(skillsSrc, targetDir, meta) {
|
|
|
8066
8382
|
|
|
8067
8383
|
// src/services/config/commands.ts
|
|
8068
8384
|
import chalk from "chalk";
|
|
8069
|
-
import
|
|
8385
|
+
import fs4 from "fs";
|
|
8070
8386
|
function registerConfigCommands(program2) {
|
|
8071
8387
|
const config = program2.command("config").description("Manage pncli configuration");
|
|
8072
8388
|
config.command("init").description("Setup wizard for service credentials (interactive)").option("--repo", "Write repo config (.pncli.json) instead of global config").action(async (opts) => {
|
|
@@ -8180,8 +8496,8 @@ function registerConfigCommands(program2) {
|
|
|
8180
8496
|
if (cfg.ado.baseUrl) {
|
|
8181
8497
|
try {
|
|
8182
8498
|
const collection = cfg.defaults.ado?.collection;
|
|
8183
|
-
const
|
|
8184
|
-
await http.ado(
|
|
8499
|
+
const path11 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
|
|
8500
|
+
await http.ado(path11);
|
|
8185
8501
|
results.ado = { ok: true, message: "connected" };
|
|
8186
8502
|
} catch (err) {
|
|
8187
8503
|
results.ado = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
@@ -9059,7 +9375,7 @@ async function initGlobalConfig(start) {
|
|
|
9059
9375
|
});
|
|
9060
9376
|
alationUserId = await input2({
|
|
9061
9377
|
message: "Alation user ID (numeric):",
|
|
9062
|
-
validate: (v) =>
|
|
9378
|
+
validate: (v) => /^[1-9]\d*$/.test(v.trim()) || "Must be a positive integer"
|
|
9063
9379
|
});
|
|
9064
9380
|
if (alationBaseUrl && alationRefreshToken && alationUserId) {
|
|
9065
9381
|
process.stderr.write("\n Verifying connection...\n");
|
|
@@ -9312,7 +9628,7 @@ async function initRepoConfig(start) {
|
|
|
9312
9628
|
process.exitCode = ExitCode.SUCCESS;
|
|
9313
9629
|
return;
|
|
9314
9630
|
}
|
|
9315
|
-
if (
|
|
9631
|
+
if (fs4.existsSync(".pncli.json")) {
|
|
9316
9632
|
const overwrite = await confirm({
|
|
9317
9633
|
message: ".pncli.json already exists. Overwrite?",
|
|
9318
9634
|
default: false
|
|
@@ -9346,14 +9662,14 @@ async function initRepoConfig(start) {
|
|
|
9346
9662
|
}
|
|
9347
9663
|
|
|
9348
9664
|
// src/services/doctor/commands.ts
|
|
9349
|
-
import
|
|
9350
|
-
import
|
|
9665
|
+
import fs5 from "fs";
|
|
9666
|
+
import path9 from "path";
|
|
9351
9667
|
function checkConfigFile(filePath) {
|
|
9352
|
-
if (!
|
|
9668
|
+
if (!fs5.existsSync(filePath)) {
|
|
9353
9669
|
return { path: filePath, exists: false, valid: false, message: "not present" };
|
|
9354
9670
|
}
|
|
9355
9671
|
try {
|
|
9356
|
-
JSON.parse(
|
|
9672
|
+
JSON.parse(fs5.readFileSync(filePath, "utf8"));
|
|
9357
9673
|
return { path: filePath, exists: true, valid: true, message: "ok" };
|
|
9358
9674
|
} catch (err) {
|
|
9359
9675
|
return {
|
|
@@ -9364,7 +9680,7 @@ function checkConfigFile(filePath) {
|
|
|
9364
9680
|
};
|
|
9365
9681
|
}
|
|
9366
9682
|
}
|
|
9367
|
-
function buildProblems(globalFile, repoFile, credentials, skillLocations) {
|
|
9683
|
+
function buildProblems(globalFile, repoFile, credentials, skillLocations, marketplaces = []) {
|
|
9368
9684
|
const problems = [];
|
|
9369
9685
|
for (const file of [globalFile, repoFile]) {
|
|
9370
9686
|
if (file.exists && !file.valid) {
|
|
@@ -9415,16 +9731,32 @@ function buildProblems(globalFile, repoFile, credentials, skillLocations) {
|
|
|
9415
9731
|
fix: "Refresh them with: pncli skills install"
|
|
9416
9732
|
});
|
|
9417
9733
|
}
|
|
9734
|
+
for (const m of marketplaces.filter((m2) => !m2.cloneExists)) {
|
|
9735
|
+
problems.push({
|
|
9736
|
+
area: "marketplaces",
|
|
9737
|
+
message: `Marketplace "${m.name}" is registered but its clone is missing (${m.localPath ?? "no local path"})`,
|
|
9738
|
+
fix: `Re-clone it with: pncli skills marketplace add ${m.repoUrl ?? "<url>"}`
|
|
9739
|
+
});
|
|
9740
|
+
}
|
|
9741
|
+
const marketplaceSkillTotal = skillLocations.reduce((sum, l) => sum + (l.marketplaceSkills ?? 0), 0);
|
|
9742
|
+
const liveMarketplaces = marketplaces.filter((m) => m.cloneExists).length;
|
|
9743
|
+
if (liveMarketplaces > 0 && marketplaceSkillTotal === 0) {
|
|
9744
|
+
problems.push({
|
|
9745
|
+
area: "marketplaces",
|
|
9746
|
+
message: `${liveMarketplaces} marketplace(s) registered but no plugin skills are installed from any of them`,
|
|
9747
|
+
fix: "Run: pncli skills marketplace sync --marketplace all --all-agents"
|
|
9748
|
+
});
|
|
9749
|
+
}
|
|
9418
9750
|
return problems;
|
|
9419
9751
|
}
|
|
9420
9752
|
function registerDoctorCommands(program2) {
|
|
9421
|
-
program2.command("doctor").description("Diagnose pncli setup: config files, credentials, and
|
|
9753
|
+
program2.command("doctor").description("Diagnose pncli setup: config files, credentials, agent skills, and skills marketplaces").option("--offline", "Skip credential checks (no network calls)").action(async (cmdOpts) => {
|
|
9422
9754
|
const start = Date.now();
|
|
9423
9755
|
try {
|
|
9424
9756
|
const opts = program2.optsWithGlobals();
|
|
9425
9757
|
const globalFile = checkConfigFile(opts.config ?? getGlobalConfigPath());
|
|
9426
9758
|
const repoRoot = findGitRoot() ?? process.cwd();
|
|
9427
|
-
const repoFile = checkConfigFile(
|
|
9759
|
+
const repoFile = checkConfigFile(path9.join(repoRoot, ".pncli.json"));
|
|
9428
9760
|
let credentials = null;
|
|
9429
9761
|
let credentialsSkipped = cmdOpts.offline ? "--offline" : null;
|
|
9430
9762
|
if (!cmdOpts.offline) {
|
|
@@ -9442,9 +9774,16 @@ function registerDoctorCommands(program2) {
|
|
|
9442
9774
|
path: l.path,
|
|
9443
9775
|
exists: l.exists,
|
|
9444
9776
|
totalSkills: l.totalSkills,
|
|
9777
|
+
marketplaceSkills: l.marketplaceSkills,
|
|
9445
9778
|
staleSkills: l.exists ? findStaleBundledSkills(l.path) : []
|
|
9446
9779
|
}));
|
|
9447
|
-
const
|
|
9780
|
+
const marketplaces = getAllMarketplaces(globalConfig).map((m) => ({
|
|
9781
|
+
name: m.name ?? m.repoUrl ?? "(unnamed)",
|
|
9782
|
+
repoUrl: m.repoUrl ?? null,
|
|
9783
|
+
localPath: m.localPath ?? null,
|
|
9784
|
+
cloneExists: !!m.localPath && fs5.existsSync(m.localPath)
|
|
9785
|
+
}));
|
|
9786
|
+
const problems = buildProblems(globalFile, repoFile, credentials, skillLocations, marketplaces);
|
|
9448
9787
|
if (credentials) {
|
|
9449
9788
|
const statuses = Object.values(credentials).map((r) => r.status);
|
|
9450
9789
|
if (statuses.includes("invalid")) process.exitCode = ExitCode.AUTH_ERROR;
|
|
@@ -9463,6 +9802,10 @@ function registerDoctorCommands(program2) {
|
|
|
9463
9802
|
locations: skillLocations,
|
|
9464
9803
|
staleTotal: skillLocations.reduce((sum, l) => sum + l.staleSkills.length, 0)
|
|
9465
9804
|
},
|
|
9805
|
+
marketplaces: {
|
|
9806
|
+
registered: marketplaces,
|
|
9807
|
+
hint: marketplaces.length === 0 ? "No org marketplaces registered. Add one with: pncli skills marketplace add <git-url> --all-agents" : "Refresh with: pncli skills marketplace sync --marketplace all --all-agents"
|
|
9808
|
+
},
|
|
9466
9809
|
problems
|
|
9467
9810
|
}, "doctor", "check", start);
|
|
9468
9811
|
} catch (err) {
|
|
@@ -9852,8 +10195,8 @@ function registerAdoProjectCommands(ado) {
|
|
|
9852
10195
|
}
|
|
9853
10196
|
|
|
9854
10197
|
// src/services/ado/commands/work.ts
|
|
9855
|
-
import
|
|
9856
|
-
import
|
|
10198
|
+
import fs6 from "fs";
|
|
10199
|
+
import path10 from "path";
|
|
9857
10200
|
|
|
9858
10201
|
// src/services/ado/input-schema.ts
|
|
9859
10202
|
var ADO_WORK_INPUT_FILE_SCHEMA = {
|
|
@@ -10094,11 +10437,11 @@ function registerAdoWorkCommands(ado) {
|
|
|
10094
10437
|
const attachments = await workClient.listAttachments(collection, parseInt(opts.id, 10));
|
|
10095
10438
|
const attachment = attachments.find((a) => a.id === opts.attachmentId);
|
|
10096
10439
|
if (!attachment) throw new PncliError(`Attachment not found: ${opts.attachmentId}`, 1);
|
|
10097
|
-
const outDir = opts.dir ??
|
|
10098
|
-
|
|
10099
|
-
const outPath =
|
|
10440
|
+
const outDir = opts.dir ?? path10.join(process.cwd(), ".pncli");
|
|
10441
|
+
fs6.mkdirSync(outDir, { recursive: true });
|
|
10442
|
+
const outPath = path10.join(outDir, path10.basename(attachment.name));
|
|
10100
10443
|
const buffer = await workClient.downloadAttachment(attachment.url);
|
|
10101
|
-
|
|
10444
|
+
fs6.writeFileSync(outPath, buffer);
|
|
10102
10445
|
success({ saved: outPath, name: attachment.name, size: buffer.length }, "ado", "work-download-attachment", start);
|
|
10103
10446
|
} catch (err) {
|
|
10104
10447
|
fail(err, "ado", "work-download-attachment", start);
|
|
@@ -10132,10 +10475,10 @@ function registerAdoWorkCommands(ado) {
|
|
|
10132
10475
|
const types = await discoverTypes(workClient, collection, project);
|
|
10133
10476
|
if (opts.save) {
|
|
10134
10477
|
const globalConfigPath = getGlobalConfigPath(globalOpts.config);
|
|
10135
|
-
const
|
|
10136
|
-
const existing = JSON.parse(
|
|
10478
|
+
const fs9 = await import("fs");
|
|
10479
|
+
const existing = JSON.parse(fs9.readFileSync(globalConfigPath, "utf8") || "{}");
|
|
10137
10480
|
existing.ado = { ...existing.ado ?? {}, discoveredTypes: types };
|
|
10138
|
-
|
|
10481
|
+
fs9.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
10139
10482
|
warn(`Saved ${types.length} types to ${globalConfigPath}`);
|
|
10140
10483
|
}
|
|
10141
10484
|
success(types, "ado", "work-types", start);
|
|
@@ -10164,11 +10507,11 @@ function registerAdoWorkCommands(ado) {
|
|
|
10164
10507
|
) : allFields;
|
|
10165
10508
|
if (opts.save) {
|
|
10166
10509
|
const globalConfigPath = getGlobalConfigPath(globalOpts.config);
|
|
10167
|
-
const
|
|
10168
|
-
const existing = JSON.parse(
|
|
10510
|
+
const fs9 = await import("fs");
|
|
10511
|
+
const existing = JSON.parse(fs9.readFileSync(globalConfigPath, "utf8") || "{}");
|
|
10169
10512
|
const aliases = buildDefaultAliases(allFields);
|
|
10170
10513
|
existing.ado = { ...existing.ado ?? {}, discoveredFields: allFields, fieldAliases: aliases };
|
|
10171
|
-
|
|
10514
|
+
fs9.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
10172
10515
|
warn(`Saved ${allFields.length} fields and ${Object.keys(aliases).length} aliases to ${globalConfigPath}`);
|
|
10173
10516
|
}
|
|
10174
10517
|
success(fields, "ado", "work-fields", start);
|
|
@@ -10469,7 +10812,7 @@ function registerAdoRepoCommands(ado) {
|
|
|
10469
10812
|
const config = lc({ configPath: globalOpts.config });
|
|
10470
10813
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
10471
10814
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
10472
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
10815
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-V7PZD7LE.js");
|
|
10473
10816
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
10474
10817
|
const coreClient = new AdoCoreClient2(http);
|
|
10475
10818
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -10939,18 +11282,18 @@ var ArtifactoryClient = class {
|
|
|
10939
11282
|
}
|
|
10940
11283
|
});
|
|
10941
11284
|
}
|
|
10942
|
-
async getStorageInfo(repo,
|
|
10943
|
-
const cleanPath =
|
|
11285
|
+
async getStorageInfo(repo, path11) {
|
|
11286
|
+
const cleanPath = path11.startsWith("/") ? path11.slice(1) : path11;
|
|
10944
11287
|
return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`);
|
|
10945
11288
|
}
|
|
10946
|
-
async getProperties(repo,
|
|
10947
|
-
const cleanPath =
|
|
11289
|
+
async getProperties(repo, path11) {
|
|
11290
|
+
const cleanPath = path11.startsWith("/") ? path11.slice(1) : path11;
|
|
10948
11291
|
return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
|
|
10949
11292
|
params: { properties: "" }
|
|
10950
11293
|
});
|
|
10951
11294
|
}
|
|
10952
|
-
async setProperties(repo,
|
|
10953
|
-
const cleanPath =
|
|
11295
|
+
async setProperties(repo, path11, properties, opts = {}) {
|
|
11296
|
+
const cleanPath = path11.startsWith("/") ? path11.slice(1) : path11;
|
|
10954
11297
|
const propString = Object.entries(properties).map(([k, v]) => `${k}=${v}`).join(";");
|
|
10955
11298
|
await this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
|
|
10956
11299
|
method: "PUT",
|
|
@@ -11032,27 +11375,27 @@ function registerArtifactoryCommands(program2) {
|
|
|
11032
11375
|
fail(err, "artifactory", "repos", start);
|
|
11033
11376
|
}
|
|
11034
11377
|
});
|
|
11035
|
-
art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo,
|
|
11378
|
+
art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo, path11) => {
|
|
11036
11379
|
const start = Date.now();
|
|
11037
11380
|
try {
|
|
11038
11381
|
const client = getClient8(program2);
|
|
11039
|
-
const data = await client.getStorageInfo(repo,
|
|
11382
|
+
const data = await client.getStorageInfo(repo, path11);
|
|
11040
11383
|
success(data, "artifactory", "artifact-info", start);
|
|
11041
11384
|
} catch (err) {
|
|
11042
11385
|
fail(err, "artifactory", "artifact-info", start);
|
|
11043
11386
|
}
|
|
11044
11387
|
});
|
|
11045
|
-
art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo,
|
|
11388
|
+
art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo, path11) => {
|
|
11046
11389
|
const start = Date.now();
|
|
11047
11390
|
try {
|
|
11048
11391
|
const client = getClient8(program2);
|
|
11049
|
-
const data = await client.getProperties(repo,
|
|
11392
|
+
const data = await client.getProperties(repo, path11);
|
|
11050
11393
|
success(data, "artifactory", "properties-get", start);
|
|
11051
11394
|
} catch (err) {
|
|
11052
11395
|
fail(err, "artifactory", "properties-get", start);
|
|
11053
11396
|
}
|
|
11054
11397
|
});
|
|
11055
|
-
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,
|
|
11398
|
+
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, path11, keyvals, opts) => {
|
|
11056
11399
|
const start = Date.now();
|
|
11057
11400
|
try {
|
|
11058
11401
|
if (!keyvals.length) throw new PncliError("Provide at least one key=value pair");
|
|
@@ -11063,8 +11406,8 @@ function registerArtifactoryCommands(program2) {
|
|
|
11063
11406
|
properties[kv.slice(0, eq)] = kv.slice(eq + 1);
|
|
11064
11407
|
}
|
|
11065
11408
|
const client = getClient8(program2);
|
|
11066
|
-
await client.setProperties(repo,
|
|
11067
|
-
success({ repo, path:
|
|
11409
|
+
await client.setProperties(repo, path11, properties, { recursive: opts.recursive });
|
|
11410
|
+
success({ repo, path: path11, properties }, "artifactory", "properties-set", start);
|
|
11068
11411
|
} catch (err) {
|
|
11069
11412
|
fail(err, "artifactory", "properties-set", start);
|
|
11070
11413
|
}
|
|
@@ -11382,8 +11725,8 @@ function registerSonatypeIqCommands(program2) {
|
|
|
11382
11725
|
const startTime = Date.now();
|
|
11383
11726
|
try {
|
|
11384
11727
|
const http = getHttpClient(program2);
|
|
11385
|
-
const
|
|
11386
|
-
const data = await http.sonatypeiq(
|
|
11728
|
+
const path11 = opts.organizationId ? `/api/v2/policies/organization/${encodeURIComponent(opts.organizationId)}` : "/api/v2/policies";
|
|
11729
|
+
const data = await http.sonatypeiq(path11);
|
|
11387
11730
|
success(data, "sonatypeiq", "policies list", startTime);
|
|
11388
11731
|
} catch (err) {
|
|
11389
11732
|
fail(err, "sonatypeiq", "policies list", startTime);
|
|
@@ -12078,7 +12421,7 @@ function registerLogscaleCommands(program2) {
|
|
|
12078
12421
|
}
|
|
12079
12422
|
|
|
12080
12423
|
// src/services/splitio/commands.ts
|
|
12081
|
-
import
|
|
12424
|
+
import fs7 from "fs";
|
|
12082
12425
|
import confirm2 from "@inquirer/confirm";
|
|
12083
12426
|
function getHttp4(program2) {
|
|
12084
12427
|
const opts = program2.optsWithGlobals();
|
|
@@ -12090,9 +12433,9 @@ function getHttp4(program2) {
|
|
|
12090
12433
|
function loadInputFile(filePath) {
|
|
12091
12434
|
let raw;
|
|
12092
12435
|
if (filePath === "-") {
|
|
12093
|
-
raw =
|
|
12436
|
+
raw = fs7.readFileSync(process.stdin.fd, "utf8");
|
|
12094
12437
|
} else {
|
|
12095
|
-
raw =
|
|
12438
|
+
raw = fs7.readFileSync(filePath, "utf8");
|
|
12096
12439
|
}
|
|
12097
12440
|
try {
|
|
12098
12441
|
return JSON.parse(raw);
|
|
@@ -12143,7 +12486,7 @@ function registerSplitioCommands(program2) {
|
|
|
12143
12486
|
try {
|
|
12144
12487
|
const http = getHttp4(program2);
|
|
12145
12488
|
const data = await http.splitio(
|
|
12146
|
-
`/internal/api/v2/
|
|
12489
|
+
`/internal/api/v2/environments/ws/${encodeURIComponent(opts.workspace)}`
|
|
12147
12490
|
);
|
|
12148
12491
|
success(
|
|
12149
12492
|
{ count: data.objects.length, environments: data.objects },
|
|
@@ -12161,12 +12504,14 @@ function registerSplitioCommands(program2) {
|
|
|
12161
12504
|
try {
|
|
12162
12505
|
const http = getHttp4(program2);
|
|
12163
12506
|
const params = {
|
|
12164
|
-
wsId: opts.workspace,
|
|
12165
12507
|
limit: parseInt(opts.limit, 10),
|
|
12166
12508
|
offset: parseInt(opts.offset, 10)
|
|
12167
12509
|
};
|
|
12168
12510
|
if (opts.flagSet) params["flagSetName"] = opts.flagSet;
|
|
12169
|
-
const data = await http.splitio(
|
|
12511
|
+
const data = await http.splitio(
|
|
12512
|
+
`/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}`,
|
|
12513
|
+
{ params }
|
|
12514
|
+
);
|
|
12170
12515
|
success(
|
|
12171
12516
|
{ count: data.objects.length, totalCount: data.totalCount, flags: data.objects },
|
|
12172
12517
|
"splitio",
|
|
@@ -12183,14 +12528,12 @@ function registerSplitioCommands(program2) {
|
|
|
12183
12528
|
const http = getHttp4(program2);
|
|
12184
12529
|
if (opts.environment) {
|
|
12185
12530
|
const data = await http.splitio(
|
|
12186
|
-
`/internal/api/v2/splits/${encodeURIComponent(opts.flag)}/
|
|
12187
|
-
{ params: { wsId: opts.workspace } }
|
|
12531
|
+
`/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}/name/${encodeURIComponent(opts.flag)}/environment/${encodeURIComponent(opts.environment)}`
|
|
12188
12532
|
);
|
|
12189
12533
|
success(data, "splitio", "flags get", start);
|
|
12190
12534
|
} else {
|
|
12191
12535
|
const data = await http.splitio(
|
|
12192
|
-
`/internal/api/v2/splits/${encodeURIComponent(opts.flag)}
|
|
12193
|
-
{ params: { wsId: opts.workspace } }
|
|
12536
|
+
`/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}/name/${encodeURIComponent(opts.flag)}`
|
|
12194
12537
|
);
|
|
12195
12538
|
success(data, "splitio", "flags get", start);
|
|
12196
12539
|
}
|
|
@@ -12215,7 +12558,7 @@ function registerSplitioCommands(program2) {
|
|
|
12215
12558
|
},
|
|
12216
12559
|
workspace: { id: opts.workspace }
|
|
12217
12560
|
};
|
|
12218
|
-
const data = await http.splitio(
|
|
12561
|
+
const data = await http.splitio(`/internal/api/v2/changeRequests/ws/${encodeURIComponent(opts.workspace)}`, {
|
|
12219
12562
|
method: "POST",
|
|
12220
12563
|
body
|
|
12221
12564
|
});
|
|
@@ -12232,8 +12575,7 @@ function registerSplitioCommands(program2) {
|
|
|
12232
12575
|
}
|
|
12233
12576
|
const http = getHttp4(program2);
|
|
12234
12577
|
const current = await http.splitio(
|
|
12235
|
-
`/internal/api/v2/splits/${encodeURIComponent(opts.flag)}/
|
|
12236
|
-
{ params: { wsId: opts.workspace } }
|
|
12578
|
+
`/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}/name/${encodeURIComponent(opts.flag)}/environment/${encodeURIComponent(opts.environment)}`
|
|
12237
12579
|
);
|
|
12238
12580
|
const rules = current.rules ?? [];
|
|
12239
12581
|
const idx = parseInt(opts.ruleIndex, 10);
|
|
@@ -12257,7 +12599,7 @@ function registerSplitioCommands(program2) {
|
|
|
12257
12599
|
},
|
|
12258
12600
|
workspace: { id: opts.workspace }
|
|
12259
12601
|
};
|
|
12260
|
-
const data = await http.splitio(
|
|
12602
|
+
const data = await http.splitio(`/internal/api/v2/changeRequests/ws/${encodeURIComponent(opts.workspace)}`, {
|
|
12261
12603
|
method: "POST",
|
|
12262
12604
|
body
|
|
12263
12605
|
});
|
|
@@ -12271,8 +12613,7 @@ function registerSplitioCommands(program2) {
|
|
|
12271
12613
|
try {
|
|
12272
12614
|
const http = getHttp4(program2);
|
|
12273
12615
|
const current = await http.splitio(
|
|
12274
|
-
`/internal/api/v2/splits/${encodeURIComponent(opts.flag)}/
|
|
12275
|
-
{ params: { wsId: opts.workspace } }
|
|
12616
|
+
`/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}/name/${encodeURIComponent(opts.flag)}/environment/${encodeURIComponent(opts.environment)}`
|
|
12276
12617
|
);
|
|
12277
12618
|
await confirmWrite(opts, `SET_DEFAULT ${opts.treatment} on ${opts.flag} in ${opts.environment}`);
|
|
12278
12619
|
const body = {
|
|
@@ -12288,7 +12629,7 @@ function registerSplitioCommands(program2) {
|
|
|
12288
12629
|
},
|
|
12289
12630
|
workspace: { id: opts.workspace }
|
|
12290
12631
|
};
|
|
12291
|
-
const data = await http.splitio(
|
|
12632
|
+
const data = await http.splitio(`/internal/api/v2/changeRequests/ws/${encodeURIComponent(opts.workspace)}`, {
|
|
12292
12633
|
method: "POST",
|
|
12293
12634
|
body
|
|
12294
12635
|
});
|
|
@@ -12310,7 +12651,7 @@ function registerSplitioCommands(program2) {
|
|
|
12310
12651
|
split: { name: opts.flag, environment: { id: opts.environment } },
|
|
12311
12652
|
workspace: { id: opts.workspace }
|
|
12312
12653
|
};
|
|
12313
|
-
const data = await http.splitio(
|
|
12654
|
+
const data = await http.splitio(`/internal/api/v2/changeRequests/ws/${encodeURIComponent(opts.workspace)}`, {
|
|
12314
12655
|
method: "POST",
|
|
12315
12656
|
body
|
|
12316
12657
|
});
|
|
@@ -12338,7 +12679,7 @@ function registerSplitioCommands(program2) {
|
|
|
12338
12679
|
split: { name: entry.flag, environment: { id: opts.environment } },
|
|
12339
12680
|
workspace: { id: opts.workspace }
|
|
12340
12681
|
};
|
|
12341
|
-
const cr = await http.splitio(
|
|
12682
|
+
const cr = await http.splitio(`/internal/api/v2/changeRequests/ws/${encodeURIComponent(opts.workspace)}`, {
|
|
12342
12683
|
method: "POST",
|
|
12343
12684
|
body
|
|
12344
12685
|
});
|
|
@@ -12367,7 +12708,7 @@ function registerSplitioCommands(program2) {
|
|
|
12367
12708
|
split: { name: opts.flag, environment: { id: opts.environment } },
|
|
12368
12709
|
workspace: { id: opts.workspace }
|
|
12369
12710
|
};
|
|
12370
|
-
const data = await http.splitio(
|
|
12711
|
+
const data = await http.splitio(`/internal/api/v2/changeRequests/ws/${encodeURIComponent(opts.workspace)}`, {
|
|
12371
12712
|
method: "POST",
|
|
12372
12713
|
body
|
|
12373
12714
|
});
|
|
@@ -12387,7 +12728,7 @@ function registerSplitioCommands(program2) {
|
|
|
12387
12728
|
split: { name: opts.flag, environment: { id: opts.environment } },
|
|
12388
12729
|
workspace: { id: opts.workspace }
|
|
12389
12730
|
};
|
|
12390
|
-
const data = await http.splitio(
|
|
12731
|
+
const data = await http.splitio(`/internal/api/v2/changeRequests/ws/${encodeURIComponent(opts.workspace)}`, {
|
|
12391
12732
|
method: "POST",
|
|
12392
12733
|
body
|
|
12393
12734
|
});
|
|
@@ -12407,7 +12748,7 @@ function registerSplitioCommands(program2) {
|
|
|
12407
12748
|
split: { name: opts.flag },
|
|
12408
12749
|
workspace: { id: opts.workspace }
|
|
12409
12750
|
};
|
|
12410
|
-
const data = await http.splitio(
|
|
12751
|
+
const data = await http.splitio(`/internal/api/v2/changeRequests/ws/${encodeURIComponent(opts.workspace)}`, {
|
|
12411
12752
|
method: "POST",
|
|
12412
12753
|
body
|
|
12413
12754
|
});
|
|
@@ -12422,14 +12763,13 @@ function registerSplitioCommands(program2) {
|
|
|
12422
12763
|
try {
|
|
12423
12764
|
const http = getHttp4(program2);
|
|
12424
12765
|
const params = {
|
|
12425
|
-
wsId: opts.workspace,
|
|
12426
12766
|
limit: parseInt(opts.limit, 10),
|
|
12427
12767
|
offset: parseInt(opts.offset, 10)
|
|
12428
12768
|
};
|
|
12429
12769
|
if (opts.environment) params["environmentId"] = opts.environment;
|
|
12430
12770
|
if (opts.status) params["status"] = opts.status;
|
|
12431
12771
|
const data = await http.splitio(
|
|
12432
|
-
|
|
12772
|
+
`/internal/api/v2/changeRequests/ws/${encodeURIComponent(opts.workspace)}`,
|
|
12433
12773
|
{ params }
|
|
12434
12774
|
);
|
|
12435
12775
|
success(
|
|
@@ -12627,7 +12967,7 @@ function registerFigmaCommands(program2) {
|
|
|
12627
12967
|
}
|
|
12628
12968
|
|
|
12629
12969
|
// src/services/alation/commands.ts
|
|
12630
|
-
import
|
|
12970
|
+
import fs8 from "fs";
|
|
12631
12971
|
var MAX_PAGE = 1e3;
|
|
12632
12972
|
function parseIntOption(value, flag) {
|
|
12633
12973
|
if (value === void 0) return void 0;
|
|
@@ -12740,8 +13080,8 @@ function getHttp6(program2) {
|
|
|
12740
13080
|
Boolean(opts.dryRun)
|
|
12741
13081
|
);
|
|
12742
13082
|
}
|
|
12743
|
-
async function getOne(http,
|
|
12744
|
-
const rows = await http.alation(
|
|
13083
|
+
async function getOne(http, path11, id, what) {
|
|
13084
|
+
const rows = await http.alation(path11, { params: { id, limit: 1 } });
|
|
12745
13085
|
const row = Array.isArray(rows) ? rows.find((r) => r.id === id) ?? rows[0] : void 0;
|
|
12746
13086
|
if (!row) throw new PncliError(`${what} ${id} not found`, 404);
|
|
12747
13087
|
return row;
|
|
@@ -12791,7 +13131,7 @@ function registerAlationCommands(program2) {
|
|
|
12791
13131
|
const opts = program2.optsWithGlobals();
|
|
12792
13132
|
const config = loadConfig({ configPath: opts.config });
|
|
12793
13133
|
if (opts.dryRun) {
|
|
12794
|
-
|
|
13134
|
+
fs8.writeSync(process.stderr.fd, `DRY RUN: POST ${config.alation.baseUrl ?? "<alation.baseUrl>"}/integration/v1/createAPIAccessToken/ (token exchange skipped)
|
|
12795
13135
|
`);
|
|
12796
13136
|
process.exitCode = ExitCode.SUCCESS;
|
|
12797
13137
|
throw new PncliError("dry-run", 0);
|
|
@@ -13035,7 +13375,7 @@ if (!process.env.PNCLI_VERIFY_TLS) {
|
|
|
13035
13375
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
13036
13376
|
}
|
|
13037
13377
|
await configureProxy();
|
|
13038
|
-
var TAGLINE = "
|
|
13378
|
+
var TAGLINE = "Connectivity without MCP.";
|
|
13039
13379
|
var program = new Command3();
|
|
13040
13380
|
program.name("pncli").description(`The Paperwork Nightmare CLI \u2014 ${TAGLINE}`).version(`${getPncliVersion()} \u2014 ${TAGLINE}`, "-v, --version").option("--pretty", "Human-readable formatted output", false).option("--verbose", "Include full response metadata", false).option("--debug", "Print diagnostic traces (method, URL, status) for all API calls \u2014 does not log credentials", false).option("--dry-run", "Print API requests without executing", false).option("--config <path>", "Override global config file location").option("--output-file <path>", "Write JSON output to file instead of stdout");
|
|
13041
13381
|
program.hook("preAction", (thisCommand) => {
|
|
@@ -13099,9 +13439,16 @@ Services:
|
|
|
13099
13439
|
figma Figma (files, comments, version history)
|
|
13100
13440
|
alation Alation (data sources, schemas, tables, columns, search, Document Hubs)
|
|
13101
13441
|
config Manage pncli configuration
|
|
13102
|
-
doctor Diagnose pncli setup (config files, credentials, agent skills)
|
|
13442
|
+
doctor Diagnose pncli setup (config files, credentials, agent skills, marketplaces)
|
|
13103
13443
|
skills Install and manage agent skills (Codex, GitHub Copilot, Claude Code)
|
|
13104
13444
|
jwt JWT token utilities (decode header and payload)
|
|
13445
|
+
|
|
13446
|
+
Agent skills and org plugins:
|
|
13447
|
+
pncli skills install --all-agents Bundled pncli skills into this repo, every agent host
|
|
13448
|
+
pncli skills marketplace add <git-url> --all-agents Org plugins (skills + AGENTS.md/CLAUDE.md) from a git marketplace
|
|
13449
|
+
pncli skills marketplace sync Pull and refresh everything installed from marketplaces
|
|
13450
|
+
pncli skills marketplace manage Interactive: toggle plugins, add/remove marketplaces
|
|
13451
|
+
pncli skills --help Every install location and command
|
|
13105
13452
|
`);
|
|
13106
13453
|
program.parseAsync(process.argv).catch((err) => {
|
|
13107
13454
|
if (process.exitCode !== void 0) return;
|