@kolatts/pncli 1.16.0 → 1.17.0

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