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.
Files changed (43) hide show
  1. package/README.md +15 -1
  2. package/dist/commands/add-wizard.test.d.ts +1 -0
  3. package/dist/commands/add-wizard.test.js +285 -0
  4. package/dist/commands/add-wizard.test.js.map +1 -0
  5. package/dist/commands/add.d.ts +3 -0
  6. package/dist/commands/add.js +143 -27
  7. package/dist/commands/add.js.map +1 -1
  8. package/dist/commands/add.test.js +123 -0
  9. package/dist/commands/add.test.js.map +1 -1
  10. package/dist/commands/audit.d.ts +2 -2
  11. package/dist/commands/audit.js +36 -49
  12. package/dist/commands/audit.js.map +1 -1
  13. package/dist/index.js +7 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/installer/canonical.d.ts +9 -0
  16. package/dist/installer/canonical.js +63 -0
  17. package/dist/installer/canonical.js.map +1 -0
  18. package/dist/installer/canonical.test.d.ts +1 -0
  19. package/dist/installer/canonical.test.js +117 -0
  20. package/dist/installer/canonical.test.js.map +1 -0
  21. package/dist/scanner/patterns.js +2 -2
  22. package/dist/scanner/patterns.js.map +1 -1
  23. package/dist/utils/agent-filter.d.ts +16 -0
  24. package/dist/utils/agent-filter.js +28 -0
  25. package/dist/utils/agent-filter.js.map +1 -0
  26. package/dist/utils/agent-filter.test.d.ts +1 -0
  27. package/dist/utils/agent-filter.test.js +64 -0
  28. package/dist/utils/agent-filter.test.js.map +1 -0
  29. package/dist/utils/project-root.d.ts +18 -0
  30. package/dist/utils/project-root.js +47 -0
  31. package/dist/utils/project-root.js.map +1 -0
  32. package/dist/utils/project-root.test.d.ts +1 -0
  33. package/dist/utils/project-root.test.js +74 -0
  34. package/dist/utils/project-root.test.js.map +1 -0
  35. package/dist/utils/prompts.d.ts +20 -0
  36. package/dist/utils/prompts.js +103 -0
  37. package/dist/utils/prompts.js.map +1 -0
  38. package/dist/utils/prompts.test.d.ts +1 -0
  39. package/dist/utils/prompts.test.js +155 -0
  40. package/dist/utils/prompts.test.js.map +1 -0
  41. package/dist/utils/validation.js +1 -1
  42. package/dist/utils/validation.js.map +1 -1
  43. 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
- npx vskill install https://github.com/owner/repo # also accepts full GitHub URLs
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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"}
@@ -4,6 +4,9 @@ interface AddOptions {
4
4
  pluginDir?: string;
5
5
  global?: boolean;
6
6
  force?: boolean;
7
+ agent?: string[];
8
+ cwd?: boolean;
9
+ yes?: boolean;
7
10
  }
8
11
  export declare function addCommand(source: string, opts: AddOptions): Promise<void>;
9
12
  export {};
@@ -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
- const agents = await detectInstalledAgents();
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.global ? resolveTilde(agent.globalSkillsDir) : join(process.cwd(), agent.localSkillsDir), pluginName);
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.global
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
- // Multi-skill install
370
- const agents = await detectInstalledAgents();
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 discovered) {
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, agents);
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
- const installed = results.filter((r) => r.installed);
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
- const agents = await detectInstalledAgents();
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.global
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
- const agents = await detectInstalledAgents();
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.global
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`));