@kolatts/pncli 1.16.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/copilot-instructions.md +45 -5
- package/dist/{chunk-P5NWDQ2B.js → chunk-FFVQ4PA7.js} +4 -3
- package/dist/chunk-FFVQ4PA7.js.map +1 -0
- package/dist/cli.js +553 -174
- package/dist/cli.js.map +1 -1
- package/dist/{config-3QD5Y2O5.js → config-S56LADVF.js} +2 -2
- package/package.json +1 -1
- package/skills/pncli/jenkins.md +22 -0
- package/skills/pncli/jira.md +15 -2
- package/skills/pncli/marketplace.md +47 -7
- package/dist/chunk-P5NWDQ2B.js.map +0 -1
- /package/dist/{config-3QD5Y2O5.js.map → config-S56LADVF.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
setRepoConfigValue,
|
|
9
9
|
writeGlobalConfig,
|
|
10
10
|
writeRepoConfig
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-FFVQ4PA7.js";
|
|
12
12
|
import {
|
|
13
13
|
ExitCode,
|
|
14
14
|
PncliError,
|
|
@@ -876,6 +876,7 @@ function guessMimeType(filePath) {
|
|
|
876
876
|
|
|
877
877
|
// src/services/jira/client.ts
|
|
878
878
|
var API2 = "/rest/api/2";
|
|
879
|
+
var AGILE_API = "/rest/agile/1.0";
|
|
879
880
|
var JiraClient = class {
|
|
880
881
|
constructor(http) {
|
|
881
882
|
this.http = http;
|
|
@@ -1071,6 +1072,42 @@ var JiraClient = class {
|
|
|
1071
1072
|
}
|
|
1072
1073
|
});
|
|
1073
1074
|
}
|
|
1075
|
+
async listBoards(project) {
|
|
1076
|
+
return this.http.jiraPaginate(async (startAt, maxResults) => {
|
|
1077
|
+
return this.http.jira(
|
|
1078
|
+
`${AGILE_API}/board`,
|
|
1079
|
+
{ params: { projectKeyOrId: project, startAt, maxResults } }
|
|
1080
|
+
);
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
async listSprintsForBoard(boardId, states) {
|
|
1084
|
+
return this.http.jiraPaginate(async (startAt, maxResults) => {
|
|
1085
|
+
return this.http.jira(
|
|
1086
|
+
`${AGILE_API}/board/${boardId}/sprint`,
|
|
1087
|
+
{ params: { startAt, maxResults, ...states?.length ? { state: states.join(",") } : {} } }
|
|
1088
|
+
);
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
async listSprintsForProject(project, states) {
|
|
1092
|
+
const boards = await this.listBoards(project);
|
|
1093
|
+
const sprints = [];
|
|
1094
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1095
|
+
for (const board of boards) {
|
|
1096
|
+
if (board.type !== "scrum") continue;
|
|
1097
|
+
for (const sprint of await this.listSprintsForBoard(board.id, states)) {
|
|
1098
|
+
if (seen.has(sprint.id)) continue;
|
|
1099
|
+
seen.add(sprint.id);
|
|
1100
|
+
sprints.push(sprint);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
return sprints;
|
|
1104
|
+
}
|
|
1105
|
+
async setSprint(sprintId, issueKeys) {
|
|
1106
|
+
await this.http.jira(`${AGILE_API}/sprint/${sprintId}/issue`, {
|
|
1107
|
+
method: "POST",
|
|
1108
|
+
body: { issues: issueKeys }
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1074
1111
|
};
|
|
1075
1112
|
function schemaToPncliType(schema) {
|
|
1076
1113
|
if (!schema) return void 0;
|
|
@@ -1406,6 +1443,38 @@ function registerJiraCommands(program2) {
|
|
|
1406
1443
|
fail(err, "jira", "fields", start);
|
|
1407
1444
|
}
|
|
1408
1445
|
});
|
|
1446
|
+
jira.command("list-boards").description("List Agile boards for a project").requiredOption("--project <key>", "Project key").action(async (opts) => {
|
|
1447
|
+
const start = Date.now();
|
|
1448
|
+
try {
|
|
1449
|
+
const client = getClient(program2);
|
|
1450
|
+
const data = await client.listBoards(opts.project);
|
|
1451
|
+
success(data, "jira", "list-boards", start);
|
|
1452
|
+
} catch (err) {
|
|
1453
|
+
fail(err, "jira", "list-boards", start);
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
jira.command("list-sprints").description("List sprints for a board or project, including start/end dates").option("--board <id>", "Board ID").option("--project <key>", "Project key \u2014 resolves to that project's board(s)").option("--state <states>", "Comma-separated states to filter (active,future,closed)").action(async (opts) => {
|
|
1457
|
+
const start = Date.now();
|
|
1458
|
+
try {
|
|
1459
|
+
if (!opts.board && !opts.project) throw new PncliError("Either --board or --project is required", 1);
|
|
1460
|
+
const client = getClient(program2);
|
|
1461
|
+
const states = opts.state ? opts.state.split(",").map((s) => s.trim()) : void 0;
|
|
1462
|
+
const data = opts.board ? await client.listSprintsForBoard(parseInt(opts.board, 10), states) : await client.listSprintsForProject(opts.project, states);
|
|
1463
|
+
success(data, "jira", "list-sprints", start);
|
|
1464
|
+
} catch (err) {
|
|
1465
|
+
fail(err, "jira", "list-sprints", start);
|
|
1466
|
+
}
|
|
1467
|
+
});
|
|
1468
|
+
jira.command("set-sprint").description("Move a Jira issue into a sprint").requiredOption("--key <issue-key>", "Issue key").requiredOption("--sprint <id>", "Sprint ID (from list-sprints)").action(async (opts) => {
|
|
1469
|
+
const start = Date.now();
|
|
1470
|
+
try {
|
|
1471
|
+
const client = getClient(program2);
|
|
1472
|
+
await client.setSprint(parseInt(opts.sprint, 10), [opts.key]);
|
|
1473
|
+
success({ updated: opts.key, sprint: opts.sprint }, "jira", "set-sprint", start);
|
|
1474
|
+
} catch (err) {
|
|
1475
|
+
fail(err, "jira", "set-sprint", start);
|
|
1476
|
+
}
|
|
1477
|
+
});
|
|
1409
1478
|
}
|
|
1410
1479
|
function parseFieldArgs(fieldArgs, fieldMap) {
|
|
1411
1480
|
const result = {};
|
|
@@ -4945,6 +5014,16 @@ var AdoWorkClient = class {
|
|
|
4945
5014
|
async downloadAttachment(absoluteUrl) {
|
|
4946
5015
|
return this.http.adoBuffer(absoluteUrl);
|
|
4947
5016
|
}
|
|
5017
|
+
async listAreas(collection, project, depth = 10) {
|
|
5018
|
+
return this.http.ado(
|
|
5019
|
+
`/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/wit/classificationnodes/areas?$depth=${depth}&api-version=${API6}`
|
|
5020
|
+
);
|
|
5021
|
+
}
|
|
5022
|
+
async listIterations(collection, project, depth = 10) {
|
|
5023
|
+
return this.http.ado(
|
|
5024
|
+
`/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/wit/classificationnodes/iterations?$depth=${depth}&api-version=${API6}`
|
|
5025
|
+
);
|
|
5026
|
+
}
|
|
4948
5027
|
async uploadAttachment(collection, workItemId, filePath, comment) {
|
|
4949
5028
|
const fileContent = readFileSync3(filePath);
|
|
4950
5029
|
const fileName = basename2(filePath);
|
|
@@ -6799,6 +6878,26 @@ function registerAdoWorkCommands(ado) {
|
|
|
6799
6878
|
fail(err, "ado", "work-download-attachment", start);
|
|
6800
6879
|
}
|
|
6801
6880
|
});
|
|
6881
|
+
work.command("list-areas").description("List classification areas for the team project (tree)").option("--depth <n>", "Depth of the area tree to retrieve", "10").action(async (opts) => {
|
|
6882
|
+
const start = Date.now();
|
|
6883
|
+
try {
|
|
6884
|
+
const { collection, project, workClient } = getAdoContext(ado);
|
|
6885
|
+
const data = await workClient.listAreas(collection, project, parseInt(opts.depth, 10));
|
|
6886
|
+
success(data, "ado", "work-list-areas", start);
|
|
6887
|
+
} catch (err) {
|
|
6888
|
+
fail(err, "ado", "work-list-areas", start);
|
|
6889
|
+
}
|
|
6890
|
+
});
|
|
6891
|
+
work.command("list-iterations").description("List classification iterations for the team project (tree), including start/finish dates").option("--depth <n>", "Depth of the iteration tree to retrieve", "10").action(async (opts) => {
|
|
6892
|
+
const start = Date.now();
|
|
6893
|
+
try {
|
|
6894
|
+
const { collection, project, workClient } = getAdoContext(ado);
|
|
6895
|
+
const data = await workClient.listIterations(collection, project, parseInt(opts.depth, 10));
|
|
6896
|
+
success(data, "ado", "work-list-iterations", start);
|
|
6897
|
+
} catch (err) {
|
|
6898
|
+
fail(err, "ado", "work-list-iterations", start);
|
|
6899
|
+
}
|
|
6900
|
+
});
|
|
6802
6901
|
work.command("types").description("List work item types in the project").option("--discover", "Fetch from server (always true for this command)").option("--save", "Save discovered types to ~/.pncli/config.json").action(async (opts) => {
|
|
6803
6902
|
const start = Date.now();
|
|
6804
6903
|
try {
|
|
@@ -7120,7 +7219,7 @@ function registerAdoRepoCommands(ado) {
|
|
|
7120
7219
|
const start = Date.now();
|
|
7121
7220
|
try {
|
|
7122
7221
|
const globalOpts = ado.optsWithGlobals();
|
|
7123
|
-
const { loadConfig: lc } = await import("./config-
|
|
7222
|
+
const { loadConfig: lc } = await import("./config-S56LADVF.js");
|
|
7124
7223
|
const config = lc({ configPath: globalOpts.config });
|
|
7125
7224
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
7126
7225
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
@@ -8270,6 +8369,8 @@ import os from "os";
|
|
|
8270
8369
|
import { fileURLToPath } from "url";
|
|
8271
8370
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
8272
8371
|
import select from "@inquirer/select";
|
|
8372
|
+
var BACK = "__back__";
|
|
8373
|
+
var ALL_MARKETPLACES = "__all_marketplaces__";
|
|
8273
8374
|
function injectTokenIntoUrl(url, token) {
|
|
8274
8375
|
let parsed;
|
|
8275
8376
|
try {
|
|
@@ -8293,10 +8394,31 @@ function repoNameFromUrl(url) {
|
|
|
8293
8394
|
function defaultMarketplacePath(url) {
|
|
8294
8395
|
return path7.join(os.homedir(), ".agents", "marketplaces", repoNameFromUrl(url));
|
|
8295
8396
|
}
|
|
8397
|
+
function marketplaceLabel(m) {
|
|
8398
|
+
return m.name ?? repoNameFromUrl(m.repoUrl ?? "");
|
|
8399
|
+
}
|
|
8400
|
+
function scrubToken(msg) {
|
|
8401
|
+
return msg.replace(/x-(?:token-auth|access-token):[^@]+@/g, "x-token-auth:***@");
|
|
8402
|
+
}
|
|
8403
|
+
function assertInteractive(hint) {
|
|
8404
|
+
if (!process.stdin.isTTY) {
|
|
8405
|
+
throw new Error(`This selection requires an interactive terminal. ${hint}`);
|
|
8406
|
+
}
|
|
8407
|
+
}
|
|
8296
8408
|
var AGENT_PATHS = {
|
|
8297
8409
|
"github-copilot": { project: ".agents/skills", user: path7.join(os.homedir(), ".copilot/skills") },
|
|
8298
8410
|
"claude-code": { project: ".claude/skills", user: path7.join(os.homedir(), ".claude/skills") }
|
|
8299
8411
|
};
|
|
8412
|
+
function resolveAgentName(opts) {
|
|
8413
|
+
return opts.claude ? "claude-code" : opts.agent ?? "github-copilot";
|
|
8414
|
+
}
|
|
8415
|
+
function resolveAgentPaths(agentName) {
|
|
8416
|
+
const agentConfig = AGENT_PATHS[agentName];
|
|
8417
|
+
if (!agentConfig) {
|
|
8418
|
+
throw new Error(`Unknown agent: "${agentName}". Use: ${Object.keys(AGENT_PATHS).join(" | ")}`);
|
|
8419
|
+
}
|
|
8420
|
+
return agentConfig;
|
|
8421
|
+
}
|
|
8300
8422
|
function getBundledSkillsDir() {
|
|
8301
8423
|
try {
|
|
8302
8424
|
const thisFile = fileURLToPath(import.meta.url);
|
|
@@ -8305,6 +8427,233 @@ function getBundledSkillsDir() {
|
|
|
8305
8427
|
return "";
|
|
8306
8428
|
}
|
|
8307
8429
|
}
|
|
8430
|
+
function getInstalledMetaPath(targetDir) {
|
|
8431
|
+
return path7.join(targetDir, ".pncli-installed.json");
|
|
8432
|
+
}
|
|
8433
|
+
function readInstalledMeta(targetDir) {
|
|
8434
|
+
const metaPath = getInstalledMetaPath(targetDir);
|
|
8435
|
+
const raw = loadJsonFile(metaPath);
|
|
8436
|
+
if (raw && raw.version === 1 && raw.skills) return raw;
|
|
8437
|
+
return { version: 1, skills: {} };
|
|
8438
|
+
}
|
|
8439
|
+
function recordInstalledSkills(targetDir, skillNames, record) {
|
|
8440
|
+
if (skillNames.length === 0) return;
|
|
8441
|
+
const meta = readInstalledMeta(targetDir);
|
|
8442
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8443
|
+
for (const skillName of skillNames) {
|
|
8444
|
+
meta.skills[skillName] = { ...record, installedAt: now };
|
|
8445
|
+
}
|
|
8446
|
+
fs4.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
8447
|
+
}
|
|
8448
|
+
function getAllMarketplaces(globalConfig) {
|
|
8449
|
+
const result = [];
|
|
8450
|
+
if (Array.isArray(globalConfig.marketplaces)) {
|
|
8451
|
+
result.push(...globalConfig.marketplaces);
|
|
8452
|
+
}
|
|
8453
|
+
if (globalConfig.marketplace?.repoUrl) {
|
|
8454
|
+
const legacyUrl = globalConfig.marketplace.repoUrl;
|
|
8455
|
+
const alreadyPresent = result.some((m) => m.repoUrl === legacyUrl);
|
|
8456
|
+
if (!alreadyPresent) {
|
|
8457
|
+
const existingNames = new Set(result.map((m) => m.name).filter((n) => !!n));
|
|
8458
|
+
let legacyName = globalConfig.marketplace.name ?? repoNameFromUrl(legacyUrl);
|
|
8459
|
+
if (existingNames.has(legacyName)) {
|
|
8460
|
+
let suffix = 2;
|
|
8461
|
+
while (existingNames.has(`${legacyName}-${suffix}`)) suffix++;
|
|
8462
|
+
legacyName = `${legacyName}-${suffix}`;
|
|
8463
|
+
}
|
|
8464
|
+
result.push({
|
|
8465
|
+
name: legacyName,
|
|
8466
|
+
repoUrl: globalConfig.marketplace.repoUrl,
|
|
8467
|
+
localPath: globalConfig.marketplace.localPath,
|
|
8468
|
+
token: globalConfig.marketplace.token
|
|
8469
|
+
});
|
|
8470
|
+
}
|
|
8471
|
+
}
|
|
8472
|
+
return result;
|
|
8473
|
+
}
|
|
8474
|
+
function saveMarketplaces(configPath, existing, marketplaces) {
|
|
8475
|
+
const updated = { ...existing, marketplaces };
|
|
8476
|
+
delete updated.marketplace;
|
|
8477
|
+
writeGlobalConfig(updated, configPath);
|
|
8478
|
+
}
|
|
8479
|
+
function loadMarketplacesConfig(configPath) {
|
|
8480
|
+
const existing = loadJsonFile(configPath) ?? {};
|
|
8481
|
+
const all = getAllMarketplaces(existing);
|
|
8482
|
+
if (existing.marketplace?.repoUrl) {
|
|
8483
|
+
saveMarketplaces(configPath, existing, all);
|
|
8484
|
+
warn("Migrated legacy single-marketplace config to the multi-marketplace format.");
|
|
8485
|
+
delete existing.marketplace;
|
|
8486
|
+
}
|
|
8487
|
+
return { existing, all };
|
|
8488
|
+
}
|
|
8489
|
+
function loadMarketplaces(configPath) {
|
|
8490
|
+
return loadMarketplacesConfig(configPath).all;
|
|
8491
|
+
}
|
|
8492
|
+
function upsertMarketplace(all, entry) {
|
|
8493
|
+
const idxByUrl = all.findIndex((m) => m.repoUrl === entry.repoUrl);
|
|
8494
|
+
const idxByName = all.findIndex((m) => m.name === entry.name);
|
|
8495
|
+
if (idxByUrl !== -1) {
|
|
8496
|
+
if (idxByName !== -1 && idxByName !== idxByUrl) {
|
|
8497
|
+
throw new Error(`Marketplace name "${entry.name}" is already used by a different marketplace (${all[idxByName].repoUrl}). Choose a different --name.`);
|
|
8498
|
+
}
|
|
8499
|
+
all[idxByUrl] = { ...entry, token: entry.token ?? all[idxByUrl].token };
|
|
8500
|
+
} else if (idxByName !== -1) {
|
|
8501
|
+
throw new Error(`Marketplace name "${entry.name}" is already registered for a different repo (${all[idxByName].repoUrl}). Choose a different --name, or remove the existing marketplace first.`);
|
|
8502
|
+
} else {
|
|
8503
|
+
all.push(entry);
|
|
8504
|
+
}
|
|
8505
|
+
}
|
|
8506
|
+
function cloneOrReuseMarketplace(url, resolvedPath, opts) {
|
|
8507
|
+
const hasGit = fs4.existsSync(path7.join(resolvedPath, ".git"));
|
|
8508
|
+
if (fs4.existsSync(resolvedPath) && !hasGit && fs4.readdirSync(resolvedPath).length > 0) {
|
|
8509
|
+
throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
|
|
8510
|
+
}
|
|
8511
|
+
if (hasGit) {
|
|
8512
|
+
warn(`Directory already contains a git repo at ${resolvedPath} \u2014 skipping clone, updating config and re-installing plugins.`);
|
|
8513
|
+
return;
|
|
8514
|
+
}
|
|
8515
|
+
const branchLabel = opts.branch ?? "remote default";
|
|
8516
|
+
warn(`Cloning ${url} (branch: ${branchLabel}) \u2192 ${resolvedPath}...`);
|
|
8517
|
+
const cloneUrl = opts.token ? injectTokenIntoUrl(url, opts.token) : url;
|
|
8518
|
+
const cloneArgs = ["clone"];
|
|
8519
|
+
if (opts.branch) cloneArgs.push("--branch", opts.branch);
|
|
8520
|
+
cloneArgs.push(cloneUrl, resolvedPath);
|
|
8521
|
+
try {
|
|
8522
|
+
execFileSync3("git", cloneArgs, { stdio: ["inherit", "inherit", "pipe"] });
|
|
8523
|
+
} catch (e) {
|
|
8524
|
+
let cloneActuallySucceeded = false;
|
|
8525
|
+
try {
|
|
8526
|
+
execFileSync3("git", ["-C", resolvedPath, "rev-parse", "HEAD"], { stdio: "pipe" });
|
|
8527
|
+
cloneActuallySucceeded = true;
|
|
8528
|
+
} catch {
|
|
8529
|
+
}
|
|
8530
|
+
if (!cloneActuallySucceeded) {
|
|
8531
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
8532
|
+
throw new Error(scrubToken(msg));
|
|
8533
|
+
}
|
|
8534
|
+
}
|
|
8535
|
+
}
|
|
8536
|
+
function pullMarketplace(marketplacePath, repoUrl, token, marketplaceName) {
|
|
8537
|
+
warn(`Pulling latest content for "${marketplaceName}"...`);
|
|
8538
|
+
const gitArgs = ["-C", marketplacePath];
|
|
8539
|
+
if (repoUrl && token) {
|
|
8540
|
+
gitArgs.push("-c", `remote.origin.url=${injectTokenIntoUrl(repoUrl, token)}`);
|
|
8541
|
+
}
|
|
8542
|
+
gitArgs.push("pull");
|
|
8543
|
+
let pullOutput;
|
|
8544
|
+
try {
|
|
8545
|
+
pullOutput = execFileSync3("git", gitArgs, { encoding: "utf8", stdio: ["inherit", "pipe", "pipe"], env: { ...process.env, LANG: "C", LC_ALL: "C" } });
|
|
8546
|
+
} catch (e) {
|
|
8547
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
8548
|
+
throw new Error(scrubToken(msg));
|
|
8549
|
+
}
|
|
8550
|
+
const updated = !pullOutput.includes("Already up to date");
|
|
8551
|
+
if (pullOutput.trim() && updated) warn(pullOutput.trim());
|
|
8552
|
+
warn(updated ? `"${marketplaceName}" updated.` : `"${marketplaceName}" already up to date \u2014 no changes to sync.`);
|
|
8553
|
+
return { updated };
|
|
8554
|
+
}
|
|
8555
|
+
function installPluginsForMarketplace(marketplacePath, marketplaceName, repoUrl, pluginNames, targetDir) {
|
|
8556
|
+
const results = {};
|
|
8557
|
+
let totalInstalled = 0;
|
|
8558
|
+
for (const pluginName of pluginNames) {
|
|
8559
|
+
const skillsSrc = resolveSkillsSrc(marketplacePath, pluginName);
|
|
8560
|
+
if (!fs4.existsSync(skillsSrc)) {
|
|
8561
|
+
results[pluginName] = { installed: [], failed: [] };
|
|
8562
|
+
warn(`No skills directory found for plugin "${pluginName}" in "${marketplaceName}" \u2014 skipping.`);
|
|
8563
|
+
continue;
|
|
8564
|
+
}
|
|
8565
|
+
const { installed, failed } = copyPluginSkills(skillsSrc, targetDir, {
|
|
8566
|
+
marketplace: marketplaceName,
|
|
8567
|
+
plugin: pluginName,
|
|
8568
|
+
installedFrom: repoUrl ?? ""
|
|
8569
|
+
});
|
|
8570
|
+
results[pluginName] = { installed, failed };
|
|
8571
|
+
totalInstalled += installed.length;
|
|
8572
|
+
for (const skill of installed) {
|
|
8573
|
+
warn(` ${skill}: ${path7.join(skillsSrc, skill)} \u2192 ${path7.join(targetDir, skill)}`);
|
|
8574
|
+
}
|
|
8575
|
+
if (failed.length > 0) {
|
|
8576
|
+
warn(`Skipped ${failed.length} skill(s) with invalid names in "${pluginName}": ${failed.join(", ")}`);
|
|
8577
|
+
}
|
|
8578
|
+
}
|
|
8579
|
+
return { results, totalInstalled };
|
|
8580
|
+
}
|
|
8581
|
+
function installAllPlugins(resolvedPath, marketplaceName, url, targetDir) {
|
|
8582
|
+
const pluginChoices = resolvePluginChoices(resolvedPath);
|
|
8583
|
+
if (pluginChoices.length === 0) {
|
|
8584
|
+
warn("No plugins found in marketplace. Check the marketplace repository structure.");
|
|
8585
|
+
return { pluginResults: {}, totalInstalled: 0 };
|
|
8586
|
+
}
|
|
8587
|
+
warn(`Installing ${pluginChoices.length} plugin(s) to ${targetDir}...`);
|
|
8588
|
+
const { results, totalInstalled } = installPluginsForMarketplace(resolvedPath, marketplaceName, url, pluginChoices.map((p) => p.name), targetDir);
|
|
8589
|
+
return { pluginResults: results, totalInstalled };
|
|
8590
|
+
}
|
|
8591
|
+
function syncMarketplacePlugins(m, targetDir, force, pluginFilter) {
|
|
8592
|
+
const marketplaceName = marketplaceLabel(m);
|
|
8593
|
+
try {
|
|
8594
|
+
const marketplacePath = m.localPath;
|
|
8595
|
+
if (!marketplacePath || !fs4.existsSync(marketplacePath)) {
|
|
8596
|
+
warn(`Marketplace "${marketplaceName}" local path not found \u2014 skipping.`);
|
|
8597
|
+
return { marketplace: marketplaceName, skipped: true, message: "Local path not found." };
|
|
8598
|
+
}
|
|
8599
|
+
const { updated } = pullMarketplace(marketplacePath, m.repoUrl, m.token, marketplaceName);
|
|
8600
|
+
if (!updated && !force) {
|
|
8601
|
+
return { marketplace: marketplaceName, marketplaceUpdated: false, skipped: true, message: "No changes detected \u2014 skipping install. Use --force to reinstall anyway." };
|
|
8602
|
+
}
|
|
8603
|
+
const pluginChoices = resolvePluginChoices(marketplacePath);
|
|
8604
|
+
if (pluginChoices.length === 0) {
|
|
8605
|
+
warn(`No plugins found in marketplace "${marketplaceName}" \u2014 skipping.`);
|
|
8606
|
+
return { marketplace: marketplaceName, skipped: true, message: "No plugins found." };
|
|
8607
|
+
}
|
|
8608
|
+
let pluginNames;
|
|
8609
|
+
if (pluginFilter === "all") {
|
|
8610
|
+
pluginNames = pluginChoices.map((p) => p.name);
|
|
8611
|
+
} else if (pluginChoices.some((p) => p.name === pluginFilter)) {
|
|
8612
|
+
pluginNames = [pluginFilter];
|
|
8613
|
+
} else {
|
|
8614
|
+
warn(`Plugin "${pluginFilter}" not found in "${marketplaceName}" \u2014 skipping.`);
|
|
8615
|
+
return { marketplace: marketplaceName, skipped: true, message: `Plugin "${pluginFilter}" not found.` };
|
|
8616
|
+
}
|
|
8617
|
+
const { results, totalInstalled } = installPluginsForMarketplace(marketplacePath, marketplaceName, m.repoUrl, pluginNames, targetDir);
|
|
8618
|
+
return { marketplace: marketplaceName, plugins: results, total: totalInstalled, marketplaceUpdated: updated };
|
|
8619
|
+
} catch (err) {
|
|
8620
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8621
|
+
warn(`Marketplace "${marketplaceName}" failed \u2014 skipping. ${message}`);
|
|
8622
|
+
return { marketplace: marketplaceName, skipped: true, error: message };
|
|
8623
|
+
}
|
|
8624
|
+
}
|
|
8625
|
+
async function marketplaceAddAction(url, localPath, opts, commandName) {
|
|
8626
|
+
const start = Date.now();
|
|
8627
|
+
try {
|
|
8628
|
+
const resolvedPath = path7.resolve(localPath ?? defaultMarketplacePath(url));
|
|
8629
|
+
const marketplaceName = opts.name ?? repoNameFromUrl(url);
|
|
8630
|
+
cloneOrReuseMarketplace(url, resolvedPath, opts);
|
|
8631
|
+
const configPath = getGlobalConfigPath();
|
|
8632
|
+
const { existing, all } = loadMarketplacesConfig(configPath);
|
|
8633
|
+
const entry = {
|
|
8634
|
+
name: marketplaceName,
|
|
8635
|
+
repoUrl: url,
|
|
8636
|
+
localPath: resolvedPath,
|
|
8637
|
+
...opts.token ? { token: opts.token } : {}
|
|
8638
|
+
};
|
|
8639
|
+
upsertMarketplace(all, entry);
|
|
8640
|
+
saveMarketplaces(configPath, existing, all);
|
|
8641
|
+
const targetDir = resolveAgentPaths(resolveAgentName(opts)).user;
|
|
8642
|
+
const { pluginResults, totalInstalled } = installAllPlugins(resolvedPath, marketplaceName, url, targetDir);
|
|
8643
|
+
success({
|
|
8644
|
+
name: marketplaceName,
|
|
8645
|
+
repoUrl: url,
|
|
8646
|
+
localPath: resolvedPath,
|
|
8647
|
+
branch: opts.branch ?? null,
|
|
8648
|
+
tokenConfigured: !!opts.token,
|
|
8649
|
+
plugins: pluginResults,
|
|
8650
|
+
total: totalInstalled,
|
|
8651
|
+
target: targetDir
|
|
8652
|
+
}, "skills", commandName, start);
|
|
8653
|
+
} catch (err) {
|
|
8654
|
+
fail(err, "skills", commandName, start);
|
|
8655
|
+
}
|
|
8656
|
+
}
|
|
8308
8657
|
function registerSkillsCommands(program2) {
|
|
8309
8658
|
const skills = program2.command("skills").description("Manage pncli Claude Code skills");
|
|
8310
8659
|
skills.command("install").description("Install pncli skills into the current repo").option("--agent <agent>", "Target agent host: github-copilot | claude-code", "github-copilot").option("--scope <scope>", "Installation scope: project | user", "project").option("--target <dir>", "Override install directory (ignores --agent and --scope)").action((opts) => {
|
|
@@ -8314,10 +8663,7 @@ function registerSkillsCommands(program2) {
|
|
|
8314
8663
|
if (opts.target) {
|
|
8315
8664
|
targetDir = path7.resolve(opts.target);
|
|
8316
8665
|
} else {
|
|
8317
|
-
const agentConfig =
|
|
8318
|
-
if (!agentConfig) {
|
|
8319
|
-
throw new Error(`Unknown agent: "${opts.agent}". Use: ${Object.keys(AGENT_PATHS).join(" | ")}`);
|
|
8320
|
-
}
|
|
8666
|
+
const agentConfig = resolveAgentPaths(opts.agent);
|
|
8321
8667
|
const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
|
|
8322
8668
|
targetDir = path7.resolve(scopePath);
|
|
8323
8669
|
}
|
|
@@ -8367,6 +8713,7 @@ function registerSkillsCommands(program2) {
|
|
|
8367
8713
|
if (failed.length > 0) {
|
|
8368
8714
|
warn(`Failed to install: ${failed.join(", ")}`);
|
|
8369
8715
|
}
|
|
8716
|
+
recordInstalledSkills(targetDir, installed, { source: "bundled" });
|
|
8370
8717
|
success({
|
|
8371
8718
|
installed,
|
|
8372
8719
|
failed,
|
|
@@ -8387,10 +8734,7 @@ function registerSkillsCommands(program2) {
|
|
|
8387
8734
|
if (opts.target) {
|
|
8388
8735
|
targetDir = path7.resolve(opts.target);
|
|
8389
8736
|
} else {
|
|
8390
|
-
const agentConfig =
|
|
8391
|
-
if (!agentConfig) {
|
|
8392
|
-
throw new Error(`Unknown agent: "${opts.agent}". Use: ${Object.keys(AGENT_PATHS).join(" | ")}`);
|
|
8393
|
-
}
|
|
8737
|
+
const agentConfig = resolveAgentPaths(opts.agent);
|
|
8394
8738
|
const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
|
|
8395
8739
|
targetDir = path7.resolve(scopePath);
|
|
8396
8740
|
}
|
|
@@ -8398,7 +8742,9 @@ function registerSkillsCommands(program2) {
|
|
|
8398
8742
|
success({ skills: [], message: `No skills directory found at ${targetDir}. Run: pncli skills install` }, "skills", "list", start);
|
|
8399
8743
|
return;
|
|
8400
8744
|
}
|
|
8745
|
+
const meta = readInstalledMeta(targetDir);
|
|
8401
8746
|
const skillDirs = fs4.readdirSync(targetDir).filter((name) => {
|
|
8747
|
+
if (name.startsWith(".")) return false;
|
|
8402
8748
|
const skillPath = path7.join(targetDir, name, "SKILL.md");
|
|
8403
8749
|
return fs4.existsSync(skillPath);
|
|
8404
8750
|
});
|
|
@@ -8433,7 +8779,8 @@ function registerSkillsCommands(program2) {
|
|
|
8433
8779
|
category: metadata.category || data.category || "other",
|
|
8434
8780
|
services: metadata.services || data.services || "",
|
|
8435
8781
|
providers: metadata.providers || data.providers || "none",
|
|
8436
|
-
userInvocable: data["user-invocable"] === "true"
|
|
8782
|
+
userInvocable: data["user-invocable"] === "true",
|
|
8783
|
+
installed: meta.skills[name] ?? null
|
|
8437
8784
|
};
|
|
8438
8785
|
});
|
|
8439
8786
|
success({ skills: skillsList, total: skillsList.length }, "skills", "list", start);
|
|
@@ -8441,193 +8788,217 @@ function registerSkillsCommands(program2) {
|
|
|
8441
8788
|
fail(err, "skills", "list", start);
|
|
8442
8789
|
}
|
|
8443
8790
|
});
|
|
8444
|
-
|
|
8445
|
-
marketplace.command("setup").description("Clone a skills marketplace repo, register it in global config, and install all plugins").argument("<url>", "Git clone URL of the marketplace repository").argument("[localPath]", "Local directory to clone into (default: ~/.agents/marketplaces/<repo-name>)").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: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").action(async (url, localPath, opts) => {
|
|
8791
|
+
skills.command("uninstall").description("Uninstall a skill (defaults to user scope, matching the marketplace install target; pass --scope project for skills installed via `skills install`)").argument("<name>", "Skill name to uninstall (the directory name under your skills folder)").option("--agent <agent>", "Target agent host: github-copilot | claude-code", "github-copilot").option("--scope <scope>", "Installation scope to uninstall from: project | user", "user").option("--claude", "Shorthand for --agent claude-code").option("--target <dir>", "Override skills directory").action((name, opts) => {
|
|
8446
8792
|
const start = Date.now();
|
|
8447
8793
|
try {
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
|
|
8452
|
-
}
|
|
8453
|
-
if (hasGit) {
|
|
8454
|
-
warn(`Directory already contains a git repo at ${resolvedPath} \u2014 skipping clone, updating config and re-installing plugins.`);
|
|
8794
|
+
let targetDir;
|
|
8795
|
+
if (opts.target) {
|
|
8796
|
+
targetDir = path7.resolve(opts.target);
|
|
8455
8797
|
} else {
|
|
8456
|
-
const
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
const cloneArgs = ["clone"];
|
|
8460
|
-
if (opts.branch) cloneArgs.push("--branch", opts.branch);
|
|
8461
|
-
cloneArgs.push(cloneUrl, resolvedPath);
|
|
8462
|
-
try {
|
|
8463
|
-
execFileSync3("git", cloneArgs, { stdio: ["inherit", "inherit", "pipe"] });
|
|
8464
|
-
} catch (e) {
|
|
8465
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
8466
|
-
throw new Error(msg.replace(/x-(?:token-auth|access-token):[^@]+@/g, "x-token-auth:***@"));
|
|
8467
|
-
}
|
|
8798
|
+
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
8799
|
+
const scopePath = opts.scope === "project" ? agentConfig.project : agentConfig.user;
|
|
8800
|
+
targetDir = path7.resolve(scopePath);
|
|
8468
8801
|
}
|
|
8802
|
+
const resolvedTarget = path7.resolve(targetDir);
|
|
8803
|
+
const skillDir = path7.resolve(targetDir, name);
|
|
8804
|
+
if (!skillDir.startsWith(resolvedTarget + path7.sep)) {
|
|
8805
|
+
throw new Error(`Invalid skill name: "${name}"`);
|
|
8806
|
+
}
|
|
8807
|
+
if (!fs4.existsSync(skillDir)) {
|
|
8808
|
+
throw new Error(`Skill "${name}" not found at ${skillDir}`);
|
|
8809
|
+
}
|
|
8810
|
+
const meta = readInstalledMeta(targetDir);
|
|
8811
|
+
const record = meta.skills[name];
|
|
8812
|
+
fs4.rmSync(skillDir, { recursive: true, force: true });
|
|
8813
|
+
delete meta.skills[name];
|
|
8814
|
+
fs4.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
8815
|
+
success({
|
|
8816
|
+
uninstalled: name,
|
|
8817
|
+
target: targetDir,
|
|
8818
|
+
wasTracked: !!record,
|
|
8819
|
+
...record ? { installedFrom: record } : {}
|
|
8820
|
+
}, "skills", "uninstall", start);
|
|
8821
|
+
} catch (err) {
|
|
8822
|
+
fail(err, "skills", "uninstall", start);
|
|
8823
|
+
}
|
|
8824
|
+
});
|
|
8825
|
+
const marketplace = skills.command("marketplace").description("Manage git-hosted skills marketplaces");
|
|
8826
|
+
function withMarketplaceAddOptions(cmd) {
|
|
8827
|
+
return cmd.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: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code");
|
|
8828
|
+
}
|
|
8829
|
+
withMarketplaceAddOptions(
|
|
8830
|
+
marketplace.command("add").description("Register a new marketplace, clone it, and install all its plugins")
|
|
8831
|
+
).action((url, localPath, opts) => marketplaceAddAction(url, localPath, opts, "marketplace-add"));
|
|
8832
|
+
withMarketplaceAddOptions(
|
|
8833
|
+
marketplace.command("setup").description("Alias for `marketplace add` \u2014 clone a marketplace and install all its plugins")
|
|
8834
|
+
).action((url, localPath, opts) => marketplaceAddAction(url, localPath, opts, "marketplace-setup"));
|
|
8835
|
+
marketplace.command("list").description("List all registered marketplaces").action(() => {
|
|
8836
|
+
const start = Date.now();
|
|
8837
|
+
try {
|
|
8469
8838
|
const configPath = getGlobalConfigPath();
|
|
8470
|
-
const
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
totalInstalled += installed.length;
|
|
8496
|
-
for (const skill of installed) {
|
|
8497
|
-
warn(` ${skill}: ${path7.join(skillsSrc, skill)} \u2192 ${path7.join(targetDir, skill)}`);
|
|
8498
|
-
}
|
|
8499
|
-
if (failed.length > 0) {
|
|
8500
|
-
warn(`Skipped ${failed.length} skill(s) with invalid names in "${pluginChoice.name}": ${failed.join(", ")}`);
|
|
8501
|
-
}
|
|
8502
|
-
}
|
|
8839
|
+
const all = loadMarketplaces(configPath);
|
|
8840
|
+
success({
|
|
8841
|
+
marketplaces: all.map((m) => ({
|
|
8842
|
+
name: marketplaceLabel(m),
|
|
8843
|
+
repoUrl: m.repoUrl,
|
|
8844
|
+
localPath: m.localPath,
|
|
8845
|
+
tokenConfigured: !!m.token
|
|
8846
|
+
})),
|
|
8847
|
+
total: all.length
|
|
8848
|
+
}, "skills", "marketplace-list", start);
|
|
8849
|
+
} catch (err) {
|
|
8850
|
+
fail(err, "skills", "marketplace-list", start);
|
|
8851
|
+
}
|
|
8852
|
+
});
|
|
8853
|
+
marketplace.command("plugins").description("List the plugins available in a registered marketplace").argument("<name>", "Marketplace name (or repo URL) to inspect").action((name) => {
|
|
8854
|
+
const start = Date.now();
|
|
8855
|
+
try {
|
|
8856
|
+
const configPath = getGlobalConfigPath();
|
|
8857
|
+
const all = loadMarketplaces(configPath);
|
|
8858
|
+
const found = all.find((m) => m.name === name || m.repoUrl === name);
|
|
8859
|
+
if (!found) {
|
|
8860
|
+
throw new Error(`Marketplace "${name}" not found. Run: pncli skills marketplace list`);
|
|
8861
|
+
}
|
|
8862
|
+
if (!found.localPath || !fs4.existsSync(found.localPath)) {
|
|
8863
|
+
throw new Error(`Marketplace "${marketplaceLabel(found)}" local path not found at ${found.localPath ?? "(not set)"}. Run: pncli skills marketplace add <url>`);
|
|
8503
8864
|
}
|
|
8865
|
+
const plugins = resolvePluginChoices(found.localPath);
|
|
8504
8866
|
success({
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
plugins: pluginResults,
|
|
8510
|
-
total: totalInstalled,
|
|
8511
|
-
target: targetDir
|
|
8512
|
-
}, "skills", "marketplace-setup", start);
|
|
8867
|
+
marketplace: marketplaceLabel(found),
|
|
8868
|
+
plugins,
|
|
8869
|
+
total: plugins.length
|
|
8870
|
+
}, "skills", "marketplace-plugins", start);
|
|
8513
8871
|
} catch (err) {
|
|
8514
|
-
fail(err, "skills", "marketplace-
|
|
8872
|
+
fail(err, "skills", "marketplace-plugins", start);
|
|
8515
8873
|
}
|
|
8516
8874
|
});
|
|
8517
|
-
marketplace.command("
|
|
8875
|
+
marketplace.command("remove").description("Remove a registered marketplace from the config (does not delete the local clone)").argument("<name>", "Name of the marketplace to remove").action((name) => {
|
|
8518
8876
|
const start = Date.now();
|
|
8519
8877
|
try {
|
|
8520
8878
|
const configPath = getGlobalConfigPath();
|
|
8521
|
-
const
|
|
8522
|
-
const
|
|
8523
|
-
if (
|
|
8524
|
-
throw new Error(
|
|
8525
|
-
}
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
}
|
|
8555
|
-
|
|
8556
|
-
let selectedPlugin;
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8879
|
+
const { existing, all } = loadMarketplacesConfig(configPath);
|
|
8880
|
+
const idx = all.findIndex((m) => m.name === name || m.repoUrl === name);
|
|
8881
|
+
if (idx === -1) {
|
|
8882
|
+
throw new Error(`Marketplace "${name}" not found. Run: pncli skills marketplace list`);
|
|
8883
|
+
}
|
|
8884
|
+
const removed = all.splice(idx, 1)[0];
|
|
8885
|
+
saveMarketplaces(configPath, existing, all);
|
|
8886
|
+
success({
|
|
8887
|
+
removed: {
|
|
8888
|
+
name: marketplaceLabel(removed),
|
|
8889
|
+
repoUrl: removed.repoUrl,
|
|
8890
|
+
localPath: removed.localPath
|
|
8891
|
+
},
|
|
8892
|
+
remaining: all.length
|
|
8893
|
+
}, "skills", "marketplace-remove", start);
|
|
8894
|
+
} catch (err) {
|
|
8895
|
+
fail(err, "skills", "marketplace-remove", start);
|
|
8896
|
+
}
|
|
8897
|
+
});
|
|
8898
|
+
marketplace.command("sync").description("Pull latest marketplace content and install plugin skills").argument("[plugin]", 'Plugin name to install, or "all" to install every plugin (skips interactive selection)').option("--marketplace <name>", 'Marketplace name to sync, or "all" to sync every registered marketplace (skips interactive selection)').option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--force", 'Reinstall even if a marketplace has no new changes (applies to single-plugin and "all" installs alike)').action(async (plugin, opts) => {
|
|
8899
|
+
const start = Date.now();
|
|
8900
|
+
try {
|
|
8901
|
+
const configPath = getGlobalConfigPath();
|
|
8902
|
+
const allMarketplaces = loadMarketplaces(configPath);
|
|
8903
|
+
if (allMarketplaces.length === 0) {
|
|
8904
|
+
throw new Error("No marketplaces configured. Run: pncli skills marketplace add <url>");
|
|
8905
|
+
}
|
|
8906
|
+
const targetDir = resolveAgentPaths(resolveAgentName(opts)).user;
|
|
8907
|
+
const force = opts.force ?? false;
|
|
8908
|
+
if (opts.marketplace === "all") {
|
|
8909
|
+
const results = allMarketplaces.map((m) => syncMarketplacePlugins(m, targetDir, force, plugin ?? "all"));
|
|
8910
|
+
success({ allMarketplaces: true, marketplaces: results, target: targetDir }, "skills", "marketplace-sync", start);
|
|
8911
|
+
return;
|
|
8912
|
+
}
|
|
8913
|
+
let selectedMarketplace;
|
|
8914
|
+
let selectedPlugin = plugin;
|
|
8915
|
+
const canGoBack = !opts.marketplace && allMarketplaces.length > 1;
|
|
8916
|
+
if (opts.marketplace) {
|
|
8917
|
+
const found = allMarketplaces.find((m) => m.name === opts.marketplace || m.repoUrl === opts.marketplace);
|
|
8918
|
+
if (!found) {
|
|
8919
|
+
throw new Error(`Marketplace "${opts.marketplace}" not found. Run: pncli skills marketplace list`);
|
|
8560
8920
|
}
|
|
8561
|
-
|
|
8562
|
-
} else {
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8921
|
+
selectedMarketplace = found;
|
|
8922
|
+
} else if (allMarketplaces.length === 1) {
|
|
8923
|
+
selectedMarketplace = allMarketplaces[0];
|
|
8924
|
+
}
|
|
8925
|
+
for (; ; ) {
|
|
8926
|
+
if (!selectedMarketplace) {
|
|
8927
|
+
assertInteractive(`Multiple marketplaces are registered (${allMarketplaces.map(marketplaceLabel).join(", ")}). Pass --marketplace <name> (or --marketplace all) to run this non-interactively.`);
|
|
8928
|
+
const chosen = await select({
|
|
8929
|
+
message: "Select a marketplace to sync:",
|
|
8930
|
+
choices: [
|
|
8931
|
+
{ value: ALL_MARKETPLACES, name: "All marketplaces \u2014 sync every plugin from every marketplace" },
|
|
8932
|
+
// Index-based values: labels alone aren't guaranteed unique (two marketplaces can share a derived name).
|
|
8933
|
+
...allMarketplaces.map((m, i) => ({ value: String(i), name: `${marketplaceLabel(m)} \u2014 ${m.repoUrl ?? ""}` }))
|
|
8934
|
+
]
|
|
8935
|
+
});
|
|
8936
|
+
if (chosen === ALL_MARKETPLACES) {
|
|
8937
|
+
const results2 = allMarketplaces.map((m) => syncMarketplacePlugins(m, targetDir, force, selectedPlugin ?? "all"));
|
|
8938
|
+
success({ allMarketplaces: true, marketplaces: results2, target: targetDir }, "skills", "marketplace-sync", start);
|
|
8939
|
+
return;
|
|
8940
|
+
}
|
|
8941
|
+
selectedMarketplace = allMarketplaces[Number(chosen)];
|
|
8942
|
+
if (!selectedMarketplace) {
|
|
8943
|
+
throw new Error(`Marketplace "${chosen}" not found.`);
|
|
8944
|
+
}
|
|
8945
|
+
}
|
|
8946
|
+
const marketplaceName = marketplaceLabel(selectedMarketplace);
|
|
8947
|
+
const marketplacePath = selectedMarketplace.localPath;
|
|
8948
|
+
if (!marketplacePath || !fs4.existsSync(marketplacePath)) {
|
|
8949
|
+
throw new Error(`Marketplace "${marketplaceName}" local path not found at ${marketplacePath ?? "(not set)"}. Run: pncli skills marketplace add <url>`);
|
|
8950
|
+
}
|
|
8951
|
+
const { updated } = pullMarketplace(marketplacePath, selectedMarketplace.repoUrl, selectedMarketplace.token, marketplaceName);
|
|
8952
|
+
const pluginChoices = resolvePluginChoices(marketplacePath);
|
|
8953
|
+
if (pluginChoices.length === 0) {
|
|
8954
|
+
throw new Error(`No plugins found in marketplace "${marketplaceName}". Check the marketplace repository structure.`);
|
|
8955
|
+
}
|
|
8956
|
+
if (!selectedPlugin) {
|
|
8957
|
+
assertInteractive(`Pass a plugin name (or "all") to run this non-interactively.`);
|
|
8958
|
+
const choices = [
|
|
8566
8959
|
{ value: "all", name: "All \u2014 install every plugin" },
|
|
8567
|
-
...pluginChoices.map((p) => ({
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
})
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8960
|
+
...pluginChoices.map((p) => ({ value: p.name, name: p.description ? `${p.name} \u2014 ${p.description}` : p.name }))
|
|
8961
|
+
];
|
|
8962
|
+
if (canGoBack) {
|
|
8963
|
+
choices.push({ value: BACK, name: "\u2190 Back to marketplace selection" });
|
|
8964
|
+
}
|
|
8965
|
+
const chosen = await select({ message: `Select a plugin from "${marketplaceName}" to install:`, choices });
|
|
8966
|
+
if (chosen === BACK) {
|
|
8967
|
+
selectedMarketplace = void 0;
|
|
8968
|
+
continue;
|
|
8969
|
+
}
|
|
8970
|
+
selectedPlugin = chosen;
|
|
8971
|
+
} else if (selectedPlugin !== "all" && !pluginChoices.some((p) => p.name === selectedPlugin)) {
|
|
8972
|
+
throw new Error(`Plugin "${selectedPlugin}" not found in "${marketplaceName}". Available: ${pluginChoices.map((p) => p.name).join(", ")}`);
|
|
8973
|
+
}
|
|
8974
|
+
if (!updated && !force) {
|
|
8576
8975
|
success({
|
|
8577
|
-
marketplace:
|
|
8976
|
+
marketplace: marketplaceName,
|
|
8578
8977
|
marketplaceUpdated: false,
|
|
8579
8978
|
updated: false,
|
|
8580
8979
|
skipped: true,
|
|
8581
|
-
message:
|
|
8980
|
+
message: `No changes detected in "${marketplaceName}" \u2014 skipping install. Use --force to reinstall anyway.`
|
|
8582
8981
|
}, "skills", "marketplace-sync", start);
|
|
8583
8982
|
return;
|
|
8584
8983
|
}
|
|
8585
|
-
const
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
}
|
|
8600
|
-
if (failed2.length > 0) {
|
|
8601
|
-
warn(`Skipped ${failed2.length} skill(s) with invalid names in "${pluginChoice.name}": ${failed2.join(", ")}`);
|
|
8602
|
-
}
|
|
8984
|
+
const pluginNames = selectedPlugin === "all" ? pluginChoices.map((p) => p.name) : [selectedPlugin];
|
|
8985
|
+
const { results, totalInstalled } = installPluginsForMarketplace(marketplacePath, marketplaceName, selectedMarketplace.repoUrl, pluginNames, targetDir);
|
|
8986
|
+
if (selectedPlugin === "all") {
|
|
8987
|
+
success({ marketplace: marketplaceName, plugins: results, total: totalInstalled, target: targetDir, marketplaceUpdated: updated }, "skills", "marketplace-sync", start);
|
|
8988
|
+
} else {
|
|
8989
|
+
const single = results[selectedPlugin] ?? { installed: [], failed: [] };
|
|
8990
|
+
success({
|
|
8991
|
+
marketplace: marketplaceName,
|
|
8992
|
+
plugin: selectedPlugin,
|
|
8993
|
+
installed: single.installed,
|
|
8994
|
+
failed: single.failed,
|
|
8995
|
+
total: single.installed.length,
|
|
8996
|
+
target: targetDir,
|
|
8997
|
+
marketplaceUpdated: updated
|
|
8998
|
+
}, "skills", "marketplace-sync", start);
|
|
8603
8999
|
}
|
|
8604
|
-
success({
|
|
8605
|
-
plugins: results,
|
|
8606
|
-
total: totalInstalled,
|
|
8607
|
-
target: targetDir,
|
|
8608
|
-
marketplaceUpdated
|
|
8609
|
-
}, "skills", "marketplace-sync", start);
|
|
8610
9000
|
return;
|
|
8611
9001
|
}
|
|
8612
|
-
const skillsSrc = resolveSkillsSrc(marketplacePath, selectedPlugin);
|
|
8613
|
-
if (!fs4.existsSync(skillsSrc)) {
|
|
8614
|
-
throw new Error(`No skills directory found at ${skillsSrc}`);
|
|
8615
|
-
}
|
|
8616
|
-
const { installed, failed } = copyPluginSkills(skillsSrc, targetDir);
|
|
8617
|
-
for (const skill of installed) {
|
|
8618
|
-
warn(` ${skill}: ${path7.join(skillsSrc, skill)} \u2192 ${path7.join(targetDir, skill)}`);
|
|
8619
|
-
}
|
|
8620
|
-
if (failed.length > 0) {
|
|
8621
|
-
warn(`Skipped ${failed.length} skill(s) with invalid names: ${failed.join(", ")}`);
|
|
8622
|
-
}
|
|
8623
|
-
success({
|
|
8624
|
-
plugin: selectedPlugin,
|
|
8625
|
-
installed,
|
|
8626
|
-
failed,
|
|
8627
|
-
total: installed.length,
|
|
8628
|
-
target: targetDir,
|
|
8629
|
-
marketplaceUpdated
|
|
8630
|
-
}, "skills", "marketplace-sync", start);
|
|
8631
9002
|
} catch (err) {
|
|
8632
9003
|
fail(err, "skills", "marketplace-sync", start);
|
|
8633
9004
|
}
|
|
@@ -8658,7 +9029,7 @@ function resolveSkillsSrc(marketplacePath, selectedPlugin) {
|
|
|
8658
9029
|
}
|
|
8659
9030
|
return skillsSrc;
|
|
8660
9031
|
}
|
|
8661
|
-
function copyPluginSkills(skillsSrc, targetDir) {
|
|
9032
|
+
function copyPluginSkills(skillsSrc, targetDir, meta) {
|
|
8662
9033
|
fs4.mkdirSync(targetDir, { recursive: true });
|
|
8663
9034
|
const resolvedTarget = path7.resolve(targetDir);
|
|
8664
9035
|
const skillNames = fs4.readdirSync(skillsSrc).filter(
|
|
@@ -8676,6 +9047,14 @@ function copyPluginSkills(skillsSrc, targetDir) {
|
|
|
8676
9047
|
fs4.cpSync(path7.join(skillsSrc, skillName), dest, { recursive: true });
|
|
8677
9048
|
installed.push(skillName);
|
|
8678
9049
|
}
|
|
9050
|
+
if (meta) {
|
|
9051
|
+
recordInstalledSkills(targetDir, installed, {
|
|
9052
|
+
source: "marketplace",
|
|
9053
|
+
marketplace: meta.marketplace,
|
|
9054
|
+
plugin: meta.plugin,
|
|
9055
|
+
installedFrom: meta.installedFrom
|
|
9056
|
+
});
|
|
9057
|
+
}
|
|
8679
9058
|
return { installed, failed };
|
|
8680
9059
|
}
|
|
8681
9060
|
|