@sstar/skill-install 1.0.0 → 1.1.0
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 +122 -122
- package/dist/cli.js +371 -12
- package/dist/core/errors.d.ts +1 -0
- package/dist/core/errors.js +1 -0
- package/dist/core/logger.js +1 -1
- package/dist/core/package-detector.d.ts +44 -0
- package/dist/core/package-detector.js +299 -0
- package/dist/http/http-client.js +1 -2
- package/dist/installer/install-service.d.ts +67 -5
- package/dist/installer/install-service.js +265 -56
- package/dist/plugins/plugin-manager.d.ts +95 -0
- package/dist/plugins/plugin-manager.js +475 -0
- package/dist/plugins/plugin-validator.d.ts +35 -0
- package/dist/plugins/plugin-validator.js +151 -0
- package/dist/plugins/types.d.ts +197 -0
- package/dist/plugins/types.js +16 -0
- package/dist/wiki/index.d.ts +3 -0
- package/dist/wiki/index.js +9 -0
- package/dist/wiki/skill-selector.d.ts +13 -0
- package/dist/wiki/skill-selector.js +104 -0
- package/dist/wiki/wiki-parser.d.ts +26 -0
- package/dist/wiki/wiki-parser.js +105 -0
- package/dist/wiki/wiki-searcher.d.ts +33 -0
- package/dist/wiki/wiki-searcher.js +98 -0
- package/package.json +1 -1
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PluginManager = void 0;
|
|
37
|
+
const promises_1 = require("fs/promises");
|
|
38
|
+
const path_1 = require("path");
|
|
39
|
+
const os_1 = require("os");
|
|
40
|
+
const logger_1 = require("../core/logger");
|
|
41
|
+
const errors_1 = require("../core/errors");
|
|
42
|
+
const plugin_validator_1 = require("./plugin-validator");
|
|
43
|
+
/**
|
|
44
|
+
* Manages Claude Code plugins and marketplaces
|
|
45
|
+
*/
|
|
46
|
+
class PluginManager {
|
|
47
|
+
constructor() {
|
|
48
|
+
this.logger = new logger_1.Logger('PluginManager');
|
|
49
|
+
this.validator = new plugin_validator_1.PluginValidator();
|
|
50
|
+
this.pluginsDir = (0, path_1.join)((0, os_1.homedir)(), '.claude', 'plugins');
|
|
51
|
+
this.marketplacesDir = (0, path_1.join)(this.pluginsDir, 'marketplaces');
|
|
52
|
+
this.cacheDir = (0, path_1.join)(this.pluginsDir, 'cache');
|
|
53
|
+
this.knownMarketplacesFile = (0, path_1.join)(this.pluginsDir, 'known_marketplaces.json');
|
|
54
|
+
this.installedPluginsFile = (0, path_1.join)(this.pluginsDir, 'installed_plugins.json');
|
|
55
|
+
this.settingsFile = (0, path_1.join)((0, os_1.homedir)(), '.claude', 'settings.json');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Initialize directories and files
|
|
59
|
+
*/
|
|
60
|
+
async initialize() {
|
|
61
|
+
await (0, promises_1.mkdir)(this.pluginsDir, { recursive: true });
|
|
62
|
+
await (0, promises_1.mkdir)(this.marketplacesDir, { recursive: true });
|
|
63
|
+
await (0, promises_1.mkdir)(this.cacheDir, { recursive: true });
|
|
64
|
+
// Initialize known_marketplaces.json if not exists
|
|
65
|
+
try {
|
|
66
|
+
await (0, promises_1.readFile)(this.knownMarketplacesFile, 'utf-8');
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
await (0, promises_1.writeFile)(this.knownMarketplacesFile, JSON.stringify({}, null, 2));
|
|
70
|
+
}
|
|
71
|
+
// Initialize installed_plugins.json if not exists
|
|
72
|
+
try {
|
|
73
|
+
await (0, promises_1.readFile)(this.installedPluginsFile, 'utf-8');
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
const empty = { version: 2, plugins: {} };
|
|
77
|
+
await (0, promises_1.writeFile)(this.installedPluginsFile, JSON.stringify(empty, null, 2));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Install a package (auto-detects Marketplace or Plugin)
|
|
82
|
+
*/
|
|
83
|
+
async installPackage(extractDir, structure) {
|
|
84
|
+
await this.initialize();
|
|
85
|
+
switch (structure.type) {
|
|
86
|
+
case 'marketplace':
|
|
87
|
+
return await this.installMarketplace(extractDir, structure);
|
|
88
|
+
case 'plugin':
|
|
89
|
+
return await this.installStandalonePlugin(extractDir, structure.plugins[0]);
|
|
90
|
+
default:
|
|
91
|
+
throw new errors_1.WikiError(errors_1.ErrorType.INVALID_PACKAGE, `Unsupported package type: ${structure.type}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Install a Marketplace
|
|
96
|
+
* 1. Copy to marketplaces/{name}/
|
|
97
|
+
* 2. Update known_marketplaces.json
|
|
98
|
+
* 3. Optionally install plugins from it
|
|
99
|
+
*/
|
|
100
|
+
async installMarketplace(extractDir, structure) {
|
|
101
|
+
const marketplace = structure.marketplace;
|
|
102
|
+
const targetDir = (0, path_1.join)(this.marketplacesDir, marketplace.name);
|
|
103
|
+
this.logger.info(`Installing marketplace "${marketplace.name}" to ${targetDir}`);
|
|
104
|
+
// Check if already exists
|
|
105
|
+
if (await this.pathExists(targetDir)) {
|
|
106
|
+
this.logger.warn(`Marketplace "${marketplace.name}" already exists, will overwrite`);
|
|
107
|
+
}
|
|
108
|
+
// 1. Copy marketplace directory
|
|
109
|
+
await this.copyDirectory(extractDir, targetDir);
|
|
110
|
+
// 2. Update known_marketplaces.json
|
|
111
|
+
// Use 'directory' source type for locally-installed marketplaces
|
|
112
|
+
await this.addKnownMarketplace({
|
|
113
|
+
source: { source: 'directory', path: targetDir },
|
|
114
|
+
installLocation: targetDir,
|
|
115
|
+
lastUpdated: new Date().toISOString(),
|
|
116
|
+
}, marketplace.name);
|
|
117
|
+
this.logger.info(`Marketplace "${marketplace.name}" installed successfully`);
|
|
118
|
+
// Return result - plugins installation is handled separately
|
|
119
|
+
return {
|
|
120
|
+
type: 'marketplace',
|
|
121
|
+
success: true,
|
|
122
|
+
marketplace: marketplace.name,
|
|
123
|
+
path: targetDir,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Install a plugin from an already-installed marketplace
|
|
128
|
+
*/
|
|
129
|
+
async installPluginFromMarketplace(marketplaceName, pluginName, version) {
|
|
130
|
+
await this.initialize();
|
|
131
|
+
const marketplaceDir = (0, path_1.join)(this.marketplacesDir, marketplaceName);
|
|
132
|
+
const marketplaceJsonPath = (0, path_1.join)(marketplaceDir, '.claude-plugin', 'marketplace.json');
|
|
133
|
+
// Check if marketplace exists
|
|
134
|
+
if (!await this.pathExists(marketplaceJsonPath)) {
|
|
135
|
+
throw new errors_1.WikiError(errors_1.ErrorType.NOT_FOUND, `Marketplace "${marketplaceName}" not found. Please install it first.`);
|
|
136
|
+
}
|
|
137
|
+
// Read marketplace.json to get plugin info
|
|
138
|
+
const marketplace = await this.validator.validateMarketplaceOrThrow(marketplaceDir);
|
|
139
|
+
const pluginDef = marketplace.plugins.find(p => p.name === pluginName);
|
|
140
|
+
if (!pluginDef) {
|
|
141
|
+
throw new errors_1.WikiError(errors_1.ErrorType.NOT_FOUND, `Plugin "${pluginName}" not found in marketplace "${marketplaceName}"`);
|
|
142
|
+
}
|
|
143
|
+
// Resolve plugin source path
|
|
144
|
+
const pluginSourcePath = this.resolvePluginSourcePath(marketplaceDir, pluginDef.source, marketplace.metadata?.pluginRoot);
|
|
145
|
+
if (!pluginSourcePath || !await this.pathExists(pluginSourcePath)) {
|
|
146
|
+
throw new errors_1.WikiError(errors_1.ErrorType.NOT_FOUND, `Plugin source not found at: ${pluginSourcePath}`);
|
|
147
|
+
}
|
|
148
|
+
// Determine version
|
|
149
|
+
const versionToInstall = version || pluginDef.version || 'unknown';
|
|
150
|
+
// Target path in cache
|
|
151
|
+
const targetPath = (0, path_1.join)(this.cacheDir, marketplaceName, pluginName, versionToInstall);
|
|
152
|
+
this.logger.info(`Installing plugin "${pluginName}@${marketplaceName}" to ${targetPath}`);
|
|
153
|
+
// Copy plugin to cache
|
|
154
|
+
await (0, promises_1.mkdir)(targetPath, { recursive: true });
|
|
155
|
+
await this.copyDirectory(pluginSourcePath, targetPath);
|
|
156
|
+
// Update installed_plugins.json
|
|
157
|
+
await this.addInstalledPlugin({
|
|
158
|
+
name: `${pluginName}@${marketplaceName}`,
|
|
159
|
+
scope: 'user',
|
|
160
|
+
installPath: targetPath,
|
|
161
|
+
version: versionToInstall,
|
|
162
|
+
installedAt: new Date().toISOString(),
|
|
163
|
+
isLocal: true,
|
|
164
|
+
});
|
|
165
|
+
// Enable the plugin automatically
|
|
166
|
+
await this.enablePlugin(`${pluginName}@${marketplaceName}`);
|
|
167
|
+
this.logger.info(`Plugin "${pluginName}@${marketplaceName}" installed successfully`);
|
|
168
|
+
return {
|
|
169
|
+
type: 'plugin',
|
|
170
|
+
success: true,
|
|
171
|
+
plugin: `${pluginName}@${marketplaceName}`,
|
|
172
|
+
version: versionToInstall,
|
|
173
|
+
marketplace: marketplaceName,
|
|
174
|
+
path: targetPath,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Install multiple plugins from a marketplace
|
|
179
|
+
*/
|
|
180
|
+
async installPluginsFromMarketplace(marketplaceName, plugins, progressCallback) {
|
|
181
|
+
const results = [];
|
|
182
|
+
for (let i = 0; i < plugins.length; i++) {
|
|
183
|
+
const plugin = plugins[i];
|
|
184
|
+
if (progressCallback) {
|
|
185
|
+
progressCallback(i + 1, plugins.length, plugin.name);
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const result = await this.installPluginFromMarketplace(marketplaceName, plugin.name, plugin.version);
|
|
189
|
+
results.push(result);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
this.logger.error(`Failed to install plugin "${plugin.name}": ${error}`);
|
|
193
|
+
results.push({
|
|
194
|
+
type: 'plugin',
|
|
195
|
+
success: false,
|
|
196
|
+
plugin: plugin.name,
|
|
197
|
+
path: '',
|
|
198
|
+
error: error instanceof Error ? error.message : String(error),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return results;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Install a standalone plugin (not from a marketplace)
|
|
206
|
+
*/
|
|
207
|
+
async installStandalonePlugin(extractDir, plugin) {
|
|
208
|
+
// Install to cache/_standalone/
|
|
209
|
+
const version = plugin.version || '1.0.0';
|
|
210
|
+
const targetPath = (0, path_1.join)(this.cacheDir, '_standalone', plugin.name, version);
|
|
211
|
+
this.logger.info(`Installing standalone plugin "${plugin.name}" to ${targetPath}`);
|
|
212
|
+
await (0, promises_1.mkdir)(targetPath, { recursive: true });
|
|
213
|
+
await this.copyDirectory(extractDir, targetPath);
|
|
214
|
+
// Update installed_plugins.json
|
|
215
|
+
await this.addInstalledPlugin({
|
|
216
|
+
name: plugin.name,
|
|
217
|
+
scope: 'user',
|
|
218
|
+
installPath: targetPath,
|
|
219
|
+
version,
|
|
220
|
+
installedAt: new Date().toISOString(),
|
|
221
|
+
isLocal: true,
|
|
222
|
+
});
|
|
223
|
+
// Enable the plugin automatically
|
|
224
|
+
await this.enablePlugin(plugin.name);
|
|
225
|
+
this.logger.info(`Standalone plugin "${plugin.name}" installed successfully`);
|
|
226
|
+
return {
|
|
227
|
+
type: 'plugin',
|
|
228
|
+
success: true,
|
|
229
|
+
plugin: plugin.name,
|
|
230
|
+
version,
|
|
231
|
+
path: targetPath,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* List installed plugins
|
|
236
|
+
*/
|
|
237
|
+
async listInstalledPlugins() {
|
|
238
|
+
try {
|
|
239
|
+
const content = await (0, promises_1.readFile)(this.installedPluginsFile, 'utf-8');
|
|
240
|
+
const data = JSON.parse(content);
|
|
241
|
+
const plugins = [];
|
|
242
|
+
for (const [key, entries] of Object.entries(data.plugins)) {
|
|
243
|
+
plugins.push(...entries);
|
|
244
|
+
}
|
|
245
|
+
return plugins;
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
this.logger.error(`Failed to read installed plugins: ${error}`);
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* List known marketplaces
|
|
254
|
+
*/
|
|
255
|
+
async listKnownMarketplaces() {
|
|
256
|
+
try {
|
|
257
|
+
const content = await (0, promises_1.readFile)(this.knownMarketplacesFile, 'utf-8');
|
|
258
|
+
const data = JSON.parse(content);
|
|
259
|
+
return Object.values(data);
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
this.logger.error(`Failed to read known marketplaces: ${error}`);
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Check if a marketplace is known
|
|
268
|
+
*/
|
|
269
|
+
async isMarketplaceKnown(marketplaceName) {
|
|
270
|
+
const known = await this.listKnownMarketplaces();
|
|
271
|
+
return known.some(m => m.installLocation.includes(marketplaceName));
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Check if a plugin is installed
|
|
275
|
+
*/
|
|
276
|
+
async isPluginInstalled(pluginKey) {
|
|
277
|
+
try {
|
|
278
|
+
const content = await (0, promises_1.readFile)(this.installedPluginsFile, 'utf-8');
|
|
279
|
+
const data = JSON.parse(content);
|
|
280
|
+
return pluginKey in data.plugins && data.plugins[pluginKey].length > 0;
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Uninstall a plugin
|
|
288
|
+
*/
|
|
289
|
+
async uninstallPlugin(pluginKey) {
|
|
290
|
+
const { rm } = await Promise.resolve().then(() => __importStar(require('fs/promises')));
|
|
291
|
+
const content = await (0, promises_1.readFile)(this.installedPluginsFile, 'utf-8');
|
|
292
|
+
const data = JSON.parse(content);
|
|
293
|
+
if (!(pluginKey in data.plugins)) {
|
|
294
|
+
throw new errors_1.WikiError(errors_1.ErrorType.NOT_FOUND, `Plugin "${pluginKey}" not found`);
|
|
295
|
+
}
|
|
296
|
+
// Remove all entries
|
|
297
|
+
for (const entry of data.plugins[pluginKey]) {
|
|
298
|
+
try {
|
|
299
|
+
await rm(entry.installPath, { recursive: true, force: true });
|
|
300
|
+
this.logger.info(`Removed plugin at: ${entry.installPath}`);
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
this.logger.warn(`Failed to remove ${entry.installPath}: ${error}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
delete data.plugins[pluginKey];
|
|
307
|
+
await (0, promises_1.writeFile)(this.installedPluginsFile, JSON.stringify(data, null, 2));
|
|
308
|
+
this.logger.info(`Plugin "${pluginKey}" uninstalled`);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Remove a marketplace
|
|
312
|
+
*/
|
|
313
|
+
async removeMarketplace(marketplaceName) {
|
|
314
|
+
const { rm } = await Promise.resolve().then(() => __importStar(require('fs/promises')));
|
|
315
|
+
const content = await (0, promises_1.readFile)(this.knownMarketplacesFile, 'utf-8');
|
|
316
|
+
const data = JSON.parse(content);
|
|
317
|
+
if (!(marketplaceName in data)) {
|
|
318
|
+
throw new errors_1.WikiError(errors_1.ErrorType.NOT_FOUND, `Marketplace "${marketplaceName}" not found`);
|
|
319
|
+
}
|
|
320
|
+
const marketplaceInfo = data[marketplaceName];
|
|
321
|
+
// Remove marketplace directory
|
|
322
|
+
try {
|
|
323
|
+
await rm(marketplaceInfo.installLocation, { recursive: true, force: true });
|
|
324
|
+
this.logger.info(`Removed marketplace at: ${marketplaceInfo.installLocation}`);
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
this.logger.warn(`Failed to remove marketplace directory: ${error}`);
|
|
328
|
+
}
|
|
329
|
+
// Also remove cached plugins from this marketplace
|
|
330
|
+
const cachePath = (0, path_1.join)(this.cacheDir, marketplaceName);
|
|
331
|
+
try {
|
|
332
|
+
await rm(cachePath, { recursive: true, force: true });
|
|
333
|
+
this.logger.info(`Removed cached plugins from: ${cachePath}`);
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
this.logger.warn(`Failed to remove cache directory: ${error}`);
|
|
337
|
+
}
|
|
338
|
+
delete data[marketplaceName];
|
|
339
|
+
await (0, promises_1.writeFile)(this.knownMarketplacesFile, JSON.stringify(data, null, 2));
|
|
340
|
+
this.logger.info(`Marketplace "${marketplaceName}" removed`);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Resolve plugin source path from marketplace
|
|
344
|
+
*/
|
|
345
|
+
resolvePluginSourcePath(marketplaceDir, source, pluginRoot) {
|
|
346
|
+
let relativePath;
|
|
347
|
+
if (typeof source === 'string') {
|
|
348
|
+
relativePath = source;
|
|
349
|
+
}
|
|
350
|
+
else if (source.source === 'github' || source.source === 'url') {
|
|
351
|
+
// External source - can't resolve locally
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
relativePath = source.source || '';
|
|
356
|
+
}
|
|
357
|
+
// Apply pluginRoot if present
|
|
358
|
+
if (pluginRoot && relativePath.startsWith('./')) {
|
|
359
|
+
relativePath = (0, path_1.join)(pluginRoot, relativePath.slice(2));
|
|
360
|
+
}
|
|
361
|
+
return (0, path_1.join)(marketplaceDir, relativePath);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Add a marketplace to known_marketplaces.json
|
|
365
|
+
*/
|
|
366
|
+
async addKnownMarketplace(info, name) {
|
|
367
|
+
const content = await (0, promises_1.readFile)(this.knownMarketplacesFile, 'utf-8');
|
|
368
|
+
const data = JSON.parse(content);
|
|
369
|
+
data[name] = info;
|
|
370
|
+
await (0, promises_1.writeFile)(this.knownMarketplacesFile, JSON.stringify(data, null, 2));
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Add a plugin to installed_plugins.json
|
|
374
|
+
*/
|
|
375
|
+
async addInstalledPlugin(plugin) {
|
|
376
|
+
const content = await (0, promises_1.readFile)(this.installedPluginsFile, 'utf-8');
|
|
377
|
+
const data = JSON.parse(content);
|
|
378
|
+
const key = plugin.name;
|
|
379
|
+
if (!data.plugins[key]) {
|
|
380
|
+
data.plugins[key] = [];
|
|
381
|
+
}
|
|
382
|
+
// Remove existing entries with same scope
|
|
383
|
+
data.plugins[key] = data.plugins[key].filter((p) => p.scope !== plugin.scope);
|
|
384
|
+
data.plugins[key].push(plugin);
|
|
385
|
+
await (0, promises_1.writeFile)(this.installedPluginsFile, JSON.stringify(data, null, 2));
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Enable a plugin in settings.json
|
|
389
|
+
* This adds the plugin to the enabledPlugins object
|
|
390
|
+
*/
|
|
391
|
+
async enablePlugin(pluginKey) {
|
|
392
|
+
try {
|
|
393
|
+
let settings = {};
|
|
394
|
+
// Read existing settings
|
|
395
|
+
try {
|
|
396
|
+
const content = await (0, promises_1.readFile)(this.settingsFile, 'utf-8');
|
|
397
|
+
settings = JSON.parse(content);
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
// File doesn't exist or is empty, create new settings
|
|
401
|
+
settings = {};
|
|
402
|
+
}
|
|
403
|
+
// Initialize enabledPlugins if not exists
|
|
404
|
+
if (!settings.enabledPlugins) {
|
|
405
|
+
settings.enabledPlugins = {};
|
|
406
|
+
}
|
|
407
|
+
// Enable the plugin
|
|
408
|
+
settings.enabledPlugins[pluginKey] = true;
|
|
409
|
+
// Write settings
|
|
410
|
+
await (0, promises_1.writeFile)(this.settingsFile, JSON.stringify(settings, null, 2));
|
|
411
|
+
this.logger.info(`Plugin "${pluginKey}" enabled in settings.json`);
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
this.logger.warn(`Failed to enable plugin in settings.json: ${error}`);
|
|
415
|
+
// Don't throw - this is not a critical failure
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Disable a plugin in settings.json
|
|
420
|
+
*/
|
|
421
|
+
async disablePlugin(pluginKey) {
|
|
422
|
+
try {
|
|
423
|
+
let settings = {};
|
|
424
|
+
// Read existing settings
|
|
425
|
+
try {
|
|
426
|
+
const content = await (0, promises_1.readFile)(this.settingsFile, 'utf-8');
|
|
427
|
+
settings = JSON.parse(content);
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
// File doesn't exist, nothing to disable
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
// Remove from enabledPlugins
|
|
434
|
+
if (settings.enabledPlugins && pluginKey in settings.enabledPlugins) {
|
|
435
|
+
delete settings.enabledPlugins[pluginKey];
|
|
436
|
+
// Write settings
|
|
437
|
+
await (0, promises_1.writeFile)(this.settingsFile, JSON.stringify(settings, null, 2));
|
|
438
|
+
this.logger.info(`Plugin "${pluginKey}" disabled in settings.json`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
catch (error) {
|
|
442
|
+
this.logger.warn(`Failed to disable plugin in settings.json: ${error}`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Copy a directory recursively
|
|
447
|
+
*/
|
|
448
|
+
async copyDirectory(source, target) {
|
|
449
|
+
await (0, promises_1.mkdir)(target, { recursive: true });
|
|
450
|
+
const entries = await (0, promises_1.readdir)(source, { withFileTypes: true });
|
|
451
|
+
for (const entry of entries) {
|
|
452
|
+
const srcPath = (0, path_1.join)(source, entry.name);
|
|
453
|
+
const tgtPath = (0, path_1.join)(target, entry.name);
|
|
454
|
+
if (entry.isDirectory()) {
|
|
455
|
+
await this.copyDirectory(srcPath, tgtPath);
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
await (0, promises_1.cp)(srcPath, tgtPath);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Check if a path exists
|
|
464
|
+
*/
|
|
465
|
+
async pathExists(path) {
|
|
466
|
+
try {
|
|
467
|
+
await (0, promises_1.stat)(path);
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
catch {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
exports.PluginManager = PluginManager;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { PluginMetadata, MarketplaceMetadata, PluginDefinition } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Validation result
|
|
4
|
+
*/
|
|
5
|
+
export interface ValidationResult<T> {
|
|
6
|
+
valid: boolean;
|
|
7
|
+
metadata?: T;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Validates plugin.json and marketplace.json files
|
|
12
|
+
*/
|
|
13
|
+
export declare class PluginValidator {
|
|
14
|
+
private readonly logger;
|
|
15
|
+
/**
|
|
16
|
+
* Validate a plugin.json file
|
|
17
|
+
*/
|
|
18
|
+
validate(pluginDir: string): Promise<ValidationResult<PluginMetadata>>;
|
|
19
|
+
/**
|
|
20
|
+
* Validate plugin.json and throw WikiError if invalid
|
|
21
|
+
*/
|
|
22
|
+
validateOrThrow(pluginDir: string): Promise<PluginMetadata>;
|
|
23
|
+
/**
|
|
24
|
+
* Validate a marketplace.json file
|
|
25
|
+
*/
|
|
26
|
+
validateMarketplace(marketplaceDir: string): Promise<ValidationResult<MarketplaceMetadata>>;
|
|
27
|
+
/**
|
|
28
|
+
* Validate marketplace.json and throw WikiError if invalid
|
|
29
|
+
*/
|
|
30
|
+
validateMarketplaceOrThrow(marketplaceDir: string): Promise<MarketplaceMetadata>;
|
|
31
|
+
/**
|
|
32
|
+
* Validate a plugin definition from marketplace.json
|
|
33
|
+
*/
|
|
34
|
+
validatePluginDefinition(pluginDef: PluginDefinition): ValidationResult<PluginDefinition>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PluginValidator = void 0;
|
|
4
|
+
const promises_1 = require("fs/promises");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const logger_1 = require("../core/logger");
|
|
7
|
+
const errors_1 = require("../core/errors");
|
|
8
|
+
/**
|
|
9
|
+
* Validates plugin.json and marketplace.json files
|
|
10
|
+
*/
|
|
11
|
+
class PluginValidator {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.logger = new logger_1.Logger('PluginValidator');
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Validate a plugin.json file
|
|
17
|
+
*/
|
|
18
|
+
async validate(pluginDir) {
|
|
19
|
+
const pluginJsonPath = (0, path_1.join)(pluginDir, 'plugin.json');
|
|
20
|
+
try {
|
|
21
|
+
const content = await (0, promises_1.readFile)(pluginJsonPath, 'utf-8');
|
|
22
|
+
const data = JSON.parse(content);
|
|
23
|
+
// Validate required fields
|
|
24
|
+
if (!data.name || typeof data.name !== 'string') {
|
|
25
|
+
return {
|
|
26
|
+
valid: false,
|
|
27
|
+
error: 'Missing or invalid "name" field in plugin.json'
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
this.logger.info(`Valid plugin found: ${data.name}`);
|
|
31
|
+
return {
|
|
32
|
+
valid: true,
|
|
33
|
+
metadata: {
|
|
34
|
+
name: data.name,
|
|
35
|
+
version: data.version,
|
|
36
|
+
description: data.description,
|
|
37
|
+
author: data.author,
|
|
38
|
+
homepage: data.homepage,
|
|
39
|
+
repository: data.repository,
|
|
40
|
+
license: data.license,
|
|
41
|
+
keywords: data.keywords,
|
|
42
|
+
commands: data.commands,
|
|
43
|
+
agents: data.agents,
|
|
44
|
+
hooks: data.hooks,
|
|
45
|
+
mcpServers: data.mcpServers,
|
|
46
|
+
lspServers: data.lspServers,
|
|
47
|
+
claudeCode: data.claudeCode,
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
53
|
+
this.logger.error(`Validation error: ${errorMsg}`);
|
|
54
|
+
return {
|
|
55
|
+
valid: false,
|
|
56
|
+
error: errorMsg
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Validate plugin.json and throw WikiError if invalid
|
|
62
|
+
*/
|
|
63
|
+
async validateOrThrow(pluginDir) {
|
|
64
|
+
const result = await this.validate(pluginDir);
|
|
65
|
+
if (!result.valid) {
|
|
66
|
+
throw new errors_1.WikiError(errors_1.ErrorType.INVALID_SKILL, result.error || 'Invalid plugin', result.error ? new Error(result.error) : undefined);
|
|
67
|
+
}
|
|
68
|
+
return result.metadata;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Validate a marketplace.json file
|
|
72
|
+
*/
|
|
73
|
+
async validateMarketplace(marketplaceDir) {
|
|
74
|
+
const marketplaceJsonPath = (0, path_1.join)(marketplaceDir, '.claude-plugin', 'marketplace.json');
|
|
75
|
+
try {
|
|
76
|
+
const content = await (0, promises_1.readFile)(marketplaceJsonPath, 'utf-8');
|
|
77
|
+
const data = JSON.parse(content);
|
|
78
|
+
// Validate required fields
|
|
79
|
+
if (!data.name || typeof data.name !== 'string') {
|
|
80
|
+
return {
|
|
81
|
+
valid: false,
|
|
82
|
+
error: 'Missing or invalid "name" field in marketplace.json'
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (!data.owner || !data.owner.name) {
|
|
86
|
+
return {
|
|
87
|
+
valid: false,
|
|
88
|
+
error: 'Missing or invalid "owner" field in marketplace.json'
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (!Array.isArray(data.plugins)) {
|
|
92
|
+
return {
|
|
93
|
+
valid: false,
|
|
94
|
+
error: 'Missing or invalid "plugins" array in marketplace.json'
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
this.logger.info(`Valid marketplace found: ${data.name} with ${data.plugins.length} plugin(s)`);
|
|
98
|
+
return {
|
|
99
|
+
valid: true,
|
|
100
|
+
metadata: {
|
|
101
|
+
name: data.name,
|
|
102
|
+
description: data.description,
|
|
103
|
+
version: data.version,
|
|
104
|
+
owner: data.owner,
|
|
105
|
+
plugins: data.plugins,
|
|
106
|
+
metadata: data.metadata,
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
112
|
+
this.logger.error(`Marketplace validation error: ${errorMsg}`);
|
|
113
|
+
return {
|
|
114
|
+
valid: false,
|
|
115
|
+
error: errorMsg
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Validate marketplace.json and throw WikiError if invalid
|
|
121
|
+
*/
|
|
122
|
+
async validateMarketplaceOrThrow(marketplaceDir) {
|
|
123
|
+
const result = await this.validateMarketplace(marketplaceDir);
|
|
124
|
+
if (!result.valid) {
|
|
125
|
+
throw new errors_1.WikiError(errors_1.ErrorType.INVALID_SKILL, result.error || 'Invalid marketplace', result.error ? new Error(result.error) : undefined);
|
|
126
|
+
}
|
|
127
|
+
return result.metadata;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Validate a plugin definition from marketplace.json
|
|
131
|
+
*/
|
|
132
|
+
validatePluginDefinition(pluginDef) {
|
|
133
|
+
if (!pluginDef.name || typeof pluginDef.name !== 'string') {
|
|
134
|
+
return {
|
|
135
|
+
valid: false,
|
|
136
|
+
error: 'Missing or invalid "name" field in plugin definition'
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (!pluginDef.source) {
|
|
140
|
+
return {
|
|
141
|
+
valid: false,
|
|
142
|
+
error: 'Missing "source" field in plugin definition'
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
valid: true,
|
|
147
|
+
metadata: pluginDef
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
exports.PluginValidator = PluginValidator;
|