@kolatts/pncli 5.0.2 → 5.2.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 +23 -5
- package/dist/{chunk-5Q4VM6GO.js → chunk-2J2HAFKO.js} +148 -2
- package/dist/chunk-2J2HAFKO.js.map +1 -0
- package/dist/{chunk-GRKPFSQD.js → chunk-QHJ2HM5J.js} +13 -1
- package/dist/chunk-QHJ2HM5J.js.map +1 -0
- package/dist/cli.js +1192 -310
- package/dist/cli.js.map +1 -1
- package/dist/{config-ZVQMQ6B2.js → config-Y6IE6GY7.js} +2 -2
- package/dist/{http-RX7E2V5C.js → http-V7PZD7LE.js} +2 -2
- package/package.json +3 -2
- package/skills/pncli/SKILL.md +5 -4
- package/skills/pncli/alation.md +101 -0
- package/skills/pncli/figma.md +9 -0
- package/skills/pncli/marketplace.md +55 -4
- package/dist/chunk-5Q4VM6GO.js.map +0 -1
- package/dist/chunk-GRKPFSQD.js.map +0 -1
- /package/dist/{config-ZVQMQ6B2.js.map → config-Y6IE6GY7.js.map} +0 -0
- /package/dist/{http-RX7E2V5C.js.map → http-V7PZD7LE.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -10,10 +10,11 @@ import {
|
|
|
10
10
|
setRepoConfigValue,
|
|
11
11
|
writeGlobalConfig,
|
|
12
12
|
writeRepoConfig
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-QHJ2HM5J.js";
|
|
14
14
|
import {
|
|
15
|
-
createHttpClient
|
|
16
|
-
|
|
15
|
+
createHttpClient,
|
|
16
|
+
validateAlationAccessToken
|
|
17
|
+
} from "./chunk-2J2HAFKO.js";
|
|
17
18
|
import {
|
|
18
19
|
ExitCode,
|
|
19
20
|
fail,
|
|
@@ -446,34 +447,34 @@ function parseAdoRemote(remoteUrl, adoBaseUrl) {
|
|
|
446
447
|
} catch {
|
|
447
448
|
baseHostname = adoBaseUrl.replace(/\/$/, "").replace(/^https?:\/\//, "").split(/[:/]/)[0].toLowerCase();
|
|
448
449
|
}
|
|
449
|
-
let
|
|
450
|
+
let path11 = remoteUrl;
|
|
450
451
|
let remoteHostname = "";
|
|
451
|
-
const sshProtoMatch =
|
|
452
|
+
const sshProtoMatch = path11.match(/^ssh:\/\/[^@]*@?([^/:]+)(?::\d+)?(.*)/);
|
|
452
453
|
if (sshProtoMatch) {
|
|
453
454
|
remoteHostname = sshProtoMatch[1].toLowerCase();
|
|
454
|
-
|
|
455
|
+
path11 = sshProtoMatch[2];
|
|
455
456
|
} else {
|
|
456
|
-
const gitAtMatch =
|
|
457
|
+
const gitAtMatch = path11.match(/^git@([^:]+)(?::\d+)?:(.*)/);
|
|
457
458
|
if (gitAtMatch) {
|
|
458
459
|
remoteHostname = gitAtMatch[1].toLowerCase();
|
|
459
|
-
|
|
460
|
+
path11 = "/" + gitAtMatch[2];
|
|
460
461
|
} else {
|
|
461
|
-
const httpsMatch =
|
|
462
|
+
const httpsMatch = path11.match(/^https?:\/\/([^/:]+)(?::\d+)?(.*)/);
|
|
462
463
|
if (httpsMatch) {
|
|
463
464
|
remoteHostname = httpsMatch[1].toLowerCase();
|
|
464
|
-
|
|
465
|
+
path11 = httpsMatch[2];
|
|
465
466
|
}
|
|
466
467
|
}
|
|
467
468
|
}
|
|
468
469
|
if (!remoteHostname || remoteHostname !== baseHostname) return null;
|
|
469
|
-
|
|
470
|
-
const gitIdx =
|
|
471
|
-
const sshIdx =
|
|
470
|
+
path11 = path11.replace(/\.git$/, "");
|
|
471
|
+
const gitIdx = path11.indexOf("/_git/");
|
|
472
|
+
const sshIdx = path11.indexOf("/_ssh/");
|
|
472
473
|
const markerIdx = gitIdx !== -1 ? gitIdx : sshIdx;
|
|
473
474
|
if (markerIdx === -1) return null;
|
|
474
|
-
const repo = decodeURIComponent(
|
|
475
|
+
const repo = decodeURIComponent(path11.slice(markerIdx + 6));
|
|
475
476
|
if (!repo) return null;
|
|
476
|
-
const before =
|
|
477
|
+
const before = path11.slice(1, markerIdx);
|
|
477
478
|
const parts = before.split("/").filter(Boolean);
|
|
478
479
|
if (parts.length < 2) return null;
|
|
479
480
|
const project = decodeURIComponent(parts[parts.length - 1]);
|
|
@@ -2000,21 +2001,21 @@ function registerBitbucketCommands(program2) {
|
|
|
2000
2001
|
fail(err, "bitbucket", "reply-comment", start);
|
|
2001
2002
|
}
|
|
2002
2003
|
});
|
|
2003
|
-
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) => {
|
|
2004
2005
|
const start = Date.now();
|
|
2005
2006
|
try {
|
|
2006
2007
|
const { client, project, repo } = getClient2(bb);
|
|
2007
|
-
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));
|
|
2008
2009
|
success({ resolved: true }, "bitbucket", "resolve-comment", start);
|
|
2009
2010
|
} catch (err) {
|
|
2010
2011
|
fail(err, "bitbucket", "resolve-comment", start);
|
|
2011
2012
|
}
|
|
2012
2013
|
});
|
|
2013
|
-
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) => {
|
|
2014
2015
|
const start = Date.now();
|
|
2015
2016
|
try {
|
|
2016
2017
|
const { client, project, repo } = getClient2(bb);
|
|
2017
|
-
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));
|
|
2018
2019
|
success({ deleted: true }, "bitbucket", "delete-comment", start);
|
|
2019
2020
|
} catch (err) {
|
|
2020
2021
|
fail(err, "bitbucket", "delete-comment", start);
|
|
@@ -2305,8 +2306,8 @@ var GitHubClient = class {
|
|
|
2305
2306
|
);
|
|
2306
2307
|
}
|
|
2307
2308
|
async createRepo(opts) {
|
|
2308
|
-
const
|
|
2309
|
-
return this.http.github(
|
|
2309
|
+
const path11 = opts.org ? `/orgs/${opts.org}/repos` : "/user/repos";
|
|
2310
|
+
return this.http.github(path11, {
|
|
2310
2311
|
method: "POST",
|
|
2311
2312
|
body: {
|
|
2312
2313
|
name: opts.name,
|
|
@@ -6271,8 +6272,8 @@ async function runCredentialChecks(cfg, http) {
|
|
|
6271
6272
|
} else {
|
|
6272
6273
|
try {
|
|
6273
6274
|
const collection = cfg.defaults.ado?.collection;
|
|
6274
|
-
const
|
|
6275
|
-
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 });
|
|
6276
6277
|
results.ado = { status: "valid", message: "ok" };
|
|
6277
6278
|
} catch (err) {
|
|
6278
6279
|
results.ado = categorize(err);
|
|
@@ -6492,13 +6493,27 @@ async function runCredentialChecks(cfg, http) {
|
|
|
6492
6493
|
results.figma = categorize(err);
|
|
6493
6494
|
}
|
|
6494
6495
|
}
|
|
6496
|
+
if (!cfg.alation.refreshToken) {
|
|
6497
|
+
results.alation = { status: "blank", message: "not configured" };
|
|
6498
|
+
} else if (!cfg.alation.baseUrl) {
|
|
6499
|
+
results.alation = { status: "error", message: "baseUrl not configured" };
|
|
6500
|
+
} else if (cfg.alation.userId === void 0 || cfg.alation.userId === "") {
|
|
6501
|
+
results.alation = { status: "error", message: "userId not configured" };
|
|
6502
|
+
} else {
|
|
6503
|
+
try {
|
|
6504
|
+
await validateAlationAccessToken(cfg);
|
|
6505
|
+
results.alation = { status: "valid", message: "ok" };
|
|
6506
|
+
} catch (err) {
|
|
6507
|
+
results.alation = categorize(err);
|
|
6508
|
+
}
|
|
6509
|
+
}
|
|
6495
6510
|
return results;
|
|
6496
6511
|
}
|
|
6497
6512
|
|
|
6498
6513
|
// src/services/skills/commands.ts
|
|
6499
|
-
import
|
|
6500
|
-
import
|
|
6501
|
-
import
|
|
6514
|
+
import fs3 from "fs";
|
|
6515
|
+
import path8 from "path";
|
|
6516
|
+
import os2 from "os";
|
|
6502
6517
|
import { fileURLToPath } from "url";
|
|
6503
6518
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
6504
6519
|
import select from "@inquirer/select";
|
|
@@ -6525,6 +6540,170 @@ function getPncliVersion() {
|
|
|
6525
6540
|
return cached;
|
|
6526
6541
|
}
|
|
6527
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
|
+
|
|
6528
6707
|
// src/services/skills/commands.ts
|
|
6529
6708
|
var BACK = "__back__";
|
|
6530
6709
|
var ALL_MARKETPLACES = "__all_marketplaces__";
|
|
@@ -6549,7 +6728,7 @@ function repoNameFromUrl(url) {
|
|
|
6549
6728
|
}
|
|
6550
6729
|
}
|
|
6551
6730
|
function defaultMarketplacePath(url) {
|
|
6552
|
-
return
|
|
6731
|
+
return path8.join(os2.homedir(), ".agents", "marketplaces", repoNameFromUrl(url));
|
|
6553
6732
|
}
|
|
6554
6733
|
function marketplaceLabel(m) {
|
|
6555
6734
|
return m.name ?? repoNameFromUrl(m.repoUrl ?? "");
|
|
@@ -6604,9 +6783,9 @@ function assertInteractive(hint) {
|
|
|
6604
6783
|
}
|
|
6605
6784
|
}
|
|
6606
6785
|
var AGENT_PATHS = {
|
|
6607
|
-
"codex": { project: ".agents/skills", user:
|
|
6608
|
-
"github-copilot": { project: ".github/skills", user:
|
|
6609
|
-
"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") }
|
|
6610
6789
|
};
|
|
6611
6790
|
var DEFAULT_AGENT = "codex";
|
|
6612
6791
|
var AGENT_CHOICES = Object.keys(AGENT_PATHS).join(" | ");
|
|
@@ -6633,23 +6812,23 @@ function findGitRoot(from = process.cwd()) {
|
|
|
6633
6812
|
encoding: "utf8",
|
|
6634
6813
|
stdio: ["pipe", "pipe", "pipe"]
|
|
6635
6814
|
}).trim();
|
|
6636
|
-
return root ?
|
|
6815
|
+
return root ? path8.resolve(root) : null;
|
|
6637
6816
|
} catch {
|
|
6638
6817
|
return null;
|
|
6639
6818
|
}
|
|
6640
6819
|
}
|
|
6641
6820
|
function resolveProjectPath(relativePath) {
|
|
6642
6821
|
const root = findGitRoot();
|
|
6643
|
-
return root ?
|
|
6822
|
+
return root ? path8.resolve(root, relativePath) : path8.resolve(relativePath);
|
|
6644
6823
|
}
|
|
6645
6824
|
function resolveScopedPath(agentPaths, scope) {
|
|
6646
|
-
return scope === "project" ? resolveProjectPath(agentPaths.project) :
|
|
6825
|
+
return scope === "project" ? resolveProjectPath(agentPaths.project) : path8.resolve(agentPaths.user);
|
|
6647
6826
|
}
|
|
6648
6827
|
function hasInstalledPncliSkill() {
|
|
6649
6828
|
try {
|
|
6650
6829
|
for (const agentPaths of Object.values(AGENT_PATHS)) {
|
|
6651
6830
|
for (const scope of ["project", "user"]) {
|
|
6652
|
-
if (
|
|
6831
|
+
if (fs3.existsSync(path8.join(resolveScopedPath(agentPaths, scope), "pncli", "SKILL.md"))) {
|
|
6653
6832
|
return true;
|
|
6654
6833
|
}
|
|
6655
6834
|
}
|
|
@@ -6659,20 +6838,30 @@ function hasInstalledPncliSkill() {
|
|
|
6659
6838
|
return false;
|
|
6660
6839
|
}
|
|
6661
6840
|
function resolveTargetDir(opts) {
|
|
6662
|
-
if (opts.target) return
|
|
6841
|
+
if (opts.target) return path8.resolve(opts.target);
|
|
6663
6842
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
6664
6843
|
return resolveScopedPath(agentConfig, opts.scope ?? "user");
|
|
6665
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
|
+
}
|
|
6666
6855
|
function getBundledSkillsDir() {
|
|
6667
6856
|
try {
|
|
6668
6857
|
const thisFile = fileURLToPath(import.meta.url);
|
|
6669
|
-
return
|
|
6858
|
+
return path8.resolve(path8.dirname(thisFile), "..", "skills");
|
|
6670
6859
|
} catch {
|
|
6671
6860
|
return "";
|
|
6672
6861
|
}
|
|
6673
6862
|
}
|
|
6674
6863
|
function getInstalledMetaPath(targetDir) {
|
|
6675
|
-
return
|
|
6864
|
+
return path8.join(targetDir, ".pncli-installed.json");
|
|
6676
6865
|
}
|
|
6677
6866
|
var SKILL_ORIGIN_FILENAME = "pncli-origin.json";
|
|
6678
6867
|
var DISABLED_SUBDIR = ".pncli-disabled";
|
|
@@ -6683,13 +6872,13 @@ function readInstalledMeta(targetDir) {
|
|
|
6683
6872
|
return { version: 1, skills: {} };
|
|
6684
6873
|
}
|
|
6685
6874
|
function getSkillOriginPath(skillDir) {
|
|
6686
|
-
return
|
|
6875
|
+
return path8.join(skillDir, SKILL_ORIGIN_FILENAME);
|
|
6687
6876
|
}
|
|
6688
6877
|
function readSkillOrigin(skillDir) {
|
|
6689
6878
|
const originPath = getSkillOriginPath(skillDir);
|
|
6690
|
-
if (!
|
|
6879
|
+
if (!fs3.existsSync(originPath)) return null;
|
|
6691
6880
|
try {
|
|
6692
|
-
const raw = JSON.parse(
|
|
6881
|
+
const raw = JSON.parse(fs3.readFileSync(originPath, "utf8"));
|
|
6693
6882
|
if (raw && raw.version === 1 && raw.source) return raw;
|
|
6694
6883
|
} catch {
|
|
6695
6884
|
}
|
|
@@ -6701,7 +6890,7 @@ function matchesMarketplaceFilter(record, filter) {
|
|
|
6701
6890
|
function resolveSkillRecord(meta, targetDir, skillName) {
|
|
6702
6891
|
const fromMeta = meta.skills[skillName];
|
|
6703
6892
|
if (fromMeta) return fromMeta;
|
|
6704
|
-
const perSkill = readSkillOrigin(
|
|
6893
|
+
const perSkill = readSkillOrigin(path8.join(targetDir, skillName));
|
|
6705
6894
|
if (!perSkill) return null;
|
|
6706
6895
|
return {
|
|
6707
6896
|
source: perSkill.source,
|
|
@@ -6714,28 +6903,28 @@ function resolveSkillRecord(meta, targetDir, skillName) {
|
|
|
6714
6903
|
};
|
|
6715
6904
|
}
|
|
6716
6905
|
function listActiveSkillDirs(targetDir) {
|
|
6717
|
-
if (!
|
|
6718
|
-
return
|
|
6906
|
+
if (!fs3.existsSync(targetDir)) return [];
|
|
6907
|
+
return fs3.readdirSync(targetDir).filter((name) => {
|
|
6719
6908
|
if (name.startsWith(".")) return false;
|
|
6720
|
-
const dir =
|
|
6909
|
+
const dir = path8.join(targetDir, name);
|
|
6721
6910
|
try {
|
|
6722
|
-
if (!
|
|
6911
|
+
if (!fs3.statSync(dir).isDirectory()) return false;
|
|
6723
6912
|
} catch {
|
|
6724
6913
|
return false;
|
|
6725
6914
|
}
|
|
6726
|
-
return
|
|
6915
|
+
return fs3.existsSync(path8.join(dir, "SKILL.md"));
|
|
6727
6916
|
});
|
|
6728
6917
|
}
|
|
6729
6918
|
function disablePluginSkills(targetDir, plugin, marketplaceFilter) {
|
|
6730
|
-
const resolvedTarget =
|
|
6731
|
-
const stashDir =
|
|
6919
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
6920
|
+
const stashDir = path8.join(targetDir, DISABLED_SUBDIR);
|
|
6732
6921
|
const meta = readInstalledMeta(targetDir);
|
|
6733
6922
|
const disabled = [];
|
|
6734
6923
|
const alreadyDisabled = [];
|
|
6735
6924
|
const skipped = [];
|
|
6736
6925
|
for (const skillName of listActiveSkillDirs(targetDir)) {
|
|
6737
|
-
const skillDir =
|
|
6738
|
-
if (!skillDir.startsWith(resolvedTarget +
|
|
6926
|
+
const skillDir = path8.resolve(targetDir, skillName);
|
|
6927
|
+
if (!skillDir.startsWith(resolvedTarget + path8.sep)) continue;
|
|
6739
6928
|
const record = resolveSkillRecord(meta, targetDir, skillName);
|
|
6740
6929
|
if (!record || record.source !== "marketplace") {
|
|
6741
6930
|
skipped.push(skillName);
|
|
@@ -6745,10 +6934,10 @@ function disablePluginSkills(targetDir, plugin, marketplaceFilter) {
|
|
|
6745
6934
|
skipped.push(skillName);
|
|
6746
6935
|
continue;
|
|
6747
6936
|
}
|
|
6748
|
-
const stashDest =
|
|
6749
|
-
|
|
6750
|
-
if (
|
|
6751
|
-
|
|
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);
|
|
6752
6941
|
delete meta.skills[skillName];
|
|
6753
6942
|
meta.disabled = meta.disabled ?? {};
|
|
6754
6943
|
meta.disabled[skillName] = record;
|
|
@@ -6760,13 +6949,13 @@ function disablePluginSkills(targetDir, plugin, marketplaceFilter) {
|
|
|
6760
6949
|
}
|
|
6761
6950
|
}
|
|
6762
6951
|
if (disabled.length > 0) {
|
|
6763
|
-
|
|
6952
|
+
fs3.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
6764
6953
|
}
|
|
6765
6954
|
return { disabled, alreadyDisabled, skipped };
|
|
6766
6955
|
}
|
|
6767
6956
|
function enablePluginSkills(targetDir, plugin, marketplaceFilter) {
|
|
6768
|
-
const resolvedTarget =
|
|
6769
|
-
const stashDir =
|
|
6957
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
6958
|
+
const stashDir = path8.join(targetDir, DISABLED_SUBDIR);
|
|
6770
6959
|
const meta = readInstalledMeta(targetDir);
|
|
6771
6960
|
const enabled = [];
|
|
6772
6961
|
const stashMissing = [];
|
|
@@ -6777,27 +6966,27 @@ function enablePluginSkills(targetDir, plugin, marketplaceFilter) {
|
|
|
6777
6966
|
skipped.push(skillName);
|
|
6778
6967
|
continue;
|
|
6779
6968
|
}
|
|
6780
|
-
const dest =
|
|
6781
|
-
if (!dest.startsWith(resolvedTarget +
|
|
6969
|
+
const dest = path8.resolve(targetDir, skillName);
|
|
6970
|
+
if (!dest.startsWith(resolvedTarget + path8.sep)) {
|
|
6782
6971
|
skipped.push(skillName);
|
|
6783
6972
|
continue;
|
|
6784
6973
|
}
|
|
6785
|
-
const stashSrc =
|
|
6786
|
-
if (!
|
|
6974
|
+
const stashSrc = path8.join(stashDir, skillName);
|
|
6975
|
+
if (!fs3.existsSync(stashSrc)) {
|
|
6787
6976
|
stashMissing.push(skillName);
|
|
6788
6977
|
continue;
|
|
6789
6978
|
}
|
|
6790
|
-
if (
|
|
6791
|
-
|
|
6979
|
+
if (fs3.existsSync(dest)) fs3.rmSync(dest, { recursive: true, force: true });
|
|
6980
|
+
fs3.renameSync(stashSrc, dest);
|
|
6792
6981
|
meta.skills[skillName] = record;
|
|
6793
6982
|
delete meta.disabled[skillName];
|
|
6794
6983
|
enabled.push(skillName);
|
|
6795
6984
|
}
|
|
6796
|
-
if (
|
|
6797
|
-
|
|
6985
|
+
if (fs3.existsSync(stashDir) && fs3.readdirSync(stashDir).length === 0) {
|
|
6986
|
+
fs3.rmdirSync(stashDir);
|
|
6798
6987
|
}
|
|
6799
6988
|
if (enabled.length > 0) {
|
|
6800
|
-
|
|
6989
|
+
fs3.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
6801
6990
|
}
|
|
6802
6991
|
return { enabled, stashMissing, skipped, hadDisabled: disabledEntries.length > 0 };
|
|
6803
6992
|
}
|
|
@@ -6863,11 +7052,11 @@ function getInstalledPluginsForMarketplace(targetDir, marketplaceName, repoUrl)
|
|
|
6863
7052
|
function readCustomTargets(globalConfig) {
|
|
6864
7053
|
const raw = globalConfig.skillsTargets;
|
|
6865
7054
|
if (!Array.isArray(raw)) return [];
|
|
6866
|
-
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)))];
|
|
6867
7056
|
}
|
|
6868
7057
|
function rememberCustomTarget(configPath, targetDir) {
|
|
6869
|
-
const resolved =
|
|
6870
|
-
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)]);
|
|
6871
7060
|
if (builtIn.includes(resolved)) return false;
|
|
6872
7061
|
const existing = loadJsonFile(configPath) ?? {};
|
|
6873
7062
|
const targets = readCustomTargets(existing);
|
|
@@ -6876,7 +7065,7 @@ function rememberCustomTarget(configPath, targetDir) {
|
|
|
6876
7065
|
return true;
|
|
6877
7066
|
}
|
|
6878
7067
|
function forgetCustomTarget(configPath, targetDir) {
|
|
6879
|
-
const resolved =
|
|
7068
|
+
const resolved = path8.resolve(targetDir);
|
|
6880
7069
|
const existing = loadJsonFile(configPath) ?? {};
|
|
6881
7070
|
const targets = readCustomTargets(existing);
|
|
6882
7071
|
if (!targets.includes(resolved)) return false;
|
|
@@ -6884,12 +7073,12 @@ function forgetCustomTarget(configPath, targetDir) {
|
|
|
6884
7073
|
return true;
|
|
6885
7074
|
}
|
|
6886
7075
|
function summarizeLocation(agent, scope, targetDir) {
|
|
6887
|
-
const resolved =
|
|
7076
|
+
const resolved = path8.resolve(targetDir);
|
|
6888
7077
|
const base = {
|
|
6889
7078
|
agent,
|
|
6890
7079
|
scope,
|
|
6891
7080
|
path: resolved,
|
|
6892
|
-
exists:
|
|
7081
|
+
exists: fs3.existsSync(resolved),
|
|
6893
7082
|
totalSkills: 0,
|
|
6894
7083
|
marketplaceSkills: 0,
|
|
6895
7084
|
bundledSkills: 0,
|
|
@@ -6908,8 +7097,8 @@ function summarizeLocation(agent, scope, targetDir) {
|
|
|
6908
7097
|
}
|
|
6909
7098
|
const disabledNames = Object.keys(meta.disabled ?? {});
|
|
6910
7099
|
base.disabledSkills = disabledNames.length;
|
|
6911
|
-
const stashDir =
|
|
6912
|
-
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)));
|
|
6913
7102
|
return base;
|
|
6914
7103
|
}
|
|
6915
7104
|
function listKnownLocations(globalConfig) {
|
|
@@ -6934,7 +7123,7 @@ function collectSkillStatus(globalConfig, locations) {
|
|
|
6934
7123
|
const { byName, byUrl } = buildMarketplaceIndex(getAllMarketplaces(globalConfig));
|
|
6935
7124
|
const remoteCache = /* @__PURE__ */ new Map();
|
|
6936
7125
|
const remoteFor = (localPath) => {
|
|
6937
|
-
if (!localPath || !
|
|
7126
|
+
if (!localPath || !fs3.existsSync(localPath)) return null;
|
|
6938
7127
|
if (!remoteCache.has(localPath)) remoteCache.set(localPath, getRepoRemoteUrl(localPath));
|
|
6939
7128
|
return remoteCache.get(localPath) ?? null;
|
|
6940
7129
|
};
|
|
@@ -6947,7 +7136,7 @@ function collectSkillStatus(globalConfig, locations) {
|
|
|
6947
7136
|
const registered = (recordedName ? byName.get(recordedName) : void 0) ?? (record?.installedFrom ? byUrl.get(record.installedFrom) : void 0) ?? null;
|
|
6948
7137
|
records.push({
|
|
6949
7138
|
skill: skillName,
|
|
6950
|
-
path: enabled ?
|
|
7139
|
+
path: enabled ? path8.join(location.path, skillName) : path8.join(location.path, DISABLED_SUBDIR, skillName),
|
|
6951
7140
|
agent: location.agent,
|
|
6952
7141
|
scope: location.scope,
|
|
6953
7142
|
location: location.path,
|
|
@@ -6980,16 +7169,16 @@ function recordInstalledSkills(targetDir, skillNames, record) {
|
|
|
6980
7169
|
for (const skillName of skillNames) {
|
|
6981
7170
|
const fullRecord = { pncliVersion: getPncliVersion(), ...record, installedAt: now };
|
|
6982
7171
|
meta.skills[skillName] = fullRecord;
|
|
6983
|
-
const skillDir =
|
|
6984
|
-
if (
|
|
7172
|
+
const skillDir = path8.join(targetDir, skillName);
|
|
7173
|
+
if (fs3.existsSync(skillDir)) {
|
|
6985
7174
|
const origin = { version: 1, ...fullRecord };
|
|
6986
7175
|
try {
|
|
6987
|
-
|
|
7176
|
+
fs3.writeFileSync(getSkillOriginPath(skillDir), JSON.stringify(origin, null, 2), "utf8");
|
|
6988
7177
|
} catch {
|
|
6989
7178
|
}
|
|
6990
7179
|
}
|
|
6991
7180
|
}
|
|
6992
|
-
|
|
7181
|
+
fs3.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
6993
7182
|
}
|
|
6994
7183
|
function findStaleBundledSkills(targetDir) {
|
|
6995
7184
|
const currentVersion = getPncliVersion();
|
|
@@ -7065,8 +7254,8 @@ function upsertMarketplace(all, entry) {
|
|
|
7065
7254
|
}
|
|
7066
7255
|
}
|
|
7067
7256
|
function cloneOrReuseMarketplace(url, resolvedPath, opts, marketplaceName) {
|
|
7068
|
-
const hasGit =
|
|
7069
|
-
if (
|
|
7257
|
+
const hasGit = fs3.existsSync(path8.join(resolvedPath, ".git"));
|
|
7258
|
+
if (fs3.existsSync(resolvedPath) && !hasGit && fs3.readdirSync(resolvedPath).length > 0) {
|
|
7070
7259
|
throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
|
|
7071
7260
|
}
|
|
7072
7261
|
if (hasGit) {
|
|
@@ -7123,7 +7312,7 @@ function installPluginsForMarketplace(marketplacePath, marketplaceName, repoUrl,
|
|
|
7123
7312
|
const branch = detectRepoBranch(marketplacePath);
|
|
7124
7313
|
for (const pluginName of pluginNames) {
|
|
7125
7314
|
const skillsSrc = resolveSkillsSrc(marketplacePath, pluginName);
|
|
7126
|
-
if (!
|
|
7315
|
+
if (!fs3.existsSync(skillsSrc)) {
|
|
7127
7316
|
results[pluginName] = { installed: [], failed: [] };
|
|
7128
7317
|
warn(`No skills directory found for plugin "${pluginName}" in "${marketplaceName}" \u2014 skipping.`);
|
|
7129
7318
|
continue;
|
|
@@ -7137,61 +7326,105 @@ function installPluginsForMarketplace(marketplacePath, marketplaceName, repoUrl,
|
|
|
7137
7326
|
results[pluginName] = { installed, failed };
|
|
7138
7327
|
totalInstalled += installed.length;
|
|
7139
7328
|
for (const skill of installed) {
|
|
7140
|
-
|
|
7329
|
+
log(` ${skill}: ${path8.join(skillsSrc, skill)} \u2192 ${path8.join(targetDir, skill)}`);
|
|
7141
7330
|
}
|
|
7142
7331
|
if (failed.length > 0) {
|
|
7143
7332
|
warn(`Skipped ${failed.length} skill(s) with invalid names in "${pluginName}": ${failed.join(", ")}`);
|
|
7144
7333
|
}
|
|
7145
7334
|
}
|
|
7335
|
+
warn(`Installed ${totalInstalled} skill(s) from ${pluginNames.length} plugin(s) in "${marketplaceName}" to ${targetDir}`);
|
|
7146
7336
|
return { results, totalInstalled };
|
|
7147
7337
|
}
|
|
7148
|
-
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) {
|
|
7149
7400
|
const pluginChoices = resolvePluginChoices(resolvedPath);
|
|
7150
7401
|
if (pluginChoices.length === 0) {
|
|
7151
7402
|
warn("No plugins found in marketplace. Check the marketplace repository structure.");
|
|
7152
|
-
return {
|
|
7403
|
+
return targets.map((t) => ({ ...t, plugins: {}, total: 0 }));
|
|
7153
7404
|
}
|
|
7154
|
-
|
|
7155
|
-
const { results, totalInstalled } = installPluginsForMarketplace(resolvedPath, marketplaceName, url, pluginChoices.map((p) => p.name), targetDir);
|
|
7156
|
-
return { pluginResults: results, totalInstalled };
|
|
7405
|
+
return targets.map((t) => installMarketplaceToTarget(m, resolvedPath, pluginChoices, "all", t, { updated: true, force: true, installedOnly: false }));
|
|
7157
7406
|
}
|
|
7158
|
-
function syncMarketplacePlugins(m,
|
|
7407
|
+
function syncMarketplacePlugins(m, targets, pluginFilter, opts) {
|
|
7159
7408
|
const marketplaceName = marketplaceLabel(m);
|
|
7160
7409
|
try {
|
|
7161
7410
|
const marketplacePath = m.localPath;
|
|
7162
|
-
if (!marketplacePath || !
|
|
7411
|
+
if (!marketplacePath || !fs3.existsSync(marketplacePath)) {
|
|
7163
7412
|
warn(`Marketplace "${marketplaceName}" local path not found \u2014 skipping.`);
|
|
7164
7413
|
return { marketplace: marketplaceName, skipped: true, message: "Local path not found." };
|
|
7165
7414
|
}
|
|
7166
7415
|
const { updated } = pullMarketplace(marketplacePath, m.repoUrl, m.token, marketplaceName);
|
|
7167
|
-
if (!updated && !force) {
|
|
7168
|
-
return { marketplace: marketplaceName, marketplaceUpdated: false, skipped: true, message: "No changes detected \u2014 skipping install. Use --force to reinstall anyway." };
|
|
7169
|
-
}
|
|
7170
7416
|
const pluginChoices = resolvePluginChoices(marketplacePath);
|
|
7171
7417
|
if (pluginChoices.length === 0) {
|
|
7172
7418
|
warn(`No plugins found in marketplace "${marketplaceName}" \u2014 skipping.`);
|
|
7173
7419
|
return { marketplace: marketplaceName, skipped: true, message: "No plugins found." };
|
|
7174
7420
|
}
|
|
7175
|
-
|
|
7176
|
-
if (pluginFilter === "all") {
|
|
7177
|
-
if (installedOnly) {
|
|
7178
|
-
const installed = getInstalledPluginsForMarketplace(targetDir, marketplaceName, m.repoUrl).filter((name) => pluginChoices.some((p) => p.name === name));
|
|
7179
|
-
if (installed.length === 0) {
|
|
7180
|
-
warn(`No installed plugins found for "${marketplaceName}" \u2014 skipping. Run marketplace sync without --installed-only to install plugins.`);
|
|
7181
|
-
return { marketplace: marketplaceName, skipped: true, installedOnly: true, message: "No installed plugins found \u2014 nothing to sync." };
|
|
7182
|
-
}
|
|
7183
|
-
pluginNames = installed;
|
|
7184
|
-
} else {
|
|
7185
|
-
pluginNames = pluginChoices.map((p) => p.name);
|
|
7186
|
-
}
|
|
7187
|
-
} else if (pluginChoices.some((p) => p.name === pluginFilter)) {
|
|
7188
|
-
pluginNames = [pluginFilter];
|
|
7189
|
-
} else {
|
|
7421
|
+
if (pluginFilter !== "all" && !pluginChoices.some((p) => p.name === pluginFilter)) {
|
|
7190
7422
|
warn(`Plugin "${pluginFilter}" not found in "${marketplaceName}" \u2014 skipping.`);
|
|
7191
7423
|
return { marketplace: marketplaceName, skipped: true, message: `Plugin "${pluginFilter}" not found.` };
|
|
7192
7424
|
}
|
|
7193
|
-
const
|
|
7194
|
-
|
|
7425
|
+
const targetResults = targets.map((t) => installMarketplaceToTarget(m, marketplacePath, pluginChoices, pluginFilter, t, { updated, ...opts }));
|
|
7426
|
+
const instructions = applyInstructionsForTargets(m, marketplacePath, targets, opts.instructions);
|
|
7427
|
+
return shapeSyncResult(marketplaceName, updated, pluginFilter, targetResults, { installedOnly: opts.installedOnly, instructions });
|
|
7195
7428
|
} catch (err) {
|
|
7196
7429
|
const message = err instanceof Error ? err.message : String(err);
|
|
7197
7430
|
warn(`Marketplace "${marketplaceName}" failed \u2014 skipping. ${message}`);
|
|
@@ -7199,7 +7432,7 @@ function syncMarketplacePlugins(m, targetDir, force, pluginFilter, installedOnly
|
|
|
7199
7432
|
}
|
|
7200
7433
|
}
|
|
7201
7434
|
function performMarketplaceAdd(url, localPath, opts) {
|
|
7202
|
-
const resolvedPath =
|
|
7435
|
+
const resolvedPath = path8.resolve(localPath ?? defaultMarketplacePath(url));
|
|
7203
7436
|
const marketplaceName = opts.name ?? repoNameFromUrl(url);
|
|
7204
7437
|
cloneOrReuseMarketplace(url, resolvedPath, opts, marketplaceName);
|
|
7205
7438
|
const configPath = getGlobalConfigPath();
|
|
@@ -7212,17 +7445,18 @@ function performMarketplaceAdd(url, localPath, opts) {
|
|
|
7212
7445
|
};
|
|
7213
7446
|
upsertMarketplace(all, entry);
|
|
7214
7447
|
saveMarketplaces(configPath, existing, all);
|
|
7215
|
-
const
|
|
7216
|
-
const
|
|
7448
|
+
const targets = resolveInstallTargets(opts);
|
|
7449
|
+
const targetResults = installAllPlugins(entry, resolvedPath, targets);
|
|
7450
|
+
const instructions = applyInstructionsForTargets(entry, resolvedPath, targets, opts.instructions !== false);
|
|
7217
7451
|
return {
|
|
7218
7452
|
name: marketplaceName,
|
|
7219
7453
|
repoUrl: url,
|
|
7220
7454
|
localPath: resolvedPath,
|
|
7221
7455
|
branch: opts.branch ?? null,
|
|
7222
7456
|
tokenConfigured: !!opts.token,
|
|
7223
|
-
|
|
7224
|
-
total:
|
|
7225
|
-
|
|
7457
|
+
// One target keeps the flat shape earlier versions emitted; several nest under `targets`.
|
|
7458
|
+
...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) },
|
|
7459
|
+
...instructions ? { instructions } : {}
|
|
7226
7460
|
};
|
|
7227
7461
|
}
|
|
7228
7462
|
function performMarketplaceRemove(name) {
|
|
@@ -7256,8 +7490,8 @@ function listBundledSkillDirs() {
|
|
|
7256
7490
|
let skillDirs = [];
|
|
7257
7491
|
if (bundledDir !== "") {
|
|
7258
7492
|
try {
|
|
7259
|
-
skillDirs =
|
|
7260
|
-
(name) =>
|
|
7493
|
+
skillDirs = fs3.readdirSync(bundledDir).filter(
|
|
7494
|
+
(name) => fs3.existsSync(path8.join(bundledDir, name, "SKILL.md"))
|
|
7261
7495
|
);
|
|
7262
7496
|
} catch {
|
|
7263
7497
|
}
|
|
@@ -7268,37 +7502,37 @@ function listBundledSkillDirs() {
|
|
|
7268
7502
|
return skillDirs;
|
|
7269
7503
|
}
|
|
7270
7504
|
function installBundledSkillsTo(targetDir, skillDirs) {
|
|
7271
|
-
const resolvedTarget =
|
|
7505
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
7272
7506
|
const bundledDir = getBundledSkillsDir();
|
|
7273
7507
|
const installed = [];
|
|
7274
7508
|
const failed = [];
|
|
7275
7509
|
for (const skillName of skillDirs) {
|
|
7276
|
-
const existingDir =
|
|
7277
|
-
if (!existingDir.startsWith(resolvedTarget +
|
|
7278
|
-
if (
|
|
7279
|
-
|
|
7510
|
+
const existingDir = path8.resolve(targetDir, skillName);
|
|
7511
|
+
if (!existingDir.startsWith(resolvedTarget + path8.sep)) continue;
|
|
7512
|
+
if (fs3.existsSync(existingDir)) {
|
|
7513
|
+
fs3.rmSync(existingDir, { recursive: true, force: true });
|
|
7280
7514
|
}
|
|
7281
7515
|
}
|
|
7282
|
-
|
|
7516
|
+
log(`Installing ${skillDirs.length} bundled skill(s) to ${targetDir}...`);
|
|
7283
7517
|
for (const skillName of skillDirs) {
|
|
7284
|
-
const skillDir =
|
|
7285
|
-
if (!skillDir.startsWith(resolvedTarget +
|
|
7518
|
+
const skillDir = path8.resolve(targetDir, skillName);
|
|
7519
|
+
if (!skillDir.startsWith(resolvedTarget + path8.sep)) {
|
|
7286
7520
|
failed.push(skillName);
|
|
7287
7521
|
continue;
|
|
7288
7522
|
}
|
|
7289
7523
|
try {
|
|
7290
|
-
const mdFiles =
|
|
7291
|
-
|
|
7524
|
+
const mdFiles = fs3.readdirSync(path8.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
|
|
7525
|
+
fs3.mkdirSync(skillDir, { recursive: true });
|
|
7292
7526
|
for (const mdFile of mdFiles) {
|
|
7293
|
-
const content =
|
|
7294
|
-
|
|
7527
|
+
const content = fs3.readFileSync(path8.join(bundledDir, skillName, mdFile), "utf8");
|
|
7528
|
+
fs3.writeFileSync(path8.join(skillDir, mdFile), content, "utf8");
|
|
7295
7529
|
}
|
|
7296
7530
|
installed.push(skillName);
|
|
7297
7531
|
} catch {
|
|
7298
7532
|
failed.push(skillName);
|
|
7299
7533
|
}
|
|
7300
7534
|
}
|
|
7301
|
-
warn(`Installed ${installed.length} skill(s) to ${targetDir}`);
|
|
7535
|
+
warn(`Installed ${installed.length} bundled skill(s) to ${targetDir}`);
|
|
7302
7536
|
if (failed.length > 0) {
|
|
7303
7537
|
warn(`Failed to install: ${failed.join(", ")}`);
|
|
7304
7538
|
}
|
|
@@ -7306,8 +7540,28 @@ function installBundledSkillsTo(targetDir, skillDirs) {
|
|
|
7306
7540
|
return { installed, failed };
|
|
7307
7541
|
}
|
|
7308
7542
|
function registerSkillsCommands(program2) {
|
|
7309
|
-
const skills = program2.command("skills").description(`
|
|
7310
|
-
skills.
|
|
7543
|
+
const skills = program2.command("skills").description(`Install bundled pncli skills and org plugins from git marketplaces (${AGENT_CHOICES})`);
|
|
7544
|
+
skills.addHelpText("after", `
|
|
7545
|
+
Where skills go:
|
|
7546
|
+
--agent codex (default) \u2192 .agents/skills (project) or ~/.agents/skills (user); read by Codex and GitHub Copilot
|
|
7547
|
+
--agent github-copilot \u2192 .github/skills or ~/.copilot/skills
|
|
7548
|
+
--agent claude-code \u2192 .claude/skills or ~/.claude/skills (--claude is a shorthand)
|
|
7549
|
+
--all-agents \u2192 every host above in one run
|
|
7550
|
+
|
|
7551
|
+
Bundled skills (ship with pncli, refresh after upgrading):
|
|
7552
|
+
pncli skills install --all-agents # into this repo, every agent host
|
|
7553
|
+
pncli skills install --all-agents --scope user # for every repo on this machine
|
|
7554
|
+
|
|
7555
|
+
Org plugins (skills and AGENTS.md/CLAUDE.md from a git-hosted marketplace):
|
|
7556
|
+
pncli skills marketplace add <git-url> --all-agents # register, clone, install everything
|
|
7557
|
+
pncli skills marketplace sync --marketplace all --all-agents
|
|
7558
|
+
pncli skills marketplace manage # interactive: toggle plugins, add/remove marketplaces
|
|
7559
|
+
pncli skills marketplace --help # the full plugin workflow
|
|
7560
|
+
|
|
7561
|
+
Check what is installed:
|
|
7562
|
+
pncli skills status | pncli skills locations | pncli doctor
|
|
7563
|
+
`);
|
|
7564
|
+
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) => {
|
|
7311
7565
|
const start = Date.now();
|
|
7312
7566
|
try {
|
|
7313
7567
|
const agentExplicit = cmd.getOptionValueSource("agent") === "cli";
|
|
@@ -7317,10 +7571,9 @@ function registerSkillsCommands(program2) {
|
|
|
7317
7571
|
const scope = opts.scope === "user" ? "user" : "project";
|
|
7318
7572
|
const skillDirs = listBundledSkillDirs();
|
|
7319
7573
|
if (opts.allAgents) {
|
|
7320
|
-
const targets =
|
|
7321
|
-
const
|
|
7322
|
-
|
|
7323
|
-
return { agent, target: targetDir2, installed: installed2, failed: failed2, total: installed2.length };
|
|
7574
|
+
const targets = resolveInstallTargets({ allAgents: true }, scope).map(({ agent, target }) => {
|
|
7575
|
+
const { installed: installed2, failed: failed2 } = installBundledSkillsTo(target, skillDirs);
|
|
7576
|
+
return { agent, target, installed: installed2, failed: failed2, total: installed2.length };
|
|
7324
7577
|
});
|
|
7325
7578
|
success({
|
|
7326
7579
|
targets,
|
|
@@ -7331,7 +7584,7 @@ function registerSkillsCommands(program2) {
|
|
|
7331
7584
|
return;
|
|
7332
7585
|
}
|
|
7333
7586
|
const agentName = resolveAgentName(opts);
|
|
7334
|
-
const targetDir = opts.target ?
|
|
7587
|
+
const targetDir = opts.target ? path8.resolve(opts.target) : resolveScopedPath(resolveAgentPaths(agentName), scope);
|
|
7335
7588
|
const { installed, failed } = installBundledSkillsTo(targetDir, skillDirs);
|
|
7336
7589
|
let tracked = false;
|
|
7337
7590
|
if (opts.target && installed.length > 0) {
|
|
@@ -7360,12 +7613,12 @@ function registerSkillsCommands(program2) {
|
|
|
7360
7613
|
try {
|
|
7361
7614
|
let targetDir;
|
|
7362
7615
|
if (opts.target) {
|
|
7363
|
-
targetDir =
|
|
7616
|
+
targetDir = path8.resolve(opts.target);
|
|
7364
7617
|
} else {
|
|
7365
7618
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
7366
7619
|
targetDir = resolveScopedPath(agentConfig, opts.scope === "user" ? "user" : "project");
|
|
7367
7620
|
}
|
|
7368
|
-
if (!
|
|
7621
|
+
if (!fs3.existsSync(targetDir)) {
|
|
7369
7622
|
success({
|
|
7370
7623
|
skills: [],
|
|
7371
7624
|
total: 0,
|
|
@@ -7376,13 +7629,13 @@ function registerSkillsCommands(program2) {
|
|
|
7376
7629
|
return;
|
|
7377
7630
|
}
|
|
7378
7631
|
const meta = readInstalledMeta(targetDir);
|
|
7379
|
-
const skillDirs =
|
|
7632
|
+
const skillDirs = fs3.readdirSync(targetDir).filter((name) => {
|
|
7380
7633
|
if (name.startsWith(".")) return false;
|
|
7381
|
-
const skillPath =
|
|
7382
|
-
return
|
|
7634
|
+
const skillPath = path8.join(targetDir, name, "SKILL.md");
|
|
7635
|
+
return fs3.existsSync(skillPath);
|
|
7383
7636
|
});
|
|
7384
7637
|
const skillsList = skillDirs.map((name) => {
|
|
7385
|
-
const content =
|
|
7638
|
+
const content = fs3.readFileSync(path8.join(targetDir, name, "SKILL.md"), "utf8");
|
|
7386
7639
|
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
7387
7640
|
const data = {};
|
|
7388
7641
|
const metadata = {};
|
|
@@ -7435,24 +7688,24 @@ function registerSkillsCommands(program2) {
|
|
|
7435
7688
|
try {
|
|
7436
7689
|
let targetDir;
|
|
7437
7690
|
if (opts.target) {
|
|
7438
|
-
targetDir =
|
|
7691
|
+
targetDir = path8.resolve(opts.target);
|
|
7439
7692
|
} else {
|
|
7440
7693
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
7441
7694
|
targetDir = resolveScopedPath(agentConfig, opts.scope === "project" ? "project" : "user");
|
|
7442
7695
|
}
|
|
7443
|
-
const resolvedTarget =
|
|
7444
|
-
const skillDir =
|
|
7445
|
-
if (!skillDir.startsWith(resolvedTarget +
|
|
7696
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
7697
|
+
const skillDir = path8.resolve(targetDir, name);
|
|
7698
|
+
if (!skillDir.startsWith(resolvedTarget + path8.sep)) {
|
|
7446
7699
|
throw new Error(`Invalid skill name: "${name}"`);
|
|
7447
7700
|
}
|
|
7448
|
-
if (!
|
|
7701
|
+
if (!fs3.existsSync(skillDir)) {
|
|
7449
7702
|
throw new Error(`Skill "${name}" not found at ${skillDir}`);
|
|
7450
7703
|
}
|
|
7451
7704
|
const meta = readInstalledMeta(targetDir);
|
|
7452
7705
|
const record = meta.skills[name];
|
|
7453
|
-
|
|
7706
|
+
fs3.rmSync(skillDir, { recursive: true, force: true });
|
|
7454
7707
|
delete meta.skills[name];
|
|
7455
|
-
|
|
7708
|
+
fs3.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
7456
7709
|
success({
|
|
7457
7710
|
uninstalled: name,
|
|
7458
7711
|
target: targetDir,
|
|
@@ -7523,7 +7776,7 @@ function registerSkillsCommands(program2) {
|
|
|
7523
7776
|
const start = Date.now();
|
|
7524
7777
|
try {
|
|
7525
7778
|
const configPath = getGlobalConfigPath();
|
|
7526
|
-
const resolved =
|
|
7779
|
+
const resolved = path8.resolve(dir);
|
|
7527
7780
|
const forgotten = forgetCustomTarget(configPath, resolved);
|
|
7528
7781
|
if (!forgotten) {
|
|
7529
7782
|
warn(`${resolved} is not a tracked custom target \u2014 nothing to forget.`);
|
|
@@ -7537,16 +7790,28 @@ function registerSkillsCommands(program2) {
|
|
|
7537
7790
|
fail(err, "skills", "forget-target", start);
|
|
7538
7791
|
}
|
|
7539
7792
|
});
|
|
7540
|
-
const marketplace = skills.command("marketplace").description("
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7793
|
+
const marketplace = skills.command("marketplace").description("Org plugins from git-hosted marketplaces: add, sync, enable/disable, and shipped AGENTS.md/CLAUDE.md");
|
|
7794
|
+
marketplace.addHelpText("after", `
|
|
7795
|
+
Workflow:
|
|
7796
|
+
pncli skills marketplace add <git-url> --all-agents # register + clone + install every plugin
|
|
7797
|
+
pncli skills marketplace sync --marketplace all --all-agents # pull and refresh everything installed
|
|
7798
|
+
pncli skills marketplace plugins <name> # browse a marketplace without installing
|
|
7799
|
+
pncli skills marketplace manage # interactive hub (toggle, add, remove)
|
|
7800
|
+
pncli skills marketplace disable <plugin> / enable <plugin> # switch a plugin off without deleting it
|
|
7801
|
+
|
|
7802
|
+
Shipped instructions:
|
|
7803
|
+
A marketplace may ship instructions/AGENTS.md and/or instructions/CLAUDE.md. add and sync merge
|
|
7804
|
+
them into each agent's user-level file (~/.codex/AGENTS.md, ~/.copilot/copilot-instructions.md,
|
|
7805
|
+
~/.claude/CLAUDE.md) as a marked block that never touches your own content.
|
|
7806
|
+
pncli skills marketplace instructions list | install | remove <name>
|
|
7807
|
+
|
|
7808
|
+
Plugin skills always install at user scope. --agent picks the host (default: ${DEFAULT_AGENT}); --all-agents covers all of them.
|
|
7809
|
+
`);
|
|
7810
|
+
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) => {
|
|
7811
|
+
const marketplaceIdx = process.argv.indexOf("marketplace");
|
|
7812
|
+
const invokedAs = marketplaceIdx !== -1 && process.argv[marketplaceIdx + 1] === "setup" ? "marketplace-setup" : "marketplace-add";
|
|
7813
|
+
return marketplaceAddAction(url, localPath, opts, invokedAs);
|
|
7814
|
+
});
|
|
7550
7815
|
marketplace.command("list").description("List all registered marketplaces").action(() => {
|
|
7551
7816
|
const start = Date.now();
|
|
7552
7817
|
try {
|
|
@@ -7558,7 +7823,7 @@ function registerSkillsCommands(program2) {
|
|
|
7558
7823
|
repoUrl: m.repoUrl,
|
|
7559
7824
|
localPath: m.localPath,
|
|
7560
7825
|
tokenConfigured: !!m.token,
|
|
7561
|
-
upstreamRemote: m.localPath &&
|
|
7826
|
+
upstreamRemote: m.localPath && fs3.existsSync(m.localPath) ? getRepoRemoteUrl(m.localPath) : null
|
|
7562
7827
|
})),
|
|
7563
7828
|
total: all.length
|
|
7564
7829
|
}, "skills", "marketplace-list", start);
|
|
@@ -7575,7 +7840,7 @@ function registerSkillsCommands(program2) {
|
|
|
7575
7840
|
if (!found) {
|
|
7576
7841
|
throw new Error(`Marketplace "${name}" not found. Run: pncli skills marketplace list`);
|
|
7577
7842
|
}
|
|
7578
|
-
if (!found.localPath || !
|
|
7843
|
+
if (!found.localPath || !fs3.existsSync(found.localPath)) {
|
|
7579
7844
|
throw new Error(`Marketplace "${marketplaceLabel(found)}" local path not found at ${found.localPath ?? "(not set)"}. Run: pncli skills marketplace add <url>`);
|
|
7580
7845
|
}
|
|
7581
7846
|
const plugins = resolvePluginChoices(found.localPath);
|
|
@@ -7596,7 +7861,7 @@ function registerSkillsCommands(program2) {
|
|
|
7596
7861
|
fail(err, "skills", "marketplace-remove", start);
|
|
7597
7862
|
}
|
|
7598
7863
|
});
|
|
7599
|
-
marketplace.command("sync").description("Pull latest marketplace content and install plugin skills (interactive picker when no plugin is given)").argument("[plugin]", 'Plugin name to install, or "all" to install every plugin (skips interactive selection)').option("--marketplace <name>", 'Marketplace name to sync, or "all" to sync every registered marketplace (skips interactive selection)').option("--agent <agent>", `Target agent host: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).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)').option("--installed-only", "Only sync plugins that are already installed \u2014 skip plugins newly added to the marketplace").action(async (plugin, opts) => {
|
|
7864
|
+
marketplace.command("sync").description("Pull latest marketplace content and install plugin skills (interactive picker when no plugin is given)").argument("[plugin]", 'Plugin name to install, or "all" to install every plugin (skips interactive selection)').option("--marketplace <name>", 'Marketplace name to sync, or "all" to sync every registered marketplace (skips interactive selection)').option("--agent <agent>", `Target agent host: ${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) => {
|
|
7600
7865
|
const start = Date.now();
|
|
7601
7866
|
try {
|
|
7602
7867
|
const configPath = getGlobalConfigPath();
|
|
@@ -7604,12 +7869,12 @@ function registerSkillsCommands(program2) {
|
|
|
7604
7869
|
if (allMarketplaces.length === 0) {
|
|
7605
7870
|
throw new Error("No marketplaces configured. Run: pncli skills marketplace add <url>");
|
|
7606
7871
|
}
|
|
7607
|
-
const
|
|
7608
|
-
const
|
|
7609
|
-
const
|
|
7872
|
+
const targets = resolveInstallTargets(opts);
|
|
7873
|
+
const syncOpts = { force: opts.force ?? false, installedOnly: opts.installedOnly ?? false, instructions: opts.instructions !== false };
|
|
7874
|
+
const targetSummary = targets.length === 1 ? { target: targets[0].target } : { targets: targets.map((t) => t.target) };
|
|
7610
7875
|
if (opts.marketplace === "all") {
|
|
7611
|
-
const results = allMarketplaces.map((m) => syncMarketplacePlugins(m,
|
|
7612
|
-
success({ allMarketplaces: true, marketplaces: results,
|
|
7876
|
+
const results = allMarketplaces.map((m) => syncMarketplacePlugins(m, targets, plugin ?? "all", syncOpts));
|
|
7877
|
+
success({ allMarketplaces: true, marketplaces: results, ...targetSummary }, "skills", "marketplace-sync", start);
|
|
7613
7878
|
return;
|
|
7614
7879
|
}
|
|
7615
7880
|
let selectedMarketplace;
|
|
@@ -7632,12 +7897,12 @@ function registerSkillsCommands(program2) {
|
|
|
7632
7897
|
choices: [
|
|
7633
7898
|
{ value: ALL_MARKETPLACES, name: "All marketplaces \u2014 sync every plugin from every marketplace" },
|
|
7634
7899
|
// Index-based values: labels alone aren't guaranteed unique (two marketplaces can share a derived name).
|
|
7635
|
-
...allMarketplaces.map((
|
|
7900
|
+
...allMarketplaces.map((m2, i) => ({ value: String(i), name: `${marketplaceLabel(m2)} \u2014 ${m2.repoUrl ?? ""}` }))
|
|
7636
7901
|
]
|
|
7637
7902
|
});
|
|
7638
7903
|
if (chosen === ALL_MARKETPLACES) {
|
|
7639
|
-
const
|
|
7640
|
-
success({ allMarketplaces: true, marketplaces:
|
|
7904
|
+
const results = allMarketplaces.map((m2) => syncMarketplacePlugins(m2, targets, selectedPlugin ?? "all", syncOpts));
|
|
7905
|
+
success({ allMarketplaces: true, marketplaces: results, ...targetSummary }, "skills", "marketplace-sync", start);
|
|
7641
7906
|
return;
|
|
7642
7907
|
}
|
|
7643
7908
|
selectedMarketplace = allMarketplaces[Number(chosen)];
|
|
@@ -7647,7 +7912,7 @@ function registerSkillsCommands(program2) {
|
|
|
7647
7912
|
}
|
|
7648
7913
|
const marketplaceName = marketplaceLabel(selectedMarketplace);
|
|
7649
7914
|
const marketplacePath = selectedMarketplace.localPath;
|
|
7650
|
-
if (!marketplacePath || !
|
|
7915
|
+
if (!marketplacePath || !fs3.existsSync(marketplacePath)) {
|
|
7651
7916
|
throw new Error(`Marketplace "${marketplaceName}" local path not found at ${marketplacePath ?? "(not set)"}. Run: pncli skills marketplace add <url>`);
|
|
7652
7917
|
}
|
|
7653
7918
|
const { updated } = pullMarketplace(marketplacePath, selectedMarketplace.repoUrl, selectedMarketplace.token, marketplaceName);
|
|
@@ -7673,55 +7938,94 @@ function registerSkillsCommands(program2) {
|
|
|
7673
7938
|
} else if (selectedPlugin !== "all" && !pluginChoices.some((p) => p.name === selectedPlugin)) {
|
|
7674
7939
|
throw new Error(`Plugin "${selectedPlugin}" not found in "${marketplaceName}". Available: ${pluginChoices.map((p) => p.name).join(", ")}`);
|
|
7675
7940
|
}
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7941
|
+
const m = selectedMarketplace;
|
|
7942
|
+
const targetResults = targets.map((t) => installMarketplaceToTarget(m, marketplacePath, pluginChoices, selectedPlugin, t, { updated, force: syncOpts.force, installedOnly: syncOpts.installedOnly }));
|
|
7943
|
+
const instructions2 = applyInstructionsForTargets(m, marketplacePath, targets, syncOpts.instructions);
|
|
7944
|
+
success(
|
|
7945
|
+
shapeSyncResult(marketplaceName, updated, selectedPlugin, targetResults, { installedOnly: syncOpts.installedOnly, instructions: instructions2 }),
|
|
7946
|
+
"skills",
|
|
7947
|
+
"marketplace-sync",
|
|
7948
|
+
start
|
|
7949
|
+
);
|
|
7950
|
+
return;
|
|
7951
|
+
}
|
|
7952
|
+
} catch (err) {
|
|
7953
|
+
fail(err, "skills", "marketplace-sync", start);
|
|
7954
|
+
}
|
|
7955
|
+
});
|
|
7956
|
+
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)");
|
|
7957
|
+
function resolveInstructionAgents(opts) {
|
|
7958
|
+
return resolveInstallTargets(opts).map((t) => t.agent);
|
|
7959
|
+
}
|
|
7960
|
+
function findMarketplaceOrThrow(all, name) {
|
|
7961
|
+
const found = all.find((m) => m.name === name || m.repoUrl === name);
|
|
7962
|
+
if (!found) throw new Error(`Marketplace "${name}" not found. Run: pncli skills marketplace list`);
|
|
7963
|
+
return found;
|
|
7964
|
+
}
|
|
7965
|
+
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) => {
|
|
7966
|
+
const start = Date.now();
|
|
7967
|
+
try {
|
|
7968
|
+
const all = loadMarketplaces(getGlobalConfigPath());
|
|
7969
|
+
const selected = opts.marketplace ? [findMarketplaceOrThrow(all, opts.marketplace)] : all;
|
|
7970
|
+
const agents = Object.keys(AGENT_PATHS);
|
|
7971
|
+
const marketplaces = selected.map((m) => {
|
|
7972
|
+
const shipped = m.localPath && fs3.existsSync(m.localPath) ? findMarketplaceInstructions(m.localPath).map((s) => s.file) : [];
|
|
7973
|
+
return {
|
|
7974
|
+
marketplace: marketplaceLabel(m),
|
|
7975
|
+
localPath: m.localPath ?? null,
|
|
7976
|
+
ships: shipped,
|
|
7977
|
+
agents: marketplaceInstructionsStatus(m.localPath, marketplaceLabel(m), agents)
|
|
7978
|
+
};
|
|
7979
|
+
});
|
|
7980
|
+
success({
|
|
7981
|
+
files: Object.fromEntries(agents.map((a) => [a, agentInstructionsFile(a)])),
|
|
7982
|
+
marketplaces,
|
|
7983
|
+
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."
|
|
7984
|
+
}, "skills", "marketplace-instructions-list", start);
|
|
7985
|
+
} catch (err) {
|
|
7986
|
+
fail(err, "skills", "marketplace-instructions-list", start);
|
|
7987
|
+
}
|
|
7988
|
+
});
|
|
7989
|
+
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) => {
|
|
7990
|
+
const start = Date.now();
|
|
7991
|
+
try {
|
|
7992
|
+
const all = loadMarketplaces(getGlobalConfigPath());
|
|
7993
|
+
if (all.length === 0) throw new Error("No marketplaces configured. Run: pncli skills marketplace add <url>");
|
|
7994
|
+
const agents = resolveInstructionAgents(opts);
|
|
7995
|
+
const selected = opts.marketplace ? [findMarketplaceOrThrow(all, opts.marketplace)] : all;
|
|
7996
|
+
const results = selected.map((m) => {
|
|
7997
|
+
const name = marketplaceLabel(m);
|
|
7998
|
+
if (!m.localPath || !fs3.existsSync(m.localPath)) {
|
|
7999
|
+
return { marketplace: name, skipped: true, message: "Local path not found. Run: pncli skills marketplace add <url>" };
|
|
7685
8000
|
}
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
if (installedOnly) {
|
|
7689
|
-
const installed = getInstalledPluginsForMarketplace(targetDir, marketplaceName, selectedMarketplace.repoUrl).filter((name) => pluginChoices.some((p) => p.name === name));
|
|
7690
|
-
if (installed.length === 0) {
|
|
7691
|
-
success({
|
|
7692
|
-
marketplace: marketplaceName,
|
|
7693
|
-
skipped: true,
|
|
7694
|
-
installedOnly: true,
|
|
7695
|
-
message: `No installed plugins found for "${marketplaceName}" \u2014 run marketplace sync without --installed-only to install plugins.`
|
|
7696
|
-
}, "skills", "marketplace-sync", start);
|
|
7697
|
-
return;
|
|
7698
|
-
}
|
|
7699
|
-
pluginNames = installed;
|
|
7700
|
-
} else {
|
|
7701
|
-
pluginNames = pluginChoices.map((p) => p.name);
|
|
7702
|
-
}
|
|
7703
|
-
} else {
|
|
7704
|
-
pluginNames = [selectedPlugin];
|
|
8001
|
+
if (findMarketplaceInstructions(m.localPath).length === 0) {
|
|
8002
|
+
return { marketplace: name, skipped: true, message: `Ships no instructions/AGENTS.md or instructions/CLAUDE.md.` };
|
|
7705
8003
|
}
|
|
7706
|
-
const
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
} else {
|
|
7710
|
-
const single = results[selectedPlugin] ?? { installed: [], failed: [] };
|
|
7711
|
-
success({
|
|
7712
|
-
marketplace: marketplaceName,
|
|
7713
|
-
plugin: selectedPlugin,
|
|
7714
|
-
installed: single.installed,
|
|
7715
|
-
failed: single.failed,
|
|
7716
|
-
total: single.installed.length,
|
|
7717
|
-
target: targetDir,
|
|
7718
|
-
marketplaceUpdated: updated
|
|
7719
|
-
}, "skills", "marketplace-sync", start);
|
|
8004
|
+
const applied = applyMarketplaceInstructions(m.localPath, name, agents);
|
|
8005
|
+
for (const r of applied) {
|
|
8006
|
+
if (r.action === "added" || r.action === "updated") warn(`Instructions ${r.action} in ${r.file} (from ${r.source})`);
|
|
7720
8007
|
}
|
|
7721
|
-
return;
|
|
8008
|
+
return { marketplace: name, agents: applied };
|
|
8009
|
+
});
|
|
8010
|
+
if (opts.marketplace && "skipped" in results[0]) throw new Error(`Marketplace "${results[0].marketplace}": ${results[0].message}`);
|
|
8011
|
+
success({ marketplaces: results, agents }, "skills", "marketplace-instructions-install", start);
|
|
8012
|
+
} catch (err) {
|
|
8013
|
+
fail(err, "skills", "marketplace-instructions-install", start);
|
|
8014
|
+
}
|
|
8015
|
+
});
|
|
8016
|
+
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) => {
|
|
8017
|
+
const start = Date.now();
|
|
8018
|
+
try {
|
|
8019
|
+
const agents = resolveInstructionAgents(opts);
|
|
8020
|
+
const registered = loadMarketplaces(getGlobalConfigPath()).find((m) => m.name === marketplaceName || m.repoUrl === marketplaceName);
|
|
8021
|
+
const name = registered ? marketplaceLabel(registered) : marketplaceName;
|
|
8022
|
+
const results = removeMarketplaceInstructions(name, agents);
|
|
8023
|
+
for (const r of results) {
|
|
8024
|
+
if (r.removed) warn(`Instructions block for "${name}" removed from ${r.file}`);
|
|
7722
8025
|
}
|
|
8026
|
+
success({ marketplace: name, agents: results, removed: results.filter((r) => r.removed).length }, "skills", "marketplace-instructions-remove", start);
|
|
7723
8027
|
} catch (err) {
|
|
7724
|
-
fail(err, "skills", "marketplace-
|
|
8028
|
+
fail(err, "skills", "marketplace-instructions-remove", start);
|
|
7725
8029
|
}
|
|
7726
8030
|
});
|
|
7727
8031
|
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) => {
|
|
@@ -7729,31 +8033,31 @@ function registerSkillsCommands(program2) {
|
|
|
7729
8033
|
try {
|
|
7730
8034
|
let targetDir;
|
|
7731
8035
|
if (opts.target) {
|
|
7732
|
-
targetDir =
|
|
8036
|
+
targetDir = path8.resolve(opts.target);
|
|
7733
8037
|
} else {
|
|
7734
8038
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
7735
8039
|
targetDir = resolveScopedPath(agentConfig, opts.scope ?? "user");
|
|
7736
8040
|
}
|
|
7737
|
-
const resolvedTarget =
|
|
7738
|
-
if (!
|
|
8041
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
8042
|
+
if (!fs3.existsSync(targetDir)) {
|
|
7739
8043
|
success({ removed: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-purge-plugin", start);
|
|
7740
8044
|
return;
|
|
7741
8045
|
}
|
|
7742
8046
|
const meta = readInstalledMeta(targetDir);
|
|
7743
8047
|
const removed = [];
|
|
7744
8048
|
const skipped = [];
|
|
7745
|
-
const skillDirs =
|
|
8049
|
+
const skillDirs = fs3.readdirSync(targetDir).filter((name) => {
|
|
7746
8050
|
if (name.startsWith(".")) return false;
|
|
7747
|
-
const p =
|
|
8051
|
+
const p = path8.join(targetDir, name);
|
|
7748
8052
|
try {
|
|
7749
|
-
return
|
|
8053
|
+
return fs3.statSync(p).isDirectory();
|
|
7750
8054
|
} catch {
|
|
7751
8055
|
return false;
|
|
7752
8056
|
}
|
|
7753
8057
|
});
|
|
7754
8058
|
for (const skillName of skillDirs) {
|
|
7755
|
-
const skillDir =
|
|
7756
|
-
if (!skillDir.startsWith(resolvedTarget +
|
|
8059
|
+
const skillDir = path8.resolve(targetDir, skillName);
|
|
8060
|
+
if (!skillDir.startsWith(resolvedTarget + path8.sep)) continue;
|
|
7757
8061
|
let record = meta.skills[skillName] ?? null;
|
|
7758
8062
|
if (!record) {
|
|
7759
8063
|
const perSkill = readSkillOrigin(skillDir);
|
|
@@ -7778,12 +8082,12 @@ function registerSkillsCommands(program2) {
|
|
|
7778
8082
|
skipped.push(skillName);
|
|
7779
8083
|
continue;
|
|
7780
8084
|
}
|
|
7781
|
-
|
|
8085
|
+
fs3.rmSync(skillDir, { recursive: true, force: true });
|
|
7782
8086
|
delete meta.skills[skillName];
|
|
7783
8087
|
removed.push(skillName);
|
|
7784
8088
|
}
|
|
7785
8089
|
if (removed.length > 0) {
|
|
7786
|
-
|
|
8090
|
+
fs3.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
|
|
7787
8091
|
}
|
|
7788
8092
|
success({
|
|
7789
8093
|
removed,
|
|
@@ -7801,7 +8105,7 @@ function registerSkillsCommands(program2) {
|
|
|
7801
8105
|
const start = Date.now();
|
|
7802
8106
|
try {
|
|
7803
8107
|
const targetDir = resolveTargetDir(opts);
|
|
7804
|
-
if (!
|
|
8108
|
+
if (!fs3.existsSync(targetDir)) {
|
|
7805
8109
|
success({ disabled: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-disable", start);
|
|
7806
8110
|
return;
|
|
7807
8111
|
}
|
|
@@ -7813,7 +8117,7 @@ function registerSkillsCommands(program2) {
|
|
|
7813
8117
|
total: result.disabled.length,
|
|
7814
8118
|
target: targetDir,
|
|
7815
8119
|
plugin,
|
|
7816
|
-
stash:
|
|
8120
|
+
stash: path8.join(targetDir, DISABLED_SUBDIR),
|
|
7817
8121
|
...opts.marketplace ? { marketplace: opts.marketplace } : {}
|
|
7818
8122
|
}, "skills", "marketplace-disable", start);
|
|
7819
8123
|
} catch (err) {
|
|
@@ -7824,7 +8128,7 @@ function registerSkillsCommands(program2) {
|
|
|
7824
8128
|
const start = Date.now();
|
|
7825
8129
|
try {
|
|
7826
8130
|
const targetDir = resolveTargetDir(opts);
|
|
7827
|
-
if (!
|
|
8131
|
+
if (!fs3.existsSync(targetDir)) {
|
|
7828
8132
|
success({ enabled: [], total: 0, target: targetDir, message: "No skills directory found." }, "skills", "marketplace-enable", start);
|
|
7829
8133
|
return;
|
|
7830
8134
|
}
|
|
@@ -7849,29 +8153,48 @@ function registerSkillsCommands(program2) {
|
|
|
7849
8153
|
fail(err, "skills", "marketplace-enable", start);
|
|
7850
8154
|
}
|
|
7851
8155
|
});
|
|
7852
|
-
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) => {
|
|
8156
|
+
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) => {
|
|
7853
8157
|
const start = Date.now();
|
|
7854
8158
|
try {
|
|
7855
|
-
assertInteractive("Use `pncli skills marketplace enable|disable <plugin>`, `add <url>`, and `remove <name>` to manage non-interactively.");
|
|
8159
|
+
assertInteractive("Use `pncli skills marketplace enable|disable <plugin>`, `sync --marketplace all`, `instructions install`, `add <url>`, and `remove <name>` to manage non-interactively.");
|
|
7856
8160
|
const targetDir = resolveTargetDir(opts);
|
|
7857
8161
|
const enabledPlugins = [];
|
|
7858
8162
|
const disabledPlugins = [];
|
|
7859
8163
|
const stashMissing = [];
|
|
7860
8164
|
const addedMarketplaces = [];
|
|
7861
8165
|
const removedMarketplaces = [];
|
|
8166
|
+
const syncedMarketplaces = /* @__PURE__ */ new Map();
|
|
8167
|
+
const appliedInstructions = /* @__PURE__ */ new Map();
|
|
8168
|
+
const instructionAgents = opts.target ? [] : [resolveAgentName(opts)];
|
|
7862
8169
|
for (; ; ) {
|
|
7863
|
-
const states =
|
|
8170
|
+
const states = fs3.existsSync(targetDir) ? listPluginStates(targetDir) : [];
|
|
7864
8171
|
const registered = loadMarketplaces(getGlobalConfigPath());
|
|
8172
|
+
const shipping = registered.filter((m) => m.localPath && fs3.existsSync(m.localPath) && findMarketplaceInstructions(m.localPath).length > 0);
|
|
7865
8173
|
const action = await select({
|
|
7866
8174
|
message: `Manage skills marketplaces (target: ${targetDir}):`,
|
|
7867
8175
|
choices: [
|
|
7868
8176
|
...states.length > 0 ? [{ value: "toggle", name: `Toggle plugins on/off (${states.length} installed)` }] : [],
|
|
8177
|
+
...registered.length > 0 ? [{ value: "sync", name: `Sync every marketplace (${registered.length} registered)` }] : [],
|
|
8178
|
+
...shipping.length > 0 && instructionAgents.length > 0 ? [{ value: "instructions", name: `Apply shipped AGENTS.md / CLAUDE.md (${shipping.length} marketplace(s) ship them)` }] : [],
|
|
7869
8179
|
{ value: "add", name: "Add a marketplace" },
|
|
7870
8180
|
...registered.length > 0 ? [{ value: "remove", name: `Remove a marketplace (${registered.length} registered)` }] : [],
|
|
7871
8181
|
{ value: "done", name: "Done" }
|
|
7872
8182
|
]
|
|
7873
8183
|
});
|
|
7874
8184
|
if (action === "done") break;
|
|
8185
|
+
if (action === "sync") {
|
|
8186
|
+
const syncTargets = [{ agent: opts.target ? "custom" : resolveAgentName(opts), target: targetDir }];
|
|
8187
|
+
for (const m of registered) {
|
|
8188
|
+
syncedMarketplaces.set(marketplaceLabel(m), syncMarketplacePlugins(m, syncTargets, "all", { force: false, installedOnly: false, instructions: instructionAgents.length > 0 }));
|
|
8189
|
+
}
|
|
8190
|
+
continue;
|
|
8191
|
+
}
|
|
8192
|
+
if (action === "instructions") {
|
|
8193
|
+
for (const m of shipping) {
|
|
8194
|
+
appliedInstructions.set(marketplaceLabel(m), { marketplace: marketplaceLabel(m), agents: applyMarketplaceInstructions(m.localPath, marketplaceLabel(m), instructionAgents) });
|
|
8195
|
+
}
|
|
8196
|
+
continue;
|
|
8197
|
+
}
|
|
7875
8198
|
if (action === "toggle") {
|
|
7876
8199
|
const sorted = [...states].sort((a, b) => (a.marketplace ?? "").localeCompare(b.marketplace ?? "") || a.plugin.localeCompare(b.plugin));
|
|
7877
8200
|
const choices = [];
|
|
@@ -7921,7 +8244,9 @@ function registerSkillsCommands(program2) {
|
|
|
7921
8244
|
addedMarketplaces.push(performMarketplaceAdd(url, void 0, {
|
|
7922
8245
|
name: name || void 0,
|
|
7923
8246
|
agent: opts.agent,
|
|
7924
|
-
claude: opts.claude
|
|
8247
|
+
claude: opts.claude,
|
|
8248
|
+
// A custom --target has no agent host to map instructions onto.
|
|
8249
|
+
instructions: instructionAgents.length > 0
|
|
7925
8250
|
}));
|
|
7926
8251
|
} else if (action === "remove") {
|
|
7927
8252
|
const chosen = await select({
|
|
@@ -7942,6 +8267,8 @@ function registerSkillsCommands(program2) {
|
|
|
7942
8267
|
disabled: disabledPlugins,
|
|
7943
8268
|
addedMarketplaces,
|
|
7944
8269
|
removedMarketplaces,
|
|
8270
|
+
syncedMarketplaces: [...syncedMarketplaces.values()],
|
|
8271
|
+
appliedInstructions: [...appliedInstructions.values()],
|
|
7945
8272
|
target: targetDir,
|
|
7946
8273
|
...stashMissing.length > 0 ? {
|
|
7947
8274
|
stashMissing,
|
|
@@ -7956,15 +8283,15 @@ function registerSkillsCommands(program2) {
|
|
|
7956
8283
|
const start = Date.now();
|
|
7957
8284
|
try {
|
|
7958
8285
|
const userDir = resolveAgentPaths(resolveAgentName(opts)).user;
|
|
7959
|
-
if (!
|
|
8286
|
+
if (!fs3.existsSync(userDir)) {
|
|
7960
8287
|
success({ removed: 0, target: userDir, message: "No user-level skills directory found \u2014 nothing to purge." }, "skills", "purge-user", start);
|
|
7961
8288
|
return;
|
|
7962
8289
|
}
|
|
7963
|
-
const skillDirs =
|
|
8290
|
+
const skillDirs = fs3.readdirSync(userDir).filter((name) => {
|
|
7964
8291
|
if (name.startsWith(".")) return false;
|
|
7965
|
-
const p =
|
|
8292
|
+
const p = path8.join(userDir, name);
|
|
7966
8293
|
try {
|
|
7967
|
-
return
|
|
8294
|
+
return fs3.statSync(p).isDirectory();
|
|
7968
8295
|
} catch {
|
|
7969
8296
|
return false;
|
|
7970
8297
|
}
|
|
@@ -7980,13 +8307,13 @@ Re-run with --force to confirm, or use \`pncli skills marketplace purge-plugin\`
|
|
|
7980
8307
|
);
|
|
7981
8308
|
}
|
|
7982
8309
|
for (const name of skillDirs) {
|
|
7983
|
-
const skillDir =
|
|
7984
|
-
if (!skillDir.startsWith(
|
|
7985
|
-
|
|
8310
|
+
const skillDir = path8.resolve(userDir, name);
|
|
8311
|
+
if (!skillDir.startsWith(path8.resolve(userDir) + path8.sep)) continue;
|
|
8312
|
+
fs3.rmSync(skillDir, { recursive: true, force: true });
|
|
7986
8313
|
}
|
|
7987
8314
|
const metaPath = getInstalledMetaPath(userDir);
|
|
7988
|
-
if (
|
|
7989
|
-
|
|
8315
|
+
if (fs3.existsSync(metaPath)) {
|
|
8316
|
+
fs3.writeFileSync(metaPath, JSON.stringify({ version: 1, skills: {} }, null, 2), "utf8");
|
|
7990
8317
|
}
|
|
7991
8318
|
success({ removed: skillDirs.length, skills: skillDirs, target: userDir }, "skills", "purge-user", start);
|
|
7992
8319
|
} catch (err) {
|
|
@@ -7995,46 +8322,46 @@ Re-run with --force to confirm, or use \`pncli skills marketplace purge-plugin\`
|
|
|
7995
8322
|
});
|
|
7996
8323
|
}
|
|
7997
8324
|
function resolvePluginChoices(marketplacePath) {
|
|
7998
|
-
const marketplaceJsonPath =
|
|
7999
|
-
if (
|
|
8325
|
+
const marketplaceJsonPath = path8.join(marketplacePath, ".claude-plugin", "marketplace.json");
|
|
8326
|
+
if (fs3.existsSync(marketplaceJsonPath)) {
|
|
8000
8327
|
try {
|
|
8001
|
-
const meta = JSON.parse(
|
|
8328
|
+
const meta = JSON.parse(fs3.readFileSync(marketplaceJsonPath, "utf8"));
|
|
8002
8329
|
if (Array.isArray(meta.plugins) && meta.plugins.length > 0) {
|
|
8003
8330
|
return meta.plugins.map((p) => ({ name: p.name, description: p.description ?? "" }));
|
|
8004
8331
|
}
|
|
8005
8332
|
} catch {
|
|
8006
8333
|
}
|
|
8007
8334
|
}
|
|
8008
|
-
const pluginsDir =
|
|
8009
|
-
if (
|
|
8010
|
-
return
|
|
8335
|
+
const pluginsDir = path8.join(marketplacePath, "plugins");
|
|
8336
|
+
if (fs3.existsSync(pluginsDir)) {
|
|
8337
|
+
return fs3.readdirSync(pluginsDir).filter((name) => fs3.statSync(path8.join(pluginsDir, name)).isDirectory()).map((name) => ({ name, description: "" }));
|
|
8011
8338
|
}
|
|
8012
8339
|
return [];
|
|
8013
8340
|
}
|
|
8014
8341
|
function resolveSkillsSrc(marketplacePath, selectedPlugin) {
|
|
8015
|
-
const pluginsBase =
|
|
8016
|
-
const skillsSrc =
|
|
8017
|
-
if (!skillsSrc.startsWith(pluginsBase +
|
|
8342
|
+
const pluginsBase = path8.resolve(marketplacePath, "plugins");
|
|
8343
|
+
const skillsSrc = path8.resolve(pluginsBase, selectedPlugin, "skills");
|
|
8344
|
+
if (!skillsSrc.startsWith(pluginsBase + path8.sep)) {
|
|
8018
8345
|
throw new Error(`Invalid plugin name: "${selectedPlugin}"`);
|
|
8019
8346
|
}
|
|
8020
8347
|
return skillsSrc;
|
|
8021
8348
|
}
|
|
8022
8349
|
function copyPluginSkills(skillsSrc, targetDir, meta) {
|
|
8023
|
-
|
|
8024
|
-
const resolvedTarget =
|
|
8025
|
-
const skillNames =
|
|
8026
|
-
(name) =>
|
|
8350
|
+
fs3.mkdirSync(targetDir, { recursive: true });
|
|
8351
|
+
const resolvedTarget = path8.resolve(targetDir);
|
|
8352
|
+
const skillNames = fs3.readdirSync(skillsSrc).filter(
|
|
8353
|
+
(name) => fs3.statSync(path8.join(skillsSrc, name)).isDirectory()
|
|
8027
8354
|
);
|
|
8028
8355
|
const installed = [];
|
|
8029
8356
|
const failed = [];
|
|
8030
8357
|
for (const skillName of skillNames) {
|
|
8031
|
-
const dest =
|
|
8032
|
-
if (!dest.startsWith(resolvedTarget +
|
|
8358
|
+
const dest = path8.resolve(targetDir, skillName);
|
|
8359
|
+
if (!dest.startsWith(resolvedTarget + path8.sep)) {
|
|
8033
8360
|
failed.push(skillName);
|
|
8034
8361
|
continue;
|
|
8035
8362
|
}
|
|
8036
|
-
|
|
8037
|
-
|
|
8363
|
+
fs3.rmSync(dest, { recursive: true, force: true });
|
|
8364
|
+
fs3.cpSync(path8.join(skillsSrc, skillName), dest, { recursive: true });
|
|
8038
8365
|
installed.push(skillName);
|
|
8039
8366
|
}
|
|
8040
8367
|
if (meta) {
|
|
@@ -8051,7 +8378,7 @@ function copyPluginSkills(skillsSrc, targetDir, meta) {
|
|
|
8051
8378
|
|
|
8052
8379
|
// src/services/config/commands.ts
|
|
8053
8380
|
import chalk from "chalk";
|
|
8054
|
-
import
|
|
8381
|
+
import fs4 from "fs";
|
|
8055
8382
|
function registerConfigCommands(program2) {
|
|
8056
8383
|
const config = program2.command("config").description("Manage pncli configuration");
|
|
8057
8384
|
config.command("init").description("Setup wizard for service credentials (interactive)").option("--repo", "Write repo config (.pncli.json) instead of global config").action(async (opts) => {
|
|
@@ -8165,8 +8492,8 @@ function registerConfigCommands(program2) {
|
|
|
8165
8492
|
if (cfg.ado.baseUrl) {
|
|
8166
8493
|
try {
|
|
8167
8494
|
const collection = cfg.defaults.ado?.collection;
|
|
8168
|
-
const
|
|
8169
|
-
await http.ado(
|
|
8495
|
+
const path11 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
|
|
8496
|
+
await http.ado(path11);
|
|
8170
8497
|
results.ado = { ok: true, message: "connected" };
|
|
8171
8498
|
} catch (err) {
|
|
8172
8499
|
results.ado = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
@@ -8356,6 +8683,16 @@ function registerConfigCommands(program2) {
|
|
|
8356
8683
|
} else {
|
|
8357
8684
|
results.figma = { ok: null, message: "not configured" };
|
|
8358
8685
|
}
|
|
8686
|
+
if (cfg.alation.baseUrl && cfg.alation.refreshToken && cfg.alation.userId !== void 0 && cfg.alation.userId !== "") {
|
|
8687
|
+
try {
|
|
8688
|
+
await validateAlationAccessToken(cfg);
|
|
8689
|
+
results.alation = { ok: true, message: "connected" };
|
|
8690
|
+
} catch (err) {
|
|
8691
|
+
results.alation = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
8692
|
+
}
|
|
8693
|
+
} else {
|
|
8694
|
+
results.alation = { ok: null, message: "not configured" };
|
|
8695
|
+
}
|
|
8359
8696
|
success(results, "config", "test", start);
|
|
8360
8697
|
} catch (err) {
|
|
8361
8698
|
fail(err, "config", "test", start);
|
|
@@ -8397,7 +8734,8 @@ function registerConfigCommands(program2) {
|
|
|
8397
8734
|
...dynamicEnvKeys,
|
|
8398
8735
|
"logscale",
|
|
8399
8736
|
"splitio",
|
|
8400
|
-
"figma"
|
|
8737
|
+
"figma",
|
|
8738
|
+
"alation"
|
|
8401
8739
|
];
|
|
8402
8740
|
if (cmdOpts.output === "table") {
|
|
8403
8741
|
const labelWidth = Math.max(14, ...allServices.map((s) => s.length + 2));
|
|
@@ -9013,6 +9351,47 @@ async function initGlobalConfig(start) {
|
|
|
9013
9351
|
}
|
|
9014
9352
|
}
|
|
9015
9353
|
}
|
|
9354
|
+
process.stderr.write("\n\u2500\u2500 Alation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
9355
|
+
const useAlation = await confirm({
|
|
9356
|
+
message: "Configure Alation for data catalog and Document Hub access?",
|
|
9357
|
+
default: false
|
|
9358
|
+
});
|
|
9359
|
+
let alationBaseUrl = "";
|
|
9360
|
+
let alationRefreshToken = "";
|
|
9361
|
+
let alationUserId = "";
|
|
9362
|
+
if (useAlation) {
|
|
9363
|
+
process.stderr.write(" Generate a refresh token in Alation under your profile \u2192 Settings \u2192 Authentication \u2192 Create Refresh Token.\n");
|
|
9364
|
+
process.stderr.write(" Note the numeric user ID shown with it; pncli exchanges the refresh token for short-lived API tokens itself.\n");
|
|
9365
|
+
alationBaseUrl = await input2({
|
|
9366
|
+
message: "Alation base URL (e.g. https://alation.imagile.dev):",
|
|
9367
|
+
validate: (v) => v.trim().length > 0 || "Required"
|
|
9368
|
+
});
|
|
9369
|
+
alationRefreshToken = await password({
|
|
9370
|
+
message: "Alation refresh token:"
|
|
9371
|
+
});
|
|
9372
|
+
alationUserId = await input2({
|
|
9373
|
+
message: "Alation user ID (numeric):",
|
|
9374
|
+
validate: (v) => /^[1-9]\d*$/.test(v.trim()) || "Must be a positive integer"
|
|
9375
|
+
});
|
|
9376
|
+
if (alationBaseUrl && alationRefreshToken && alationUserId) {
|
|
9377
|
+
process.stderr.write("\n Verifying connection...\n");
|
|
9378
|
+
try {
|
|
9379
|
+
const tempConfig = {
|
|
9380
|
+
...loadConfig(),
|
|
9381
|
+
alation: {
|
|
9382
|
+
baseUrl: normalizeBaseUrl(alationBaseUrl),
|
|
9383
|
+
refreshToken: alationRefreshToken,
|
|
9384
|
+
userId: alationUserId.trim()
|
|
9385
|
+
}
|
|
9386
|
+
};
|
|
9387
|
+
await validateAlationAccessToken(tempConfig);
|
|
9388
|
+
process.stderr.write(" Connected.\n");
|
|
9389
|
+
} catch (err) {
|
|
9390
|
+
warn(`Could not connect to Alation: ${err instanceof Error ? err.message : String(err)}`);
|
|
9391
|
+
warn("Config will be saved anyway. Check your URL, refresh token, and user ID and re-run pncli config init or pncli config test.");
|
|
9392
|
+
}
|
|
9393
|
+
}
|
|
9394
|
+
}
|
|
9016
9395
|
process.stderr.write("\n\u2500\u2500 Defaults \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
9017
9396
|
const jiraProject = await input2({
|
|
9018
9397
|
message: "Default Jira project key (optional):",
|
|
@@ -9170,6 +9549,13 @@ async function initGlobalConfig(start) {
|
|
|
9170
9549
|
token: figmaToken
|
|
9171
9550
|
}
|
|
9172
9551
|
} : {},
|
|
9552
|
+
...useAlation && alationBaseUrl ? {
|
|
9553
|
+
alation: {
|
|
9554
|
+
baseUrl: normalizeBaseUrl(alationBaseUrl),
|
|
9555
|
+
refreshToken: alationRefreshToken || void 0,
|
|
9556
|
+
userId: alationUserId.trim() || void 0
|
|
9557
|
+
}
|
|
9558
|
+
} : {},
|
|
9173
9559
|
defaults: {
|
|
9174
9560
|
jira: {
|
|
9175
9561
|
project: jiraProject || void 0
|
|
@@ -9238,7 +9624,7 @@ async function initRepoConfig(start) {
|
|
|
9238
9624
|
process.exitCode = ExitCode.SUCCESS;
|
|
9239
9625
|
return;
|
|
9240
9626
|
}
|
|
9241
|
-
if (
|
|
9627
|
+
if (fs4.existsSync(".pncli.json")) {
|
|
9242
9628
|
const overwrite = await confirm({
|
|
9243
9629
|
message: ".pncli.json already exists. Overwrite?",
|
|
9244
9630
|
default: false
|
|
@@ -9272,14 +9658,14 @@ async function initRepoConfig(start) {
|
|
|
9272
9658
|
}
|
|
9273
9659
|
|
|
9274
9660
|
// src/services/doctor/commands.ts
|
|
9275
|
-
import
|
|
9276
|
-
import
|
|
9661
|
+
import fs5 from "fs";
|
|
9662
|
+
import path9 from "path";
|
|
9277
9663
|
function checkConfigFile(filePath) {
|
|
9278
|
-
if (!
|
|
9664
|
+
if (!fs5.existsSync(filePath)) {
|
|
9279
9665
|
return { path: filePath, exists: false, valid: false, message: "not present" };
|
|
9280
9666
|
}
|
|
9281
9667
|
try {
|
|
9282
|
-
JSON.parse(
|
|
9668
|
+
JSON.parse(fs5.readFileSync(filePath, "utf8"));
|
|
9283
9669
|
return { path: filePath, exists: true, valid: true, message: "ok" };
|
|
9284
9670
|
} catch (err) {
|
|
9285
9671
|
return {
|
|
@@ -9290,7 +9676,7 @@ function checkConfigFile(filePath) {
|
|
|
9290
9676
|
};
|
|
9291
9677
|
}
|
|
9292
9678
|
}
|
|
9293
|
-
function buildProblems(globalFile, repoFile, credentials, skillLocations) {
|
|
9679
|
+
function buildProblems(globalFile, repoFile, credentials, skillLocations, marketplaces = []) {
|
|
9294
9680
|
const problems = [];
|
|
9295
9681
|
for (const file of [globalFile, repoFile]) {
|
|
9296
9682
|
if (file.exists && !file.valid) {
|
|
@@ -9341,16 +9727,32 @@ function buildProblems(globalFile, repoFile, credentials, skillLocations) {
|
|
|
9341
9727
|
fix: "Refresh them with: pncli skills install"
|
|
9342
9728
|
});
|
|
9343
9729
|
}
|
|
9730
|
+
for (const m of marketplaces.filter((m2) => !m2.cloneExists)) {
|
|
9731
|
+
problems.push({
|
|
9732
|
+
area: "marketplaces",
|
|
9733
|
+
message: `Marketplace "${m.name}" is registered but its clone is missing (${m.localPath ?? "no local path"})`,
|
|
9734
|
+
fix: `Re-clone it with: pncli skills marketplace add ${m.repoUrl ?? "<url>"}`
|
|
9735
|
+
});
|
|
9736
|
+
}
|
|
9737
|
+
const marketplaceSkillTotal = skillLocations.reduce((sum, l) => sum + (l.marketplaceSkills ?? 0), 0);
|
|
9738
|
+
const liveMarketplaces = marketplaces.filter((m) => m.cloneExists).length;
|
|
9739
|
+
if (liveMarketplaces > 0 && marketplaceSkillTotal === 0) {
|
|
9740
|
+
problems.push({
|
|
9741
|
+
area: "marketplaces",
|
|
9742
|
+
message: `${liveMarketplaces} marketplace(s) registered but no plugin skills are installed from any of them`,
|
|
9743
|
+
fix: "Run: pncli skills marketplace sync --marketplace all --all-agents"
|
|
9744
|
+
});
|
|
9745
|
+
}
|
|
9344
9746
|
return problems;
|
|
9345
9747
|
}
|
|
9346
9748
|
function registerDoctorCommands(program2) {
|
|
9347
|
-
program2.command("doctor").description("Diagnose pncli setup: config files, credentials, and
|
|
9749
|
+
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) => {
|
|
9348
9750
|
const start = Date.now();
|
|
9349
9751
|
try {
|
|
9350
9752
|
const opts = program2.optsWithGlobals();
|
|
9351
9753
|
const globalFile = checkConfigFile(opts.config ?? getGlobalConfigPath());
|
|
9352
9754
|
const repoRoot = findGitRoot() ?? process.cwd();
|
|
9353
|
-
const repoFile = checkConfigFile(
|
|
9755
|
+
const repoFile = checkConfigFile(path9.join(repoRoot, ".pncli.json"));
|
|
9354
9756
|
let credentials = null;
|
|
9355
9757
|
let credentialsSkipped = cmdOpts.offline ? "--offline" : null;
|
|
9356
9758
|
if (!cmdOpts.offline) {
|
|
@@ -9368,9 +9770,16 @@ function registerDoctorCommands(program2) {
|
|
|
9368
9770
|
path: l.path,
|
|
9369
9771
|
exists: l.exists,
|
|
9370
9772
|
totalSkills: l.totalSkills,
|
|
9773
|
+
marketplaceSkills: l.marketplaceSkills,
|
|
9371
9774
|
staleSkills: l.exists ? findStaleBundledSkills(l.path) : []
|
|
9372
9775
|
}));
|
|
9373
|
-
const
|
|
9776
|
+
const marketplaces = getAllMarketplaces(globalConfig).map((m) => ({
|
|
9777
|
+
name: m.name ?? m.repoUrl ?? "(unnamed)",
|
|
9778
|
+
repoUrl: m.repoUrl ?? null,
|
|
9779
|
+
localPath: m.localPath ?? null,
|
|
9780
|
+
cloneExists: !!m.localPath && fs5.existsSync(m.localPath)
|
|
9781
|
+
}));
|
|
9782
|
+
const problems = buildProblems(globalFile, repoFile, credentials, skillLocations, marketplaces);
|
|
9374
9783
|
if (credentials) {
|
|
9375
9784
|
const statuses = Object.values(credentials).map((r) => r.status);
|
|
9376
9785
|
if (statuses.includes("invalid")) process.exitCode = ExitCode.AUTH_ERROR;
|
|
@@ -9389,6 +9798,10 @@ function registerDoctorCommands(program2) {
|
|
|
9389
9798
|
locations: skillLocations,
|
|
9390
9799
|
staleTotal: skillLocations.reduce((sum, l) => sum + l.staleSkills.length, 0)
|
|
9391
9800
|
},
|
|
9801
|
+
marketplaces: {
|
|
9802
|
+
registered: marketplaces,
|
|
9803
|
+
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"
|
|
9804
|
+
},
|
|
9392
9805
|
problems
|
|
9393
9806
|
}, "doctor", "check", start);
|
|
9394
9807
|
} catch (err) {
|
|
@@ -9778,8 +10191,8 @@ function registerAdoProjectCommands(ado) {
|
|
|
9778
10191
|
}
|
|
9779
10192
|
|
|
9780
10193
|
// src/services/ado/commands/work.ts
|
|
9781
|
-
import
|
|
9782
|
-
import
|
|
10194
|
+
import fs6 from "fs";
|
|
10195
|
+
import path10 from "path";
|
|
9783
10196
|
|
|
9784
10197
|
// src/services/ado/input-schema.ts
|
|
9785
10198
|
var ADO_WORK_INPUT_FILE_SCHEMA = {
|
|
@@ -10020,11 +10433,11 @@ function registerAdoWorkCommands(ado) {
|
|
|
10020
10433
|
const attachments = await workClient.listAttachments(collection, parseInt(opts.id, 10));
|
|
10021
10434
|
const attachment = attachments.find((a) => a.id === opts.attachmentId);
|
|
10022
10435
|
if (!attachment) throw new PncliError(`Attachment not found: ${opts.attachmentId}`, 1);
|
|
10023
|
-
const outDir = opts.dir ??
|
|
10024
|
-
|
|
10025
|
-
const outPath =
|
|
10436
|
+
const outDir = opts.dir ?? path10.join(process.cwd(), ".pncli");
|
|
10437
|
+
fs6.mkdirSync(outDir, { recursive: true });
|
|
10438
|
+
const outPath = path10.join(outDir, path10.basename(attachment.name));
|
|
10026
10439
|
const buffer = await workClient.downloadAttachment(attachment.url);
|
|
10027
|
-
|
|
10440
|
+
fs6.writeFileSync(outPath, buffer);
|
|
10028
10441
|
success({ saved: outPath, name: attachment.name, size: buffer.length }, "ado", "work-download-attachment", start);
|
|
10029
10442
|
} catch (err) {
|
|
10030
10443
|
fail(err, "ado", "work-download-attachment", start);
|
|
@@ -10058,10 +10471,10 @@ function registerAdoWorkCommands(ado) {
|
|
|
10058
10471
|
const types = await discoverTypes(workClient, collection, project);
|
|
10059
10472
|
if (opts.save) {
|
|
10060
10473
|
const globalConfigPath = getGlobalConfigPath(globalOpts.config);
|
|
10061
|
-
const
|
|
10062
|
-
const existing = JSON.parse(
|
|
10474
|
+
const fs9 = await import("fs");
|
|
10475
|
+
const existing = JSON.parse(fs9.readFileSync(globalConfigPath, "utf8") || "{}");
|
|
10063
10476
|
existing.ado = { ...existing.ado ?? {}, discoveredTypes: types };
|
|
10064
|
-
|
|
10477
|
+
fs9.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
10065
10478
|
warn(`Saved ${types.length} types to ${globalConfigPath}`);
|
|
10066
10479
|
}
|
|
10067
10480
|
success(types, "ado", "work-types", start);
|
|
@@ -10090,11 +10503,11 @@ function registerAdoWorkCommands(ado) {
|
|
|
10090
10503
|
) : allFields;
|
|
10091
10504
|
if (opts.save) {
|
|
10092
10505
|
const globalConfigPath = getGlobalConfigPath(globalOpts.config);
|
|
10093
|
-
const
|
|
10094
|
-
const existing = JSON.parse(
|
|
10506
|
+
const fs9 = await import("fs");
|
|
10507
|
+
const existing = JSON.parse(fs9.readFileSync(globalConfigPath, "utf8") || "{}");
|
|
10095
10508
|
const aliases = buildDefaultAliases(allFields);
|
|
10096
10509
|
existing.ado = { ...existing.ado ?? {}, discoveredFields: allFields, fieldAliases: aliases };
|
|
10097
|
-
|
|
10510
|
+
fs9.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
10098
10511
|
warn(`Saved ${allFields.length} fields and ${Object.keys(aliases).length} aliases to ${globalConfigPath}`);
|
|
10099
10512
|
}
|
|
10100
10513
|
success(fields, "ado", "work-fields", start);
|
|
@@ -10391,11 +10804,11 @@ function registerAdoRepoCommands(ado) {
|
|
|
10391
10804
|
const start = Date.now();
|
|
10392
10805
|
try {
|
|
10393
10806
|
const globalOpts = ado.optsWithGlobals();
|
|
10394
|
-
const { loadConfig: lc } = await import("./config-
|
|
10807
|
+
const { loadConfig: lc } = await import("./config-Y6IE6GY7.js");
|
|
10395
10808
|
const config = lc({ configPath: globalOpts.config });
|
|
10396
10809
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
10397
10810
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
10398
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
10811
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-V7PZD7LE.js");
|
|
10399
10812
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
10400
10813
|
const coreClient = new AdoCoreClient2(http);
|
|
10401
10814
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -10865,18 +11278,18 @@ var ArtifactoryClient = class {
|
|
|
10865
11278
|
}
|
|
10866
11279
|
});
|
|
10867
11280
|
}
|
|
10868
|
-
async getStorageInfo(repo,
|
|
10869
|
-
const cleanPath =
|
|
11281
|
+
async getStorageInfo(repo, path11) {
|
|
11282
|
+
const cleanPath = path11.startsWith("/") ? path11.slice(1) : path11;
|
|
10870
11283
|
return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`);
|
|
10871
11284
|
}
|
|
10872
|
-
async getProperties(repo,
|
|
10873
|
-
const cleanPath =
|
|
11285
|
+
async getProperties(repo, path11) {
|
|
11286
|
+
const cleanPath = path11.startsWith("/") ? path11.slice(1) : path11;
|
|
10874
11287
|
return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
|
|
10875
11288
|
params: { properties: "" }
|
|
10876
11289
|
});
|
|
10877
11290
|
}
|
|
10878
|
-
async setProperties(repo,
|
|
10879
|
-
const cleanPath =
|
|
11291
|
+
async setProperties(repo, path11, properties, opts = {}) {
|
|
11292
|
+
const cleanPath = path11.startsWith("/") ? path11.slice(1) : path11;
|
|
10880
11293
|
const propString = Object.entries(properties).map(([k, v]) => `${k}=${v}`).join(";");
|
|
10881
11294
|
await this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
|
|
10882
11295
|
method: "PUT",
|
|
@@ -10958,27 +11371,27 @@ function registerArtifactoryCommands(program2) {
|
|
|
10958
11371
|
fail(err, "artifactory", "repos", start);
|
|
10959
11372
|
}
|
|
10960
11373
|
});
|
|
10961
|
-
art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo,
|
|
11374
|
+
art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo, path11) => {
|
|
10962
11375
|
const start = Date.now();
|
|
10963
11376
|
try {
|
|
10964
11377
|
const client = getClient8(program2);
|
|
10965
|
-
const data = await client.getStorageInfo(repo,
|
|
11378
|
+
const data = await client.getStorageInfo(repo, path11);
|
|
10966
11379
|
success(data, "artifactory", "artifact-info", start);
|
|
10967
11380
|
} catch (err) {
|
|
10968
11381
|
fail(err, "artifactory", "artifact-info", start);
|
|
10969
11382
|
}
|
|
10970
11383
|
});
|
|
10971
|
-
art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo,
|
|
11384
|
+
art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo, path11) => {
|
|
10972
11385
|
const start = Date.now();
|
|
10973
11386
|
try {
|
|
10974
11387
|
const client = getClient8(program2);
|
|
10975
|
-
const data = await client.getProperties(repo,
|
|
11388
|
+
const data = await client.getProperties(repo, path11);
|
|
10976
11389
|
success(data, "artifactory", "properties-get", start);
|
|
10977
11390
|
} catch (err) {
|
|
10978
11391
|
fail(err, "artifactory", "properties-get", start);
|
|
10979
11392
|
}
|
|
10980
11393
|
});
|
|
10981
|
-
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,
|
|
11394
|
+
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) => {
|
|
10982
11395
|
const start = Date.now();
|
|
10983
11396
|
try {
|
|
10984
11397
|
if (!keyvals.length) throw new PncliError("Provide at least one key=value pair");
|
|
@@ -10989,8 +11402,8 @@ function registerArtifactoryCommands(program2) {
|
|
|
10989
11402
|
properties[kv.slice(0, eq)] = kv.slice(eq + 1);
|
|
10990
11403
|
}
|
|
10991
11404
|
const client = getClient8(program2);
|
|
10992
|
-
await client.setProperties(repo,
|
|
10993
|
-
success({ repo, path:
|
|
11405
|
+
await client.setProperties(repo, path11, properties, { recursive: opts.recursive });
|
|
11406
|
+
success({ repo, path: path11, properties }, "artifactory", "properties-set", start);
|
|
10994
11407
|
} catch (err) {
|
|
10995
11408
|
fail(err, "artifactory", "properties-set", start);
|
|
10996
11409
|
}
|
|
@@ -11308,8 +11721,8 @@ function registerSonatypeIqCommands(program2) {
|
|
|
11308
11721
|
const startTime = Date.now();
|
|
11309
11722
|
try {
|
|
11310
11723
|
const http = getHttpClient(program2);
|
|
11311
|
-
const
|
|
11312
|
-
const data = await http.sonatypeiq(
|
|
11724
|
+
const path11 = opts.organizationId ? `/api/v2/policies/organization/${encodeURIComponent(opts.organizationId)}` : "/api/v2/policies";
|
|
11725
|
+
const data = await http.sonatypeiq(path11);
|
|
11313
11726
|
success(data, "sonatypeiq", "policies list", startTime);
|
|
11314
11727
|
} catch (err) {
|
|
11315
11728
|
fail(err, "sonatypeiq", "policies list", startTime);
|
|
@@ -12004,7 +12417,7 @@ function registerLogscaleCommands(program2) {
|
|
|
12004
12417
|
}
|
|
12005
12418
|
|
|
12006
12419
|
// src/services/splitio/commands.ts
|
|
12007
|
-
import
|
|
12420
|
+
import fs7 from "fs";
|
|
12008
12421
|
import confirm2 from "@inquirer/confirm";
|
|
12009
12422
|
function getHttp4(program2) {
|
|
12010
12423
|
const opts = program2.optsWithGlobals();
|
|
@@ -12016,9 +12429,9 @@ function getHttp4(program2) {
|
|
|
12016
12429
|
function loadInputFile(filePath) {
|
|
12017
12430
|
let raw;
|
|
12018
12431
|
if (filePath === "-") {
|
|
12019
|
-
raw =
|
|
12432
|
+
raw = fs7.readFileSync(process.stdin.fd, "utf8");
|
|
12020
12433
|
} else {
|
|
12021
|
-
raw =
|
|
12434
|
+
raw = fs7.readFileSync(filePath, "utf8");
|
|
12022
12435
|
}
|
|
12023
12436
|
try {
|
|
12024
12437
|
return JSON.parse(raw);
|
|
@@ -12394,6 +12807,35 @@ Expected format: https://www.figma.com/design/<file-key>/...`);
|
|
|
12394
12807
|
}
|
|
12395
12808
|
return input3;
|
|
12396
12809
|
}
|
|
12810
|
+
var NODE_ID_PATTERN = /^[A-Za-z0-9]+:[A-Za-z0-9]+(?:;[A-Za-z0-9]+:[A-Za-z0-9]+)*$/;
|
|
12811
|
+
var NODE_ID_HINT = "Expected a node ID like 12:34 or 12-34, or a Figma URL with a node-id query param";
|
|
12812
|
+
function isUrl(input3) {
|
|
12813
|
+
return input3.startsWith("http://") || input3.startsWith("https://");
|
|
12814
|
+
}
|
|
12815
|
+
function parseNodeId(input3) {
|
|
12816
|
+
let raw = input3;
|
|
12817
|
+
if (isUrl(input3)) {
|
|
12818
|
+
try {
|
|
12819
|
+
raw = new URL(input3).searchParams.get("node-id");
|
|
12820
|
+
} catch {
|
|
12821
|
+
raw = null;
|
|
12822
|
+
}
|
|
12823
|
+
if (raw === null) {
|
|
12824
|
+
throw new Error(`Could not extract a Figma node ID from URL: ${input3}
|
|
12825
|
+
${NODE_ID_HINT}`);
|
|
12826
|
+
}
|
|
12827
|
+
}
|
|
12828
|
+
const nodeId = raw.trim().replace(/-/g, ":");
|
|
12829
|
+
if (!NODE_ID_PATTERN.test(nodeId)) {
|
|
12830
|
+
throw new Error(`Invalid Figma node ID: ${input3}
|
|
12831
|
+
${NODE_ID_HINT}`);
|
|
12832
|
+
}
|
|
12833
|
+
return nodeId;
|
|
12834
|
+
}
|
|
12835
|
+
function findNodeIdInUrl(url) {
|
|
12836
|
+
if (!new URL(url).searchParams.has("node-id")) return void 0;
|
|
12837
|
+
return parseNodeId(url);
|
|
12838
|
+
}
|
|
12397
12839
|
function getHttp5(program2) {
|
|
12398
12840
|
const opts = program2.optsWithGlobals();
|
|
12399
12841
|
return createHttpClient(
|
|
@@ -12403,11 +12845,38 @@ function getHttp5(program2) {
|
|
|
12403
12845
|
}
|
|
12404
12846
|
function registerFigmaCommands(program2) {
|
|
12405
12847
|
const figma = program2.command("figma").description("Figma design operations (files, comments, versions)");
|
|
12406
|
-
figma.command("file").description("Get a Figma file \u2014 metadata, structure, and component/style inventory").argument("<file-key-or-url>", "Figma file key or full Figma URL (https://www.figma.com/design/<key>/...)").option("--document", "Include the full document node tree (can be large; omitted by default)").action(async (fileKeyOrUrl, opts) => {
|
|
12848
|
+
figma.command("file").description("Get a Figma file \u2014 metadata, structure, and component/style inventory (or a single node with --node-id)").argument("<file-key-or-url>", "Figma file key or full Figma URL (https://www.figma.com/design/<key>/...); a node-id query param is auto-detected").option("--document", "Include the full document node tree (can be large; omitted by default)").option("--node-id <id>", "Fetch only this node and its descendants instead of the whole file (overrides any node-id detected in the URL)").action(async (fileKeyOrUrl, opts) => {
|
|
12407
12849
|
const start = Date.now();
|
|
12408
12850
|
const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
|
|
12409
12851
|
try {
|
|
12410
12852
|
const fileKey = parseFileKey(fileKeyOrUrl);
|
|
12853
|
+
const nodeId = opts.nodeId ? parseNodeId(opts.nodeId) : isUrl(fileKeyOrUrl) ? findNodeIdInUrl(fileKeyOrUrl) : void 0;
|
|
12854
|
+
if (nodeId) {
|
|
12855
|
+
const data2 = await getHttp5(program2).figma(
|
|
12856
|
+
`/v1/files/${encodeURIComponent(fileKey)}/nodes`,
|
|
12857
|
+
{ params: { ids: nodeId } }
|
|
12858
|
+
);
|
|
12859
|
+
const node = data2.nodes[nodeId];
|
|
12860
|
+
if (!node) {
|
|
12861
|
+
throw new Error(`Node ${nodeId} not found in file ${fileKey}`);
|
|
12862
|
+
}
|
|
12863
|
+
success2(
|
|
12864
|
+
{
|
|
12865
|
+
fileKey,
|
|
12866
|
+
nodeId,
|
|
12867
|
+
name: data2.name,
|
|
12868
|
+
lastModified: data2.lastModified,
|
|
12869
|
+
thumbnailUrl: data2.thumbnailUrl,
|
|
12870
|
+
document: node.document,
|
|
12871
|
+
componentCount: node.components ? Object.keys(node.components).length : 0,
|
|
12872
|
+
styleCount: node.styles ? Object.keys(node.styles).length : 0
|
|
12873
|
+
},
|
|
12874
|
+
"figma",
|
|
12875
|
+
"file",
|
|
12876
|
+
start
|
|
12877
|
+
);
|
|
12878
|
+
return;
|
|
12879
|
+
}
|
|
12411
12880
|
const params = {};
|
|
12412
12881
|
if (!opts.document) {
|
|
12413
12882
|
params["depth"] = 1;
|
|
@@ -12496,12 +12965,416 @@ function registerFigmaCommands(program2) {
|
|
|
12496
12965
|
});
|
|
12497
12966
|
}
|
|
12498
12967
|
|
|
12968
|
+
// src/services/alation/commands.ts
|
|
12969
|
+
import fs8 from "fs";
|
|
12970
|
+
var MAX_PAGE = 1e3;
|
|
12971
|
+
function parseIntOption(value, flag) {
|
|
12972
|
+
if (value === void 0) return void 0;
|
|
12973
|
+
const trimmed = value.trim();
|
|
12974
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
12975
|
+
throw new PncliError(`Invalid ${flag} "${value}". Expected a non-negative integer.`, 1);
|
|
12976
|
+
}
|
|
12977
|
+
return Number(trimmed);
|
|
12978
|
+
}
|
|
12979
|
+
function parsePositiveId(value, what) {
|
|
12980
|
+
const trimmed = value.trim();
|
|
12981
|
+
if (!/^[1-9]\d*$/.test(trimmed)) {
|
|
12982
|
+
throw new PncliError(`Invalid ${what} "${value}". Expected a positive integer ID.`, 1);
|
|
12983
|
+
}
|
|
12984
|
+
return Number(trimmed);
|
|
12985
|
+
}
|
|
12986
|
+
function pageParams(opts) {
|
|
12987
|
+
const limit = parseIntOption(opts.limit, "--limit") ?? 100;
|
|
12988
|
+
const skip = parseIntOption(opts.skip, "--skip") ?? 0;
|
|
12989
|
+
if (limit < 1 || limit > MAX_PAGE) {
|
|
12990
|
+
throw new PncliError(`--limit must be between 1 and ${MAX_PAGE} (Alation's per-request cap).`, 1);
|
|
12991
|
+
}
|
|
12992
|
+
return { limit, skip };
|
|
12993
|
+
}
|
|
12994
|
+
var SEARCH_OTYPES = [
|
|
12995
|
+
"api_resource",
|
|
12996
|
+
"api_resource_field",
|
|
12997
|
+
"api_resource_folder",
|
|
12998
|
+
"article",
|
|
12999
|
+
"bi_field",
|
|
13000
|
+
"catalog_set",
|
|
13001
|
+
"column",
|
|
13002
|
+
"dataflow",
|
|
13003
|
+
"datasource",
|
|
13004
|
+
"doc_schema",
|
|
13005
|
+
"docstore_collection",
|
|
13006
|
+
"docstore_folder",
|
|
13007
|
+
"domain",
|
|
13008
|
+
"execution_result",
|
|
13009
|
+
"file",
|
|
13010
|
+
"filesystem",
|
|
13011
|
+
"function",
|
|
13012
|
+
"glossary",
|
|
13013
|
+
"glossary_term",
|
|
13014
|
+
"glossary_v3",
|
|
13015
|
+
"group",
|
|
13016
|
+
"query_or_statement",
|
|
13017
|
+
"report_collection",
|
|
13018
|
+
"report_datasource",
|
|
13019
|
+
"report_object",
|
|
13020
|
+
"report_source",
|
|
13021
|
+
"schema",
|
|
13022
|
+
"table",
|
|
13023
|
+
"tag",
|
|
13024
|
+
"thread",
|
|
13025
|
+
"user",
|
|
13026
|
+
"value"
|
|
13027
|
+
];
|
|
13028
|
+
function parseOtypes(raw) {
|
|
13029
|
+
if (raw === void 0) return void 0;
|
|
13030
|
+
const otypes = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
13031
|
+
if (otypes.length === 0) throw new PncliError("--otype cannot be empty", 1);
|
|
13032
|
+
const bad = otypes.filter((o) => !SEARCH_OTYPES.includes(o));
|
|
13033
|
+
if (bad.length) {
|
|
13034
|
+
throw new PncliError(`Unknown --otype value(s): ${bad.join(", ")}. Valid: ${SEARCH_OTYPES.join(", ")}`, 1);
|
|
13035
|
+
}
|
|
13036
|
+
return otypes;
|
|
13037
|
+
}
|
|
13038
|
+
function toCustomFields(fields) {
|
|
13039
|
+
if (fields === void 0) return [];
|
|
13040
|
+
if (typeof fields !== "object" || fields === null || Array.isArray(fields)) {
|
|
13041
|
+
throw new PncliError('--input-file "fields" must be an object keyed by Alation custom field ID', 1);
|
|
13042
|
+
}
|
|
13043
|
+
return Object.entries(fields).map(([key, value]) => {
|
|
13044
|
+
if (!/^\d+$/.test(key)) {
|
|
13045
|
+
throw new PncliError(`--input-file "fields" key "${key}" is not a numeric Alation field_id`, 1);
|
|
13046
|
+
}
|
|
13047
|
+
return { field_id: Number(key), value: resolveAtFileRef(value) };
|
|
13048
|
+
});
|
|
13049
|
+
}
|
|
13050
|
+
var ALATION_DOCUMENT_INPUT_FILE_SCHEMA = {
|
|
13051
|
+
type: "object",
|
|
13052
|
+
properties: {
|
|
13053
|
+
title: { type: "string", description: "Document title" },
|
|
13054
|
+
description: { type: "string", description: 'Document summary; "@path" reads the value from a file' },
|
|
13055
|
+
hub: { type: "integer", description: "document_hub_id (required on create)" },
|
|
13056
|
+
folder: { type: "integer", description: "parent_folder_id (required on create)" },
|
|
13057
|
+
template: { type: "integer", description: "template_id; required whenever fields are supplied" },
|
|
13058
|
+
parentDocument: { type: "integer", description: "parent_document_id for a sub-document" },
|
|
13059
|
+
navLinkFolders: { type: "array", items: { type: "integer" }, description: "nav_link_folder_ids" },
|
|
13060
|
+
fields: {
|
|
13061
|
+
type: "object",
|
|
13062
|
+
description: 'Custom field values keyed by numeric Alation field_id; string values starting with "@" are read from that file',
|
|
13063
|
+
additionalProperties: true
|
|
13064
|
+
}
|
|
13065
|
+
}
|
|
13066
|
+
};
|
|
13067
|
+
var ALATION_DOCUMENT_INPUT_FILE_EXAMPLE = {
|
|
13068
|
+
title: "Customer table onboarding notes",
|
|
13069
|
+
description: "@docs/customer-notes.md",
|
|
13070
|
+
hub: 1,
|
|
13071
|
+
folder: 42,
|
|
13072
|
+
template: 7,
|
|
13073
|
+
fields: { "10001": "Team Data Platform" }
|
|
13074
|
+
};
|
|
13075
|
+
function getHttp6(program2) {
|
|
13076
|
+
const opts = program2.optsWithGlobals();
|
|
13077
|
+
return createHttpClient(
|
|
13078
|
+
loadConfig({ configPath: opts.config }),
|
|
13079
|
+
Boolean(opts.dryRun)
|
|
13080
|
+
);
|
|
13081
|
+
}
|
|
13082
|
+
async function getOne(http, path11, id, what) {
|
|
13083
|
+
const rows = await http.alation(path11, { params: { id, limit: 1 } });
|
|
13084
|
+
const row = Array.isArray(rows) ? rows.find((r) => r.id === id) ?? rows[0] : void 0;
|
|
13085
|
+
if (!row) throw new PncliError(`${what} ${id} not found`, 404);
|
|
13086
|
+
return row;
|
|
13087
|
+
}
|
|
13088
|
+
function buildDocumentPayload(opts) {
|
|
13089
|
+
const fromJson = {};
|
|
13090
|
+
if (opts.inputFile) {
|
|
13091
|
+
const parsed = readJsonInputFile(opts.inputFile);
|
|
13092
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
13093
|
+
throw new PncliError("--input-file must contain a JSON object. Run: pncli alation document schema", 1);
|
|
13094
|
+
}
|
|
13095
|
+
Object.assign(fromJson, parsed);
|
|
13096
|
+
}
|
|
13097
|
+
const fromFlags = {
|
|
13098
|
+
title: opts.title,
|
|
13099
|
+
description: opts.description,
|
|
13100
|
+
hub: opts.hub !== void 0 ? parsePositiveId(opts.hub, "--hub") : void 0,
|
|
13101
|
+
folder: opts.folder !== void 0 ? parsePositiveId(opts.folder, "--folder") : void 0,
|
|
13102
|
+
template: opts.template !== void 0 ? parsePositiveId(opts.template, "--template") : void 0,
|
|
13103
|
+
parentDocument: opts.parentDocument !== void 0 ? parsePositiveId(opts.parentDocument, "--parent-document") : void 0
|
|
13104
|
+
};
|
|
13105
|
+
const { merged, overrides } = mergeWithOverrides(fromJson, fromFlags);
|
|
13106
|
+
if (overrides.length) warn(`--input-file values overridden by flags: ${overrides.join(", ")}`);
|
|
13107
|
+
const payload = {};
|
|
13108
|
+
if (merged.title !== void 0) payload.title = merged.title;
|
|
13109
|
+
if (merged.description !== void 0) payload.description = resolveAtFileRef(merged.description);
|
|
13110
|
+
if (merged.hub !== void 0) payload.document_hub_id = merged.hub;
|
|
13111
|
+
if (merged.folder !== void 0) payload.parent_folder_id = merged.folder;
|
|
13112
|
+
if (merged.template !== void 0) payload.template_id = merged.template;
|
|
13113
|
+
if (merged.parentDocument !== void 0) payload.parent_document_id = merged.parentDocument;
|
|
13114
|
+
if (merged.navLinkFolders !== void 0) payload.nav_link_folder_ids = merged.navLinkFolders;
|
|
13115
|
+
const customFields = toCustomFields(merged.fields);
|
|
13116
|
+
if (customFields.length) {
|
|
13117
|
+
if (payload.template_id === void 0) {
|
|
13118
|
+
throw new PncliError('Alation requires template_id whenever custom fields are set. Pass --template <id> or "template" in --input-file.', 1);
|
|
13119
|
+
}
|
|
13120
|
+
payload.custom_fields = customFields;
|
|
13121
|
+
}
|
|
13122
|
+
return { payload, overrides };
|
|
13123
|
+
}
|
|
13124
|
+
function registerAlationCommands(program2) {
|
|
13125
|
+
const alation = program2.command("alation").description("Alation data catalog (data sources, schemas, tables, columns, search, documents)");
|
|
13126
|
+
const token = alation.command("token").description("API access token operations");
|
|
13127
|
+
token.command("status").description("Mint an API access token from the configured refresh token and report its expiry (verifies credentials)").action(async () => {
|
|
13128
|
+
const start = Date.now();
|
|
13129
|
+
try {
|
|
13130
|
+
const opts = program2.optsWithGlobals();
|
|
13131
|
+
const config = loadConfig({ configPath: opts.config });
|
|
13132
|
+
if (opts.dryRun) {
|
|
13133
|
+
fs8.writeSync(process.stderr.fd, `DRY RUN: POST ${config.alation.baseUrl ?? "<alation.baseUrl>"}/integration/v1/createAPIAccessToken/ (token exchange skipped)
|
|
13134
|
+
`);
|
|
13135
|
+
process.exitCode = ExitCode.SUCCESS;
|
|
13136
|
+
throw new PncliError("dry-run", 0);
|
|
13137
|
+
}
|
|
13138
|
+
const data = await validateAlationAccessToken(config);
|
|
13139
|
+
success(data, "alation", "token-status", start);
|
|
13140
|
+
} catch (err) {
|
|
13141
|
+
fail(err, "alation", "token-status", start);
|
|
13142
|
+
}
|
|
13143
|
+
});
|
|
13144
|
+
const datasource = alation.command("datasource").description("Data source operations");
|
|
13145
|
+
datasource.command("list").description("List data sources").option("--include-undeployed", "Include data sources whose setup is incomplete").option("--include-hidden", "Include hidden data sources").action(async (opts) => {
|
|
13146
|
+
const start = Date.now();
|
|
13147
|
+
try {
|
|
13148
|
+
const params = {};
|
|
13149
|
+
if (opts.includeUndeployed) params.include_undeployed = true;
|
|
13150
|
+
if (opts.includeHidden) params.include_hidden = true;
|
|
13151
|
+
const data = await getHttp6(program2).alation("integration/v1/datasource/", { params });
|
|
13152
|
+
success({ count: data.length, datasources: data }, "alation", "datasource-list", start);
|
|
13153
|
+
} catch (err) {
|
|
13154
|
+
fail(err, "alation", "datasource-list", start);
|
|
13155
|
+
}
|
|
13156
|
+
});
|
|
13157
|
+
datasource.command("get").description("Get a data source by ID").argument("<id>", "Data source ID").action(async (id) => {
|
|
13158
|
+
const start = Date.now();
|
|
13159
|
+
try {
|
|
13160
|
+
const dsId = parsePositiveId(id, "data source ID");
|
|
13161
|
+
const data = await getHttp6(program2).alation(`integration/v1/datasource/${dsId}/`);
|
|
13162
|
+
success(data, "alation", "datasource-get", start);
|
|
13163
|
+
} catch (err) {
|
|
13164
|
+
fail(err, "alation", "datasource-get", start);
|
|
13165
|
+
}
|
|
13166
|
+
});
|
|
13167
|
+
const schema = alation.command("schema").description("Schema operations");
|
|
13168
|
+
schema.command("list").description("List schemas, optionally within one data source").option("--ds <id>", "Filter by data source ID").option("--name <name>", "Filter by exact schema name").option("--limit <n>", "Page size (max 1000)", "100").option("--skip <n>", "Number of results to skip", "0").action(async (opts) => {
|
|
13169
|
+
const start = Date.now();
|
|
13170
|
+
try {
|
|
13171
|
+
const params = { ...pageParams(opts) };
|
|
13172
|
+
if (opts.ds) params.ds_id = parsePositiveId(opts.ds, "--ds");
|
|
13173
|
+
if (opts.name) params.name = opts.name;
|
|
13174
|
+
const data = await getHttp6(program2).alation("integration/v2/schema/", { params });
|
|
13175
|
+
success({ count: data.length, limit: params.limit, skip: params.skip, schemas: data }, "alation", "schema-list", start);
|
|
13176
|
+
} catch (err) {
|
|
13177
|
+
fail(err, "alation", "schema-list", start);
|
|
13178
|
+
}
|
|
13179
|
+
});
|
|
13180
|
+
schema.command("get").description("Get a schema by ID").argument("<id>", "Schema ID").action(async (id) => {
|
|
13181
|
+
const start = Date.now();
|
|
13182
|
+
try {
|
|
13183
|
+
const data = await getOne(getHttp6(program2), "integration/v2/schema/", parsePositiveId(id, "schema ID"), "Schema");
|
|
13184
|
+
success(data, "alation", "schema-get", start);
|
|
13185
|
+
} catch (err) {
|
|
13186
|
+
fail(err, "alation", "schema-get", start);
|
|
13187
|
+
}
|
|
13188
|
+
});
|
|
13189
|
+
const table = alation.command("table").description("Table operations");
|
|
13190
|
+
table.command("list").description("List tables (descriptions, custom fields, fully-qualified keys)").option("--ds <id>", "Filter by data source ID").option("--schema <id>", "Filter by schema ID").option("--schema-name <name>", "Filter by schema name").option("--name <name>", "Filter by exact table name").option("--search <text>", "Case-insensitive substring match on table name").option("--limit <n>", "Page size (max 1000)", "100").option("--skip <n>", "Number of results to skip", "0").action(async (opts) => {
|
|
13191
|
+
const start = Date.now();
|
|
13192
|
+
try {
|
|
13193
|
+
const params = { ...pageParams(opts) };
|
|
13194
|
+
if (opts.ds) params.ds_id = parsePositiveId(opts.ds, "--ds");
|
|
13195
|
+
if (opts.schema) params.schema_id = parsePositiveId(opts.schema, "--schema");
|
|
13196
|
+
if (opts.schemaName) params.schema_name = opts.schemaName;
|
|
13197
|
+
if (opts.name) params.name = opts.name;
|
|
13198
|
+
if (opts.search) params.name__icontains = opts.search;
|
|
13199
|
+
const data = await getHttp6(program2).alation("integration/v2/table/", { params });
|
|
13200
|
+
success({ count: data.length, limit: params.limit, skip: params.skip, tables: data }, "alation", "table-list", start);
|
|
13201
|
+
} catch (err) {
|
|
13202
|
+
fail(err, "alation", "table-list", start);
|
|
13203
|
+
}
|
|
13204
|
+
});
|
|
13205
|
+
table.command("get").description("Get a table by ID, optionally with its columns").argument("<id>", "Table ID").option("--columns", "Also fetch the table's columns (up to 1000)").action(async (id, opts) => {
|
|
13206
|
+
const start = Date.now();
|
|
13207
|
+
try {
|
|
13208
|
+
const http = getHttp6(program2);
|
|
13209
|
+
const tableId = parsePositiveId(id, "table ID");
|
|
13210
|
+
const data = await getOne(http, "integration/v2/table/", tableId, "Table");
|
|
13211
|
+
if (opts.columns) {
|
|
13212
|
+
const columns = await http.alation("integration/v2/column/", {
|
|
13213
|
+
params: { table_id: tableId, limit: MAX_PAGE, order_by: "id" }
|
|
13214
|
+
});
|
|
13215
|
+
success({ ...data, columnCount: columns.length, columns }, "alation", "table-get", start);
|
|
13216
|
+
return;
|
|
13217
|
+
}
|
|
13218
|
+
success(data, "alation", "table-get", start);
|
|
13219
|
+
} catch (err) {
|
|
13220
|
+
fail(err, "alation", "table-get", start);
|
|
13221
|
+
}
|
|
13222
|
+
});
|
|
13223
|
+
const column = alation.command("column").description("Column operations");
|
|
13224
|
+
column.command("list").description("List columns of a table (types, descriptions, keys, custom fields)").option("--table <id>", "Filter by table ID").option("--table-name <name>", "Filter by table name").option("--schema <id>", "Filter by schema ID").option("--ds <id>", "Filter by data source ID").option("--name <name>", "Filter by exact column name").option("--search <text>", "Case-insensitive substring match on column name").option("--limit <n>", "Page size (max 1000)", "100").option("--skip <n>", "Number of results to skip", "0").action(async (opts) => {
|
|
13225
|
+
const start = Date.now();
|
|
13226
|
+
try {
|
|
13227
|
+
if (!opts.table && !opts.tableName && !opts.schema && !opts.ds && !opts.name && !opts.search) {
|
|
13228
|
+
throw new PncliError("Pass at least one filter (--table, --table-name, --schema, --ds, --name, or --search); listing every column in the catalog is not supported.", 1);
|
|
13229
|
+
}
|
|
13230
|
+
const params = { ...pageParams(opts) };
|
|
13231
|
+
if (opts.table) params.table_id = parsePositiveId(opts.table, "--table");
|
|
13232
|
+
if (opts.tableName) params.table_name = opts.tableName;
|
|
13233
|
+
if (opts.schema) params.schema_id = parsePositiveId(opts.schema, "--schema");
|
|
13234
|
+
if (opts.ds) params.ds_id = parsePositiveId(opts.ds, "--ds");
|
|
13235
|
+
if (opts.name) params.name = opts.name;
|
|
13236
|
+
if (opts.search) params.name__icontains = opts.search;
|
|
13237
|
+
const data = await getHttp6(program2).alation("integration/v2/column/", { params });
|
|
13238
|
+
success({ count: data.length, limit: params.limit, skip: params.skip, columns: data }, "alation", "column-list", start);
|
|
13239
|
+
} catch (err) {
|
|
13240
|
+
fail(err, "alation", "column-list", start);
|
|
13241
|
+
}
|
|
13242
|
+
});
|
|
13243
|
+
column.command("get").description("Get a column by ID").argument("<id>", "Column ID").action(async (id) => {
|
|
13244
|
+
const start = Date.now();
|
|
13245
|
+
try {
|
|
13246
|
+
const data = await getOne(getHttp6(program2), "integration/v2/column/", parsePositiveId(id, "column ID"), "Column");
|
|
13247
|
+
success(data, "alation", "column-get", start);
|
|
13248
|
+
} catch (err) {
|
|
13249
|
+
fail(err, "alation", "column-get", start);
|
|
13250
|
+
}
|
|
13251
|
+
});
|
|
13252
|
+
alation.command("search").description("Full-text search across the catalog").argument("<query>", "Search text").option("--otype <types>", `Comma-separated object types to restrict to (e.g. table,column). Valid: ${SEARCH_OTYPES.join(", ")}`).option("--limit <n>", "Maximum results (default 20, max 10000)", "20").option("--offset <n>", "Index of the first result", "0").action(async (query, opts) => {
|
|
13253
|
+
const start = Date.now();
|
|
13254
|
+
try {
|
|
13255
|
+
const limit = parseIntOption(opts.limit, "--limit") ?? 20;
|
|
13256
|
+
const offset = parseIntOption(opts.offset, "--offset") ?? 0;
|
|
13257
|
+
if (limit > 1e4) throw new PncliError("--limit must be at most 10000", 1);
|
|
13258
|
+
const params = { q: query, limit, offset };
|
|
13259
|
+
const otypes = parseOtypes(opts.otype);
|
|
13260
|
+
if (otypes) params.filters = JSON.stringify({ otypes });
|
|
13261
|
+
const data = await getHttp6(program2).alation("integration/v1/search/", { params });
|
|
13262
|
+
success(
|
|
13263
|
+
{ query, total: data.total, limit: data.limit, offset: data.offset, fullSearchUrl: data.full_search_url, results: data.results },
|
|
13264
|
+
"alation",
|
|
13265
|
+
"search",
|
|
13266
|
+
start
|
|
13267
|
+
);
|
|
13268
|
+
} catch (err) {
|
|
13269
|
+
fail(err, "alation", "search", start);
|
|
13270
|
+
}
|
|
13271
|
+
});
|
|
13272
|
+
const folder = alation.command("folder").description("Document Hub folder operations");
|
|
13273
|
+
folder.command("list").description("List folders in a Document Hub").option("--hub <id>", "Document hub ID").option("--parent <id>", "Parent folder ID (list its subfolders)").option("--search <text>", "Filter on textual fields").option("--limit <n>", "Page size (max 1000)", "100").option("--skip <n>", "Number of results to skip", "0").action(async (opts) => {
|
|
13274
|
+
const start = Date.now();
|
|
13275
|
+
try {
|
|
13276
|
+
const params = { ...pageParams(opts) };
|
|
13277
|
+
if (opts.hub) params.document_hub_id = parsePositiveId(opts.hub, "--hub");
|
|
13278
|
+
if (opts.parent) params.parent_folder_id = parsePositiveId(opts.parent, "--parent");
|
|
13279
|
+
if (opts.search) params.search = opts.search;
|
|
13280
|
+
const data = await getHttp6(program2).alation("integration/v2/folder/", { params });
|
|
13281
|
+
success({ count: data.length, limit: params.limit, skip: params.skip, folders: data }, "alation", "folder-list", start);
|
|
13282
|
+
} catch (err) {
|
|
13283
|
+
fail(err, "alation", "folder-list", start);
|
|
13284
|
+
}
|
|
13285
|
+
});
|
|
13286
|
+
folder.command("get").description("Get a Document Hub folder by ID").argument("<id>", "Folder ID").action(async (id) => {
|
|
13287
|
+
const start = Date.now();
|
|
13288
|
+
try {
|
|
13289
|
+
const data = await getOne(getHttp6(program2), "integration/v2/folder/", parsePositiveId(id, "folder ID"), "Folder");
|
|
13290
|
+
success(data, "alation", "folder-get", start);
|
|
13291
|
+
} catch (err) {
|
|
13292
|
+
fail(err, "alation", "folder-get", start);
|
|
13293
|
+
}
|
|
13294
|
+
});
|
|
13295
|
+
const document = alation.command("document").description("Document Hub document operations");
|
|
13296
|
+
document.command("list").description("List documents in a Document Hub or folder").option("--hub <id>", "Document hub ID").option("--folder <id>", "Parent folder ID").option("--parent-document <id>", "Parent document ID (list sub-documents)").option("--search <text>", "Filter on all textual fields").option("--deleted", "Return only deleted documents").option("--limit <n>", "Page size (max 1000)", "100").option("--skip <n>", "Number of results to skip", "0").action(async (opts) => {
|
|
13297
|
+
const start = Date.now();
|
|
13298
|
+
try {
|
|
13299
|
+
const params = { ...pageParams(opts) };
|
|
13300
|
+
if (opts.hub) params.document_hub_id = parsePositiveId(opts.hub, "--hub");
|
|
13301
|
+
if (opts.folder) params.parent_folder_id = parsePositiveId(opts.folder, "--folder");
|
|
13302
|
+
if (opts.parentDocument) params.parent_document_id = parsePositiveId(opts.parentDocument, "--parent-document");
|
|
13303
|
+
if (opts.search) params.search = opts.search;
|
|
13304
|
+
if (opts.deleted) params.deleted = true;
|
|
13305
|
+
const data = await getHttp6(program2).alation("integration/v2/document/", { params });
|
|
13306
|
+
success({ count: data.length, limit: params.limit, skip: params.skip, documents: data }, "alation", "document-list", start);
|
|
13307
|
+
} catch (err) {
|
|
13308
|
+
fail(err, "alation", "document-list", start);
|
|
13309
|
+
}
|
|
13310
|
+
});
|
|
13311
|
+
document.command("get").description("Get a document by ID, including its custom field values").argument("<id>", "Document ID").action(async (id) => {
|
|
13312
|
+
const start = Date.now();
|
|
13313
|
+
try {
|
|
13314
|
+
const data = await getOne(getHttp6(program2), "integration/v2/document/", parsePositiveId(id, "document ID"), "Document");
|
|
13315
|
+
success(data, "alation", "document-get", start);
|
|
13316
|
+
} catch (err) {
|
|
13317
|
+
fail(err, "alation", "document-get", start);
|
|
13318
|
+
}
|
|
13319
|
+
});
|
|
13320
|
+
document.command("schema").description("Print the --input-file JSON schema and an example for document create/update").option("--example-only", "Print only the runnable example JSON (no envelope) \u2014 pipeable straight into --input-file").action((opts) => {
|
|
13321
|
+
const start = Date.now();
|
|
13322
|
+
if (opts.exampleOnly) {
|
|
13323
|
+
writeRawOutput(JSON.stringify(ALATION_DOCUMENT_INPUT_FILE_EXAMPLE, null, 2) + "\n");
|
|
13324
|
+
return;
|
|
13325
|
+
}
|
|
13326
|
+
success({ schema: ALATION_DOCUMENT_INPUT_FILE_SCHEMA, example: ALATION_DOCUMENT_INPUT_FILE_EXAMPLE }, "alation", "document-schema", start);
|
|
13327
|
+
});
|
|
13328
|
+
document.command("create").description("Create a document (async \u2014 returns a job ID; check it with `alation job get`)").option("--title <title>", "Document title").option("--description <text>", "Document summary").option("--hub <id>", "Document hub ID").option("--folder <id>", "Parent folder ID").option("--template <id>", "Template ID (required when custom fields are set)").option("--parent-document <id>", "Parent document ID for a sub-document").option("--input-file <path>", 'JSON file with title/description/hub/folder/template/fields (see `alation document schema`); "-" reads stdin').action(async (opts) => {
|
|
13329
|
+
const start = Date.now();
|
|
13330
|
+
try {
|
|
13331
|
+
const { payload, overrides } = buildDocumentPayload(opts);
|
|
13332
|
+
for (const [key, flag] of [["title", "--title"], ["document_hub_id", "--hub"], ["parent_folder_id", "--folder"]]) {
|
|
13333
|
+
if (payload[key] === void 0) throw new PncliError(`${flag} is required to create a document (or set it in --input-file)`, 1);
|
|
13334
|
+
}
|
|
13335
|
+
const data = await getHttp6(program2).alation("integration/v2/document/", { method: "POST", body: [payload] });
|
|
13336
|
+
success({ jobId: data.job_id, document: payload, ...overrides.length ? { overrides } : {} }, "alation", "document-create", start);
|
|
13337
|
+
} catch (err) {
|
|
13338
|
+
fail(err, "alation", "document-create", start);
|
|
13339
|
+
}
|
|
13340
|
+
});
|
|
13341
|
+
document.command("update").description("Update a document (async \u2014 returns a job ID; custom field values replace existing values per field)").argument("<id>", "Document ID").option("--title <title>", "New title").option("--description <text>", "New summary").option("--folder <id>", "Move to this parent folder").option("--template <id>", "Template ID (required when custom fields are set)").option("--input-file <path>", 'JSON file with title/description/folder/template/fields (see `alation document schema`); "-" reads stdin').action(async (id, opts) => {
|
|
13342
|
+
const start = Date.now();
|
|
13343
|
+
try {
|
|
13344
|
+
const documentId = parsePositiveId(id, "document ID");
|
|
13345
|
+
const { payload, overrides } = buildDocumentPayload(opts);
|
|
13346
|
+
if (payload.document_hub_id !== void 0) {
|
|
13347
|
+
throw new PncliError('A document cannot change hubs; remove "hub" from --input-file.', 1);
|
|
13348
|
+
}
|
|
13349
|
+
if (Object.keys(payload).length === 0) {
|
|
13350
|
+
throw new PncliError("Nothing to update. Pass --title, --description, --folder, or --input-file.", 1);
|
|
13351
|
+
}
|
|
13352
|
+
const body = [{ id: documentId, ...payload }];
|
|
13353
|
+
const data = await getHttp6(program2).alation("integration/v2/document/", { method: "PUT", body });
|
|
13354
|
+
success({ jobId: data.job_id, document: body[0], ...overrides.length ? { overrides } : {} }, "alation", "document-update", start);
|
|
13355
|
+
} catch (err) {
|
|
13356
|
+
fail(err, "alation", "document-update", start);
|
|
13357
|
+
}
|
|
13358
|
+
});
|
|
13359
|
+
const job = alation.command("job").description("Background job operations");
|
|
13360
|
+
job.command("get").description("Get the status of a bulk job (returned by document create/update)").argument("<id>", "Job ID").action(async (id) => {
|
|
13361
|
+
const start = Date.now();
|
|
13362
|
+
try {
|
|
13363
|
+
const jobId = parsePositiveId(id, "job ID");
|
|
13364
|
+
const data = await getHttp6(program2).alation("api/v1/bulk_metadata/job/", { params: { id: jobId } });
|
|
13365
|
+
success({ jobId, ...data }, "alation", "job-get", start);
|
|
13366
|
+
} catch (err) {
|
|
13367
|
+
fail(err, "alation", "job-get", start);
|
|
13368
|
+
}
|
|
13369
|
+
});
|
|
13370
|
+
}
|
|
13371
|
+
|
|
12499
13372
|
// src/cli.ts
|
|
12500
13373
|
if (!process.env.PNCLI_VERIFY_TLS) {
|
|
12501
13374
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
12502
13375
|
}
|
|
12503
13376
|
await configureProxy();
|
|
12504
|
-
var TAGLINE = "
|
|
13377
|
+
var TAGLINE = "Connectivity without MCP.";
|
|
12505
13378
|
var program = new Command3();
|
|
12506
13379
|
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");
|
|
12507
13380
|
program.hook("preAction", (thisCommand) => {
|
|
@@ -12541,6 +13414,7 @@ registerDynatraceCommands(program);
|
|
|
12541
13414
|
registerLogscaleCommands(program);
|
|
12542
13415
|
registerSplitioCommands(program);
|
|
12543
13416
|
registerFigmaCommands(program);
|
|
13417
|
+
registerAlationCommands(program);
|
|
12544
13418
|
program.addHelpText("after", `
|
|
12545
13419
|
Services:
|
|
12546
13420
|
git Local git operations (status, diff, log, branch)
|
|
@@ -12562,10 +13436,18 @@ Services:
|
|
|
12562
13436
|
logscale LogScale (log query, repositories)
|
|
12563
13437
|
splitio Split.IO (feature flags, Change Requests)
|
|
12564
13438
|
figma Figma (files, comments, version history)
|
|
13439
|
+
alation Alation (data sources, schemas, tables, columns, search, Document Hubs)
|
|
12565
13440
|
config Manage pncli configuration
|
|
12566
|
-
doctor Diagnose pncli setup (config files, credentials, agent skills)
|
|
13441
|
+
doctor Diagnose pncli setup (config files, credentials, agent skills, marketplaces)
|
|
12567
13442
|
skills Install and manage agent skills (Codex, GitHub Copilot, Claude Code)
|
|
12568
13443
|
jwt JWT token utilities (decode header and payload)
|
|
13444
|
+
|
|
13445
|
+
Agent skills and org plugins:
|
|
13446
|
+
pncli skills install --all-agents Bundled pncli skills into this repo, every agent host
|
|
13447
|
+
pncli skills marketplace add <git-url> --all-agents Org plugins (skills + AGENTS.md/CLAUDE.md) from a git marketplace
|
|
13448
|
+
pncli skills marketplace sync --marketplace all Pull and refresh everything installed from marketplaces
|
|
13449
|
+
pncli skills marketplace manage Interactive: toggle plugins, add/remove marketplaces
|
|
13450
|
+
pncli skills --help Every install location and command
|
|
12569
13451
|
`);
|
|
12570
13452
|
program.parseAsync(process.argv).catch((err) => {
|
|
12571
13453
|
if (process.exitCode !== void 0) return;
|