vskill 0.4.14 → 0.4.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.
@@ -23,7 +23,6 @@ import { parseSkillsShUrl, isCompleteParsed, isIncompleteParsed, } from "../reso
23
23
  import { bold, green, red, yellow, dim, cyan, spinner, link, formatInstalls, } from "../utils/output.js";
24
24
  import { isTTY, createPrompter } from "../utils/prompts.js";
25
25
  import { installSymlink, installCopy } from "../installer/canonical.js";
26
- import { isClaudeCliAvailable, registerMarketplace, deregisterMarketplace, installNativePlugin, uninstallNativePlugin, } from "../utils/claude-cli.js";
27
26
  import { getMarketplaceName } from "../marketplace/index.js";
28
27
  async function parseManifestFromContentsApi(data) {
29
28
  // Prefer download_url for raw content
@@ -109,8 +108,7 @@ export async function detectMarketplaceRepo(owner, repo) {
109
108
  * Install plugins from a Claude Code plugin marketplace repo.
110
109
  *
111
110
  * Shows a checkbox list of available plugins (all unchecked by default),
112
- * then installs each selected plugin via native `claude plugin` CLI.
113
- * Falls back to extraction-based install if Claude CLI is unavailable.
111
+ * then installs each selected plugin via file-system extraction to agent skill dirs.
114
112
  */
115
113
  async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSelected) {
116
114
  const plugins = getAvailablePlugins(manifestContent);
@@ -264,8 +262,6 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
264
262
  }
265
263
  usedInteractiveCheckbox = true;
266
264
  }
267
- // Attempt native Claude Code install
268
- const hasClaude = !opts.copy && isClaudeCliAvailable();
269
265
  // Uninstall plugins that were previously installed but now unchecked
270
266
  const selectedNames = new Set([
271
267
  ...selectedPlugins.map((p) => p.name),
@@ -293,10 +289,6 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
293
289
  }
294
290
  }
295
291
  }
296
- // Uninstall from Claude Code native plugin system
297
- if (hasClaude && marketplaceName) {
298
- uninstallNativePlugin(skillName, marketplaceName);
299
- }
300
292
  // Remove from lockfile
301
293
  removeSkillFromLock(skillName, lockDir);
302
294
  console.log(red(` ✗ ${bold(skillName)} uninstalled`) + (removedCount > 0 ? dim(` (${removedCount} location${removedCount === 1 ? "" : "s"})`) : ""));
@@ -390,48 +382,8 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
390
382
  selectedUnregistered = [];
391
383
  }
392
384
  }
393
- // ── Step 1: Optional Claude Code native plugin install ──────────────
394
- let marketplaceRegistered = false;
395
385
  const results = [];
396
- if (hasClaude && selectedPlugins.length > 0) {
397
- let wantNative = false;
398
- if (isTTY() && !opts.yes) {
399
- const prompter = createPrompter();
400
- wantNative = await prompter.promptConfirm("Claude Code detected. Install selected plugins as native Claude Code plugins?", true);
401
- }
402
- else {
403
- // Non-interactive or --yes: install as native plugin by default
404
- wantNative = true;
405
- }
406
- if (wantNative && marketplaceName) {
407
- const gitUrl = `https://github.com/${owner}/${repo}`;
408
- const regSpin = spinner("Registering marketplace with Claude Code");
409
- let regResult = registerMarketplace(gitUrl);
410
- if (!regResult.success) {
411
- deregisterMarketplace(gitUrl);
412
- regResult = registerMarketplace(gitUrl);
413
- }
414
- marketplaceRegistered = regResult.success;
415
- regSpin.stop();
416
- if (marketplaceRegistered) {
417
- for (const plugin of selectedPlugins) {
418
- const installSpin = spinner(`Installing ${bold(plugin.name)} via Claude Code plugin system`);
419
- const ok = installNativePlugin(plugin.name, marketplaceName, opts.global ? "user" : "project");
420
- installSpin.stop();
421
- if (ok) {
422
- console.log(green(` ✓ ${bold(plugin.name)}`) + dim(` (${marketplaceName}:${plugin.name})`));
423
- }
424
- else {
425
- console.log(yellow(` ⚠ ${bold(plugin.name)}`) + dim(" — native install failed"));
426
- }
427
- }
428
- }
429
- else {
430
- console.log(yellow(" Failed to register marketplace — skipping native install."));
431
- }
432
- }
433
- }
434
- // ── Step 2: Agent selection + skill file install (ALWAYS) ──────────
386
+ // ── Step 1: Agent selection + skill file install ──────────
435
387
  const branch = await getDefaultBranch(owner, repo);
436
388
  let agents = await detectInstalledAgents();
