vskill 1.0.13 → 1.0.15

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.
Files changed (102) hide show
  1. package/README.md +63 -2
  2. package/agents.json +1 -1
  3. package/dist/bin.js +0 -0
  4. package/dist/clone/github-scaffold.d.ts +38 -0
  5. package/dist/clone/github-scaffold.js +108 -0
  6. package/dist/clone/github-scaffold.js.map +1 -0
  7. package/dist/clone/provenance-fork.d.ts +34 -0
  8. package/dist/clone/provenance-fork.js +97 -0
  9. package/dist/clone/provenance-fork.js.map +1 -0
  10. package/dist/clone/reference-scanner.d.ts +19 -0
  11. package/dist/clone/reference-scanner.js +144 -0
  12. package/dist/clone/reference-scanner.js.map +1 -0
  13. package/dist/clone/skill-locator.d.ts +26 -0
  14. package/dist/clone/skill-locator.js +248 -0
  15. package/dist/clone/skill-locator.js.map +1 -0
  16. package/dist/clone/target-router.d.ts +73 -0
  17. package/dist/clone/target-router.js +200 -0
  18. package/dist/clone/target-router.js.map +1 -0
  19. package/dist/clone/types.d.ts +82 -0
  20. package/dist/clone/types.js +11 -0
  21. package/dist/clone/types.js.map +1 -0
  22. package/dist/commands/add.js +96 -32
  23. package/dist/commands/add.js.map +1 -1
  24. package/dist/commands/auth.d.ts +23 -0
  25. package/dist/commands/auth.js +273 -0
  26. package/dist/commands/auth.js.map +1 -0
  27. package/dist/commands/check.d.ts +55 -0
  28. package/dist/commands/check.js +279 -0
  29. package/dist/commands/check.js.map +1 -0
  30. package/dist/commands/clone-prompts.d.ts +13 -0
  31. package/dist/commands/clone-prompts.js +67 -0
  32. package/dist/commands/clone-prompts.js.map +1 -0
  33. package/dist/commands/clone.d.ts +70 -0
  34. package/dist/commands/clone.js +649 -0
  35. package/dist/commands/clone.js.map +1 -0
  36. package/dist/commands/eval/serve.js +8 -1
  37. package/dist/commands/eval/serve.js.map +1 -1
  38. package/dist/commands/keys.js +54 -2
  39. package/dist/commands/keys.js.map +1 -1
  40. package/dist/core/agent-prompts.d.ts +35 -0
  41. package/dist/core/agent-prompts.js +201 -0
  42. package/dist/core/agent-prompts.js.map +1 -0
  43. package/dist/core/skill-generator.d.ts +25 -3
  44. package/dist/core/skill-generator.js +131 -0
  45. package/dist/core/skill-generator.js.map +1 -1
  46. package/dist/eval/skill-scanner.d.ts +2 -12
  47. package/dist/eval/skill-scanner.js +27 -5
  48. package/dist/eval/skill-scanner.js.map +1 -1
  49. package/dist/eval-server/api-routes.d.ts +14 -0
  50. package/dist/eval-server/api-routes.js +376 -31
  51. package/dist/eval-server/api-routes.js.map +1 -1
  52. package/dist/eval-server/data-events.d.ts +1 -1
  53. package/dist/eval-server/data-events.js.map +1 -1
  54. package/dist/eval-server/install-engine-routes-helpers.d.ts +1 -3
  55. package/dist/eval-server/install-engine-routes-helpers.js +6 -14
  56. package/dist/eval-server/install-engine-routes-helpers.js.map +1 -1
  57. package/dist/eval-server/origin-resolver.d.ts +42 -0
  58. package/dist/eval-server/origin-resolver.js +168 -0
  59. package/dist/eval-server/origin-resolver.js.map +1 -0
  60. package/dist/eval-server/platform-proxy.d.ts +10 -0
  61. package/dist/eval-server/platform-proxy.js +58 -2
  62. package/dist/eval-server/platform-proxy.js.map +1 -1
  63. package/dist/eval-server/skill-create-routes.d.ts +8 -0
  64. package/dist/eval-server/skill-create-routes.js +96 -0
  65. package/dist/eval-server/skill-create-routes.js.map +1 -1
  66. package/dist/eval-server/skill-resolver.js +40 -0
  67. package/dist/eval-server/skill-resolver.js.map +1 -1
  68. package/dist/eval-server/utils/resolve-editor.d.ts +18 -0
  69. package/dist/eval-server/utils/resolve-editor.js +77 -0
  70. package/dist/eval-server/utils/resolve-editor.js.map +1 -0
  71. package/dist/eval-server/utils/scan-install-locations.d.ts +7 -0
  72. package/dist/eval-server/utils/scan-install-locations.js +20 -0
  73. package/dist/eval-server/utils/scan-install-locations.js.map +1 -1
  74. package/dist/eval-server/utils/which.d.ts +15 -0
  75. package/dist/eval-server/utils/which.js +76 -0
  76. package/dist/eval-server/utils/which.js.map +1 -0
  77. package/dist/eval-ui/assets/{CreateSkillPage-T0YWZWw-.js → CreateSkillPage-BmbvQEzE.js} +1 -1
  78. package/dist/eval-ui/assets/{FindSkillsPalette-KcFM32hZ.js → FindSkillsPalette-D0Zjhm31.js} +2 -2
  79. package/dist/eval-ui/assets/{SearchPaletteCore-EhBtr4Xx.js → SearchPaletteCore-EhcN1xEa.js} +1 -1
  80. package/dist/eval-ui/assets/SkillDetailPanel-B5J60ffv.js +1 -0
  81. package/dist/eval-ui/assets/{UpdateDropdown-pjFhHTi6.js → UpdateDropdown-Celf0_Cr.js} +1 -1
  82. package/dist/eval-ui/assets/index-BV7k6fdk.js +124 -0
  83. package/dist/eval-ui/assets/{index-BKAvJDDF.css → index-CKLqBL52.css} +1 -1
  84. package/dist/eval-ui/index.html +2 -2
  85. package/dist/index.js +47 -0
  86. package/dist/index.js.map +1 -1
  87. package/dist/installer/frontmatter.d.ts +26 -0
  88. package/dist/installer/frontmatter.js +90 -0
  89. package/dist/installer/frontmatter.js.map +1 -1
  90. package/dist/lib/github-fetch.d.ts +22 -0
  91. package/dist/lib/github-fetch.js +152 -0
  92. package/dist/lib/github-fetch.js.map +1 -0
  93. package/dist/lib/keychain.d.ts +41 -0
  94. package/dist/lib/keychain.js +232 -0
  95. package/dist/lib/keychain.js.map +1 -0
  96. package/dist/studio/types.d.ts +13 -0
  97. package/dist/utils/claude-plugin.d.ts +26 -0
  98. package/dist/utils/claude-plugin.js +60 -0
  99. package/dist/utils/claude-plugin.js.map +1 -1
  100. package/package.json +2 -1
  101. package/dist/eval-ui/assets/SkillDetailPanel-cyzLsLcK.js +0 -1
  102. package/dist/eval-ui/assets/index-C3S9iHnq.js +0 -122
