vskill 0.2.27 → 0.2.28

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 (77) hide show
  1. package/dist/commands/__tests__/eval-router.test.d.ts +1 -0
  2. package/dist/commands/__tests__/eval-router.test.js +60 -0
  3. package/dist/commands/__tests__/eval-router.test.js.map +1 -0
  4. package/dist/commands/add.js +113 -19
  5. package/dist/commands/add.js.map +1 -1
  6. package/dist/commands/eval/__tests__/coverage.test.d.ts +1 -0
  7. package/dist/commands/eval/__tests__/coverage.test.js +122 -0
  8. package/dist/commands/eval/__tests__/coverage.test.js.map +1 -0
  9. package/dist/commands/eval/__tests__/generate-all.test.d.ts +1 -0
  10. package/dist/commands/eval/__tests__/generate-all.test.js +133 -0
  11. package/dist/commands/eval/__tests__/generate-all.test.js.map +1 -0
  12. package/dist/commands/eval/__tests__/init.test.d.ts +1 -0
  13. package/dist/commands/eval/__tests__/init.test.js +116 -0
  14. package/dist/commands/eval/__tests__/init.test.js.map +1 -0
  15. package/dist/commands/eval/__tests__/run.test.d.ts +1 -0
  16. package/dist/commands/eval/__tests__/run.test.js +149 -0
  17. package/dist/commands/eval/__tests__/run.test.js.map +1 -0
  18. package/dist/commands/eval/coverage.d.ts +1 -0
  19. package/dist/commands/eval/coverage.js +79 -0
  20. package/dist/commands/eval/coverage.js.map +1 -0
  21. package/dist/commands/eval/generate-all.d.ts +1 -0
  22. package/dist/commands/eval/generate-all.js +64 -0
  23. package/dist/commands/eval/generate-all.js.map +1 -0
  24. package/dist/commands/eval/init.d.ts +1 -0
  25. package/dist/commands/eval/init.js +38 -0
  26. package/dist/commands/eval/init.js.map +1 -0
  27. package/dist/commands/eval/run.d.ts +1 -0
  28. package/dist/commands/eval/run.js +107 -0
  29. package/dist/commands/eval/run.js.map +1 -0
  30. package/dist/commands/eval.d.ts +4 -0
  31. package/dist/commands/eval.js +48 -0
  32. package/dist/commands/eval.js.map +1 -0
  33. package/dist/eval/__tests__/benchmark.test.d.ts +1 -0
  34. package/dist/eval/__tests__/benchmark.test.js +65 -0
  35. package/dist/eval/__tests__/benchmark.test.js.map +1 -0
  36. package/dist/eval/__tests__/judge.test.d.ts +1 -0
  37. package/dist/eval/__tests__/judge.test.js +45 -0
  38. package/dist/eval/__tests__/judge.test.js.map +1 -0
  39. package/dist/eval/__tests__/llm.test.d.ts +1 -0
  40. package/dist/eval/__tests__/llm.test.js +85 -0
  41. package/dist/eval/__tests__/llm.test.js.map +1 -0
  42. package/dist/eval/__tests__/prompt-builder.test.d.ts +1 -0
  43. package/dist/eval/__tests__/prompt-builder.test.js +72 -0
  44. package/dist/eval/__tests__/prompt-builder.test.js.map +1 -0
  45. package/dist/eval/__tests__/schema.test.d.ts +1 -0
  46. package/dist/eval/__tests__/schema.test.js +209 -0
  47. package/dist/eval/__tests__/schema.test.js.map +1 -0
  48. package/dist/eval/__tests__/skill-scanner.test.d.ts +1 -0
  49. package/dist/eval/__tests__/skill-scanner.test.js +78 -0
  50. package/dist/eval/__tests__/skill-scanner.test.js.map +1 -0
  51. package/dist/eval/benchmark.d.ts +22 -0
  52. package/dist/eval/benchmark.js +24 -0
  53. package/dist/eval/benchmark.js.map +1 -0
  54. package/dist/eval/judge.d.ts +9 -0
  55. package/dist/eval/judge.js +40 -0
  56. package/dist/eval/judge.js.map +1 -0
  57. package/dist/eval/llm.d.ts +5 -0
  58. package/dist/eval/llm.js +34 -0
  59. package/dist/eval/llm.js.map +1 -0
  60. package/dist/eval/prompt-builder.d.ts +3 -0
  61. package/dist/eval/prompt-builder.js +155 -0
  62. package/dist/eval/prompt-builder.js.map +1 -0
  63. package/dist/eval/schema.d.ts +26 -0
  64. package/dist/eval/schema.js +128 -0
  65. package/dist/eval/schema.js.map +1 -0
  66. package/dist/eval/skill-scanner.d.ts +8 -0
  67. package/dist/eval/skill-scanner.js +44 -0
  68. package/dist/eval/skill-scanner.js.map +1 -0
  69. package/dist/index.js +9 -0
  70. package/dist/index.js.map +1 -1
  71. package/dist/marketplace/index.d.ts +2 -2
  72. package/dist/marketplace/index.js +1 -1
  73. package/dist/marketplace/index.js.map +1 -1
  74. package/dist/marketplace/marketplace.d.ts +13 -0
  75. package/dist/marketplace/marketplace.js +35 -0
  76. package/dist/marketplace/marketplace.js.map +1 -1
  77. package/package.json +2 -1
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,60 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ // ---------------------------------------------------------------------------
3
+ // Mocks
4
+ // ---------------------------------------------------------------------------
5
+ const mocks = vi.hoisted(() => ({
6
+ runEvalInit: vi.fn(),
7
+ runEvalRun: vi.fn(),
8
+ runEvalCoverage: vi.fn(),
9
+ runEvalGenerateAll: vi.fn(),
10
+ }));
11
+ vi.mock("../eval/init.js", () => ({ runEvalInit: mocks.runEvalInit }));
12
+ vi.mock("../eval/run.js", () => ({ runEvalRun: mocks.runEvalRun }));
13
+ vi.mock("../eval/coverage.js", () => ({
14
+ runEvalCoverage: mocks.runEvalCoverage,
15
+ }));
16
+ vi.mock("../eval/generate-all.js", () => ({
17
+ runEvalGenerateAll: mocks.runEvalGenerateAll,
18
+ }));
19
+ // ---------------------------------------------------------------------------
20
+ // Import module under test AFTER mocks
21
+ // ---------------------------------------------------------------------------
22
+ const { evalCommand } = await import("../eval.js");
23
+ // ---------------------------------------------------------------------------
24
+ // Tests
25
+ // ---------------------------------------------------------------------------
26
+ describe("evalCommand router", () => {
27
+ beforeEach(() => {
28
+ vi.resetAllMocks();
29
+ });
30
+ it("routes init subcommand to runEvalInit", async () => {
31
+ await evalCommand("init", "marketing/social-media-posting", {
32
+ root: "/tmp/test",
33
+ });
34
+ expect(mocks.runEvalInit).toHaveBeenCalledWith(expect.stringContaining("marketing/skills/social-media-posting"), false);
35
+ });
36
+ it("routes run subcommand to runEvalRun", async () => {
37
+ await evalCommand("run", "marketing/social-media-posting", {
38
+ root: "/tmp/test",
39
+ });
40
+ expect(mocks.runEvalRun).toHaveBeenCalledWith(expect.stringContaining("marketing/skills/social-media-posting"));
41
+ });
42
+ it("routes coverage subcommand to runEvalCoverage", async () => {
43
+ await evalCommand("coverage", undefined, { root: "/tmp/test" });
44
+ expect(mocks.runEvalCoverage).toHaveBeenCalledWith("/tmp/test");
45
+ });
46
+ it("routes generate-all subcommand to runEvalGenerateAll", async () => {
47
+ await evalCommand("generate-all", undefined, {
48
+ root: "/tmp/test",
49
+ force: true,
50
+ });
51
+ expect(mocks.runEvalGenerateAll).toHaveBeenCalledWith("/tmp/test", true);
52
+ });
53
+ it("prints error for unknown subcommand", async () => {
54
+ const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => { });
55
+ await evalCommand("unknown", undefined, { root: "/tmp/test" });
56
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Unknown subcommand"));
57
+ consoleSpy.mockRestore();
58
+ });
59
+ });
60
+ //# sourceMappingURL=eval-router.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eval-router.test.js","sourceRoot":"","sources":["../../../src/commands/__tests__/eval-router.test.ts"],"names":[],"mappings":"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"}
@@ -7,12 +7,12 @@ import { createHash } from "node:crypto";
7
7
  import { execSync } from "node:child_process";