437
389
  const selections = await promptInstallOptions(agents, opts);
@@ -562,9 +514,6 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
562
514
  else {
563
515
  console.log(dim("\nNo changes made."));
564
516
  }
565
- if (hasClaude && marketplaceRegistered && marketplaceName) {
566
- console.log(dim(`\nManage: claude plugin list | claude plugin uninstall "<plugin>@${marketplaceName}"`));
567
- }
568
517
  }
569
518
  // ---------------------------------------------------------------------------
570
519
  // Command file filter (prevents plugin internals leaking as slash commands)
@@ -622,97 +571,17 @@ function copyPluginFiltered(sourceDir, targetDir, relBase = "") {
622
571
  // Plugin cache cleanup
623
572
  // ---------------------------------------------------------------------------
624
573
  /**
625
- * Remove a plugin installed via Claude Code's plugin system.
626
- * The cache at ~/.claude/plugins/cache/ contains ALL files without filtering,
627
- * causing internal .md files to leak as ghost slash commands.
628
- *
629
- * Uses `claude plugin uninstall` CLI to properly remove the plugin through
630
- * Claude Code's own API rather than directly manipulating internal files.
574
+ * Remove stale plugin cache from ~/.claude/plugins/cache/.
575
+ * Previous native installs may have left cached files that leak as ghost slash commands.
631
576
  */
632
577
  function cleanPluginCache(pluginName, marketplace) {
633
- const pluginKey = `${pluginName}@${marketplace}`;
578
+ const cacheDir = join(resolveTilde("~/.claude/plugins/cache"), marketplace, pluginName);
634
579
  try {
635
- execSync(`claude plugin uninstall "${pluginKey}"`, { stdio: "ignore", timeout: 10_000 });
636
- }
637
- catch { /* ignore - plugin might not be installed via CLI */ }
638
- }
639
- // ---------------------------------------------------------------------------
640
- // Native Claude Code plugin install
641
- // ---------------------------------------------------------------------------
642
- /**
643
- * Check if a path is inside the OS temp directory (e.g. /var/folders/.../T/).
644
- * Temp dirs get cleaned up, making registered marketplace paths stale.
645
- */
646
- function isTempPath(p) {
647
- const tmpDir = os.tmpdir();
648
- return p.startsWith(tmpDir);
649
- }
650
- /**
651
- * Attempt to install a plugin via Claude Code's native plugin system.
652
- *
653
- * Returns true if native install succeeded (Claude Code should be excluded
654
- * from the extraction agent list). Returns false if native install was
655
- * skipped or failed (fall back to extraction).
656
- *
657
- * Conditions for native install:
658
- * - `claude` CLI is available on PATH
659
- * - --copy flag is NOT set
660
- * - User opts in (interactive prompt) OR --yes flag is set
661
- */
662
- async function tryNativeClaudeInstall(marketplacePath, marketplaceContent, pluginName, opts, gitUrl) {
663
- if (opts.copy)
664
- return false;
665
- if (!isClaudeCliAvailable())
666
- return false;
667
- const marketplaceName = getMarketplaceName(marketplaceContent);
668
- if (!marketplaceName)
669
- return false;
670
- // Guard: refuse to register temp dirs (they get deleted after install)
671
- if (!gitUrl && isTempPath(marketplacePath)) {
672
- console.log(yellow(" Cannot register temp directory as marketplace — falling back to extraction."));
673
- return false;
674
- }
675
- // Prompt user unless --yes
676
- if (isTTY() && !opts.yes) {
677
- const prompter = createPrompter();
678
- const choice = await prompter.promptChoice("Claude Code plugin install method:", [
679
- { label: "Native plugin install", hint: `recommended — enables ${marketplaceName}:${pluginName} namespacing` },
680
- { label: "Extract skills individually", hint: "copies files to .claude/skills/ directory" },
681
- ]);
682
- if (choice !== 0)
683
- return false;
684
- }
685
- // Register marketplace — prefer git URL (persistent) over local path
686
- const registrationSource = gitUrl || marketplacePath;
687
- const regSpin = spinner("Registering marketplace with Claude Code");
688
- let regResult = registerMarketplace(registrationSource);
689
- // Retry once after deregistering stale entry
690
- if (!regResult.success) {
691
- deregisterMarketplace(registrationSource);
692
- regResult = registerMarketplace(registrationSource);
693
- }
694
- if (!regResult.success) {
695
- regSpin.stop();
696
- console.log(yellow(" Failed to register marketplace — falling back to extraction."));
697
- if (regResult.stderr) {
698
- console.log(dim(` Reason: ${regResult.stderr}`));
580
+ if (existsSync(cacheDir)) {
581
+ rmSync(cacheDir, { recursive: true, force: true });
699
582
  }
700
- return false;
701
583
  }
702
- regSpin.stop();
703
- // Install plugin
704
- const installSpin = spinner(`Installing ${pluginName} via Claude Code plugin system`);
705
- const installed = installNativePlugin(pluginName, marketplaceName, opts.global ? "user" : "project");
706
- if (!installed) {
707
- installSpin.stop();
708
- console.log(yellow(" Native install failed — falling back to extraction."));
709
- return false;
710
- }
711
- installSpin.stop();
712
- console.log(green(` ${bold(pluginName)} installed as native Claude Code plugin`));
713
- console.log(dim(` Namespace: ${marketplaceName}:${pluginName}`));
714
- console.log(dim(` Manage: claude plugin list | claude plugin uninstall "${pluginName}@${marketplaceName}"`));
715
- return true;
584
+ catch { /* ignore - cache dir might not exist */ }
716
585
  }
717
586
  /**
718
587
  * Resolve the project root for skill installation, with a HOME-directory guard.
@@ -1061,40 +930,25 @@ async function installPluginDir(basePath, pluginName, opts) {
1061
930
  const mktPath = join(basePath, ".claude-plugin", "marketplace.json");
1062
931
  const mktContent = readFileSync(mktPath, "utf-8");
1063
932
  const version = getPluginVersion(pluginName, mktContent) || "0.0.0";
1064
- // Native Claude Code plugin install
1065
- // Extract git remote URL (used for both native Claude install and install tracking)
933
+ // Extract git remote URL for install tracking
1066
934
  let gitUrl;
1067
935
  try {
1068
936
  gitUrl = execSync("git remote get-url origin", { cwd: resolve(basePath), stdio: ["pipe", "pipe", "ignore"], timeout: 5_000 })
1069
937
  .toString().trim() || undefined;
1070
938
  }
1071
939
  catch { /* not a git repo or no remote — use local path */ }
1072
- const hasClaude = selectedAgents.some((a) => a.id === "claude-code");
1073
- let claudeNativeSuccess = false;
1074
- if (hasClaude) {
1075
- claudeNativeSuccess = await tryNativeClaudeInstall(resolve(basePath), mktContent, pluginName, opts, gitUrl);
1076
- }
1077
- // Filter Claude Code out of extraction if native install succeeded
1078
- const extractionAgents = claudeNativeSuccess
1079
- ? selectedAgents.filter((a) => a.id !== "claude-code")
1080
- : selectedAgents;
1081
- // Clean stale plugin cache only when NOT using native install
1082
- if (!claudeNativeSuccess) {
1083
- try {
1084
- const marketplaceName = JSON.parse(mktContent).name;
1085
- if (marketplaceName) {
1086
- cleanPluginCache(pluginName, marketplaceName);
1087
- }
940
+ // Clean stale plugin cache
941
+ try {
942
+ const marketplaceName = JSON.parse(mktContent).name;
943
+ if (marketplaceName) {
944
+ cleanPluginCache(pluginName, marketplaceName);
1088
945
  }
1089
- catch { /* ignore parse errors */ }
1090
946
  }
947
+ catch { /* ignore parse errors */ }
1091
948
  // Install: recursively copy plugin directory to each agent
1092
949
  const sha = createHash("sha256").update(content).digest("hex").slice(0, 12);
1093
950
  const locations = [];
1094
- if (claudeNativeSuccess) {
1095
- locations.push("Claude Code: native plugin");
1096
- }
1097
- for (const agent of extractionAgents) {
951
+ for (const agent of selectedAgents) {
1098
952
  const cacheDir = join(resolveInstallBase(opts, agent), pluginName);
1099
953
  try {
1100
954
  // Full clean before copy: removes stale files from older installs
@@ -1461,13 +1315,6 @@ async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
1461
1315
  opts.global = true;
1462
1316
  if (!selections.symlink)
1463
1317
  opts.copy = true;
1464
- // Native Claude Code plugin install is only available for local plugins.
1465
- // Remote plugins require marketplace registration with a local path.
1466
- const hasClaude = selectedAgents.some((a) => a.id === "claude-code");
1467
- if (hasClaude && !opts.copy) {
1468
- console.log(dim("\n Note: Native Claude Code plugin install requires a local path.") +
1469
- dim("\n Use --plugin-dir for native install. Extracting skills for Claude Code.\n"));
1470
- }
1471
1318
  // Compute project root for consistent lockfile + skill locations
1472
1319
  const projectRoot = safeProjectRoot(opts);
1473
1320
  // Install skills and commands with namespace prefix