@@ -17,6 +17,7 @@ import { checkInstallSafety } from "../blocklist/blocklist.js";
17
17
  import { getSkill, searchSkills } from "../api/client.js";
18
18
  import { checkPlatformSecurity } from "../security/index.js";
19
19
  import { discoverSkills, getDefaultBranch, checkRepoExists, warnRateLimitOnce } from "../discovery/github-tree.js";
20
+ import { githubFetch } from "../lib/github-fetch.js";
20
21
  import { parseGitHubSource } from "../utils/validation.js";
21
22
  import { parseSkillsShUrl, isCompleteParsed, isIncompleteParsed, } from "../resolvers/url-resolver.js";
22
23
  import { bold, green, red, yellow, dim, cyan, spinner, link, formatInstalls, } from "../utils/output.js";
@@ -32,7 +33,7 @@ import { extractFrontmatterVersion } from "../utils/version.js";
32
33
  // mutation to the claude CLI (ADR 0724-01), and we honour the --no-enable
33
34
  // flag to skip it. Uninstall is imported for F-003 rollback of earlier
34
35
  // already-enabled plugins on a partway failure.
35
- import { claudePluginInstall, claudePluginUninstall } from "../utils/claude-plugin.js";
36
+ import { claudePluginInstall, claudePluginUninstall, claudePluginMarketplaceAdd, claudePluginMarketplaceList, } from "../utils/claude-plugin.js";
36
37
  import { buildPerAgentReport, resolvePluginId, } from "../lib/skill-lifecycle.js";
