@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/dist/cli.js CHANGED
@@ -10,10 +10,11 @@ import {
10
10
  setRepoConfigValue,
11
11
  writeGlobalConfig,
12
12
  writeRepoConfig
13
- } from "./chunk-GRKPFSQD.js";
13
+ } from "./chunk-QHJ2HM5J.js";
14
14
  import {
15
- createHttpClient
16
- } from "./chunk-5Q4VM6GO.js";
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 path10 = remoteUrl;
450
+ let path11 = remoteUrl;
450
451
  let remoteHostname = "";
451
- const sshProtoMatch = path10.match(/^ssh:\/\/[^@]*@?([^/:]+)(?::\d+)?(.*)/);
452
+ const sshProtoMatch = path11.match(/^ssh:\/\/[^@]*@?([^/:]+)(?::\d+)?(.*)/);
452
453
  if (sshProtoMatch) {
453
454
  remoteHostname = sshProtoMatch[1].toLowerCase();
454
- path10 = sshProtoMatch[2];
455
+ path11 = sshProtoMatch[2];
455
456
  } else {
456
- const gitAtMatch = path10.match(/^git@([^:]+)(?::\d+)?:(.*)/);
457
+ const gitAtMatch = path11.match(/^git@([^:]+)(?::\d+)?:(.*)/);
457
458
  if (gitAtMatch) {
458
459
  remoteHostname = gitAtMatch[1].toLowerCase();
459
- path10 = "/" + gitAtMatch[2];
460
+ path11 = "/" + gitAtMatch[2];
460
461
  } else {
461
- const httpsMatch = path10.match(/^https?:\/\/([^/:]+)(?::\d+)?(.*)/);
462
+ const httpsMatch = path11.match(/^https?:\/\/([^/:]+)(?::\d+)?(.*)/);
462
463
  if (httpsMatch) {
463
464
  remoteHostname = httpsMatch[1].toLowerCase();
464
- path10 = httpsMatch[2];
465
+ path11 = httpsMatch[2];
465
466
  }
466
467
  }
467
468
  }
468
469
  if (!remoteHostname || remoteHostname !== baseHostname) return null;
469
- path10 = path10.replace(/\.git$/, "");
470
- const gitIdx = path10.indexOf("/_git/");
471
- const sshIdx = path10.indexOf("/_ssh/");
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(path10.slice(markerIdx + 6));
475
+ const repo = decodeURIComponent(path11.slice(markerIdx + 6));
475
476
  if (!repo) return null;
476
- const before = path10.slice(1, markerIdx);
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.version ?? "0", 10));
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.version ?? "0", 10));
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 path10 = opts.org ? `/orgs/${opts.org}/repos` : "/user/repos";
2309
- return this.http.github(path10, {
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 path10 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
6275
- await http.ado(path10, { timeoutMs: 1e4 });
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 fs2 from "fs";
6500
- import path7 from "path";
6501
- import os from "os";
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 path7.join(os.homedir(), ".agents", "marketplaces", repoNameFromUrl(url));
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: path7.join(os.homedir(), ".agents/skills") },
6608
- "github-copilot": { project: ".github/skills", user: path7.join(os.homedir(), ".copilot/skills") },
6609
- "claude-code": { project: ".claude/skills", user: path7.join(os.homedir(), ".claude/skills") }
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 ? path7.resolve(root) : null;
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 ? path7.resolve(root, relativePath) : path7.resolve(relativePath);
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) : path7.resolve(agentPaths.user);
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 (fs2.existsSync(path7.join(resolveScopedPath(agentPaths, scope), "pncli", "SKILL.md"))) {
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 path7.resolve(opts.target);
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 path7.resolve(path7.dirname(thisFile), "..", "skills");
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 path7.join(targetDir, ".pncli-installed.json");
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 path7.join(skillDir, SKILL_ORIGIN_FILENAME);
6875
+ return path8.join(skillDir, SKILL_ORIGIN_FILENAME);
6687
6876
  }