8
8
  import os from "node:os";
9
9
  import { resolveTilde } from "../utils/paths.js";
10
- import { reportInstall, reportInstallBatch } from "../api/client.js";
10
+ import { reportInstall, reportInstallBatch, submitSkill } from "../api/client.js";
11
11
  import { filterAgents } from "../utils/agent-filter.js";
12
12
  import { detectInstalledAgents, AGENTS_REGISTRY } from "../agents/agents-registry.js";
13
13
  import { ensureLockfile, writeLockfile, readLockfile } from "../lockfile/index.js";
14
14
  import { runTier1Scan } from "../scanner/index.js";
15
- import { getAvailablePlugins, getPluginSource, getPluginVersion, hasPlugin } from "../marketplace/index.js";
15
+ import { getAvailablePlugins, getPluginSource, getPluginVersion, hasPlugin, discoverUnregisteredPlugins } from "../marketplace/index.js";
16
16
  import { checkInstallSafety } from "../blocklist/blocklist.js";
17
17
  import { getSkill } from "../api/client.js";
18
18
  import { checkPlatformSecurity } from "../security/index.js";
@@ -104,6 +104,24 @@ export async function detectMarketplaceRepo(owner, repo) {
104
104
  }
105
105
  return { isMarketplace: false };
106
106
  }
