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.
@@ -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 uses git URL for registration", async () => {
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 — git URL is used directly
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 uninstall from native Claude Code
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