6688
6877
  function readSkillOrigin(skillDir) {
6689
6878
  const originPath = getSkillOriginPath(skillDir);
6690
- if (!fs2.existsSync(originPath)) return null;
6879
+ if (!fs3.existsSync(originPath)) return null;
6691
6880
  try {
6692
- const raw = JSON.parse(fs2.readFileSync(originPath, "utf8"));
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(path7.join(targetDir, skillName));
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 (!fs2.existsSync(targetDir)) return [];
6718
- return fs2.readdirSync(targetDir).filter((name) => {
6906
+ if (!fs3.existsSync(targetDir)) return [];
6907
+ return fs3.readdirSync(targetDir).filter((name) => {
6719
6908
  if (name.startsWith(".")) return false;
6720
- const dir = path7.join(targetDir, name);
6909
+ const dir = path8.join(targetDir, name);
6721
6910
  try {
6722
- if (!fs2.statSync(dir).isDirectory()) return false;
6911
+ if (!fs3.statSync(dir).isDirectory()) return false;
6723
6912
  } catch {
6724
6913
  return false;
6725
6914
  }
6726
- return fs2.existsSync(path7.join(dir, "SKILL.md"));
6915
+ return fs3.existsSync(path8.join(dir, "SKILL.md"));
6727
6916
  });
6728
6917
  }
6729
6918
  function disablePluginSkills(targetDir, plugin, marketplaceFilter) {
6730
- const resolvedTarget = path7.resolve(targetDir);
6731
- const stashDir = path7.join(targetDir, DISABLED_SUBDIR);
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 = path7.resolve(targetDir, skillName);
6738
- if (!skillDir.startsWith(resolvedTarget + path7.sep)) continue;
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 = path7.join(stashDir, skillName);
6749
- fs2.mkdirSync(stashDir, { recursive: true });
6750
- if (fs2.existsSync(stashDest)) fs2.rmSync(stashDest, { recursive: true, force: true });
6751
- fs2.renameSync(skillDir, stashDest);
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
- fs2.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
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 = path7.resolve(targetDir);
6769
- const stashDir = path7.join(targetDir, DISABLED_SUBDIR);
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 = path7.resolve(targetDir, skillName);
6781
- if (!dest.startsWith(resolvedTarget + path7.sep)) {
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 = path7.join(stashDir, skillName);
6786
- if (!fs2.existsSync(stashSrc)) {
6974
+ const stashSrc = path8.join(stashDir, skillName);
6975
+ if (!fs3.existsSync(stashSrc)) {
6787
6976
  stashMissing.push(skillName);
6788
6977
  continue;
6789
6978
  }
6790
- if (fs2.existsSync(dest)) fs2.rmSync(dest, { recursive: true, force: true });
6791
- fs2.renameSync(stashSrc, dest);
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 (fs2.existsSync(stashDir) && fs2.readdirSync(stashDir).length === 0) {
6797
- fs2.rmdirSync(stashDir);
6985
+ if (fs3.existsSync(stashDir) && fs3.readdirSync(stashDir).length === 0) {
6986
+ fs3.rmdirSync(stashDir);
6798
6987
  }
6799
6988
  if (enabled.length > 0) {
6800
- fs2.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
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) => path7.resolve(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 = path7.resolve(targetDir);
6870
- const builtIn = Object.values(AGENT_PATHS).flatMap((a) => [path7.resolve(a.user), resolveProjectPath(a.project)]);
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 = path7.resolve(targetDir);
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 = path7.resolve(targetDir);
7076
+ const resolved = path8.resolve(targetDir);
6888
7077
  const base = {
6889
7078
  agent,
6890
7079
  scope,
6891
7080
  path: resolved,
6892
- exists: fs2.existsSync(resolved),
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 = path7.join(resolved, DISABLED_SUBDIR);
6912
- base.disabledStashMissing = disabledNames.filter((name) => !fs2.existsSync(path7.join(stashDir, 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 || !fs2.existsSync(localPath)) return null;
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 ? path7.join(location.path, skillName) : path7.join(location.path, DISABLED_SUBDIR, skillName),
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 = path7.join(targetDir, skillName);
6984
- if (fs2.existsSync(skillDir)) {
7172
+ const skillDir = path8.join(targetDir, skillName);
7173
+ if (fs3.existsSync(skillDir)) {
6985
7174
  const origin = { version: 1, ...fullRecord };
6986
7175
  try {
6987
- fs2.writeFileSync(getSkillOriginPath(skillDir), JSON.stringify(origin, null, 2), "utf8");
7176
+ fs3.writeFileSync(getSkillOriginPath(skillDir), JSON.stringify(origin, null, 2), "utf8");
6988
7177
  } catch {
6989
7178
  }
6990
7179
  }
6991
7180
  }
6992
- fs2.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
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 = fs2.existsSync(path7.join(resolvedPath, ".git"));
7069
- if (fs2.existsSync(resolvedPath) && !hasGit && fs2.readdirSync(resolvedPath).length > 0) {
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 (!fs2.existsSync(skillsSrc)) {
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
- warn(` ${skill}: ${path7.join(skillsSrc, skill)} \u2192 ${path7.join(targetDir, skill)}`);
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 installAllPlugins(resolvedPath, marketplaceName, url, targetDir) {
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 { pluginResults: {}, totalInstalled: 0 };
7403
+ return targets.map((t) => ({ ...t, plugins: {}, total: 0 }));
7153
7404
  }
7154
- warn(`Installing ${pluginChoices.length} plugin(s) to ${targetDir}...`);
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, targetDir, force, pluginFilter, installedOnly = false) {
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 || !fs2.existsSync(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
- let pluginNames;
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 { results, totalInstalled } = installPluginsForMarketplace(marketplacePath, marketplaceName, m.repoUrl, pluginNames, targetDir);
7194
- return { marketplace: marketplaceName, plugins: results, total: totalInstalled, marketplaceUpdated: updated };
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 = path7.resolve(localPath ?? defaultMarketplacePath(url));
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 targetDir = resolveAgentPaths(resolveAgentName(opts)).user;
7216
- const { pluginResults, totalInstalled } = installAllPlugins(resolvedPath, marketplaceName, url, targetDir);
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
- plugins: pluginResults,
7224
- total: totalInstalled,
7225
- target: targetDir
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 = fs2.readdirSync(bundledDir).filter(
7260
- (name) => fs2.existsSync(path7.join(bundledDir, name, "SKILL.md"))
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 = path7.resolve(targetDir);
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 = path7.resolve(targetDir, skillName);
7277
- if (!existingDir.startsWith(resolvedTarget + path7.sep)) continue;
7278
- if (fs2.existsSync(existingDir)) {
7279
- fs2.rmSync(existingDir, { recursive: true, force: true });
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
- warn(`Installing ${skillDirs.length} bundled skill(s) to ${targetDir}...`);
7516
+ log(`Installing ${skillDirs.length} bundled skill(s) to ${targetDir}...`);
7283
7517
  for (const skillName of skillDirs) {
7284
- const skillDir = path7.resolve(targetDir, skillName);
7285
- if (!skillDir.startsWith(resolvedTarget + path7.sep)) {
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 = fs2.readdirSync(path7.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
7291
- fs2.mkdirSync(skillDir, { recursive: true });
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 = fs2.readFileSync(path7.join(bundledDir, skillName, mdFile), "utf8");
7294
- fs2.writeFileSync(path7.join(skillDir, mdFile), content, "utf8");
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(`Manage agent skills (${AGENT_CHOICES})`);
7310
- skills.command("install").description("Install pncli skills into the current 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) => {
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 = Object.entries(AGENT_PATHS).map(([agent, agentPaths]) => {
7321
- const targetDir2 = resolveScopedPath(agentPaths, scope);
7322
- const { installed: installed2, failed: failed2 } = installBundledSkillsTo(targetDir2, skillDirs);
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 ? path7.resolve(opts.target) : resolveScopedPath(resolveAgentPaths(agentName), scope);
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 = path7.resolve(opts.target);
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 (!fs2.existsSync(targetDir)) {
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 = fs2.readdirSync(targetDir).filter((name) => {
7632
+ const skillDirs = fs3.readdirSync(targetDir).filter((name) => {
7380
7633
  if (name.startsWith(".")) return false;
7381
- const skillPath = path7.join(targetDir, name, "SKILL.md");
7382
- return fs2.existsSync(skillPath);
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 = fs2.readFileSync(path7.join(targetDir, name, "SKILL.md"), "utf8");
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 = path7.resolve(opts.target);
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 = path7.resolve(targetDir);
7444
- const skillDir = path7.resolve(targetDir, name);
7445
- if (!skillDir.startsWith(resolvedTarget + path7.sep)) {
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 (!fs2.existsSync(skillDir)) {
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
- fs2.rmSync(skillDir, { recursive: true, force: true });
7706
+ fs3.rmSync(skillDir, { recursive: true, force: true });
7454
7707
  delete meta.skills[name];
7455
- fs2.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
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 = path7.resolve(dir);
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("Manage git-hosted skills marketplaces");
7541
- function withMarketplaceAddOptions(cmd) {
7542
- return cmd.argument("<url>", "Git clone URL of the marketplace repository").argument("[localPath]", "Local directory to clone into (default: ~/.agents/marketplaces/<repo-name>)").option("--name <name>", "Human-readable name for this marketplace (default: derived from URL)").option("--branch <branch>", "Branch to clone (default: remote HEAD)").option("--token <token>", "HTTP access token for authenticated clone and pull (GitHub PAT or Bitbucket token)").option("--agent <agent>", `Target agent host for plugin install: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).option("--claude", "Shorthand for --agent claude-code");
7543
- }
7544
- withMarketplaceAddOptions(
7545
- marketplace.command("add").description("Register a new marketplace, clone it, and install all its plugins")
7546
- ).action((url, localPath, opts) => marketplaceAddAction(url, localPath, opts, "marketplace-add"));
7547
- withMarketplaceAddOptions(
7548
- marketplace.command("setup").description("Alias for `marketplace add` \u2014 clone a marketplace and install all its plugins")
7549
- ).action((url, localPath, opts) => marketplaceAddAction(url, localPath, opts, "marketplace-setup"));
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 && fs2.existsSync(m.localPath) ? getRepoRemoteUrl(m.localPath) : null
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 || !fs2.existsSync(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 targetDir = resolveAgentPaths(resolveAgentName(opts)).user;
7608
- const force = opts.force ?? false;
7609
- const installedOnly = opts.installedOnly ?? false;
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, targetDir, force, plugin ?? "all", installedOnly));
7612
- success({ allMarketplaces: true, marketplaces: results, target: targetDir }, "skills", "marketplace-sync", start);
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((m, i) => ({ value: String(i), name: `${marketplaceLabel(m)} \u2014 ${m.repoUrl ?? ""}` }))
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 results2 = allMarketplaces.map((m) => syncMarketplacePlugins(m, targetDir, force, selectedPlugin ?? "all", installedOnly));
7640
- success({ allMarketplaces: true, marketplaces: results2, target: targetDir }, "skills", "marketplace-sync", start);
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 || !fs2.existsSync(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
- if (!updated && !force) {
7677
- success({
7678
- marketplace: marketplaceName,
7679
- marketplaceUpdated: false,
7680
- updated: false,
7681
- skipped: true,
7682
- message: `No changes detected in "${marketplaceName}" \u2014 skipping install. Use --force to reinstall anyway.`
7683
- }, "skills", "marketplace-sync", start);
7684
- return;
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
- let pluginNames;
7687
- if (selectedPlugin === "all") {
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 { results, totalInstalled } = installPluginsForMarketplace(marketplacePath, marketplaceName, selectedMarketplace.repoUrl, pluginNames, targetDir);
7707
- if (selectedPlugin === "all") {
7708
- success({ marketplace: marketplaceName, plugins: results, total: totalInstalled, target: targetDir, marketplaceUpdated: updated, ...installedOnly ? { installedOnly: true } : {} }, "skills", "marketplace-sync", start);
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-sync", start);
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 = path7.resolve(opts.target);
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 = path7.resolve(targetDir);
7738
- if (!fs2.existsSync(targetDir)) {
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 = fs2.readdirSync(targetDir).filter((name) => {
8049
+ const skillDirs = fs3.readdirSync(targetDir).filter((name) => {
7746
8050
  if (name.startsWith(".")) return false;
7747
- const p = path7.join(targetDir, name);
8051
+ const p = path8.join(targetDir, name);
7748
8052
  try {
7749
- return fs2.statSync(p).isDirectory();
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 = path7.resolve(targetDir, skillName);
7756
- if (!skillDir.startsWith(resolvedTarget + path7.sep)) continue;
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
- fs2.rmSync(skillDir, { recursive: true, force: true });
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
- fs2.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
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 (!fs2.existsSync(targetDir)) {
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: path7.join(targetDir, DISABLED_SUBDIR),
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 (!fs2.existsSync(targetDir)) {
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 = fs2.existsSync(targetDir) ? listPluginStates(targetDir) : [];
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 (!fs2.existsSync(userDir)) {
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 = fs2.readdirSync(userDir).filter((name) => {
8290
+ const skillDirs = fs3.readdirSync(userDir).filter((name) => {
7964
8291
  if (name.startsWith(".")) return false;
7965
- const p = path7.join(userDir, name);
8292
+ const p = path8.join(userDir, name);
7966
8293
  try {
7967
- return fs2.statSync(p).isDirectory();
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 = path7.resolve(userDir, name);
7984
- if (!skillDir.startsWith(path7.resolve(userDir) + path7.sep)) continue;
7985
- fs2.rmSync(skillDir, { recursive: true, force: true });
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 (fs2.existsSync(metaPath)) {
7989
- fs2.writeFileSync(metaPath, JSON.stringify({ version: 1, skills: {} }, null, 2), "utf8");
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 = path7.join(marketplacePath, ".claude-plugin", "marketplace.json");
7999
- if (fs2.existsSync(marketplaceJsonPath)) {
8325
+ const marketplaceJsonPath = path8.join(marketplacePath, ".claude-plugin", "marketplace.json");
8326
+ if (fs3.existsSync(marketplaceJsonPath)) {
8000
8327
  try {
8001
- const meta = JSON.parse(fs2.readFileSync(marketplaceJsonPath, "utf8"));
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 = path7.join(marketplacePath, "plugins");
8009
- if (fs2.existsSync(pluginsDir)) {
8010
- return fs2.readdirSync(pluginsDir).filter((name) => fs2.statSync(path7.join(pluginsDir, name)).isDirectory()).map((name) => ({ name, description: "" }));
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 = path7.resolve(marketplacePath, "plugins");
8016
- const skillsSrc = path7.resolve(pluginsBase, selectedPlugin, "skills");
8017
- if (!skillsSrc.startsWith(pluginsBase + path7.sep)) {
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
- fs2.mkdirSync(targetDir, { recursive: true });
8024
- const resolvedTarget = path7.resolve(targetDir);
8025
- const skillNames = fs2.readdirSync(skillsSrc).filter(
8026
- (name) => fs2.statSync(path7.join(skillsSrc, name)).isDirectory()
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 = path7.resolve(targetDir, skillName);
8032
- if (!dest.startsWith(resolvedTarget + path7.sep)) {
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
- fs2.rmSync(dest, { recursive: true, force: true });
8037
- fs2.cpSync(path7.join(skillsSrc, skillName), dest, { recursive: true });
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 fs3 from "fs";
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 path10 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
8169
- await http.ado(path10);
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 (fs3.existsSync(".pncli.json")) {
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 fs4 from "fs";
9276
- import path8 from "path";
9661
+ import fs5 from "fs";
9662
+ import path9 from "path";
9277
9663
  function checkConfigFile(filePath) {
9278
- if (!fs4.existsSync(filePath)) {
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(fs4.readFileSync(filePath, "utf8"));
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 agent skills").option("--offline", "Skip credential checks (no network calls)").action(async (cmdOpts) => {
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(path8.join(repoRoot, ".pncli.json"));
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 problems = buildProblems(globalFile, repoFile, credentials, skillLocations);
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 fs5 from "fs";
9782
- import path9 from "path";
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 ?? path9.join(process.cwd(), ".pncli");
10024
- fs5.mkdirSync(outDir, { recursive: true });
10025
- const outPath = path9.join(outDir, path9.basename(attachment.name));
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
- fs5.writeFileSync(outPath, buffer);
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 fs7 = await import("fs");
10062
- const existing = JSON.parse(fs7.readFileSync(globalConfigPath, "utf8") || "{}");
10474
+ const fs9 = await import("fs");
10475
+ const existing = JSON.parse(fs9.readFileSync(globalConfigPath, "utf8") || "{}");
10063
10476
  existing.ado = { ...existing.ado ?? {}, discoveredTypes: types };
10064
- fs7.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
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 fs7 = await import("fs");
10094
- const existing = JSON.parse(fs7.readFileSync(globalConfigPath, "utf8") || "{}");
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
- fs7.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
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-ZVQMQ6B2.js");
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-RX7E2V5C.js");
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, path10) {
10869
- const cleanPath = path10.startsWith("/") ? path10.slice(1) : path10;
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, path10) {
10873
- const cleanPath = path10.startsWith("/") ? path10.slice(1) : path10;
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, path10, properties, opts = {}) {
10879
- const cleanPath = path10.startsWith("/") ? path10.slice(1) : path10;
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, path10) => {
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, path10);
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, path10) => {
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, path10);
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, path10, keyvals, opts) => {
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, path10, properties, { recursive: opts.recursive });
10993
- success({ repo, path: path10, properties }, "artifactory", "properties-set", start);
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 path10 = opts.organizationId ? `/api/v2/policies/organization/${encodeURIComponent(opts.organizationId)}` : "/api/v2/policies";
11312
- const data = await http.sonatypeiq(path10);
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 fs6 from "fs";
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 = fs6.readFileSync(process.stdin.fd, "utf8");
12432
+ raw = fs7.readFileSync(process.stdin.fd, "utf8");
12020
12433
  } else {
12021
- raw = fs6.readFileSync(filePath, "utf8");
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 = "One command does what three meetings couldn't.";
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;