107
+ /**
108
+ * Offer to submit a marketplace repo for re-scanning on the platform.
109
+ * Uses the existing submitSkill() API. Non-throwing — prints fallback URL on failure.
110
+ */
111
+ async function triggerResubmission(owner, repo) {
112
+ const repoUrl = `https://github.com/${owner}/${repo}`;
113
+ try {
114
+ const result = await submitSkill({ repoUrl });
115
+ console.log(green(" Submitted for re-scanning!"));
116
+ if (result.trackingUrl) {
117
+ console.log(dim(` Track progress: ${result.trackingUrl}`));
118
+ }
119
+ }
120
+ catch {
121
+ console.log(yellow(" Could not submit automatically."));
122
+ console.log(dim(` Submit manually: https://verified-skill.com/submit?repo=${owner}/${repo}`));
123
+ }
124
+ }
107
125
  /**
108
126
  * Install plugins from a Claude Code plugin marketplace repo.
109
127
  *
@@ -118,8 +136,20 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
118
136
  console.error(red("No plugins found in marketplace.json"));
119
137
  process.exit(1);
120
138
  }
121
- console.log(`\n${bold("Claude Code Plugin Marketplace")} detected: ${cyan(`${owner}/${repo}`)}\n` +
122
- dim(`Marketplace: ${marketplaceName || "unknown"} ${plugins.length} plugin${plugins.length === 1 ? "" : "s"} available\n`));
139
+ // Discover plugin directories not yet in marketplace.json
140
+ const unregistered = await discoverUnregisteredPlugins(owner, repo, manifestContent);
141
+ const headerParts = [
142
+ `\n${bold("Claude Code Plugin Marketplace")} detected: ${cyan(`${owner}/${repo}`)}\n`,
143
+ dim(`Marketplace: ${marketplaceName || "unknown"} — ${plugins.length} registered plugin${plugins.length === 1 ? "" : "s"}`),
144
+ ];
145
+ if (unregistered.length > 0) {
146
+ headerParts.push(dim(`, ${unregistered.length} unregistered`));
147
+ }
148
+ headerParts.push("\n");
149
+ if (unregistered.length > 0) {
150
+ headerParts.push(yellow(` ${unregistered.length} new plugin${unregistered.length === 1 ? "" : "s"} not yet in marketplace.json\n`));
151
+ }
152
+ console.log(headerParts.join(""));
123
153
  // Check lockfile for already-installed plugins
124
154
  const lockDir = lockfileRoot(opts);
125
155
  const lock = readLockfile(lockDir);
@@ -133,6 +163,7 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
133
163
  }
134
164
  // Select plugins
135
165
  let selectedPlugins;
166
+ let selectedUnregistered = [];
136
167
  if (!isTTY() && !opts.yes && !opts.all) {
137
168
  // Non-TTY: list plugins and exit with guidance
138
169
  console.log("Available plugins:\n");
@@ -140,6 +171,13 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
140
171
  const installed = installedSet.has(p.name) ? dim(" (installed)") : "";
141
172
  console.log(` ${bold(p.name)}${installed}${p.description ? dim(` — ${p.description}`) : ""}`);
142
173
  }
174
+ if (unregistered.length > 0) {
175
+ console.log("\nUnregistered plugins (not in marketplace.json):\n");
176
+ for (const u of unregistered) {
177
+ console.log(` ${yellow(u.name)} ${dim("(new — not in marketplace.json)")}`);
178
+ }
179
+ console.log(dim("\nUse --force --plugin <name> to install unregistered plugins."));
180
+ }
143
181
  console.error(red("\nNon-interactive mode. Use --plugin <name> or --yes to select all."));
144
182
  process.exit(1);
145
183
  }
@@ -158,9 +196,14 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
158
196
  selectedPlugins = plugins;
159
197
  console.log(dim(`Auto-selecting all ${plugins.length} plugins (--yes/--all)`));
160
198
  }
199
+ if (unregistered.length > 0) {
200
+ console.log(dim(` Skipping ${unregistered.length} unregistered plugin${unregistered.length === 1 ? "" : "s"}: `) +
201
+ dim(unregistered.map((u) => u.name).join(", ")) +
202
+ dim(" (use --force to include)"));
203
+ }
161
204
  }
162
- else if (plugins.length === 1) {
163
- // Single plugin — show details and ask for confirmation
205
+ else if (plugins.length === 1 && unregistered.length === 0) {
206
+ // Single plugin, no unregistered — show details and ask for confirmation
164
207
  const p = plugins[0];
165
208
  const isInstalled = installedSet.has(p.name);
166
209
  const versionTag = p.version ? ` v${p.version}` : "";
@@ -185,17 +228,55 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
185
228
  selectedPlugins = plugins;
186
229
  }
187
230
  else {
231
+ // Build combined picker: registered plugins first, then unregistered
232
+ const combinedItems = [
233
+ ...plugins.map((p) => ({
234
+ label: p.name + (installedSet.has(p.name) ? dim(" (installed)") : ""),
235
+ description: p.description,
236
+ checked: preSelected ? preSelected.includes(p.name) : installedSet.has(p.name),
237
+ })),
238
+ ...unregistered.map((u) => ({
239
+ label: u.name + yellow(" (new — not in marketplace.json)"),
240
+ description: undefined,
241
+ checked: false,
242
+ })),
243
+ ];
188
244
  const prompter = createPrompter();
189
- const indices = await prompter.promptCheckboxList(plugins.map((p) => ({
190
- label: p.name + (installedSet.has(p.name) ? dim(" (installed)") : ""),
191
- description: p.description,
192
- checked: preSelected ? preSelected.includes(p.name) : installedSet.has(p.name),
193
- })), { title: "Select plugins to install" });
245
+ const indices = await prompter.promptCheckboxList(combinedItems, { title: "Select plugins to install" });
194
246
  if (indices.length === 0) {
195
247
  console.log(dim("No plugins selected. Aborting."));
196
248
  return;
197
249
  }
198
- selectedPlugins = indices.map((i) => plugins[i]);
250
+ // Partition selections into registered and unregistered
251
+ selectedPlugins = [];
252
+ selectedUnregistered = [];
253
+ for (const i of indices) {
254
+ if (i < plugins.length) {
255
+ selectedPlugins.push(plugins[i]);
256
+ }
257
+ else {
258
+ selectedUnregistered.push(unregistered[i - plugins.length]);
259
+ }
260
+ }
261
+ }
262
+ // Gate: unregistered plugins require --force
263
+ if (selectedUnregistered.length > 0 && !opts.force) {
264
+ console.log(yellow(`\n ${selectedUnregistered.length} unregistered plugin${selectedUnregistered.length === 1 ? "" : "s"} selected but --force not set.`) + "\n" +
265
+ dim(" Unregistered plugins have not been scanned or verified.") + "\n" +
266
+ dim(" Use --force to install them anyway, or submit the repo for re-scanning.") + "\n");
267
+ // Offer resubmission in interactive mode
268
+ if (isTTY()) {
269
+ const prompter = createPrompter();
270
+ const resubmit = await prompter.promptConfirm(`Submit ${bold(`${owner}/${repo}`)} for re-scanning?`, true);
271
+ if (resubmit) {
272
+ await triggerResubmission(owner, repo);
273
+ }
274
+ }
275
+ else {
276
+ console.log(dim(` Submit manually: https://verified-skill.com/submit?repo=${owner}/${repo}`));
277
+ }
278
+ // Clear unregistered selection — only install registered plugins
279
+ selectedUnregistered = [];
199
280
  }
200
281
  // Attempt native Claude Code install
201
282
  const hasClaude = !opts.copy && isClaudeCliAvailable();
@@ -220,7 +301,7 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
220
301
  }
221
302
  }
222
303
  }
223
- // Install each plugin
304
+ // Install registered plugins
224
305
  const results = [];
225
306
  for (const plugin of selectedPlugins) {
226
307
  if (hasClaude && marketplaceRegistered && marketplaceName) {
@@ -256,15 +337,28 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
256
337
  }
257
338
  }
258
339
  }
340
+ // Install unregistered plugins via extraction (--force only)
341
+ for (const unreg of selectedUnregistered) {
342
+ console.log(yellow(` Installing unregistered plugin: ${bold(unreg.name)} (--force)`));
343
+ try {
344
+ await installRepoPlugin(`${owner}/${repo}`, unreg.name, opts, unreg.source);
345
+ results.push({ name: unreg.name, installed: true, method: "extraction-unregistered" });
346
+ }
347
+ catch (err) {
348
+ console.error(red(` ✗ ${unreg.name}: ${err.message}`));
349
+ results.push({ name: unreg.name, installed: false, method: "failed" });
350
+ }
351
+ }
259
352
  // Update lockfile
260
353
  const lockForWrite = ensureLockfile(lockDir);
261
354
  for (const r of results) {
262
355
  if (r.installed) {
263
- const pluginVersion = getPluginVersion(r.name, manifestContent) || "0.0.0";
356
+ const isUnregistered = r.method === "extraction-unregistered";
357
+ const pluginVersion = isUnregistered ? "0.0.0" : (getPluginVersion(r.name, manifestContent) || "0.0.0");
264
358
  lockForWrite.skills[r.name] = {
265
359
  version: pluginVersion,
266
360
  sha: "",
267
- tier: "VERIFIED",
361
+ tier: isUnregistered ? "UNSCANNED" : "VERIFIED",
268
362
  installedAt: new Date().toISOString(),
269
363
  source: `marketplace:${owner}/${repo}#${r.name}`,
270
364
  marketplace: marketplaceName || undefined,
@@ -1028,7 +1122,7 @@ async function installAllRepoPlugins(ownerRepo, opts) {
1028
1122
  // ---------------------------------------------------------------------------
1029
1123
  // Remote plugin installation from a GitHub repository with marketplace.json
1030
1124
  // ---------------------------------------------------------------------------
1031
- async function installRepoPlugin(ownerRepo, pluginName, opts) {
1125
+ async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
1032
1126
  const [owner, repo] = ownerRepo.split("/");
1033
1127
  if (!owner || !repo) {
1034
1128
  throw new Error("--repo must be in owner/repo format (e.g. anton-abyzov/vskill)");
@@ -1054,14 +1148,14 @@ async function installRepoPlugin(ownerRepo, pluginName, opts) {
1054
1148
  throw err;
1055
1149
  throw new Error(`Network error: ${err.message}`);
1056
1150
  }
1057
- // Find the plugin in the marketplace
1058
- const pluginSource = getPluginSource(pluginName, manifestContent);
1151
+ // Find the plugin in the marketplace (or use override for unregistered plugins)
1152
+ const pluginSource = overrideSource || getPluginSource(pluginName, manifestContent);
1059
1153
  if (!pluginSource) {
1060
1154
  const available = getAvailablePlugins(manifestContent).map((p) => p.name);
1061
1155
  throw new Error(`Plugin "${pluginName}" not found in marketplace.json. ` +
1062
1156
  `Available plugins: ${available.join(", ")}`);
1063
1157
  }
1064
- const pluginVersion = getPluginVersion(pluginName, manifestContent) || "0.0.0";
1158
+ const pluginVersion = overrideSource ? "0.0.0" : (getPluginVersion(pluginName, manifestContent) || "0.0.0");
1065
1159
  const pluginPath = pluginSource.replace(/^\.\//, "");
1066
1160
  // Blocklist + rejection check BEFORE fetching content
1067
1161
  const safety = await checkInstallSafety(pluginName);