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
|
@@ -178,21 +178,6 @@ vi.mock("../installer/canonical.js", () => ({
|
|
|
178
178
|
installCopy: (...args) => mockInstallCopy(...args),
|
|
179
179
|
}));
|
|
180
180
|
// ---------------------------------------------------------------------------
|
|
181
|
-
// Mock claude-cli (native Claude Code plugin install)
|
|
182
|
-
// ---------------------------------------------------------------------------
|
|
183
|
-
const mockIsClaudeCliAvailable = vi.fn().mockReturnValue(false);
|
|
184
|
-
const mockRegisterMarketplace = vi.fn().mockReturnValue({ success: false });
|
|
185
|
-
const mockDeregisterMarketplace = vi.fn().mockReturnValue(false);
|
|
186
|
-
const mockInstallNativePlugin = vi.fn().mockReturnValue(false);
|
|
187
|
-
const mockUninstallNativePlugin = vi.fn().mockReturnValue(false);
|
|
188
|
-
vi.mock("../utils/claude-cli.js", () => ({
|
|
189
|
-
isClaudeCliAvailable: (...args) => mockIsClaudeCliAvailable(...args),
|
|
190
|
-
registerMarketplace: (...args) => mockRegisterMarketplace(...args),
|
|
191
|
-
deregisterMarketplace: (...args) => mockDeregisterMarketplace(...args),
|
|
192
|
-
installNativePlugin: (...args) => mockInstallNativePlugin(...args),
|
|
193
|
-
uninstallNativePlugin: (...args) => mockUninstallNativePlugin(...args),
|
|
194
|
-
}));
|
|
195
|
-
// ---------------------------------------------------------------------------
|
|
196
181
|
// Mock getMarketplaceName + discoverUnregisteredPlugins (preserve real marketplace functions)
|
|
197
182
|
// ---------------------------------------------------------------------------
|
|
198
183
|
const mockGetMarketplaceName = vi.fn().mockReturnValue(null);
|
|
@@ -1529,90 +1514,6 @@ describe("addCommand with --repo flag (remote plugin install)", () => {
|
|
|
1529
1514
|
});
|
|
1530
1515
|
});
|
|
1531
1516
|
// ---------------------------------------------------------------------------
|
|
1532
|
-
// Native Claude Code plugin install (via installPluginDir)
|
|
1533
|
-
// ---------------------------------------------------------------------------
|
|
1534
|
-
describe("addCommand native Claude Code plugin install", () => {
|
|
1535
|
-
// Use a non-temp path — temp paths are now correctly blocked by isTempPath() guard
|
|
1536
|
-
const pluginDir = "/home/test-user/projects/test-plugin";
|
|
1537
|
-
const marketplaceJson = JSON.stringify({
|
|
1538
|
-
name: "test-mkt",
|
|
1539
|
-
version: "1.0.0",
|
|
1540
|
-
plugins: [{ name: "sw-test", source: "./plugins/test", version: "1.0.0" }],
|
|
1541
|
-
});
|
|
1542
|
-
beforeEach(() => {
|
|
1543
|
-
mockCheckInstallSafety.mockResolvedValue({ blocked: false, rejected: false });
|
|
1544
|
-
mockRunTier1Scan.mockReturnValue(makeScanResult());
|
|
1545
|
-
mockEnsureLockfile.mockReturnValue({
|
|
1546
|
-
version: 1,
|
|
1547
|
-
agents: [],
|
|
1548
|
-
skills: {},
|
|
1549
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
1550
|
-
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
1551
|
-
});
|
|
1552
|
-
// Plugin dir structure
|
|
1553
|
-
mockExistsSync.mockImplementation((p) => {
|
|
1554
|
-
if (p.includes("plugins/test"))
|
|
1555
|
-
return true;
|
|
1556
|
-
if (p.includes(".claude-plugin/marketplace.json"))
|
|
1557
|
-
return true;
|
|
1558
|
-
return false;
|
|
1559
|
-
});
|
|
1560
|
-
mockReadFileSync.mockReturnValue(marketplaceJson);
|
|
1561
|
-
mockReaddirSync.mockReturnValue([]);
|
|
1562
|
-
mockStatSync.mockReturnValue({ isDirectory: () => true, isFile: () => false });
|
|
1563
|
-
// Claude Code agent with claude CLI available
|
|
1564
|
-
const claudeAgent = makeAgent({ id: "claude-code", displayName: "Claude Code" });
|
|
1565
|
-
mockDetectInstalledAgents.mockResolvedValue([claudeAgent]);
|
|
1566
|
-
});
|
|
1567
|
-
it("skips native install when claude CLI is not available", async () => {
|
|
1568
|
-
mockIsClaudeCliAvailable.mockReturnValue(false);
|
|
1569
|
-
await addCommand(pluginDir, { pluginDir, plugin: "sw-test", yes: true });
|
|
1570
|
-
expect(mockIsClaudeCliAvailable).toHaveBeenCalled();
|
|
1571
|
-
expect(mockRegisterMarketplace).not.toHaveBeenCalled();
|
|
1572
|
-
expect(mockInstallNativePlugin).not.toHaveBeenCalled();
|
|
1573
|
-
});
|
|
1574
|
-
it("skips native install when --copy flag is set", async () => {
|
|
1575
|
-
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
1576
|
-
mockGetMarketplaceName.mockReturnValue("test-mkt");
|
|
1577
|
-
await addCommand(pluginDir, { pluginDir, plugin: "sw-test", yes: true, copy: true });
|
|
1578
|
-
expect(mockRegisterMarketplace).not.toHaveBeenCalled();
|
|
1579
|
-
expect(mockInstallNativePlugin).not.toHaveBeenCalled();
|
|
1580
|
-
});
|
|
1581
|
-
it("calls registerMarketplace and installNativePlugin when claude CLI available", async () => {
|
|
1582
|
-
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
1583
|
-
mockGetMarketplaceName.mockReturnValue("test-mkt");
|
|
1584
|
-
mockRegisterMarketplace.mockReturnValue({ success: true });
|
|
1585
|
-
mockInstallNativePlugin.mockReturnValue(true);
|
|
1586
|
-
await addCommand(pluginDir, { pluginDir, plugin: "sw-test", yes: true });
|
|
1587
|
-
expect(mockRegisterMarketplace).toHaveBeenCalled();
|
|
1588
|
-
expect(mockInstallNativePlugin).toHaveBeenCalledWith("sw-test", "test-mkt", "project");
|
|
1589
|
-
});
|
|
1590
|
-
it("falls back to extraction when marketplace registration fails", async () => {
|
|
1591
|
-
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
1592
|
-
mockGetMarketplaceName.mockReturnValue("test-mkt");
|
|
1593
|
-
mockRegisterMarketplace.mockReturnValue({ success: false, stderr: "registration failed" });
|
|
1594
|
-
await addCommand(pluginDir, { pluginDir, plugin: "sw-test", yes: true });
|
|
1595
|
-
expect(mockRegisterMarketplace).toHaveBeenCalled();
|
|
1596
|
-
expect(mockInstallNativePlugin).not.toHaveBeenCalled();
|
|
1597
|
-
});
|
|
1598
|
-
it("falls back to extraction when native install command fails", async () => {
|
|
1599
|
-
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
1600
|
-
mockGetMarketplaceName.mockReturnValue("test-mkt");
|
|
1601
|
-
mockRegisterMarketplace.mockReturnValue({ success: true });
|
|
1602
|
-
mockInstallNativePlugin.mockReturnValue(false);
|
|
1603
|
-
await addCommand(pluginDir, { pluginDir, plugin: "sw-test", yes: true });
|
|
1604
|
-
expect(mockRegisterMarketplace).toHaveBeenCalled();
|
|
1605
|
-
expect(mockInstallNativePlugin).toHaveBeenCalled();
|
|
1606
|
-
// Falls back to extraction — copyFileSync or mkdirSync should be called
|
|
1607
|
-
});
|
|
1608
|
-
it("skips native install when getMarketplaceName returns null", async () => {
|
|
1609
|
-
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
1610
|
-
mockGetMarketplaceName.mockReturnValue(null);
|
|
1611
|
-
await addCommand(pluginDir, { pluginDir, plugin: "sw-test", yes: true });
|
|
1612
|
-
expect(mockRegisterMarketplace).not.toHaveBeenCalled();
|
|
1613
|
-
});
|
|
1614
|
-
});
|
|
1615
|
-
// ---------------------------------------------------------------------------
|
|
1616
1517
|
// --all flag tests (bulk repo install)
|
|
1617
1518
|
// ---------------------------------------------------------------------------
|
|
1618
1519
|
describe("addCommand with --repo --all flag (bulk install)", () => {
|
|
@@ -2198,18 +2099,11 @@ describe("addCommand marketplace integration", () => {
|
|
|
2198
2099
|
json: async () => [{ name: "test-skill", type: "dir" }],
|
|
2199
2100
|
text: async () => "# Skill Content",
|
|
2200
2101
|
});
|
|
2201
|
-
// Claude CLI available + native install succeeds
|
|
2202
|
-
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
2203
|
-
mockRegisterMarketplace.mockReturnValue({ success: true });
|
|
2204
|
-
mockInstallNativePlugin.mockReturnValue(true);
|
|
2205
2102
|
mockDetectInstalledAgents.mockResolvedValue([makeAgent()]);
|
|
2206
2103
|
// --yes to auto-select all
|
|
2207
2104
|
await addCommand("owner/repo", { yes: true });
|
|
2208
2105
|
// Should NOT call discoverSkills
|
|
2209
2106
|
expect(mockDiscoverSkills).not.toHaveBeenCalled();
|
|
2210
|
-
// Should call registerMarketplace with git URL (not temp dir path)
|
|
2211
|
-
expect(mockRegisterMarketplace).toHaveBeenCalledWith("https://github.com/owner/repo");
|
|
2212
|
-
expect(mockInstallNativePlugin).toHaveBeenCalledTimes(3);
|
|
2213
2107
|
// Should write lockfile with marketplace source
|
|
2214
2108
|
expect(mockWriteLockfile).toHaveBeenCalled();
|
|
2215
2109
|
const lockArg = mockWriteLockfile.mock.calls[0][0];
|
|
@@ -2270,23 +2164,15 @@ describe("addCommand marketplace integration", () => {
|
|
|
2270
2164
|
json: async () => [{ name: "test-skill", type: "dir" }],
|
|
2271
2165
|
text: async () => "# Skill Content",
|
|
2272
2166
|
});
|
|
2273
|
-
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
2274
|
-
mockRegisterMarketplace.mockReturnValue({ success: true });
|
|
2275
|
-
// plugin-a succeeds, plugin-b fails, plugin-c succeeds (native)
|
|
2276
|
-
mockInstallNativePlugin
|
|
2277
|
-
.mockReturnValueOnce(true)
|
|
2278
|
-
.mockReturnValueOnce(false)
|
|
2279
|
-
.mockReturnValueOnce(true);
|
|
2280
2167
|
mockDetectInstalledAgents.mockResolvedValue([makeAgent()]);
|
|
2281
2168
|
await addCommand("owner/repo", { yes: true });
|
|
2282
|
-
// All plugins now install skill files to agents regardless of native status
|
|
2283
2169
|
expect(mockWriteLockfile).toHaveBeenCalled();
|
|
2284
2170
|
const lockArg = mockWriteLockfile.mock.calls[0][0];
|
|
2285
2171
|
expect(lockArg.skills["plugin-a"]).toBeDefined();
|
|
2286
2172
|
expect(lockArg.skills["plugin-b"]).toBeDefined();
|
|
2287
2173
|
expect(lockArg.skills["plugin-c"]).toBeDefined();
|
|
2288
2174
|
});
|
|
2289
|
-
it("TC-014: no temp directory created
|
|
2175
|
+
it("TC-014: no temp directory created during marketplace install", async () => {
|
|
2290
2176
|
globalThis.fetch = vi.fn()
|
|
2291
2177
|
.mockResolvedValueOnce({
|
|
2292
2178
|
ok: true,
|
|
@@ -2296,11 +2182,8 @@ describe("addCommand marketplace integration", () => {
|
|
|
2296
2182
|
ok: true,
|
|
2297
2183
|
text: async () => SAMPLE_MARKETPLACE_JSON,
|
|
2298
2184
|
});
|
|
2299
|
-
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
2300
|
-
mockRegisterMarketplace.mockReturnValue({ success: true });
|
|
2301
|
-
mockInstallNativePlugin.mockReturnValue(true);
|
|
2302
2185
|
await addCommand("owner/repo", { yes: true });
|
|
2303
|
-
// No temp dir should be created or cleaned up —
|
|
2186
|
+
// No temp dir should be created or cleaned up — files extracted directly
|
|
2304
2187
|
expect(mockMkdtempSync).not.toHaveBeenCalled();
|
|
2305
2188
|
expect(mockRmSync).not.toHaveBeenCalled();
|
|
2306
2189
|
});
|
|
@@ -2319,9 +2202,6 @@ describe("addCommand marketplace integration", () => {
|
|
|
2319
2202
|
json: async () => [{ name: "test-skill", type: "dir" }],
|
|
2320
2203
|
text: async () => "# Skill Content",
|
|
2321
2204
|
});
|
|
2322
|
-
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
2323
|
-
mockRegisterMarketplace.mockReturnValue({ success: true });
|
|
2324
|
-
mockInstallNativePlugin.mockReturnValue(true);
|
|
2325
2205
|
mockDetectInstalledAgents.mockResolvedValue([makeAgent()]);
|
|
2326
2206
|
await addCommand("owner/repo", { yes: true });
|
|
2327
2207
|
const lockArg = mockWriteLockfile.mock.calls[0][0];
|
|
@@ -2365,12 +2245,6 @@ describe("addCommand marketplace integration", () => {
|
|
|
2365
2245
|
mockPromptCheckboxList
|
|
2366
2246
|
.mockResolvedValueOnce([2])
|
|
2367
2247
|
.mockResolvedValueOnce([0]);
|
|
2368
|
-
// "Install as Claude Code plugins?" → yes
|
|
2369
|
-
mockPromptConfirm.mockResolvedValue(true);
|
|
2370
|
-
// Claude CLI available
|
|
2371
|
-
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
2372
|
-
mockRegisterMarketplace.mockReturnValue({ success: true });
|
|
2373
|
-
mockInstallNativePlugin.mockReturnValue(true);
|
|
2374
2248
|
// Agents for uninstall directory removal + skill install
|
|
2375
2249
|
mockDetectInstalledAgents.mockResolvedValue([makeAgent()]);
|
|
2376
2250
|
// Skill directories exist
|
|
@@ -2380,13 +2254,8 @@ describe("addCommand marketplace integration", () => {
|
|
|
2380
2254
|
expect(mockRemoveSkillFromLock).toHaveBeenCalledTimes(2);
|
|
2381
2255
|
expect(mockRemoveSkillFromLock).toHaveBeenCalledWith("plugin-a", expect.any(String));
|
|
2382
2256
|
expect(mockRemoveSkillFromLock).toHaveBeenCalledWith("plugin-b", expect.any(String));
|
|
2383
|
-
// Should
|
|
2384
|
-
expect(mockUninstallNativePlugin).toHaveBeenCalledWith("plugin-a", "test-marketplace");
|
|
2385
|
-
expect(mockUninstallNativePlugin).toHaveBeenCalledWith("plugin-b", "test-marketplace");
|
|
2386
|
-
// Should remove skill directories
|
|
2257
|
+
// Should remove skill directories from filesystem
|
|
2387
2258
|
expect(mockRmSync).toHaveBeenCalled();
|
|
2388
|
-
// Should install plugin-c via native Claude plugin
|
|
2389
|
-
expect(mockInstallNativePlugin).toHaveBeenCalledWith("plugin-c", "test-marketplace", "project");
|
|
2390
2259
|
});
|
|
2391
2260
|
it("TC-018: --yes mode does NOT uninstall previously installed plugins", async () => {
|
|
2392
2261
|
// Set up lockfile with 2 previously installed plugins
|
|
@@ -2410,34 +2279,9 @@ describe("addCommand marketplace integration", () => {
|
|
|
2410
2279
|
ok: true,
|
|
2411
2280
|
text: async () => SAMPLE_MARKETPLACE_JSON,
|
|
2412
2281
|
});
|
|
2413
|
-
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
2414
|
-
mockRegisterMarketplace.mockReturnValue({ success: true });
|
|
2415
|
-
mockInstallNativePlugin.mockReturnValue(true);
|
|
2416
2282
|
// --yes auto-selects all — should NOT uninstall anything
|
|
2417
2283
|
await addCommand("owner/repo", { yes: true });
|
|
2418
2284
|
expect(mockRemoveSkillFromLock).not.toHaveBeenCalled();
|
|
2419
|
-
expect(mockUninstallNativePlugin).not.toHaveBeenCalled();
|
|
2420
|
-
});
|
|
2421
|
-
it("TC-012: falls back to extraction when claude CLI is unavailable", async () => {
|
|
2422
|
-
globalThis.fetch = vi.fn()
|
|
2423
|
-
.mockResolvedValueOnce({
|
|
2424
|
-
ok: true,
|
|
2425
|
-
json: async () => ({ download_url: "https://example.com/marketplace.json" }),
|
|
2426
|
-
})
|
|
2427
|
-
.mockResolvedValueOnce({
|
|
2428
|
-
ok: true,
|
|
2429
|
-
text: async () => JSON.stringify({
|
|
2430
|
-
name: "test-mp",
|
|
2431
|
-
owner: { name: "Test" },
|
|
2432
|
-
plugins: [{ name: "solo-plugin", source: "./plugins/solo", version: "1.0.0", description: "Only plugin" }],
|
|
2433
|
-
}),
|
|
2434
|
-
});
|
|
2435
|
-
mockIsClaudeCliAvailable.mockReturnValue(false);
|
|
2436
|
-
// installRepoPlugin will be called as fallback — it will fail but that's ok for this test
|
|
2437
|
-
await addCommand("owner/repo", { yes: true }).catch(() => { });
|
|
2438
|
-
// Should NOT call registerMarketplace or installNativePlugin
|
|
2439
|
-
expect(mockRegisterMarketplace).not.toHaveBeenCalled();
|
|
2440
|
-
expect(mockInstallNativePlugin).not.toHaveBeenCalled();
|
|
2441
2285
|
});
|
|
2442
2286
|
});
|
|
2443
2287
|
// ---------------------------------------------------------------------------
|
|
@@ -2484,14 +2328,7 @@ describe("addCommand unregistered plugin discovery", () => {
|
|
|
2484
2328
|
],
|
|
2485
2329
|
failed: false,
|
|
2486
2330
|
});
|
|
2487
|
-
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
2488
|
-
mockRegisterMarketplace.mockReturnValue({ success: true });
|
|
2489
|
-
mockInstallNativePlugin.mockReturnValue(true);
|
|
2490
2331
|
await addCommand("owner/repo", { yes: true });
|
|
2491
|
-
// Should install only registered plugins (3 from SAMPLE_MARKETPLACE_JSON), not unregistered
|
|
2492
|
-
expect(mockInstallNativePlugin).toHaveBeenCalledTimes(3);
|
|
2493
|
-
const installedNames = mockInstallNativePlugin.mock.calls.map((c) => c[0]);
|
|
2494
|
-
expect(installedNames).not.toContain("new-plugin");
|
|
2495
2332
|
// Console output should mention skipping unregistered
|
|
2496
2333
|
const logOutput = console.log.mock.calls
|
|
2497
2334
|
.map((c) => String(c[0]))
|
|
@@ -2522,9 +2359,6 @@ describe("addCommand unregistered plugin discovery", () => {
|
|
|
2522
2359
|
],
|
|
2523
2360
|
failed: false,
|
|
2524
2361
|
});
|
|
2525
|
-
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
2526
|
-
mockRegisterMarketplace.mockReturnValue({ success: true });
|
|
2527
|
-
mockInstallNativePlugin.mockReturnValue(true);
|
|
2528
2362
|
await addCommand("owner/repo", { yes: true, force: true });
|
|
2529
2363
|
// Console output should mention including unregistered
|
|
2530
2364
|
const logOutput = console.log.mock.calls
|
|
@@ -2554,17 +2388,12 @@ describe("addCommand unregistered plugin discovery", () => {
|
|
|
2554
2388
|
plugins: [],
|
|
2555
2389
|
failed: true,
|
|
2556
2390
|
});
|
|
2557
|
-
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
2558
|
-
mockRegisterMarketplace.mockReturnValue({ success: true });
|
|
2559
|
-
mockInstallNativePlugin.mockReturnValue(true);
|
|
2560
2391
|
await addCommand("owner/repo", { yes: true });
|
|
2561
2392
|
// Should show warning about incomplete plugin list
|
|
2562
2393
|
const logOutput = console.log.mock.calls
|
|
2563
2394
|
.map((c) => String(c[0]))
|
|
2564
2395
|
.join("\n");
|
|
2565
2396
|
expect(logOutput).toContain("incomplete");
|
|
2566
|
-
// Should still install registered plugins normally
|
|
2567
|
-
expect(mockInstallNativePlugin).toHaveBeenCalledTimes(3);
|
|
2568
2397
|
});
|
|
2569
2398
|
});
|
|
2570
2399
|
//# sourceMappingURL=add.test.js.map
|