37
38
  // ---------------------------------------------------------------------------
38
39
  /** Validate that a download_url from GitHub Contents API points to a trusted GitHub domain. */
@@ -48,7 +49,7 @@ function isGitHubDownloadUrl(url) {
48
49
  async function parseManifestFromContentsApi(data) {
49
50
  // Prefer download_url for raw content — validate URL before fetching (SSRF prevention)
50
51
  if (data.download_url && isGitHubDownloadUrl(data.download_url)) {
51
- const rawRes = await fetch(data.download_url);
52
+ const rawRes = await githubFetch(data.download_url);
52
53
  if (rawRes.ok) {
53
54
  const content = await rawRes.text();
54
55
  if (getAvailablePlugins(content).length > 0)
@@ -75,7 +76,7 @@ export async function detectMarketplaceRepo(owner, repo) {
75
76
  const headers = { Accept: "application/vnd.github.v3+json", "User-Agent": "vskill-cli" };
76
77
  // Attempt 1: Contents API
77
78
  try {
78
- const res = await fetch(contentsUrl, { headers });
79
+ const res = await githubFetch(contentsUrl, { headers });
79
80
  if (res.status === 404)
80
81
  return { isMarketplace: false };
81
82
  if (res.status === 403)
@@ -93,7 +94,7 @@ export async function detectMarketplaceRepo(owner, repo) {
93
94
  // Attempt 2: Retry Contents API after 1s delay
94
95
  try {
95
96
  await new Promise((r) => setTimeout(r, 1000));
96
- const res = await fetch(contentsUrl, { headers });
97
+ const res = await githubFetch(contentsUrl, { headers });
97
98
  if (res.status === 404)
98
99
  return { isMarketplace: false };
99
100
  if (res.status === 403)
@@ -112,7 +113,7 @@ export async function detectMarketplaceRepo(owner, repo) {
112
113
  try {
113
114
  const branch = await getDefaultBranch(owner, repo);
114
115
  const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/.claude-plugin/marketplace.json`;
115
- const res = await fetch(rawUrl);
116
+ const res = await githubFetch(rawUrl);
116
117
  if (res.ok) {
117
118
  const content = await res.text();
118
119
  if (getAvailablePlugins(content).length > 0) {
@@ -191,7 +192,7 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
191
192
  // Not in marketplace or unregistered list — probe plugins/<name>/ folder directly
192
193
  let probeSource = null;
193
194
  try {
194
- const probeRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/contents/plugins/${preSelected[0]}`, { headers: { "User-Agent": "vskill-cli" } });
195
+ const probeRes = await githubFetch(`https://api.github.com/repos/${owner}/${repo}/contents/plugins/${preSelected[0]}`, { headers: { "User-Agent": "vskill-cli" } });
195
196
  if (probeRes.ok) {
196
197
  const data = await probeRes.json();
197
198
  if (Array.isArray(data)) {
@@ -351,7 +352,7 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
351
352
  // Discover skills: try nested {pluginPath}/skills/ first, fall back to flat {pluginPath}/SKILL.md
352
353
  const skillsToSubmit = [];
353
354
  const skillsUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${pluginPath}/skills`;
354
- const skillsRes = await fetch(skillsUrl, { headers: { "User-Agent": "vskill-cli" } });
355
+ const skillsRes = await githubFetch(skillsUrl, { headers: { "User-Agent": "vskill-cli" } });
355
356
  if (skillsRes.ok) {
356
357
  const skillDirs = (await skillsRes.json())
357
358
  .filter((e) => e.type === "dir");
@@ -450,24 +451,27 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
450
451
  })),
451
452
  ];
452
453
  for (const plugin of allPluginsToInstall) {
453
- const pluginPath = plugin.source.replace(/^\.\//, "");
454
- if (!pluginPath) {
455
- results.push({ name: plugin.name, installed: false, method: "failed" });
456
- continue;
457
- }
454
+ // 0826: a marketplace plugin whose `source` is `"./"` lives at the repo
455
+ // root — the previous guard (`if (!pluginPath) … failed`) short-circuited
456
+ // those installs. Keep the path normalization but allow an empty value to
457
+ // mean "repo root" instead of treating it as a hard failure. Build URL
458
+ // segments with a leading-slash prefix only when there's a real path so
459
+ // we don't emit `//` between the contents-API base and `skills`.
460
+ const pluginPath = plugin.source.replace(/^\.\//, "").replace(/\/$/, "");
461
+ const pathSegment = pluginPath ? `/${pluginPath}` : "";
458
462
  const installSpin = spinner(`Installing skills: ${bold(plugin.name)}`);
459
463
  try {
460
464
  // Discover skills: try {pluginPath}/skills/ first, then fall back to {pluginPath}/SKILL.md
461
465
  const installedSkillNames = [];
462
- const skillsUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${pluginPath}/skills`;
463
- const skillsRes = await fetch(skillsUrl, { headers: { "User-Agent": "vskill-cli" } });
466
+ const skillsUrl = `https://api.github.com/repos/${owner}/${repo}/contents${pathSegment}/skills`;
467
+ const skillsRes = await githubFetch(skillsUrl, { headers: { "User-Agent": "vskill-cli" } });
464
468
  if (skillsRes.ok) {
465
469
  // Nested layout: {pluginPath}/skills/{skillName}/SKILL.md
466
470
  const skillDirs = (await skillsRes.json())
467
471
  .filter((e) => e.type === "dir");
468
472
  for (const sd of skillDirs) {
469
- const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${pluginPath}/skills/${sd.name}/SKILL.md`;
470
- const contentRes = await fetch(rawUrl);
473
+ const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}${pathSegment}/skills/${sd.name}/SKILL.md`;
474
+ const contentRes = await githubFetch(rawUrl);
471
475
  if (!contentRes.ok)
472
476
  continue;
473
477
  const content = await contentRes.text();
@@ -488,8 +492,8 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
488
492
  }
489
493
  else {
490
494
  // Flat layout: {pluginPath}/SKILL.md directly in the plugin root
491
- const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${pluginPath}/SKILL.md`;
492
- const contentRes = await fetch(rawUrl);
495
+ const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}${pathSegment}/SKILL.md`;
496
+ const contentRes = await githubFetch(rawUrl);
493
497
  if (contentRes.ok) {
494
498
  const content = await contentRes.text();
495
499
  const processedContent = ensureFrontmatter(content, plugin.name);
@@ -565,7 +569,14 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
565
569
  // `enabledSoFar` so that a later failure rolls back all earlier
566
570
  // already-enabled plugins (otherwise we'd leave exactly the stale
567
571
  // `enabledPlugins` entries that `vskill cleanup` is meant to fix).
572
+ // 0826: include `opts.global` here. Previously a Studio "Global" install
573
+ // (or CLI `vskill install … --global`) wrote the SKILL.md stub to disk
574
+ // but never invoked `claude plugin install` — leaving the plugin
575
+ // unenabled. The user saw the skill on disk but Claude Code didn't load
576
+ // it. Treat --global as opt-in to the enable hook (scope = user, since
577
+ // --global is per-machine, not per-repo).
568
578
  const userOptedIn = opts.scope !== undefined ||
579
+ opts.global === true ||
569
580
  opts.dryRun === true ||
570
581
  opts.enable === false;
571
582
  if (userOptedIn) {
@@ -692,9 +703,62 @@ export function enableAfterInstall(skillName, entry, opts) {
692
703
  console.log(dim(`Dry-run: would invoke ${cyan(`claude plugin install --scope ${scope} -- ${pluginId}`)}`));
693
704
  return { invoked: false, pluginId, scope };
694
705
  }
706
+ // 0826: when the source repo is a Claude Code plugin marketplace that
707
+ // hasn't been registered yet, `claude plugin install foo@bar` fails with
708
+ // "Plugin foo not found in marketplace bar" — and the rollback then
709
+ // wipes the on-disk extraction we just succeeded at. Detect that case
710
+ // up-front from the lockfile entry's marketplace metadata and register
711
+ // the marketplace via `claude plugin marketplace add` before delegating
712
+ // the install. Best-effort — if the add fails (network, invalid source)
713
+ // we still attempt the install so the original error is surfaced.
714
+ ensureMarketplaceRegistered(entry, pluginId, scope);
695
715
  claudePluginInstall(pluginId, scope, scope === "project" ? { cwd: process.cwd() } : undefined);
696
716
  return { invoked: true, pluginId, scope };
697
717
  }
718
+ /**
719
+ * 0826: Ensure the marketplace referenced by `entry` is registered with the
720
+ * claude CLI before we try to enable the plugin. The lockfile entry shape
721
+ * stores the marketplace name (e.g. `postiz-agent`) plus the originating
722
+ * `sourceRepoUrl` — we derive `<owner>/<repo>` from the URL and run
723
+ * `claude plugin marketplace add` if the name is missing from the current
724
+ * `claude plugin marketplace list`.
725
+ *
726
+ * Pure best-effort: any failure (network, parse) falls through silently and
727
+ * lets the subsequent `claudePluginInstall` surface the real error.
728
+ */
729
+ function ensureMarketplaceRegistered(entry, pluginId, scope) {
730
+ const marketplaceName = entry.marketplace;
731
+ if (!marketplaceName)
732
+ return;
733
+ // Already registered — nothing to do.
734
+ let registered = [];
735
+ try {
736
+ registered = claudePluginMarketplaceList();
737
+ }
738
+ catch {
739
+ return;
740
+ }
741
+ if (registered.includes(marketplaceName))
742
+ return;
743
+ // Derive a `claude plugin marketplace add` source from the lockfile.
744
+ // GitHub URL → `owner/repo`; falls back to `marketplace@<id>` form which
745
+ // claude rejects with a clear message.
746
+ const repoSource = entry.sourceRepoUrl?.match(/github\.com\/([^/]+\/[^/]+?)(?:\.git)?\/?$/);
747
+ const source = repoSource ? repoSource[1] : "";
748
+ if (!source) {
749
+ console.error(dim(` marketplace "${marketplaceName}" not registered and no source repo URL available — skipping auto-add (claude plugin install will likely fail).`));
750
+ return;
751
+ }
752
+ console.log(dim(` Registering marketplace "${marketplaceName}" (${source}) — required for ${pluginId}`));
753
+ try {
754
+ claudePluginMarketplaceAdd(source, scope);
755
+ }
756
+ catch (err) {
757
+ // Surface the underlying error but keep going — claudePluginInstall
758
+ // will still throw a clearer "not found in marketplace" if needed.
759
+ console.error(dim(` Failed to auto-register marketplace "${marketplaceName}": ${err.message.split("\n")[0]}`));
760
+ }
761
+ }
698
762
  /**
699
763
  * 0724 T-006: rollback the on-disk extraction + lockfile entry when
700
764
  * `claudePluginInstall` throws (AC-US1-05). Best-effort — wraps each rm in
@@ -871,7 +935,7 @@ async function promptInstallOptions(agents, opts) {
871
935
  async function fetchSkillContent(url) {
872
936
  const spin = spinner("Fetching skill");
873
937
  try {
874
- const res = await fetch(url);
938
+ const res = await githubFetch(url);
875
939
  if (!res.ok) {
876
940
  spin.stop();
877
941
  if (res.status === 404) {
@@ -1173,7 +1237,7 @@ sourceSkillPath) {
1173
1237
  // Fetch content (non-exiting for multi-skill support)
1174
1238
  let content;
1175
1239
  try {
1176
- const res = await fetch(rawUrl);
1240
+ const res = await githubFetch(rawUrl);
1177
1241
  if (!res.ok) {
1178
1242
  return { skillName, installed: false, verdict: "FETCH_FAILED" };
1179
1243
  }
@@ -1221,7 +1285,7 @@ sourceSkillPath) {
1221
1285
  agentFiles = {};
1222
1286
  const fetches = Object.entries(agentRawUrls).map(async ([relPath, url]) => {
1223
1287
  try {
1224
- const res = await fetch(url);
1288
+ const res = await githubFetch(url);
1225
1289
  if (res.ok)
1226
1290
  agentFiles[relPath] = await res.text();
1227
1291
  }
@@ -1264,7 +1328,7 @@ async function installAllRepoPlugins(ownerRepo, opts) {
1264
1328
  const manifestSpin = spinner("Fetching marketplace.json");
1265
1329
  let manifestContent;
1266
1330
  try {
1267
- const res = await fetch(manifestUrl);
1331
+ const res = await githubFetch(manifestUrl);
1268
1332
  if (!res.ok) {
1269
1333
  manifestSpin.stop();
1270
1334
  console.error(red(`marketplace.json not found at ${owner}/${repo}\n`) +
@@ -1326,7 +1390,7 @@ async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
1326
1390
  const manifestSpin = spinner("Fetching marketplace.json");
1327
1391
  let manifestContent;
1328
1392
  try {
1329
- const res = await fetch(manifestUrl);
1393
+ const res = await githubFetch(manifestUrl);
1330
1394
  if (!res.ok) {
1331
1395
  manifestSpin.stop();
1332
1396
  throw new Error(`marketplace.json not found at ${owner}/${repo}. ` +
@@ -1347,7 +1411,7 @@ async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
1347
1411
  // Not in marketplace.json — probe plugins/<name>/ folder in the repo
1348
1412
  const probeSpin = spinner(`Looking for "${pluginName}" folder in repo`);
1349
1413
  try {
1350
- const probeRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/contents/plugins/${pluginName}`, { headers: { "User-Agent": "vskill-cli" } });
1414
+ const probeRes = await githubFetch(`https://api.github.com/repos/${owner}/${repo}/contents/plugins/${pluginName}`, { headers: { "User-Agent": "vskill-cli" } });
1351
1415
  if (probeRes.ok) {
1352
1416
  const data = await probeRes.json();
1353
1417
  if (Array.isArray(data)) {
@@ -1387,7 +1451,7 @@ async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
1387
1451
  let skillEntries = [];
1388
1452
  let flatLayout = false;
1389
1453
  try {
1390
- const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/contents/${pluginPath}/skills`, { headers: { "User-Agent": "vskill-cli" } });
1454
+ const res = await githubFetch(`https://api.github.com/repos/${owner}/${repo}/contents/${pluginPath}/skills`, { headers: { "User-Agent": "vskill-cli" } });
1391
1455
  if (res.ok) {
1392
1456
  skillEntries = (await res.json()).filter((e) => e.type === "dir");
1393
1457
  }
@@ -1396,7 +1460,7 @@ async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
1396
1460
  // Flat layout fallback: check for SKILL.md directly at plugin root
1397
1461
  if (skillEntries.length === 0) {
1398
1462
  try {
1399
- const res = await fetch(`https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${pluginPath}/SKILL.md`);
1463
+ const res = await githubFetch(`https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${pluginPath}/SKILL.md`);
1400
1464
  if (res.ok) {
1401
1465
  flatLayout = true;
1402
1466
  skillEntries = [{ name: pluginName, type: "dir" }];
@@ -1406,7 +1470,7 @@ async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
1406
1470
  }
1407
1471
  let cmdEntries = [];
1408
1472
  try {
1409
- const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/contents/${pluginPath}/commands`, { headers: { "User-Agent": "vskill-cli" } });
1473
+ const res = await githubFetch(`https://api.github.com/repos/${owner}/${repo}/contents/${pluginPath}/commands`, { headers: { "User-Agent": "vskill-cli" } });
1410
1474
  if (res.ok) {
1411
1475
  cmdEntries = (await res.json()).filter((e) => e.name.endsWith(".md"));
1412
1476
  }
@@ -1433,7 +1497,7 @@ async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
1433
1497
  ? `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${pluginPath}/SKILL.md`
1434
1498
  : `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${pluginPath}/skills/${entry.name}/SKILL.md`;
1435
1499
  try {
1436
- const res = await fetch(rawUrl);
1500
+ const res = await githubFetch(rawUrl);
1437
1501
  if (res.ok) {
1438
1502
  const content = await res.text();
1439
1503
  skills.push({ name: entry.name, content });
@@ -1446,7 +1510,7 @@ async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
1446
1510
  for (const entry of cmdEntries) {
1447
1511
  const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${pluginPath}/commands/${entry.name}`;
1448
1512
  try {
1449
- const res = await fetch(rawUrl);
1513
+ const res = await githubFetch(rawUrl);
1450
1514
  if (res.ok) {
1451
1515
  const content = await res.text();
1452
1516
  commands.push({ name: entry.name, content });
@@ -1630,7 +1694,7 @@ export async function addCommand(source, opts) {
1630
1694
  const pluginPath = plugin.source.replace(/^\.\//, "");
1631
1695
  const subpath = `${pluginPath}/skills/${threeSkill}/SKILL.md`;
1632
1696
  const probeUrl = `https://raw.githubusercontent.com/${threeOwner}/${threeRepo}/${branch}/${subpath}`;
1633
- const probeRes = await fetch(probeUrl);
1697
+ const probeRes = await githubFetch(probeUrl);
1634
1698
  if (probeRes.ok) {
1635
1699
  return installSingleSkillLegacy(threeOwner, threeRepo, threeSkill, opts, subpath, plugin.name);
1636
1700
  }
@@ -2157,7 +2221,7 @@ async function installSingleSkillLegacy(owner, repo, skill, opts, skillSubpathOv
2157
2221
  ? skillSubpathOverride.replace(/\/SKILL\.md$/, "/agents")
2158
2222
  : `skills/${skill}/agents`;
2159
2223
  const agentsDirUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${agentsBasePath}`;
2160
- const dirRes = await fetch(agentsDirUrl, { headers: { Accept: "application/vnd.github.v3+json" } });
2224
+ const dirRes = await githubFetch(agentsDirUrl, { headers: { Accept: "application/vnd.github.v3+json" } });
2161
2225
  if (dirRes.ok) {
2162
2226
  const entries = (await dirRes.json());
2163
2227
  const mdEntries = entries.filter((e) => e.name.endsWith(".md") && e.download_url && isGitHubDownloadUrl(e.download_url));
@@ -2165,7 +2229,7 @@ async function installSingleSkillLegacy(owner, repo, skill, opts, skillSubpathOv
2165
2229
  legacyAgentFiles = {};
2166
2230
  const fetches = mdEntries.map(async (entry) => {
2167
2231
  try {
2168
- const res = await fetch(entry.download_url);
2232
+ const res = await githubFetch(entry.download_url);
2169
2233
  if (res.ok)
2170
2234
  legacyAgentFiles[`agents/${entry.name}`] = await res.text();
2171
2235
  }