vskill 0.4.13 → 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.
- package/dist/commands/add.js +16 -169
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +3 -174
- package/dist/commands/add.test.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/add.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
626
|
-
*
|
|
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
|
|
578
|
+
const cacheDir = join(resolveTilde("~/.claude/plugins/cache"), marketplace, pluginName);
|
|
634
579
|
try {
|
|
635
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
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
|
-
|
|
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
|