@sstar/skill-install 1.0.1 → 1.1.1

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.
@@ -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;