vskill 0.2.27 → 0.2.29
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/__tests__/eval-router.test.d.ts +1 -0
- package/dist/commands/__tests__/eval-router.test.js +60 -0
- package/dist/commands/__tests__/eval-router.test.js.map +1 -0
- package/dist/commands/add.js +166 -24
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +96 -1
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/eval/__tests__/coverage.test.d.ts +1 -0
- package/dist/commands/eval/__tests__/coverage.test.js +122 -0
- package/dist/commands/eval/__tests__/coverage.test.js.map +1 -0
- package/dist/commands/eval/__tests__/generate-all.test.d.ts +1 -0
- package/dist/commands/eval/__tests__/generate-all.test.js +133 -0
- package/dist/commands/eval/__tests__/generate-all.test.js.map +1 -0
- package/dist/commands/eval/__tests__/init.test.d.ts +1 -0
- package/dist/commands/eval/__tests__/init.test.js +116 -0
- package/dist/commands/eval/__tests__/init.test.js.map +1 -0
- package/dist/commands/eval/__tests__/run.test.d.ts +1 -0
- package/dist/commands/eval/__tests__/run.test.js +149 -0
- package/dist/commands/eval/__tests__/run.test.js.map +1 -0
- package/dist/commands/eval/coverage.d.ts +1 -0
- package/dist/commands/eval/coverage.js +79 -0
- package/dist/commands/eval/coverage.js.map +1 -0
- package/dist/commands/eval/generate-all.d.ts +1 -0
- package/dist/commands/eval/generate-all.js +64 -0
- package/dist/commands/eval/generate-all.js.map +1 -0
- package/dist/commands/eval/init.d.ts +1 -0
- package/dist/commands/eval/init.js +38 -0
- package/dist/commands/eval/init.js.map +1 -0
- package/dist/commands/eval/run.d.ts +1 -0
- package/dist/commands/eval/run.js +107 -0
- package/dist/commands/eval/run.js.map +1 -0
- package/dist/commands/eval.d.ts +4 -0
- package/dist/commands/eval.js +48 -0
- package/dist/commands/eval.js.map +1 -0
- package/dist/eval/__tests__/benchmark.test.d.ts +1 -0
- package/dist/eval/__tests__/benchmark.test.js +65 -0
- package/dist/eval/__tests__/benchmark.test.js.map +1 -0
- package/dist/eval/__tests__/judge.test.d.ts +1 -0
- package/dist/eval/__tests__/judge.test.js +45 -0
- package/dist/eval/__tests__/judge.test.js.map +1 -0
- package/dist/eval/__tests__/llm.test.d.ts +1 -0
- package/dist/eval/__tests__/llm.test.js +85 -0
- package/dist/eval/__tests__/llm.test.js.map +1 -0
- package/dist/eval/__tests__/prompt-builder.test.d.ts +1 -0
- package/dist/eval/__tests__/prompt-builder.test.js +72 -0
- package/dist/eval/__tests__/prompt-builder.test.js.map +1 -0
- package/dist/eval/__tests__/schema.test.d.ts +1 -0
- package/dist/eval/__tests__/schema.test.js +209 -0
- package/dist/eval/__tests__/schema.test.js.map +1 -0
- package/dist/eval/__tests__/skill-scanner.test.d.ts +1 -0
- package/dist/eval/__tests__/skill-scanner.test.js +78 -0
- package/dist/eval/__tests__/skill-scanner.test.js.map +1 -0
- package/dist/eval/benchmark.d.ts +22 -0
- package/dist/eval/benchmark.js +24 -0
- package/dist/eval/benchmark.js.map +1 -0
- package/dist/eval/judge.d.ts +9 -0
- package/dist/eval/judge.js +40 -0
- package/dist/eval/judge.js.map +1 -0
- package/dist/eval/llm.d.ts +5 -0
- package/dist/eval/llm.js +34 -0
- package/dist/eval/llm.js.map +1 -0
- package/dist/eval/prompt-builder.d.ts +3 -0
- package/dist/eval/prompt-builder.js +155 -0
- package/dist/eval/prompt-builder.js.map +1 -0
- package/dist/eval/schema.d.ts +26 -0
- package/dist/eval/schema.js +128 -0
- package/dist/eval/schema.js.map +1 -0
- package/dist/eval/skill-scanner.d.ts +8 -0
- package/dist/eval/skill-scanner.js +44 -0
- package/dist/eval/skill-scanner.js.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/marketplace/index.d.ts +2 -2
- package/dist/marketplace/index.js +1 -1
- package/dist/marketplace/index.js.map +1 -1
- package/dist/marketplace/marketplace.d.ts +13 -0
- package/dist/marketplace/marketplace.js +35 -0
- package/dist/marketplace/marketplace.js.map +1 -1
- 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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"}
|
package/dist/commands/add.js
CHANGED
|
@@ -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
|
-
import { ensureLockfile, writeLockfile, readLockfile } from "../lockfile/index.js";
|
|
13
|
+
import { ensureLockfile, writeLockfile, readLockfile, removeSkillFromLock } 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";
|
|
@@ -22,7 +22,7 @@ import { parseSkillsShUrl, isCompleteParsed, isIncompleteParsed, } from "../reso
|
|
|
22
22
|
import { bold, green, red, yellow, dim, cyan, spinner, } from "../utils/output.js";
|
|
23
23
|
import { isTTY, createPrompter } from "../utils/prompts.js";
|
|
24
24
|
import { installSymlink, installCopy } from "../installer/canonical.js";
|
|
25
|
-
import { isClaudeCliAvailable, registerMarketplace, deregisterMarketplace, installNativePlugin, } from "../utils/claude-cli.js";
|
|
25
|
+
import { isClaudeCliAvailable, registerMarketplace, deregisterMarketplace, installNativePlugin, uninstallNativePlugin, } from "../utils/claude-cli.js";
|
|
26
26
|
import { getMarketplaceName } from "../marketplace/index.js";
|
|
27
27
|
async function parseManifestFromContentsApi(data) {
|
|
28
28
|
// Prefer download_url for raw content
|
|
@@ -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
|
-
|
|
122
|
-
|
|
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,8 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
|
|
|
133
163
|
}
|
|
134
164
|
// Select plugins
|
|
135
165
|
let selectedPlugins;
|
|
166
|
+
let selectedUnregistered = [];
|
|
167
|
+
let usedInteractiveCheckbox = false;
|
|
136
168
|
if (!isTTY() && !opts.yes && !opts.all) {
|
|
137
169
|
// Non-TTY: list plugins and exit with guidance
|
|
138
170
|
console.log("Available plugins:\n");
|
|
@@ -140,6 +172,13 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
|
|
|
140
172
|
const installed = installedSet.has(p.name) ? dim(" (installed)") : "";
|
|
141
173
|
console.log(` ${bold(p.name)}${installed}${p.description ? dim(` — ${p.description}`) : ""}`);
|
|
142
174
|
}
|
|
175
|
+
if (unregistered.length > 0) {
|
|
176
|
+
console.log("\nUnregistered plugins (not in marketplace.json):\n");
|
|
177
|
+
for (const u of unregistered) {
|
|
178
|
+
console.log(` ${yellow(u.name)} ${dim("(new — not in marketplace.json)")}`);
|
|
179
|
+
}
|
|
180
|
+
console.log(dim("\nUse --force --plugin <name> to install unregistered plugins."));
|
|
181
|
+
}
|
|
143
182
|
console.error(red("\nNon-interactive mode. Use --plugin <name> or --yes to select all."));
|
|
144
183
|
process.exit(1);
|
|
145
184
|
}
|
|
@@ -158,9 +197,14 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
|
|
|
158
197
|
selectedPlugins = plugins;
|
|
159
198
|
console.log(dim(`Auto-selecting all ${plugins.length} plugins (--yes/--all)`));
|
|
160
199
|
}
|
|
200
|
+
if (unregistered.length > 0) {
|
|
201
|
+
console.log(dim(` Skipping ${unregistered.length} unregistered plugin${unregistered.length === 1 ? "" : "s"}: `) +
|
|
202
|
+
dim(unregistered.map((u) => u.name).join(", ")) +
|
|
203
|
+
dim(" (use --force to include)"));
|
|
204
|
+
}
|
|
161
205
|
}
|
|
162
|
-
else if (plugins.length === 1) {
|
|
163
|
-
// Single plugin — show details and ask for confirmation
|
|
206
|
+
else if (plugins.length === 1 && unregistered.length === 0) {
|
|
207
|
+
// Single plugin, no unregistered — show details and ask for confirmation
|
|
164
208
|
const p = plugins[0];
|
|
165
209
|
const isInstalled = installedSet.has(p.name);
|
|
166
210
|
const versionTag = p.version ? ` v${p.version}` : "";
|
|
@@ -185,20 +229,92 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
|
|
|
185
229
|
selectedPlugins = plugins;
|
|
186
230
|
}
|
|
187
231
|
else {
|
|
232
|
+
// Build combined picker: registered plugins first, then unregistered
|
|
233
|
+
const combinedItems = [
|
|
234
|
+
...plugins.map((p) => ({
|
|
235
|
+
label: p.name + (installedSet.has(p.name) ? dim(" (installed)") : ""),
|
|
236
|
+
description: p.description,
|
|
237
|
+
checked: preSelected ? preSelected.includes(p.name) : installedSet.has(p.name),
|
|
238
|
+
})),
|
|
239
|
+
...unregistered.map((u) => ({
|
|
240
|
+
label: u.name + yellow(" (new — not in marketplace.json)"),
|
|
241
|
+
description: undefined,
|
|
242
|
+
checked: false,
|
|
243
|
+
})),
|
|
244
|
+
];
|
|
188
245
|
const prompter = createPrompter();
|
|
189
|
-
const indices = await prompter.promptCheckboxList(plugins
|
|
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" });
|
|
246
|
+
const indices = await prompter.promptCheckboxList(combinedItems, { title: "Select plugins to install" });
|
|
194
247
|
if (indices.length === 0) {
|
|
195
248
|
console.log(dim("No plugins selected. Aborting."));
|
|
196
249
|
return;
|
|
197
250
|
}
|
|
198
|
-
|
|
251
|
+
// Partition selections into registered and unregistered
|
|
252
|
+
selectedPlugins = [];
|
|
253
|
+
selectedUnregistered = [];
|
|
254
|
+
for (const i of indices) {
|
|
255
|
+
if (i < plugins.length) {
|
|
256
|
+
selectedPlugins.push(plugins[i]);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
selectedUnregistered.push(unregistered[i - plugins.length]);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
usedInteractiveCheckbox = true;
|
|
199
263
|
}
|
|
200
264
|
// Attempt native Claude Code install
|
|
201
265
|
const hasClaude = !opts.copy && isClaudeCliAvailable();
|
|
266
|
+
// Uninstall plugins that were previously installed but now unchecked
|
|
267
|
+
const selectedNames = new Set(selectedPlugins.map((p) => p.name));
|
|
268
|
+
const toUninstall = usedInteractiveCheckbox
|
|
269
|
+
? [...installedSet].filter((name) => !selectedNames.has(name))
|
|
270
|
+
: [];
|
|
271
|
+
if (toUninstall.length > 0) {
|
|
272
|
+
console.log(dim(`\nUninstalling ${toUninstall.length} deselected plugin${toUninstall.length === 1 ? "" : "s"}...\n`));
|
|
273
|
+
const agents = await detectInstalledAgents();
|
|
274
|
+
for (const skillName of toUninstall) {
|
|
275
|
+
// Remove skill directories from all agents
|
|
276
|
+
let removedCount = 0;
|
|
277
|
+
for (const agent of agents) {
|
|
278
|
+
const localDir = join(process.cwd(), agent.localSkillsDir, skillName);
|
|
279
|
+
const globalDir = resolveTilde(join(agent.globalSkillsDir, skillName));
|
|
280
|
+
for (const dir of [localDir, globalDir]) {
|
|
281
|
+
if (existsSync(dir)) {
|
|
282
|
+
try {
|
|
283
|
+
rmSync(dir, { recursive: true, force: true });
|
|
284
|
+
removedCount++;
|
|
285
|
+
}
|
|
286
|
+
catch { /* ignore removal errors */ }
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Uninstall from Claude Code native plugin system
|
|
291
|
+
if (hasClaude && marketplaceName) {
|
|
292
|
+
uninstallNativePlugin(skillName, marketplaceName);
|
|
293
|
+
}
|
|
294
|
+
// Remove from lockfile
|
|
295
|
+
removeSkillFromLock(skillName, lockDir);
|
|
296
|
+
console.log(red(` ✗ ${bold(skillName)} uninstalled`) + (removedCount > 0 ? dim(` (${removedCount} location${removedCount === 1 ? "" : "s"})`) : ""));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// Gate: unregistered plugins require --force
|
|
300
|
+
if (selectedUnregistered.length > 0 && !opts.force) {
|
|
301
|
+
console.log(yellow(`\n ${selectedUnregistered.length} unregistered plugin${selectedUnregistered.length === 1 ? "" : "s"} selected but --force not set.`) + "\n" +
|
|
302
|
+
dim(" Unregistered plugins have not been scanned or verified.") + "\n" +
|
|
303
|
+
dim(" Use --force to install them anyway, or submit the repo for re-scanning.") + "\n");
|
|
304
|
+
// Offer resubmission in interactive mode
|
|
305
|
+
if (isTTY()) {
|
|
306
|
+
const prompter = createPrompter();
|
|
307
|
+
const resubmit = await prompter.promptConfirm(`Submit ${bold(`${owner}/${repo}`)} for re-scanning?`, true);
|
|
308
|
+
if (resubmit) {
|
|
309
|
+
await triggerResubmission(owner, repo);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
console.log(dim(` Submit manually: https://verified-skill.com/submit?repo=${owner}/${repo}`));
|
|
314
|
+
}
|
|
315
|
+
// Clear unregistered selection — only install registered plugins
|
|
316
|
+
selectedUnregistered = [];
|
|
317
|
+
}
|
|
202
318
|
let marketplaceRegistered = false;
|
|
203
319
|
if (hasClaude) {
|
|
204
320
|
// Register marketplace via git URL — Claude Code clones to its own
|
|
@@ -220,7 +336,7 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
|
|
|
220
336
|
}
|
|
221
337
|
}
|
|
222
338
|
}
|
|
223
|
-
// Install
|
|
339
|
+
// Install registered plugins
|
|
224
340
|
const results = [];
|
|
225
341
|
for (const plugin of selectedPlugins) {
|
|
226
342
|
if (hasClaude && marketplaceRegistered && marketplaceName) {
|
|
@@ -256,15 +372,28 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
|
|
|
256
372
|
}
|
|
257
373
|
}
|
|
258
374
|
}
|
|
375
|
+
// Install unregistered plugins via extraction (--force only)
|
|
376
|
+
for (const unreg of selectedUnregistered) {
|
|
377
|
+
console.log(yellow(` Installing unregistered plugin: ${bold(unreg.name)} (--force)`));
|
|
378
|
+
try {
|
|
379
|
+
await installRepoPlugin(`${owner}/${repo}`, unreg.name, opts, unreg.source);
|
|
380
|
+
results.push({ name: unreg.name, installed: true, method: "extraction-unregistered" });
|
|
381
|
+
}
|
|
382
|
+
catch (err) {
|
|
383
|
+
console.error(red(` ✗ ${unreg.name}: ${err.message}`));
|
|
384
|
+
results.push({ name: unreg.name, installed: false, method: "failed" });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
259
387
|
// Update lockfile
|
|
260
388
|
const lockForWrite = ensureLockfile(lockDir);
|
|
261
389
|
for (const r of results) {
|
|
262
390
|
if (r.installed) {
|
|
263
|
-
const
|
|
391
|
+
const isUnregistered = r.method === "extraction-unregistered";
|
|
392
|
+
const pluginVersion = isUnregistered ? "0.0.0" : (getPluginVersion(r.name, manifestContent) || "0.0.0");
|
|
264
393
|
lockForWrite.skills[r.name] = {
|
|
265
394
|
version: pluginVersion,
|
|
266
395
|
sha: "",
|
|
267
|
-
tier: "VERIFIED",
|
|
396
|
+
tier: isUnregistered ? "UNSCANNED" : "VERIFIED",
|
|
268
397
|
installedAt: new Date().toISOString(),
|
|
269
398
|
source: `marketplace:${owner}/${repo}#${r.name}`,
|
|
270
399
|
marketplace: marketplaceName || undefined,
|
|
@@ -285,9 +414,22 @@ async function installMarketplaceRepo(owner, repo, manifestContent, opts, preSel
|
|
|
285
414
|
// Summary
|
|
286
415
|
const installed = results.filter((r) => r.installed);
|
|
287
416
|
const failed = results.filter((r) => !r.installed);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
417
|
+
const parts = [];
|
|
418
|
+
if (installed.length > 0 || results.length > 0) {
|
|
419
|
+
parts.push(`${green(bold(`${installed.length} installed`))}`);
|
|
420
|
+
}
|
|
421
|
+
if (failed.length > 0) {
|
|
422
|
+
parts.push(`${red(bold(`${failed.length} failed`))}`);
|
|
423
|
+
}
|
|
424
|
+
if (toUninstall.length > 0) {
|
|
425
|
+
parts.push(`${red(bold(`${toUninstall.length} uninstalled`))}`);
|
|
426
|
+
}
|
|
427
|
+
if (parts.length > 0) {
|
|
428
|
+
console.log(`\n${parts.join(", ")} of ${results.length + toUninstall.length} plugins`);
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
console.log(dim("\nNo changes made."));
|
|
432
|
+
}
|
|
291
433
|
if (hasClaude && marketplaceRegistered && marketplaceName) {
|
|
292
434
|
console.log(dim(`\nManage: claude plugin list | claude plugin uninstall "<plugin>@${marketplaceName}"`));
|
|
293
435
|
}
|
|
@@ -1028,7 +1170,7 @@ async function installAllRepoPlugins(ownerRepo, opts) {
|
|
|
1028
1170
|
// ---------------------------------------------------------------------------
|
|
1029
1171
|
// Remote plugin installation from a GitHub repository with marketplace.json
|
|
1030
1172
|
// ---------------------------------------------------------------------------
|
|
1031
|
-
async function installRepoPlugin(ownerRepo, pluginName, opts) {
|
|
1173
|
+
async function installRepoPlugin(ownerRepo, pluginName, opts, overrideSource) {
|
|
1032
1174
|
const [owner, repo] = ownerRepo.split("/");
|
|
1033
1175
|
if (!owner || !repo) {
|
|
1034
1176
|
throw new Error("--repo must be in owner/repo format (e.g. anton-abyzov/vskill)");
|
|
@@ -1054,14 +1196,14 @@ async function installRepoPlugin(ownerRepo, pluginName, opts) {
|
|
|
1054
1196
|
throw err;
|
|
1055
1197
|
throw new Error(`Network error: ${err.message}`);
|
|
1056
1198
|
}
|
|
1057
|
-
// Find the plugin in the marketplace
|
|
1058
|
-
const pluginSource = getPluginSource(pluginName, manifestContent);
|
|
1199
|
+
// Find the plugin in the marketplace (or use override for unregistered plugins)
|
|
1200
|
+
const pluginSource = overrideSource || getPluginSource(pluginName, manifestContent);
|
|
1059
1201
|
if (!pluginSource) {
|
|
1060
1202
|
const available = getAvailablePlugins(manifestContent).map((p) => p.name);
|
|
1061
1203
|
throw new Error(`Plugin "${pluginName}" not found in marketplace.json. ` +
|
|
1062
1204
|
`Available plugins: ${available.join(", ")}`);
|
|
1063
1205
|
}
|
|
1064
|
-
const pluginVersion = getPluginVersion(pluginName, manifestContent) || "0.0.0";
|
|
1206
|
+
const pluginVersion = overrideSource ? "0.0.0" : (getPluginVersion(pluginName, manifestContent) || "0.0.0");
|
|
1065
1207
|
const pluginPath = pluginSource.replace(/^\.\//, "");
|
|
1066
1208
|
// Blocklist + rejection check BEFORE fetching content
|
|
1067
1209
|
const safety = await checkInstallSafety(pluginName);
|