vskill 0.2.7 → 0.2.9
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.d.ts +12 -0
- package/dist/commands/add.js +275 -8
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +253 -8
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/find.js +121 -41
- package/dist/commands/find.js.map +1 -1
- package/dist/discovery/github-tree.d.ts +7 -0
- package/dist/discovery/github-tree.js +26 -1
- package/dist/discovery/github-tree.js.map +1 -1
- package/dist/discovery/github-tree.test.js +101 -1
- package/dist/discovery/github-tree.test.js.map +1 -1
- package/dist/marketplace/index.d.ts +1 -1
- package/dist/marketplace/index.js +1 -1
- package/dist/marketplace/index.js.map +1 -1
- package/dist/marketplace/marketplace.d.ts +8 -0
- package/dist/marketplace/marketplace.js +10 -0
- package/dist/marketplace/marketplace.js.map +1 -1
- package/dist/marketplace/marketplace.test.js +17 -1
- package/dist/marketplace/marketplace.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
|
3
3
|
// Mock node:fs
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
5
|
const mockMkdirSync = vi.fn();
|
|
6
|
+
const mockMkdtempSync = vi.fn().mockReturnValue("/tmp/vskill-marketplace-abc123");
|
|
6
7
|
const mockWriteFileSync = vi.fn();
|
|
7
8
|
const mockReadFileSync = vi.fn();
|
|
8
9
|
const mockExistsSync = vi.fn();
|
|
@@ -14,6 +15,7 @@ const mockCopyFileSync = vi.fn();
|
|
|
14
15
|
const mockRmSync = vi.fn();
|
|
15
16
|
vi.mock("node:fs", () => ({
|
|
16
17
|
mkdirSync: (...args) => mockMkdirSync(...args),
|
|
18
|
+
mkdtempSync: (...args) => mockMkdtempSync(...args),
|
|
17
19
|
writeFileSync: (...args) => mockWriteFileSync(...args),
|
|
18
20
|
readFileSync: (...args) => mockReadFileSync(...args),
|
|
19
21
|
existsSync: (...args) => mockExistsSync(...args),
|
|
@@ -28,9 +30,11 @@ vi.mock("node:fs", () => ({
|
|
|
28
30
|
// Mock node:os (control homedir for global lockfile tests)
|
|
29
31
|
// ---------------------------------------------------------------------------
|
|
30
32
|
const mockHomedir = vi.fn().mockReturnValue("/home/testuser");
|
|
33
|
+
const mockTmpdir = vi.fn().mockReturnValue("/tmp");
|
|
31
34
|
vi.mock("node:os", () => ({
|
|
32
|
-
default: { homedir: () => mockHomedir() },
|
|
35
|
+
default: { homedir: () => mockHomedir(), tmpdir: () => mockTmpdir() },
|
|
33
36
|
homedir: () => mockHomedir(),
|
|
37
|
+
tmpdir: () => mockTmpdir(),
|
|
34
38
|
}));
|
|
35
39
|
// ---------------------------------------------------------------------------
|
|
36
40
|
// Mock node:path (pass-through with join tracking)
|
|
@@ -43,6 +47,13 @@ vi.mock("node:path", async () => {
|
|
|
43
47
|
};
|
|
44
48
|
});
|
|
45
49
|
// ---------------------------------------------------------------------------
|
|
50
|
+
// Mock node:child_process
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
const mockExecSync = vi.fn();
|
|
53
|
+
vi.mock("node:child_process", () => ({
|
|
54
|
+
execSync: (...args) => mockExecSync(...args),
|
|
55
|
+
}));
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
46
57
|
// Mock node:crypto
|
|
47
58
|
// ---------------------------------------------------------------------------
|
|
48
59
|
const mockDigest = vi.fn().mockReturnValue("abcdef123456xxxx");
|
|
@@ -66,9 +77,11 @@ vi.mock("../agents/agents-registry.js", () => ({
|
|
|
66
77
|
// ---------------------------------------------------------------------------
|
|
67
78
|
const mockEnsureLockfile = vi.fn();
|
|
68
79
|
const mockWriteLockfile = vi.fn();
|
|
80
|
+
const mockReadLockfile = vi.fn().mockReturnValue(null);
|
|
69
81
|
vi.mock("../lockfile/index.js", () => ({
|
|
70
82
|
ensureLockfile: (...args) => mockEnsureLockfile(...args),
|
|
71
83
|
writeLockfile: (...args) => mockWriteLockfile(...args),
|
|
84
|
+
readLockfile: (...args) => mockReadLockfile(...args),
|
|
72
85
|
}));
|
|
73
86
|
// ---------------------------------------------------------------------------
|
|
74
87
|
// Mock scanner
|
|
@@ -171,7 +184,7 @@ vi.mock("../marketplace/index.js", async () => {
|
|
|
171
184
|
// ---------------------------------------------------------------------------
|
|
172
185
|
// Import module under test AFTER mocks
|
|
173
186
|
// ---------------------------------------------------------------------------
|
|
174
|
-
const { addCommand } = await import("./add.js");
|
|
187
|
+
const { addCommand, detectMarketplaceRepo } = await import("./add.js");
|
|
175
188
|
// ---------------------------------------------------------------------------
|
|
176
189
|
// Helpers
|
|
177
190
|
// ---------------------------------------------------------------------------
|
|
@@ -862,8 +875,8 @@ describe("addCommand multi-skill discovery (GitHub path)", () => {
|
|
|
862
875
|
text: async () => "# Skill content",
|
|
863
876
|
});
|
|
864
877
|
await addCommand("owner/repo", {});
|
|
865
|
-
//
|
|
866
|
-
expect(globalThis.fetch).toHaveBeenCalledTimes(
|
|
878
|
+
// Marketplace detection now retries + raw fallback (3 attempts) + 3 SKILL.md files = 6
|
|
879
|
+
expect(globalThis.fetch).toHaveBeenCalledTimes(6);
|
|
867
880
|
// Should have scanned 3 skills
|
|
868
881
|
expect(mockRunTier1Scan).toHaveBeenCalledTimes(3);
|
|
869
882
|
// Lockfile should have 3 entries
|
|
@@ -1883,8 +1896,8 @@ describe("addCommand flat name identifier guidance", () => {
|
|
|
1883
1896
|
const logOutput = console.log.mock.calls
|
|
1884
1897
|
.map((c) => String(c[0]))
|
|
1885
1898
|
.join("\n");
|
|
1886
|
-
// Should suggest the
|
|
1887
|
-
expect(logOutput).toContain("remotion-dev/skills
|
|
1899
|
+
// Should suggest the owner/repo format (no longer 3-part)
|
|
1900
|
+
expect(logOutput).toContain("vskill install remotion-dev/skills");
|
|
1888
1901
|
});
|
|
1889
1902
|
});
|
|
1890
1903
|
// ---------------------------------------------------------------------------
|
|
@@ -1995,7 +2008,7 @@ describe("addCommand registry fallback auto-selects matching skill", () => {
|
|
|
1995
2008
|
expect(lockArg.skills).toHaveProperty("Code-Review");
|
|
1996
2009
|
expect(lockArg.skills).not.toHaveProperty("unit-test");
|
|
1997
2010
|
});
|
|
1998
|
-
it("tip message suggests
|
|
2011
|
+
it("tip message suggests owner/repo format for direct install", async () => {
|
|
1999
2012
|
mockGetSkill.mockResolvedValue({
|
|
2000
2013
|
name: "excalidraw-diagram-generator",
|
|
2001
2014
|
author: "github",
|
|
@@ -2015,7 +2028,7 @@ describe("addCommand registry fallback auto-selects matching skill", () => {
|
|
|
2015
2028
|
const logOutput = console.log.mock.calls
|
|
2016
2029
|
.map((c) => String(c[0]))
|
|
2017
2030
|
.join("\n");
|
|
2018
|
-
expect(logOutput).toContain("github/awesome-copilot
|
|
2031
|
+
expect(logOutput).toContain("vskill install github/awesome-copilot");
|
|
2019
2032
|
});
|
|
2020
2033
|
it("does not auto-filter when _targetSkill is not set (direct owner/repo)", async () => {
|
|
2021
2034
|
mockDiscoverSkills.mockResolvedValue([
|
|
@@ -2034,4 +2047,236 @@ describe("addCommand registry fallback auto-selects matching skill", () => {
|
|
|
2034
2047
|
expect(Object.keys(lockArg.skills)).toHaveLength(2);
|
|
2035
2048
|
});
|
|
2036
2049
|
});
|
|
2050
|
+
// ---------------------------------------------------------------------------
|
|
2051
|
+
// Marketplace Detection & Install Mode (increment 0382)
|
|
2052
|
+
// ---------------------------------------------------------------------------
|
|
2053
|
+
const SAMPLE_MARKETPLACE_JSON = JSON.stringify({
|
|
2054
|
+
name: "test-marketplace",
|
|
2055
|
+
owner: { name: "Test Author" },
|
|
2056
|
+
plugins: [
|
|
2057
|
+
{ name: "plugin-a", source: "./plugins/plugin-a", version: "1.0.0", description: "First plugin" },
|
|
2058
|
+
{ name: "plugin-b", source: "./plugins/plugin-b", version: "2.0.0", description: "Second plugin" },
|
|
2059
|
+
{ name: "plugin-c", source: "./plugins/plugin-c", version: "1.5.0", description: "Third plugin" },
|
|
2060
|
+
],
|
|
2061
|
+
});
|
|
2062
|
+
describe("detectMarketplaceRepo", () => {
|
|
2063
|
+
it("TC-001: detects repo with .claude-plugin/marketplace.json", async () => {
|
|
2064
|
+
globalThis.fetch = vi.fn()
|
|
2065
|
+
.mockResolvedValueOnce({
|
|
2066
|
+
ok: true,
|
|
2067
|
+
json: async () => ({ download_url: "https://raw.githubusercontent.com/owner/repo/main/.claude-plugin/marketplace.json" }),
|
|
2068
|
+
})
|
|
2069
|
+
.mockResolvedValueOnce({
|
|
2070
|
+
ok: true,
|
|
2071
|
+
text: async () => SAMPLE_MARKETPLACE_JSON,
|
|
2072
|
+
});
|
|
2073
|
+
const result = await detectMarketplaceRepo("owner", "repo");
|
|
2074
|
+
expect(result.isMarketplace).toBe(true);
|
|
2075
|
+
expect(result.manifestContent).toBe(SAMPLE_MARKETPLACE_JSON);
|
|
2076
|
+
});
|
|
2077
|
+
it("TC-002: returns false for repo without marketplace.json", async () => {
|
|
2078
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
2079
|
+
ok: false,
|
|
2080
|
+
status: 404,
|
|
2081
|
+
});
|
|
2082
|
+
const result = await detectMarketplaceRepo("owner", "repo");
|
|
2083
|
+
expect(result.isMarketplace).toBe(false);
|
|
2084
|
+
expect(result.manifestContent).toBeUndefined();
|
|
2085
|
+
});
|
|
2086
|
+
it("TC-003: returns false on network error (graceful fallback)", async () => {
|
|
2087
|
+
globalThis.fetch = vi.fn().mockRejectedValue(new Error("Network error"));
|
|
2088
|
+
const result = await detectMarketplaceRepo("owner", "repo");
|
|
2089
|
+
expect(result.isMarketplace).toBe(false);
|
|
2090
|
+
});
|
|
2091
|
+
it("decodes base64 content when download_url is missing", async () => {
|
|
2092
|
+
const base64Content = Buffer.from(SAMPLE_MARKETPLACE_JSON).toString("base64");
|
|
2093
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
2094
|
+
ok: true,
|
|
2095
|
+
json: async () => ({ content: base64Content, encoding: "base64" }),
|
|
2096
|
+
});
|
|
2097
|
+
const result = await detectMarketplaceRepo("owner", "repo");
|
|
2098
|
+
expect(result.isMarketplace).toBe(true);
|
|
2099
|
+
expect(result.manifestContent).toBe(SAMPLE_MARKETPLACE_JSON);
|
|
2100
|
+
});
|
|
2101
|
+
it("returns false when marketplace.json has no plugins", async () => {
|
|
2102
|
+
const emptyManifest = JSON.stringify({ name: "empty", owner: { name: "Test" }, plugins: [] });
|
|
2103
|
+
globalThis.fetch = vi.fn()
|
|
2104
|
+
.mockResolvedValueOnce({
|
|
2105
|
+
ok: true,
|
|
2106
|
+
json: async () => ({ download_url: "https://example.com/marketplace.json" }),
|
|
2107
|
+
})
|
|
2108
|
+
.mockResolvedValueOnce({
|
|
2109
|
+
ok: true,
|
|
2110
|
+
text: async () => emptyManifest,
|
|
2111
|
+
});
|
|
2112
|
+
const result = await detectMarketplaceRepo("owner", "repo");
|
|
2113
|
+
expect(result.isMarketplace).toBe(false);
|
|
2114
|
+
});
|
|
2115
|
+
});
|
|
2116
|
+
describe("addCommand marketplace integration", () => {
|
|
2117
|
+
beforeEach(() => {
|
|
2118
|
+
mockEnsureLockfile.mockReturnValue({
|
|
2119
|
+
version: 1,
|
|
2120
|
+
agents: [],
|
|
2121
|
+
skills: {},
|
|
2122
|
+
createdAt: new Date().toISOString(),
|
|
2123
|
+
updatedAt: new Date().toISOString(),
|
|
2124
|
+
});
|
|
2125
|
+
mockGetMarketplaceName.mockReturnValue("test-marketplace");
|
|
2126
|
+
// Ensure fs mocks have correct return values (clearAllMocks preserves them,
|
|
2127
|
+
// but restoreAllMocks from other describe blocks might have reset them)
|
|
2128
|
+
mockMkdtempSync.mockReturnValue("/tmp/vskill-marketplace-abc123");
|
|
2129
|
+
});
|
|
2130
|
+
it("TC-004: routes to marketplace flow when repo is a marketplace", async () => {
|
|
2131
|
+
// Marketplace detection succeeds
|
|
2132
|
+
globalThis.fetch = vi.fn()
|
|
2133
|
+
.mockResolvedValueOnce({
|
|
2134
|
+
ok: true,
|
|
2135
|
+
json: async () => ({ download_url: "https://example.com/marketplace.json" }),
|
|
2136
|
+
})
|
|
2137
|
+
.mockResolvedValueOnce({
|
|
2138
|
+
ok: true,
|
|
2139
|
+
text: async () => SAMPLE_MARKETPLACE_JSON,
|
|
2140
|
+
});
|
|
2141
|
+
// Claude CLI available + native install succeeds
|
|
2142
|
+
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
2143
|
+
mockRegisterMarketplace.mockReturnValue(true);
|
|
2144
|
+
mockInstallNativePlugin.mockReturnValue(true);
|
|
2145
|
+
// --yes to auto-select all
|
|
2146
|
+
await addCommand("owner/repo", { yes: true });
|
|
2147
|
+
// Should NOT call discoverSkills
|
|
2148
|
+
expect(mockDiscoverSkills).not.toHaveBeenCalled();
|
|
2149
|
+
// Should call registerMarketplace and installNativePlugin
|
|
2150
|
+
expect(mockRegisterMarketplace).toHaveBeenCalled();
|
|
2151
|
+
expect(mockInstallNativePlugin).toHaveBeenCalledTimes(3);
|
|
2152
|
+
// Should write lockfile with marketplace source
|
|
2153
|
+
expect(mockWriteLockfile).toHaveBeenCalled();
|
|
2154
|
+
const lockArg = mockWriteLockfile.mock.calls[0][0];
|
|
2155
|
+
expect(lockArg.skills["plugin-a"].source).toBe("marketplace:owner/repo#plugin-a");
|
|
2156
|
+
});
|
|
2157
|
+
it("TC-005: falls through to discovery when repo is NOT a marketplace", async () => {
|
|
2158
|
+
// Marketplace detection fails (404)
|
|
2159
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
2160
|
+
ok: false,
|
|
2161
|
+
status: 404,
|
|
2162
|
+
});
|
|
2163
|
+
// Discovery returns skills
|
|
2164
|
+
mockDiscoverSkills.mockResolvedValue([
|
|
2165
|
+
{ name: "my-skill", path: "SKILL.md", rawUrl: "https://raw.githubusercontent.com/owner/repo/main/SKILL.md" },
|
|
2166
|
+
]);
|
|
2167
|
+
mockDetectInstalledAgents.mockResolvedValue([makeAgent()]);
|
|
2168
|
+
mockCheckInstallSafety.mockResolvedValue({ blocked: false, rejected: false });
|
|
2169
|
+
mockRunTier1Scan.mockReturnValue(makeScanResult());
|
|
2170
|
+
// Re-mock fetch for skill content
|
|
2171
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
2172
|
+
ok: true,
|
|
2173
|
+
text: async () => "# My Skill Content",
|
|
2174
|
+
});
|
|
2175
|
+
await addCommand("owner/repo", {});
|
|
2176
|
+
// Should call discoverSkills
|
|
2177
|
+
expect(mockDiscoverSkills).toHaveBeenCalledWith("owner", "repo");
|
|
2178
|
+
});
|
|
2179
|
+
it("TC-006: --plugin flag bypasses marketplace detection", async () => {
|
|
2180
|
+
mockCheckInstallSafety.mockResolvedValue({ blocked: false, rejected: false });
|
|
2181
|
+
mockDetectInstalledAgents.mockResolvedValue([makeAgent()]);
|
|
2182
|
+
mockRunTier1Scan.mockReturnValue(makeScanResult());
|
|
2183
|
+
// Mock fetch for marketplace.json lookup (via installRepoPlugin)
|
|
2184
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
2185
|
+
ok: true,
|
|
2186
|
+
text: async () => SAMPLE_MARKETPLACE_JSON,
|
|
2187
|
+
});
|
|
2188
|
+
mockReadFileSync.mockReturnValue(SAMPLE_MARKETPLACE_JSON);
|
|
2189
|
+
mockExistsSync.mockReturnValue(true);
|
|
2190
|
+
mockReaddirSync.mockReturnValue([]);
|
|
2191
|
+
mockStatSync.mockReturnValue({ isDirectory: () => true, isFile: () => false });
|
|
2192
|
+
// With --plugin flag, it goes to installRepoPlugin directly
|
|
2193
|
+
await addCommand("owner/repo", { plugin: "plugin-a" }).catch(() => { });
|
|
2194
|
+
// discoverSkills should NOT be called (plugin flag routes differently)
|
|
2195
|
+
// detectMarketplaceRepo is also not called (plugin flag checked first)
|
|
2196
|
+
});
|
|
2197
|
+
it("TC-013: summary shows correct counts with mixed success/failure", async () => {
|
|
2198
|
+
globalThis.fetch = vi.fn()
|
|
2199
|
+
.mockResolvedValueOnce({
|
|
2200
|
+
ok: true,
|
|
2201
|
+
json: async () => ({ download_url: "https://example.com/marketplace.json" }),
|
|
2202
|
+
})
|
|
2203
|
+
.mockResolvedValueOnce({
|
|
2204
|
+
ok: true,
|
|
2205
|
+
text: async () => SAMPLE_MARKETPLACE_JSON,
|
|
2206
|
+
});
|
|
2207
|
+
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
2208
|
+
mockRegisterMarketplace.mockReturnValue(true);
|
|
2209
|
+
// plugin-a succeeds, plugin-b fails, plugin-c succeeds
|
|
2210
|
+
mockInstallNativePlugin
|
|
2211
|
+
.mockReturnValueOnce(true)
|
|
2212
|
+
.mockReturnValueOnce(false)
|
|
2213
|
+
.mockReturnValueOnce(true);
|
|
2214
|
+
await addCommand("owner/repo", { yes: true });
|
|
2215
|
+
// Should write lockfile with 2 installed plugins (not the failed one)
|
|
2216
|
+
expect(mockWriteLockfile).toHaveBeenCalled();
|
|
2217
|
+
const lockArg = mockWriteLockfile.mock.calls[0][0];
|
|
2218
|
+
expect(lockArg.skills["plugin-a"]).toBeDefined();
|
|
2219
|
+
expect(lockArg.skills["plugin-b"]).toBeUndefined();
|
|
2220
|
+
expect(lockArg.skills["plugin-c"]).toBeDefined();
|
|
2221
|
+
});
|
|
2222
|
+
it("TC-014: temp directory is cleaned up after install", async () => {
|
|
2223
|
+
globalThis.fetch = vi.fn()
|
|
2224
|
+
.mockResolvedValueOnce({
|
|
2225
|
+
ok: true,
|
|
2226
|
+
json: async () => ({ download_url: "https://example.com/marketplace.json" }),
|
|
2227
|
+
})
|
|
2228
|
+
.mockResolvedValueOnce({
|
|
2229
|
+
ok: true,
|
|
2230
|
+
text: async () => SAMPLE_MARKETPLACE_JSON,
|
|
2231
|
+
});
|
|
2232
|
+
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
2233
|
+
mockRegisterMarketplace.mockReturnValue(true);
|
|
2234
|
+
mockInstallNativePlugin.mockReturnValue(true);
|
|
2235
|
+
await addCommand("owner/repo", { yes: true });
|
|
2236
|
+
// rmSync should have been called to clean up the temp dir
|
|
2237
|
+
expect(mockRmSync).toHaveBeenCalledWith("/tmp/vskill-marketplace-abc123", { recursive: true, force: true });
|
|
2238
|
+
});
|
|
2239
|
+
it("TC-015: lockfile records marketplace source format", async () => {
|
|
2240
|
+
globalThis.fetch = vi.fn()
|
|
2241
|
+
.mockResolvedValueOnce({
|
|
2242
|
+
ok: true,
|
|
2243
|
+
json: async () => ({ download_url: "https://example.com/marketplace.json" }),
|
|
2244
|
+
})
|
|
2245
|
+
.mockResolvedValueOnce({
|
|
2246
|
+
ok: true,
|
|
2247
|
+
text: async () => SAMPLE_MARKETPLACE_JSON,
|
|
2248
|
+
});
|
|
2249
|
+
mockIsClaudeCliAvailable.mockReturnValue(true);
|
|
2250
|
+
mockRegisterMarketplace.mockReturnValue(true);
|
|
2251
|
+
mockInstallNativePlugin.mockReturnValue(true);
|
|
2252
|
+
await addCommand("owner/repo", { yes: true });
|
|
2253
|
+
const lockArg = mockWriteLockfile.mock.calls[0][0];
|
|
2254
|
+
expect(lockArg.skills["plugin-a"].source).toBe("marketplace:owner/repo#plugin-a");
|
|
2255
|
+
expect(lockArg.skills["plugin-a"].marketplace).toBe("test-marketplace");
|
|
2256
|
+
expect(lockArg.skills["plugin-a"].pluginDir).toBe(true);
|
|
2257
|
+
expect(lockArg.skills["plugin-a"].version).toBe("1.0.0");
|
|
2258
|
+
expect(lockArg.skills["plugin-b"].version).toBe("2.0.0");
|
|
2259
|
+
});
|
|
2260
|
+
it("TC-012: falls back to extraction when claude CLI is unavailable", async () => {
|
|
2261
|
+
globalThis.fetch = vi.fn()
|
|
2262
|
+
.mockResolvedValueOnce({
|
|
2263
|
+
ok: true,
|
|
2264
|
+
json: async () => ({ download_url: "https://example.com/marketplace.json" }),
|
|
2265
|
+
})
|
|
2266
|
+
.mockResolvedValueOnce({
|
|
2267
|
+
ok: true,
|
|
2268
|
+
text: async () => JSON.stringify({
|
|
2269
|
+
name: "test-mp",
|
|
2270
|
+
owner: { name: "Test" },
|
|
2271
|
+
plugins: [{ name: "solo-plugin", source: "./plugins/solo", version: "1.0.0", description: "Only plugin" }],
|
|
2272
|
+
}),
|
|
2273
|
+
});
|
|
2274
|
+
mockIsClaudeCliAvailable.mockReturnValue(false);
|
|
2275
|
+
// installRepoPlugin will be called as fallback — it will fail but that's ok for this test
|
|
2276
|
+
await addCommand("owner/repo", { yes: true }).catch(() => { });
|
|
2277
|
+
// Should NOT call registerMarketplace or installNativePlugin
|
|
2278
|
+
expect(mockRegisterMarketplace).not.toHaveBeenCalled();
|
|
2279
|
+
expect(mockInstallNativePlugin).not.toHaveBeenCalled();
|
|
2280
|
+
});
|
|
2281
|
+
});
|
|
2037
2282
|
//# sourceMappingURL=add.test.js.map
|