vskill 0.1.9 → 0.1.11
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/README.md +15 -1
- package/dist/commands/add-wizard.test.d.ts +1 -0
- package/dist/commands/add-wizard.test.js +285 -0
- package/dist/commands/add-wizard.test.js.map +1 -0
- package/dist/commands/add.d.ts +3 -0
- package/dist/commands/add.js +143 -27
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +123 -0
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/audit.d.ts +2 -2
- package/dist/commands/audit.js +36 -49
- package/dist/commands/audit.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/installer/canonical.d.ts +9 -0
- package/dist/installer/canonical.js +63 -0
- package/dist/installer/canonical.js.map +1 -0
- package/dist/installer/canonical.test.d.ts +1 -0
- package/dist/installer/canonical.test.js +117 -0
- package/dist/installer/canonical.test.js.map +1 -0
- package/dist/scanner/patterns.js +2 -2
- package/dist/scanner/patterns.js.map +1 -1
- package/dist/utils/agent-filter.d.ts +16 -0
- package/dist/utils/agent-filter.js +28 -0
- package/dist/utils/agent-filter.js.map +1 -0
- package/dist/utils/agent-filter.test.d.ts +1 -0
- package/dist/utils/agent-filter.test.js +64 -0
- package/dist/utils/agent-filter.test.js.map +1 -0
- package/dist/utils/project-root.d.ts +18 -0
- package/dist/utils/project-root.js +47 -0
- package/dist/utils/project-root.js.map +1 -0
- package/dist/utils/project-root.test.d.ts +1 -0
- package/dist/utils/project-root.test.js +74 -0
- package/dist/utils/project-root.test.js.map +1 -0
- package/dist/utils/prompts.d.ts +20 -0
- package/dist/utils/prompts.js +103 -0
- package/dist/utils/prompts.js.map +1 -0
- package/dist/utils/prompts.test.d.ts +1 -0
- package/dist/utils/prompts.test.js +155 -0
- package/dist/utils/prompts.test.js.map +1 -0
- package/dist/utils/validation.js +1 -1
- package/dist/utils/validation.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,9 +3,21 @@
|
|
|
3
3
|
**Secure multi-platform AI skill installer.** Scan before you install.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
+
# npm
|
|
6
7
|
npx vskill find remotion # search the registry
|
|
7
8
|
npx vskill install google/remotion # install after security scan
|
|
8
|
-
|
|
9
|
+
|
|
10
|
+
# bun
|
|
11
|
+
bunx vskill find remotion
|
|
12
|
+
bunx vskill install google/remotion
|
|
13
|
+
|
|
14
|
+
# pnpm
|
|
15
|
+
pnpx vskill find remotion
|
|
16
|
+
pnpx vskill install google/remotion
|
|
17
|
+
|
|
18
|
+
# yarn
|
|
19
|
+
yarn dlx vskill find remotion
|
|
20
|
+
yarn dlx vskill install google/remotion
|
|
9
21
|
```
|
|
10
22
|
|
|
11
23
|
## Why?
|
|
@@ -27,6 +39,8 @@ vskill submit <source> # Submit for verification (owner/repo or GitHub URL
|
|
|
27
39
|
vskill update # Update with diff scanning
|
|
28
40
|
```
|
|
29
41
|
|
|
42
|
+
> Replace `vskill` with `npx vskill`, `bunx vskill`, `pnpx vskill`, or `yarn dlx vskill` if not installed globally.
|
|
43
|
+
|
|
30
44
|
## 39 Agent Platforms
|
|
31
45
|
|
|
32
46
|
Works across Claude Code, Cursor, GitHub Copilot, Windsurf, Codex, Gemini CLI, Cline, Amp, Roo Code, and 30 more.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Mock node:fs
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
const mockMkdirSync = vi.fn();
|
|
6
|
+
const mockWriteFileSync = vi.fn();
|
|
7
|
+
const mockReadFileSync = vi.fn();
|
|
8
|
+
const mockExistsSync = vi.fn();
|
|
9
|
+
const mockChmodSync = vi.fn();
|
|
10
|
+
const mockReaddirSync = vi.fn();
|
|
11
|
+
const mockStatSync = vi.fn();
|
|
12
|
+
const mockCopyFileSync = vi.fn();
|
|
13
|
+
const mockRmSync = vi.fn();
|
|
14
|
+
vi.mock("node:fs", () => ({
|
|
15
|
+
mkdirSync: (...args) => mockMkdirSync(...args),
|
|
16
|
+
writeFileSync: (...args) => mockWriteFileSync(...args),
|
|
17
|
+
readFileSync: (...args) => mockReadFileSync(...args),
|
|
18
|
+
existsSync: (...args) => mockExistsSync(...args),
|
|
19
|
+
chmodSync: (...args) => mockChmodSync(...args),
|
|
20
|
+
readdirSync: (...args) => mockReaddirSync(...args),
|
|
21
|
+
statSync: (...args) => mockStatSync(...args),
|
|
22
|
+
copyFileSync: (...args) => mockCopyFileSync(...args),
|
|
23
|
+
rmSync: (...args) => mockRmSync(...args),
|
|
24
|
+
}));
|
|
25
|
+
vi.mock("node:path", async () => {
|
|
26
|
+
const actual = await vi.importActual("node:path");
|
|
27
|
+
return { ...actual };
|
|
28
|
+
});
|
|
29
|
+
const mockDigest = vi.fn().mockReturnValue("abcdef123456xxxx");
|
|
30
|
+
const mockUpdate = vi.fn().mockReturnValue({ digest: mockDigest });
|
|
31
|
+
vi.mock("node:crypto", () => ({
|
|
32
|
+
createHash: () => ({ update: mockUpdate }),
|
|
33
|
+
}));
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Mock agents registry
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
const mockDetectInstalledAgents = vi.fn();
|
|
38
|
+
vi.mock("../agents/agents-registry.js", () => ({
|
|
39
|
+
detectInstalledAgents: (...args) => mockDetectInstalledAgents(...args),
|
|
40
|
+
}));
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Mock lockfile
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
const mockEnsureLockfile = vi.fn();
|
|
45
|
+
const mockWriteLockfile = vi.fn();
|
|
46
|
+
vi.mock("../lockfile/index.js", () => ({
|
|
47
|
+
ensureLockfile: (...args) => mockEnsureLockfile(...args),
|
|
48
|
+
writeLockfile: (...args) => mockWriteLockfile(...args),
|
|
49
|
+
}));
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Mock scanner, blocklist, security, API client, discovery
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
const mockRunTier1Scan = vi.fn();
|
|
54
|
+
vi.mock("../scanner/index.js", () => ({
|
|
55
|
+
runTier1Scan: (...args) => mockRunTier1Scan(...args),
|
|
56
|
+
}));
|
|
57
|
+
const mockCheckBlocklist = vi.fn();
|
|
58
|
+
vi.mock("../blocklist/blocklist.js", () => ({
|
|
59
|
+
checkBlocklist: (...args) => mockCheckBlocklist(...args),
|
|
60
|
+
}));
|
|
61
|
+
const mockCheckPlatformSecurity = vi.fn();
|
|
62
|
+
vi.mock("../security/index.js", () => ({
|
|
63
|
+
checkPlatformSecurity: (...args) => mockCheckPlatformSecurity(...args),
|
|
64
|
+
}));
|
|
65
|
+
const mockGetSkill = vi.fn();
|
|
66
|
+
vi.mock("../api/client.js", () => ({
|
|
67
|
+
getSkill: (...args) => mockGetSkill(...args),
|
|
68
|
+
}));
|
|
69
|
+
const mockDiscoverSkills = vi.fn();
|
|
70
|
+
vi.mock("../discovery/github-tree.js", () => ({
|
|
71
|
+
discoverSkills: (...args) => mockDiscoverSkills(...args),
|
|
72
|
+
}));
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Mock project root & agent filter
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
const mockFindProjectRoot = vi.fn();
|
|
77
|
+
vi.mock("../utils/project-root.js", () => ({
|
|
78
|
+
findProjectRoot: (...args) => mockFindProjectRoot(...args),
|
|
79
|
+
}));
|
|
80
|
+
const mockFilterAgents = vi.fn();
|
|
81
|
+
vi.mock("../utils/agent-filter.js", () => ({
|
|
82
|
+
filterAgents: (...args) => mockFilterAgents(...args),
|
|
83
|
+
}));
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Mock output (suppress ANSI output)
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
vi.mock("../utils/output.js", () => ({
|
|
88
|
+
bold: (s) => s,
|
|
89
|
+
green: (s) => s,
|
|
90
|
+
red: (s) => s,
|
|
91
|
+
yellow: (s) => s,
|
|
92
|
+
dim: (s) => s,
|
|
93
|
+
cyan: (s) => s,
|
|
94
|
+
spinner: () => ({ stop: vi.fn() }),
|
|
95
|
+
}));
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Mock prompts (to test wizard integration without real readline)
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
const mockPromptCheckboxList = vi.fn();
|
|
100
|
+
const mockPromptChoice = vi.fn();
|
|
101
|
+
const mockPromptConfirm = vi.fn();
|
|
102
|
+
const mockIsTTY = vi.fn();
|
|
103
|
+
vi.mock("../utils/prompts.js", () => ({
|
|
104
|
+
isTTY: (...args) => mockIsTTY(...args),
|
|
105
|
+
createPrompter: () => ({
|
|
106
|
+
promptCheckboxList: (...args) => mockPromptCheckboxList(...args),
|
|
107
|
+
promptChoice: (...args) => mockPromptChoice(...args),
|
|
108
|
+
promptConfirm: (...args) => mockPromptConfirm(...args),
|
|
109
|
+
}),
|
|
110
|
+
}));
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Mock canonical installer
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
const mockInstallSymlink = vi.fn();
|
|
115
|
+
const mockInstallCopy = vi.fn();
|
|
116
|
+
vi.mock("../installer/canonical.js", () => ({
|
|
117
|
+
installSymlink: (...args) => mockInstallSymlink(...args),
|
|
118
|
+
installCopy: (...args) => mockInstallCopy(...args),
|
|
119
|
+
}));
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// Import module under test AFTER mocks
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
const { addCommand } = await import("./add.js");
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Helpers
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
function makeScanResult(overrides = {}) {
|
|
128
|
+
return {
|
|
129
|
+
verdict: "PASS",
|
|
130
|
+
findings: [],
|
|
131
|
+
score: 100,
|
|
132
|
+
patternsChecked: 37,
|
|
133
|
+
criticalCount: 0,
|
|
134
|
+
highCount: 0,
|
|
135
|
+
mediumCount: 0,
|
|
136
|
+
lowCount: 0,
|
|
137
|
+
infoCount: 0,
|
|
138
|
+
durationMs: 1,
|
|
139
|
+
...overrides,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function makeAgent(overrides = {}) {
|
|
143
|
+
return {
|
|
144
|
+
id: "claude-code",
|
|
145
|
+
displayName: "Claude Code",
|
|
146
|
+
localSkillsDir: ".claude/commands",
|
|
147
|
+
globalSkillsDir: "~/.claude/commands",
|
|
148
|
+
...overrides,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const originalFetch = globalThis.fetch;
|
|
152
|
+
beforeEach(() => {
|
|
153
|
+
vi.clearAllMocks();
|
|
154
|
+
vi.spyOn(console, "log").mockImplementation(() => { });
|
|
155
|
+
vi.spyOn(console, "error").mockImplementation(() => { });
|
|
156
|
+
// Default: safe skill content
|
|
157
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
158
|
+
ok: true,
|
|
159
|
+
text: async () => "# Safe Skill\nNormal content",
|
|
160
|
+
});
|
|
161
|
+
mockRunTier1Scan.mockReturnValue(makeScanResult());
|
|
162
|
+
mockCheckBlocklist.mockResolvedValue(null);
|
|
163
|
+
mockCheckPlatformSecurity.mockResolvedValue(null);
|
|
164
|
+
mockEnsureLockfile.mockReturnValue({ skills: {}, agents: [] });
|
|
165
|
+
mockFindProjectRoot.mockReturnValue("/projects/myapp");
|
|
166
|
+
mockFilterAgents.mockImplementation((agents) => agents);
|
|
167
|
+
// Default: TTY
|
|
168
|
+
mockIsTTY.mockReturnValue(true);
|
|
169
|
+
});
|
|
170
|
+
afterEach(() => {
|
|
171
|
+
globalThis.fetch = originalFetch;
|
|
172
|
+
});
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Tests: --yes flag and non-interactive mode
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
describe("wizard integration: --yes flag", () => {
|
|
177
|
+
it("skips all prompts when --yes is set", async () => {
|
|
178
|
+
const agents = [makeAgent(), makeAgent({ id: "cursor", localSkillsDir: ".cursor/skills" })];
|
|
179
|
+
mockDetectInstalledAgents.mockResolvedValue(agents);
|
|
180
|
+
mockDiscoverSkills.mockResolvedValue([
|
|
181
|
+
{ name: "skill-a", rawUrl: "https://raw.githubusercontent.com/o/r/main/skills/skill-a/SKILL.md" },
|
|
182
|
+
{ name: "skill-b", rawUrl: "https://raw.githubusercontent.com/o/r/main/skills/skill-b/SKILL.md" },
|
|
183
|
+
]);
|
|
184
|
+
await addCommand("owner/repo", { yes: true });
|
|
185
|
+
// No prompt functions should have been called
|
|
186
|
+
expect(mockPromptCheckboxList).not.toHaveBeenCalled();
|
|
187
|
+
expect(mockPromptChoice).not.toHaveBeenCalled();
|
|
188
|
+
expect(mockPromptConfirm).not.toHaveBeenCalled();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
describe("wizard integration: non-TTY mode", () => {
|
|
192
|
+
it("skips prompts when stdin is not a TTY", async () => {
|
|
193
|
+
mockIsTTY.mockReturnValue(false);
|
|
194
|
+
const agents = [makeAgent()];
|
|
195
|
+
mockDetectInstalledAgents.mockResolvedValue(agents);
|
|
196
|
+
mockDiscoverSkills.mockResolvedValue([
|
|
197
|
+
{ name: "skill-a", rawUrl: "https://raw.githubusercontent.com/o/r/main/skills/skill-a/SKILL.md" },
|
|
198
|
+
{ name: "skill-b", rawUrl: "https://raw.githubusercontent.com/o/r/main/skills/skill-b/SKILL.md" },
|
|
199
|
+
]);
|
|
200
|
+
await addCommand("owner/repo", {});
|
|
201
|
+
expect(mockPromptCheckboxList).not.toHaveBeenCalled();
|
|
202
|
+
expect(mockPromptChoice).not.toHaveBeenCalled();
|
|
203
|
+
expect(mockPromptConfirm).not.toHaveBeenCalled();
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
describe("wizard integration: single skill skips wizard", () => {
|
|
207
|
+
it("does not show wizard for single-skill repos", async () => {
|
|
208
|
+
const agents = [makeAgent()];
|
|
209
|
+
mockDetectInstalledAgents.mockResolvedValue(agents);
|
|
210
|
+
mockDiscoverSkills.mockResolvedValue([
|
|
211
|
+
{ name: "only-skill", rawUrl: "https://raw.githubusercontent.com/o/r/main/SKILL.md" },
|
|
212
|
+
]);
|
|
213
|
+
await addCommand("owner/repo", {});
|
|
214
|
+
// Single skill: no wizard
|
|
215
|
+
expect(mockPromptCheckboxList).not.toHaveBeenCalled();
|
|
216
|
+
expect(mockPromptChoice).not.toHaveBeenCalled();
|
|
217
|
+
expect(mockPromptConfirm).not.toHaveBeenCalled();
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
describe("wizard integration: --agent flag skips agent selection", () => {
|
|
221
|
+
it("does not prompt for agents when --agent is provided", async () => {
|
|
222
|
+
const agents = [makeAgent()];
|
|
223
|
+
mockDetectInstalledAgents.mockResolvedValue(agents);
|
|
224
|
+
mockDiscoverSkills.mockResolvedValue([
|
|
225
|
+
{ name: "skill-a", rawUrl: "https://raw.githubusercontent.com/o/r/main/skills/skill-a/SKILL.md" },
|
|
226
|
+
{ name: "skill-b", rawUrl: "https://raw.githubusercontent.com/o/r/main/skills/skill-b/SKILL.md" },
|
|
227
|
+
]);
|
|
228
|
+
// Wizard prompts: select all skills, skip agent (because --agent), project scope, symlink method, confirm
|
|
229
|
+
mockPromptCheckboxList.mockResolvedValue([0, 1]); // select all skills
|
|
230
|
+
mockPromptChoice
|
|
231
|
+
.mockResolvedValueOnce(0) // scope: project
|
|
232
|
+
.mockResolvedValueOnce(0); // method: symlink
|
|
233
|
+
mockPromptConfirm.mockResolvedValue(true);
|
|
234
|
+
await addCommand("owner/repo", { agent: ["claude-code"] });
|
|
235
|
+
// Agent checkbox should NOT be called (--agent flag)
|
|
236
|
+
// Skill checkbox IS called (multi-skill)
|
|
237
|
+
expect(mockPromptCheckboxList).toHaveBeenCalledTimes(1);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
describe("wizard integration: --global flag skips scope selection", () => {
|
|
241
|
+
it("does not prompt for scope when --global is provided", async () => {
|
|
242
|
+
const agents = [makeAgent()];
|
|
243
|
+
mockDetectInstalledAgents.mockResolvedValue(agents);
|
|
244
|
+
mockDiscoverSkills.mockResolvedValue([
|
|
245
|
+
{ name: "skill-a", rawUrl: "https://raw.githubusercontent.com/o/r/main/skills/skill-a/SKILL.md" },
|
|
246
|
+
{ name: "skill-b", rawUrl: "https://raw.githubusercontent.com/o/r/main/skills/skill-b/SKILL.md" },
|
|
247
|
+
]);
|
|
248
|
+
// Wizard: skills, agents, (no scope), method, confirm
|
|
249
|
+
mockPromptCheckboxList
|
|
250
|
+
.mockResolvedValueOnce([0, 1]) // skills
|
|
251
|
+
.mockResolvedValueOnce([0]); // agents
|
|
252
|
+
mockPromptChoice.mockResolvedValueOnce(0); // method: symlink (no scope prompt)
|
|
253
|
+
mockPromptConfirm.mockResolvedValue(true);
|
|
254
|
+
await addCommand("owner/repo", { global: true });
|
|
255
|
+
// Only 1 promptChoice call (method), not 2 (scope + method)
|
|
256
|
+
expect(mockPromptChoice).toHaveBeenCalledTimes(1);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
describe("wizard integration: abort at confirmation", () => {
|
|
260
|
+
it("exits cleanly when user declines confirmation", async () => {
|
|
261
|
+
const agents = [makeAgent()];
|
|
262
|
+
mockDetectInstalledAgents.mockResolvedValue(agents);
|
|
263
|
+
mockDiscoverSkills.mockResolvedValue([
|
|
264
|
+
{ name: "skill-a", rawUrl: "https://raw.githubusercontent.com/o/r/main/skills/skill-a/SKILL.md" },
|
|
265
|
+
{ name: "skill-b", rawUrl: "https://raw.githubusercontent.com/o/r/main/skills/skill-b/SKILL.md" },
|
|
266
|
+
]);
|
|
267
|
+
mockPromptCheckboxList
|
|
268
|
+
.mockResolvedValueOnce([0, 1])
|
|
269
|
+
.mockResolvedValueOnce([0]);
|
|
270
|
+
mockPromptChoice
|
|
271
|
+
.mockResolvedValueOnce(0)
|
|
272
|
+
.mockResolvedValueOnce(0);
|
|
273
|
+
mockPromptConfirm.mockResolvedValue(false); // user says NO
|
|
274
|
+
const mockExit = vi.spyOn(process, "exit").mockImplementation(() => {
|
|
275
|
+
throw new Error("process.exit");
|
|
276
|
+
});
|
|
277
|
+
await expect(addCommand("owner/repo", {})).rejects.toThrow("process.exit");
|
|
278
|
+
mockExit.mockRestore();
|
|
279
|
+
// No skills should have been installed
|
|
280
|
+
expect(mockWriteFileSync).not.toHaveBeenCalled();
|
|
281
|
+
expect(mockInstallSymlink).not.toHaveBeenCalled();
|
|
282
|
+
expect(mockInstallCopy).not.toHaveBeenCalled();
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
//# sourceMappingURL=add-wizard.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-wizard.test.js","sourceRoot":"","sources":["../../src/commands/add-wizard.test.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/add.d.ts
CHANGED
package/dist/commands/add.js
CHANGED
|
@@ -6,6 +6,8 @@ import { join, resolve } from "node:path";
|
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
import { execSync } from "node:child_process";
|
|
8
8
|
import { resolveTilde } from "../utils/paths.js";
|
|
9
|
+
import { findProjectRoot } from "../utils/project-root.js";
|
|
10
|
+
import { filterAgents } from "../utils/agent-filter.js";
|
|
9
11
|
import { detectInstalledAgents } from "../agents/agents-registry.js";
|
|
10
12
|
import { ensureLockfile, writeLockfile } from "../lockfile/index.js";
|
|
11
13
|
import { runTier1Scan } from "../scanner/index.js";
|
|
@@ -14,7 +16,9 @@ import { checkBlocklist } from "../blocklist/blocklist.js";
|
|
|
14
16
|
import { getSkill } from "../api/client.js";
|
|
15
17
|
import { checkPlatformSecurity } from "../security/index.js";
|
|
16
18
|
import { discoverSkills } from "../discovery/github-tree.js";
|
|
19
|
+
import { parseGitHubSource } from "../utils/validation.js";
|
|
17
20
|
import { bold, green, red, yellow, dim, cyan, spinner, } from "../utils/output.js";
|
|
21
|
+
import { isTTY, createPrompter } from "../utils/prompts.js";
|
|
18
22
|
// ---------------------------------------------------------------------------
|
|
19
23
|
// Command file filter (prevents plugin internals leaking as slash commands)
|
|
20
24
|
// ---------------------------------------------------------------------------
|
|
@@ -81,6 +85,32 @@ function cleanPluginCache(pluginName, marketplace) {
|
|
|
81
85
|
}
|
|
82
86
|
catch { /* ignore - plugin might not be installed via CLI */ }
|
|
83
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve the base directory for local skill installation.
|
|
90
|
+
*
|
|
91
|
+
* Priority:
|
|
92
|
+
* 1. `--global` -> agent's globalSkillsDir
|
|
93
|
+
* 2. `--cwd` -> process.cwd() + agent's localSkillsDir
|
|
94
|
+
* 3. default -> findProjectRoot(cwd) + agent's localSkillsDir (with fallback warning)
|
|
95
|
+
*/
|
|
96
|
+
function resolveInstallBase(opts, agent) {
|
|
97
|
+
if (opts.global) {
|
|
98
|
+
return resolveTilde(agent.globalSkillsDir);
|
|
99
|
+
}
|
|
100
|
+
const cwd = process.cwd();
|
|
101
|
+
if (opts.cwd) {
|
|
102
|
+
return join(cwd, agent.localSkillsDir);
|
|
103
|
+
}
|
|
104
|
+
const projectRoot = findProjectRoot(cwd);
|
|
105
|
+
if (!projectRoot) {
|
|
106
|
+
console.log(yellow("No project root found; installing relative to current directory."));
|
|
107
|
+
return join(cwd, agent.localSkillsDir);
|
|
108
|
+
}
|
|
109
|
+
if (projectRoot !== cwd) {
|
|
110
|
+
console.log(dim(`Project root: ${projectRoot}`));
|
|
111
|
+
}
|
|
112
|
+
return join(projectRoot, agent.localSkillsDir);
|
|
113
|
+
}
|
|
84
114
|
async function fetchSkillContent(url) {
|
|
85
115
|
const spin = spinner("Fetching skill");
|
|
86
116
|
try {
|
|
@@ -232,14 +262,22 @@ async function installPluginDir(basePath, pluginName, opts) {
|
|
|
232
262
|
if (scanResult.verdict === "CONCERNS" && opts.force) {
|
|
233
263
|
console.log(yellow("\n--force: installing despite CONCERNS.\n"));
|
|
234
264
|
}
|
|
235
|
-
// Detect installed agents
|
|
236
|
-
|
|
265
|
+
// Detect installed agents and apply --agent filter
|
|
266
|
+
let agents = await detectInstalledAgents();
|
|
237
267
|
if (agents.length === 0) {
|
|
238
268
|
console.error(red("No AI agents detected. Run ") +
|
|
239
269
|
cyan("vskill init") +
|
|
240
270
|
red(" first."));
|
|
241
271
|
process.exit(1);
|
|
242
272
|
}
|
|
273
|
+
try {
|
|
274
|
+
agents = filterAgents(agents, opts.agent);
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
console.error(red(e.message));
|
|
278
|
+
process.exit(1);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
243
281
|
// Read version from marketplace.json
|
|
244
282
|
const marketplacePath = join(basePath, ".claude-plugin", "marketplace.json");
|
|
245
283
|
const marketplaceContent = readFileSync(marketplacePath, "utf-8");
|
|
@@ -258,7 +296,7 @@ async function installPluginDir(basePath, pluginName, opts) {
|
|
|
258
296
|
const sha = createHash("sha256").update(content).digest("hex").slice(0, 12);
|
|
259
297
|
const locations = [];
|
|
260
298
|
for (const agent of agents) {
|
|
261
|
-
const cacheDir = join(opts
|
|
299
|
+
const cacheDir = join(resolveInstallBase(opts, agent), pluginName);
|
|
262
300
|
try {
|
|
263
301
|
// Full clean before copy: removes stale files from older installs
|
|
264
302
|
if (existsSync(cacheDir)) {
|
|
@@ -282,7 +320,7 @@ async function installPluginDir(basePath, pluginName, opts) {
|
|
|
282
320
|
installedAt: new Date().toISOString(),
|
|
283
321
|
source: `local:${basePath}`,
|
|
284
322
|
};
|
|
285
|
-
lock.agents = agents.map((a) => a.id);
|
|
323
|
+
lock.agents = [...new Set([...(lock.agents || []), ...agents.map((a) => a.id)])];
|
|
286
324
|
writeLockfile(lock);
|
|
287
325
|
// Print summary
|
|
288
326
|
console.log(green(`\nInstalled ${bold(pluginName)} to ${locations.length} agent${locations.length === 1 ? "" : "s"}:\n`));
|
|
@@ -329,9 +367,7 @@ async function installOneGitHubSkill(owner, repo, skillName, rawUrl, opts, agent
|
|
|
329
367
|
// Install to each agent
|
|
330
368
|
const sha = createHash("sha256").update(content).digest("hex").slice(0, 12);
|
|
331
369
|
for (const agent of agents) {
|
|
332
|
-
const baseDir = opts
|
|
333
|
-
? resolveTilde(agent.globalSkillsDir)
|
|
334
|
-
: join(process.cwd(), agent.localSkillsDir);
|
|
370
|
+
const baseDir = resolveInstallBase(opts, agent);
|
|
335
371
|
const skillDir = join(baseDir, skillName);
|
|
336
372
|
try {
|
|
337
373
|
mkdirSync(skillDir, { recursive: true });
|
|
@@ -349,6 +385,11 @@ export async function addCommand(source, opts) {
|
|
|
349
385
|
if (opts.pluginDir && opts.plugin) {
|
|
350
386
|
return installPluginDir(opts.pluginDir, opts.plugin, opts);
|
|
351
387
|
}
|
|
388
|
+
// Normalize full GitHub URLs to owner/repo shorthand
|
|
389
|
+
const parsed = parseGitHubSource(source);
|
|
390
|
+
if (parsed) {
|
|
391
|
+
source = `${parsed.owner}/${parsed.repo}`;
|
|
392
|
+
}
|
|
352
393
|
// GitHub mode: owner/repo
|
|
353
394
|
const parts = source.split("/");
|
|
354
395
|
if (parts.length !== 2) {
|
|
@@ -366,16 +407,81 @@ export async function addCommand(source, opts) {
|
|
|
366
407
|
// Fallback: discovery returned nothing — try root SKILL.md directly
|
|
367
408
|
return installSingleSkillLegacy(owner, repo, undefined, opts);
|
|
368
409
|
}
|
|
369
|
-
//
|
|
370
|
-
|
|
410
|
+
// Detect agents
|
|
411
|
+
let agents = await detectInstalledAgents();
|
|
371
412
|
if (agents.length === 0) {
|
|
372
413
|
console.error(red("No AI agents detected. Run ") + cyan("vskill init") + red(" first."));
|
|
373
414
|
process.exit(1);
|
|
374
415
|
}
|
|
416
|
+
try {
|
|
417
|
+
agents = filterAgents(agents, opts.agent);
|
|
418
|
+
}
|
|
419
|
+
catch (e) {
|
|
420
|
+
console.error(red(e.message));
|
|
421
|
+
process.exit(1);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
// Determine selected skills, agents, scope, and method
|
|
425
|
+
let selectedSkills = discovered;
|
|
426
|
+
let selectedAgents = agents;
|
|
427
|
+
let useGlobal = !!opts.global;
|
|
428
|
+
let method = "symlink";
|
|
429
|
+
const interactive = discovered.length > 1 && isTTY() && !opts.yes;
|
|
430
|
+
if (interactive) {
|
|
431
|
+
const prompter = createPrompter();
|
|
432
|
+
// Step 1: Skill selection
|
|
433
|
+
const skillIndices = await prompter.promptCheckboxList(discovered.map((s) => ({ label: s.name, checked: true })), { title: "Select skills to install (space to toggle)" });
|
|
434
|
+
selectedSkills = skillIndices.map((i) => discovered[i]);
|
|
435
|
+
if (selectedSkills.length === 0) {
|
|
436
|
+
console.log(dim("No skills selected. Aborting."));
|
|
437
|
+
return process.exit(0);
|
|
438
|
+
}
|
|
439
|
+
// Step 2: Agent selection (skip if --agent flag provided or only 1 agent)
|
|
440
|
+
if (!opts.agent?.length && agents.length > 1) {
|
|
441
|
+
const prompter2 = createPrompter();
|
|
442
|
+
const agentIndices = await prompter2.promptCheckboxList(agents.map((a) => ({ label: a.displayName, description: a.parentCompany, checked: true })), { title: `Detected ${agents.length} agents` });
|
|
443
|
+
selectedAgents = agentIndices.map((i) => agents[i]);
|
|
444
|
+
if (selectedAgents.length === 0) {
|
|
445
|
+
console.log(dim("No agents selected. Aborting."));
|
|
446
|
+
return process.exit(0);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// Step 3: Scope selection (skip if --global flag provided)
|
|
450
|
+
if (!opts.global) {
|
|
451
|
+
const prompter3 = createPrompter();
|
|
452
|
+
const scopeIdx = await prompter3.promptChoice("Installation scope:", [
|
|
453
|
+
{ label: "Project", hint: "Install in current directory (committed with your project)" },
|
|
454
|
+
{ label: "Global", hint: "Install to ~/.<agent>/ directories" },
|
|
455
|
+
]);
|
|
456
|
+
useGlobal = scopeIdx === 1;
|
|
457
|
+
}
|
|
458
|
+
// Step 4: Install method
|
|
459
|
+
const prompter4 = createPrompter();
|
|
460
|
+
const methodIdx = await prompter4.promptChoice("Installation method:", [
|
|
461
|
+
{ label: "Symlink", hint: "Single source of truth, easy updates" },
|
|
462
|
+
{ label: "Copy", hint: "Independent copies to all agents" },
|
|
463
|
+
]);
|
|
464
|
+
method = methodIdx === 0 ? "symlink" : "copy";
|
|
465
|
+
// Step 5: Summary and confirmation
|
|
466
|
+
console.log(dim("\n--- Installation Summary ---"));
|
|
467
|
+
console.log(` Skills: ${selectedSkills.map((s) => s.name).join(", ")}`);
|
|
468
|
+
console.log(` Agents: ${selectedAgents.map((a) => a.displayName).join(", ")}`);
|
|
469
|
+
console.log(` Scope: ${useGlobal ? "Global" : "Project"}`);
|
|
470
|
+
console.log(` Method: ${method}`);
|
|
471
|
+
const prompter5 = createPrompter();
|
|
472
|
+
const proceed = await prompter5.promptConfirm("\nProceed?", true);
|
|
473
|
+
if (!proceed) {
|
|
474
|
+
return process.exit(0);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// Override global flag based on wizard choice
|
|
478
|
+
if (useGlobal)
|
|
479
|
+
opts.global = true;
|
|
480
|
+
// Install selected skills
|
|
375
481
|
const results = [];
|
|
376
|
-
for (const skill of
|
|
482
|
+
for (const skill of selectedSkills) {
|
|
377
483
|
console.log(dim(`\nInstalling skill: ${bold(skill.name)}...`));
|
|
378
|
-
const result = await installOneGitHubSkill(owner, repo, skill.name, skill.rawUrl, opts,
|
|
484
|
+
const result = await installOneGitHubSkill(owner, repo, skill.name, skill.rawUrl, opts, selectedAgents);
|
|
379
485
|
results.push(result);
|
|
380
486
|
}
|
|
381
487
|
// Update lockfile with all installed skills
|
|
@@ -391,12 +497,10 @@ export async function addCommand(source, opts) {
|
|
|
391
497
|
};
|
|
392
498
|
}
|
|
393
499
|
}
|
|
394
|
-
lock.agents = agents.map((a) => a.id);
|
|
500
|
+
lock.agents = [...new Set([...(lock.agents || []), ...selectedAgents.map((a) => a.id)])];
|
|
395
501
|
writeLockfile(lock);
|
|
396
502
|
// Summary
|
|
397
|
-
|
|
398
|
-
const skipped = results.filter((r) => !r.installed);
|
|
399
|
-
console.log(green(`\nInstalled ${bold(String(installed.length))} of ${results.length} skills:\n`));
|
|
503
|
+
console.log(green(`\nInstalled ${bold(String(results.filter((r) => r.installed).length))} of ${results.length} skills:\n`));
|
|
400
504
|
for (const r of results) {
|
|
401
505
|
const icon = r.installed ? green("✓") : red("✗");
|
|
402
506
|
const detail = r.installed ? dim(`(${r.verdict})`) : red(`(${r.verdict})`);
|
|
@@ -466,19 +570,25 @@ async function installFromRegistry(skillName, opts) {
|
|
|
466
570
|
dim("Use --force to install anyway."));
|
|
467
571
|
process.exit(1);
|
|
468
572
|
}
|
|
469
|
-
// Detect installed agents
|
|
470
|
-
|
|
573
|
+
// Detect installed agents and apply --agent filter
|
|
574
|
+
let agents = await detectInstalledAgents();
|
|
471
575
|
if (agents.length === 0) {
|
|
472
576
|
console.error(red("No AI agents detected. Run ") + cyan("vskill init") + red(" first."));
|
|
473
577
|
process.exit(1);
|
|
474
578
|
}
|
|
579
|
+
try {
|
|
580
|
+
agents = filterAgents(agents, opts.agent);
|
|
581
|
+
}
|
|
582
|
+
catch (e) {
|
|
583
|
+
console.error(red(e.message));
|
|
584
|
+
process.exit(1);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
475
587
|
// Install to each agent
|
|
476
588
|
const sha = createHash("sha256").update(content).digest("hex").slice(0, 12);
|
|
477
589
|
const locations = [];
|
|
478
590
|
for (const agent of agents) {
|
|
479
|
-
const baseDir = opts
|
|
480
|
-
? resolveTilde(agent.globalSkillsDir)
|
|
481
|
-
: join(process.cwd(), agent.localSkillsDir);
|
|
591
|
+
const baseDir = resolveInstallBase(opts, agent);
|
|
482
592
|
const skillDir = join(baseDir, skillName);
|
|
483
593
|
try {
|
|
484
594
|
mkdirSync(skillDir, { recursive: true });
|
|
@@ -499,7 +609,7 @@ async function installFromRegistry(skillName, opts) {
|
|
|
499
609
|
installedAt: new Date().toISOString(),
|
|
500
610
|
source: `registry:${skillName}`,
|
|
501
611
|
};
|
|
502
|
-
lock.agents = agents.map((a) => a.id);
|
|
612
|
+
lock.agents = [...new Set([...(lock.agents || []), ...agents.map((a) => a.id)])];
|
|
503
613
|
writeLockfile(lock);
|
|
504
614
|
console.log(green(`\nInstalled ${bold(skillName)} to ${locations.length} agent${locations.length === 1 ? "" : "s"}:\n`));
|
|
505
615
|
for (const loc of locations) {
|
|
@@ -582,21 +692,27 @@ async function installSingleSkillLegacy(owner, repo, skill, opts) {
|
|
|
582
692
|
if (scanResult.verdict === "CONCERNS" && opts.force) {
|
|
583
693
|
console.log(yellow("\n--force: installing despite CONCERNS.\n"));
|
|
584
694
|
}
|
|
585
|
-
// Detect installed agents
|
|
586
|
-
|
|
695
|
+
// Detect installed agents and apply --agent filter
|
|
696
|
+
let agents = await detectInstalledAgents();
|
|
587
697
|
if (agents.length === 0) {
|
|
588
698
|
console.error(red("No AI agents detected. Run ") +
|
|
589
699
|
cyan("vskill init") +
|
|
590
700
|
red(" first."));
|
|
591
701
|
process.exit(1);
|
|
592
702
|
}
|
|
703
|
+
try {
|
|
704
|
+
agents = filterAgents(agents, opts.agent);
|
|
705
|
+
}
|
|
706
|
+
catch (e) {
|
|
707
|
+
console.error(red(e.message));
|
|
708
|
+
process.exit(1);
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
593
711
|
// Install to each agent
|
|
594
712
|
const sha = createHash("sha256").update(content).digest("hex").slice(0, 12);
|
|
595
713
|
const locations = [];
|
|
596
714
|
for (const agent of agents) {
|
|
597
|
-
const baseDir = opts
|
|
598
|
-
? resolveTilde(agent.globalSkillsDir)
|
|
599
|
-
: join(process.cwd(), agent.localSkillsDir);
|
|
715
|
+
const baseDir = resolveInstallBase(opts, agent);
|
|
600
716
|
const skillDir = join(baseDir, skillName);
|
|
601
717
|
try {
|
|
602
718
|
mkdirSync(skillDir, { recursive: true });
|
|
@@ -617,7 +733,7 @@ async function installSingleSkillLegacy(owner, repo, skill, opts) {
|
|
|
617
733
|
installedAt: new Date().toISOString(),
|
|
618
734
|
source: `github:${owner}/${repo}`,
|
|
619
735
|
};
|
|
620
|
-
lock.agents = agents.map((a) => a.id);
|
|
736
|
+
lock.agents = [...new Set([...(lock.agents || []), ...agents.map((a) => a.id)])];
|
|
621
737
|
writeLockfile(lock);
|
|
622
738
|
// Print summary
|
|
623
739
|
console.log(green(`\nInstalled ${bold(skillName)} to ${locations.length} agent${locations.length === 1 ? "" : "s"}:\n`));
|