claude-code-templates 1.21.12 ā 1.22.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.
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const express = require('express');
|
|
5
|
+
const open = require('open');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
|
|
8
|
+
class PluginDashboard {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
this.app = express();
|
|
12
|
+
this.port = 3336;
|
|
13
|
+
this.httpServer = null;
|
|
14
|
+
this.homeDir = os.homedir();
|
|
15
|
+
this.claudeDir = path.join(this.homeDir, '.claude');
|
|
16
|
+
this.settingsFile = path.join(this.claudeDir, 'settings.json');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async initialize() {
|
|
20
|
+
// Check if Claude directory exists
|
|
21
|
+
if (!(await fs.pathExists(this.claudeDir))) {
|
|
22
|
+
throw new Error(`Claude Code directory not found at ${this.claudeDir}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Load plugin data
|
|
26
|
+
await this.loadPluginData();
|
|
27
|
+
this.setupWebServer();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async loadPluginData() {
|
|
31
|
+
try {
|
|
32
|
+
// Read Claude settings to get marketplace and plugin info
|
|
33
|
+
const settings = await this.readSettings();
|
|
34
|
+
|
|
35
|
+
// Load marketplaces
|
|
36
|
+
this.marketplaces = await this.loadMarketplaces(settings);
|
|
37
|
+
|
|
38
|
+
// Load installed plugins
|
|
39
|
+
this.plugins = await this.loadInstalledPlugins();
|
|
40
|
+
|
|
41
|
+
// Load permissions (agents, commands, hooks, MCPs)
|
|
42
|
+
this.permissions = await this.loadPermissions();
|
|
43
|
+
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error(chalk.red('Error loading plugin data:'), error.message);
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async readSettings() {
|
|
51
|
+
try {
|
|
52
|
+
if (await fs.pathExists(this.settingsFile)) {
|
|
53
|
+
const content = await fs.readFile(this.settingsFile, 'utf8');
|
|
54
|
+
const settings = JSON.parse(content);
|
|
55
|
+
|
|
56
|
+
// Extract enabled plugins from settings
|
|
57
|
+
// Plugins are stored in settings.enabledPlugins as "plugin-name@marketplace-name": true
|
|
58
|
+
this.enabledPlugins = new Set();
|
|
59
|
+
if (settings.enabledPlugins && typeof settings.enabledPlugins === 'object') {
|
|
60
|
+
for (const [key, value] of Object.entries(settings.enabledPlugins)) {
|
|
61
|
+
if (value === true) {
|
|
62
|
+
this.enabledPlugins.add(key);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return settings;
|
|
68
|
+
}
|
|
69
|
+
this.enabledPlugins = new Set();
|
|
70
|
+
return {};
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.warn(chalk.yellow('Warning: Could not read settings file'), error.message);
|
|
73
|
+
this.enabledPlugins = new Set();
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async loadMarketplaces(settings) {
|
|
79
|
+
const marketplaces = [];
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Read known_marketplaces.json from plugins directory
|
|
83
|
+
const knownMarketplacesFile = path.join(this.claudeDir, 'plugins', 'known_marketplaces.json');
|
|
84
|
+
|
|
85
|
+
if (!(await fs.pathExists(knownMarketplacesFile))) {
|
|
86
|
+
console.warn(chalk.yellow('Warning: known_marketplaces.json not found'));
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const content = await fs.readFile(knownMarketplacesFile, 'utf8');
|
|
91
|
+
const knownMarketplacesData = JSON.parse(content);
|
|
92
|
+
|
|
93
|
+
// Parse the marketplace configuration
|
|
94
|
+
for (const [name, config] of Object.entries(knownMarketplacesData)) {
|
|
95
|
+
// Load marketplace details including plugin count
|
|
96
|
+
const marketplaceInfo = await this.loadMarketplaceDetails(name, config);
|
|
97
|
+
|
|
98
|
+
// Check if marketplace is enabled (exists in the filesystem)
|
|
99
|
+
const marketplacePath = path.join(this.claudeDir, 'plugins', 'marketplaces', name);
|
|
100
|
+
const enabled = await fs.pathExists(marketplacePath);
|
|
101
|
+
|
|
102
|
+
marketplaces.push({
|
|
103
|
+
name,
|
|
104
|
+
source: config,
|
|
105
|
+
type: this.getMarketplaceType(config),
|
|
106
|
+
enabled,
|
|
107
|
+
pluginCount: marketplaceInfo.pluginCount || 0,
|
|
108
|
+
lastUpdated: config.lastUpdated || null,
|
|
109
|
+
url: this.getMarketplaceUrl(config)
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return marketplaces;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.warn(chalk.yellow('Warning: Error loading marketplaces'), error.message);
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getMarketplaceUrl(config) {
|
|
121
|
+
const source = config.source || config;
|
|
122
|
+
if (source.url) return source.url;
|
|
123
|
+
if (source.repo) return `https://github.com/${source.repo}`;
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async loadMarketplaceDetails(name, config) {
|
|
128
|
+
try {
|
|
129
|
+
// Try to read marketplace.json from the marketplace source
|
|
130
|
+
const marketplacePath = path.join(this.claudeDir, 'plugins', 'marketplaces', name);
|
|
131
|
+
const marketplaceJsonPath = path.join(marketplacePath, '.claude-plugin', 'marketplace.json');
|
|
132
|
+
|
|
133
|
+
if (await fs.pathExists(marketplaceJsonPath)) {
|
|
134
|
+
const content = await fs.readFile(marketplaceJsonPath, 'utf8');
|
|
135
|
+
const marketplaceData = JSON.parse(content);
|
|
136
|
+
return {
|
|
137
|
+
pluginCount: marketplaceData.plugins ? marketplaceData.plugins.length : 0,
|
|
138
|
+
marketplaceData
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.warn(chalk.yellow(`Warning: Could not load marketplace details for ${name}`));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { pluginCount: 0 };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
getMarketplaceType(config) {
|
|
149
|
+
// Handle nested source structure
|
|
150
|
+
const source = config.source || config;
|
|
151
|
+
|
|
152
|
+
if (source.source === 'github') return 'GitHub';
|
|
153
|
+
if (source.source === 'git') return 'Git';
|
|
154
|
+
if (source.source === 'local') return 'Local';
|
|
155
|
+
return 'Unknown';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async loadInstalledPlugins() {
|
|
159
|
+
const plugins = [];
|
|
160
|
+
const pluginsMarketplacesDir = path.join(this.claudeDir, 'plugins', 'marketplaces');
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
if (!(await fs.pathExists(pluginsMarketplacesDir))) {
|
|
164
|
+
console.warn(chalk.yellow('Warning: plugins/marketplaces directory not found'));
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const marketplaceDirs = await fs.readdir(pluginsMarketplacesDir);
|
|
169
|
+
|
|
170
|
+
for (const marketplaceDir of marketplaceDirs) {
|
|
171
|
+
const marketplacePath = path.join(pluginsMarketplacesDir, marketplaceDir);
|
|
172
|
+
const stat = await fs.stat(marketplacePath);
|
|
173
|
+
|
|
174
|
+
if (!stat.isDirectory()) continue;
|
|
175
|
+
|
|
176
|
+
// Check if this is a marketplace directory (contains .claude-plugin/marketplace.json)
|
|
177
|
+
const marketplaceJsonPath = path.join(marketplacePath, '.claude-plugin', 'marketplace.json');
|
|
178
|
+
|
|
179
|
+
if (await fs.pathExists(marketplaceJsonPath)) {
|
|
180
|
+
// Load plugins from marketplace.json
|
|
181
|
+
const marketplacePlugins = await this.loadPluginsFromMarketplace(marketplacePath, marketplaceDir);
|
|
182
|
+
plugins.push(...marketplacePlugins);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Scan for plugin directories (legacy support)
|
|
187
|
+
const pluginDirs = await fs.readdir(marketplacePath);
|
|
188
|
+
|
|
189
|
+
for (const pluginDir of pluginDirs) {
|
|
190
|
+
const pluginPath = path.join(marketplacePath, pluginDir);
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const pluginStat = await fs.stat(pluginPath);
|
|
194
|
+
if (!pluginStat.isDirectory()) continue;
|
|
195
|
+
|
|
196
|
+
// Read plugin.json
|
|
197
|
+
const pluginJsonPath = path.join(pluginPath, '.claude-plugin', 'plugin.json');
|
|
198
|
+
|
|
199
|
+
if (await fs.pathExists(pluginJsonPath)) {
|
|
200
|
+
const pluginJson = JSON.parse(await fs.readFile(pluginJsonPath, 'utf8'));
|
|
201
|
+
|
|
202
|
+
// Count components
|
|
203
|
+
const components = await this.countPluginComponents(pluginPath);
|
|
204
|
+
|
|
205
|
+
plugins.push({
|
|
206
|
+
name: pluginJson.name,
|
|
207
|
+
version: pluginJson.version || '1.0.0',
|
|
208
|
+
description: pluginJson.description || 'No description',
|
|
209
|
+
marketplace: marketplaceDir,
|
|
210
|
+
path: pluginPath,
|
|
211
|
+
components,
|
|
212
|
+
author: pluginJson.author,
|
|
213
|
+
homepage: pluginJson.homepage,
|
|
214
|
+
license: pluginJson.license
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.warn(chalk.yellow(`Warning: Error loading plugin ${pluginDir}`), error.message);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return plugins;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.warn(chalk.yellow('Warning: Error loading plugins'), error.message);
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async loadPluginsFromMarketplace(marketplacePath, marketplaceName) {
|
|
231
|
+
const plugins = [];
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
const marketplaceJsonPath = path.join(marketplacePath, '.claude-plugin', 'marketplace.json');
|
|
235
|
+
const content = await fs.readFile(marketplaceJsonPath, 'utf8');
|
|
236
|
+
const marketplaceData = JSON.parse(content);
|
|
237
|
+
|
|
238
|
+
if (!marketplaceData.plugins || !Array.isArray(marketplaceData.plugins)) {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Process each plugin definition
|
|
243
|
+
for (const pluginDef of marketplaceData.plugins) {
|
|
244
|
+
try {
|
|
245
|
+
let components = {
|
|
246
|
+
agents: 0,
|
|
247
|
+
commands: 0,
|
|
248
|
+
hooks: 0,
|
|
249
|
+
mcps: 0
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const pluginSourcePath = pluginDef.source ? path.join(marketplacePath, pluginDef.source) : marketplacePath;
|
|
253
|
+
|
|
254
|
+
// Check if plugin has inline component definitions (claude-code-templates style)
|
|
255
|
+
if (pluginDef.agents || pluginDef.commands || pluginDef.mcpServers) {
|
|
256
|
+
components = {
|
|
257
|
+
agents: pluginDef.agents ? pluginDef.agents.length : 0,
|
|
258
|
+
commands: pluginDef.commands ? pluginDef.commands.length : 0,
|
|
259
|
+
hooks: pluginDef.hooks ? (Array.isArray(pluginDef.hooks) ? pluginDef.hooks.length : Object.keys(pluginDef.hooks).length) : 0,
|
|
260
|
+
mcps: pluginDef.mcpServers ? pluginDef.mcpServers.length : 0
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// Otherwise, try to count from source directory (claude-code-plugins style)
|
|
264
|
+
else if (pluginDef.source) {
|
|
265
|
+
if (await fs.pathExists(pluginSourcePath)) {
|
|
266
|
+
components = await this.countPluginComponents(pluginSourcePath);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Check if plugin is enabled in settings.json
|
|
271
|
+
const enabled = await this.isPluginEnabled(pluginDef.name, marketplaceName);
|
|
272
|
+
|
|
273
|
+
plugins.push({
|
|
274
|
+
name: pluginDef.name,
|
|
275
|
+
version: pluginDef.version || '1.0.0',
|
|
276
|
+
description: pluginDef.description || 'No description',
|
|
277
|
+
marketplace: marketplaceName,
|
|
278
|
+
path: pluginSourcePath,
|
|
279
|
+
components,
|
|
280
|
+
author: pluginDef.author,
|
|
281
|
+
homepage: pluginDef.homepage,
|
|
282
|
+
license: pluginDef.license,
|
|
283
|
+
keywords: pluginDef.keywords || [],
|
|
284
|
+
category: pluginDef.category,
|
|
285
|
+
enabled
|
|
286
|
+
});
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.warn(chalk.yellow(`Warning: Error processing plugin ${pluginDef.name}`), error.message);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.warn(chalk.yellow(`Warning: Error loading plugins from marketplace ${marketplaceName}`), error.message);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return plugins;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async isPluginEnabled(pluginName, marketplace) {
|
|
299
|
+
// Check if plugin is enabled in settings.json
|
|
300
|
+
// Plugins are stored as "plugin-name@marketplace-name": true
|
|
301
|
+
const pluginKey = `${pluginName}@${marketplace}`;
|
|
302
|
+
return this.enabledPlugins && this.enabledPlugins.has(pluginKey);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async countPluginComponents(pluginPath) {
|
|
306
|
+
const components = {
|
|
307
|
+
agents: 0,
|
|
308
|
+
commands: 0,
|
|
309
|
+
hooks: 0,
|
|
310
|
+
mcps: 0
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
// Count agents
|
|
315
|
+
const agentsDir = path.join(pluginPath, 'agents');
|
|
316
|
+
if (await fs.pathExists(agentsDir)) {
|
|
317
|
+
const agentFiles = await fs.readdir(agentsDir);
|
|
318
|
+
components.agents = agentFiles.filter(f => f.endsWith('.md')).length;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Count commands
|
|
322
|
+
const commandsDir = path.join(pluginPath, 'commands');
|
|
323
|
+
if (await fs.pathExists(commandsDir)) {
|
|
324
|
+
const commandFiles = await fs.readdir(commandsDir);
|
|
325
|
+
components.commands = commandFiles.filter(f => f.endsWith('.md')).length;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Count hooks
|
|
329
|
+
const hooksFile = path.join(pluginPath, 'hooks', 'hooks.json');
|
|
330
|
+
if (await fs.pathExists(hooksFile)) {
|
|
331
|
+
const hooksData = JSON.parse(await fs.readFile(hooksFile, 'utf8'));
|
|
332
|
+
components.hooks = Object.values(hooksData.hooks || {}).flat().length;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Count MCPs
|
|
336
|
+
const mcpFile = path.join(pluginPath, '.mcp.json');
|
|
337
|
+
if (await fs.pathExists(mcpFile)) {
|
|
338
|
+
const mcpData = JSON.parse(await fs.readFile(mcpFile, 'utf8'));
|
|
339
|
+
components.mcps = Object.keys(mcpData.mcpServers || {}).length;
|
|
340
|
+
}
|
|
341
|
+
} catch (error) {
|
|
342
|
+
console.warn(chalk.yellow(`Warning: Error counting components for plugin at ${pluginPath}`), error.message);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return components;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async loadPermissions() {
|
|
349
|
+
const permissions = {
|
|
350
|
+
agents: [],
|
|
351
|
+
commands: [],
|
|
352
|
+
hooks: [],
|
|
353
|
+
mcps: []
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
// Load user-level permissions
|
|
358
|
+
const userPermissions = await this.loadUserPermissions();
|
|
359
|
+
|
|
360
|
+
// Load plugin permissions
|
|
361
|
+
for (const plugin of this.plugins || []) {
|
|
362
|
+
const pluginPermissions = await this.loadPluginPermissions(plugin);
|
|
363
|
+
|
|
364
|
+
permissions.agents.push(...pluginPermissions.agents);
|
|
365
|
+
permissions.commands.push(...pluginPermissions.commands);
|
|
366
|
+
permissions.hooks.push(...pluginPermissions.hooks);
|
|
367
|
+
permissions.mcps.push(...pluginPermissions.mcps);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Add user permissions
|
|
371
|
+
permissions.agents.push(...userPermissions.agents);
|
|
372
|
+
permissions.commands.push(...userPermissions.commands);
|
|
373
|
+
permissions.hooks.push(...userPermissions.hooks);
|
|
374
|
+
permissions.mcps.push(...userPermissions.mcps);
|
|
375
|
+
|
|
376
|
+
return permissions;
|
|
377
|
+
} catch (error) {
|
|
378
|
+
console.warn(chalk.yellow('Warning: Error loading permissions'), error.message);
|
|
379
|
+
return permissions;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async loadUserPermissions() {
|
|
384
|
+
const permissions = {
|
|
385
|
+
agents: [],
|
|
386
|
+
commands: [],
|
|
387
|
+
hooks: [],
|
|
388
|
+
mcps: []
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
// Load user-level agents
|
|
393
|
+
const userAgentsDir = path.join(this.claudeDir, 'agents');
|
|
394
|
+
if (await fs.pathExists(userAgentsDir)) {
|
|
395
|
+
const agentFiles = await fs.readdir(userAgentsDir);
|
|
396
|
+
for (const file of agentFiles.filter(f => f.endsWith('.md'))) {
|
|
397
|
+
permissions.agents.push({
|
|
398
|
+
name: file.replace('.md', ''),
|
|
399
|
+
source: 'User',
|
|
400
|
+
plugin: null,
|
|
401
|
+
path: path.join(userAgentsDir, file)
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Load user-level commands
|
|
407
|
+
const userCommandsDir = path.join(this.claudeDir, 'commands');
|
|
408
|
+
if (await fs.pathExists(userCommandsDir)) {
|
|
409
|
+
const commandFiles = await fs.readdir(userCommandsDir);
|
|
410
|
+
for (const file of commandFiles.filter(f => f.endsWith('.md'))) {
|
|
411
|
+
permissions.commands.push({
|
|
412
|
+
name: file.replace('.md', ''),
|
|
413
|
+
source: 'User',
|
|
414
|
+
plugin: null,
|
|
415
|
+
path: path.join(userCommandsDir, file)
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Load user-level hooks
|
|
421
|
+
const userHooksFile = path.join(this.claudeDir, 'hooks', 'hooks.json');
|
|
422
|
+
if (await fs.pathExists(userHooksFile)) {
|
|
423
|
+
const hooksData = JSON.parse(await fs.readFile(userHooksFile, 'utf8'));
|
|
424
|
+
for (const [event, hooks] of Object.entries(hooksData.hooks || {})) {
|
|
425
|
+
for (const hook of hooks) {
|
|
426
|
+
permissions.hooks.push({
|
|
427
|
+
name: `${event} hook`,
|
|
428
|
+
event,
|
|
429
|
+
source: 'User',
|
|
430
|
+
plugin: null,
|
|
431
|
+
config: hook
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Load user-level MCPs
|
|
438
|
+
const userMcpFile = path.join(this.claudeDir, '.mcp.json');
|
|
439
|
+
if (await fs.pathExists(userMcpFile)) {
|
|
440
|
+
const mcpData = JSON.parse(await fs.readFile(userMcpFile, 'utf8'));
|
|
441
|
+
for (const [name, config] of Object.entries(mcpData.mcpServers || {})) {
|
|
442
|
+
permissions.mcps.push({
|
|
443
|
+
name,
|
|
444
|
+
source: 'User',
|
|
445
|
+
plugin: null,
|
|
446
|
+
config
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
} catch (error) {
|
|
451
|
+
console.warn(chalk.yellow('Warning: Error loading user permissions'), error.message);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return permissions;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async loadPluginPermissions(plugin) {
|
|
458
|
+
const permissions = {
|
|
459
|
+
agents: [],
|
|
460
|
+
commands: [],
|
|
461
|
+
hooks: [],
|
|
462
|
+
mcps: []
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
// Load plugin agents
|
|
467
|
+
const agentsDir = path.join(plugin.path, 'agents');
|
|
468
|
+
if (await fs.pathExists(agentsDir)) {
|
|
469
|
+
const agentFiles = await fs.readdir(agentsDir);
|
|
470
|
+
for (const file of agentFiles.filter(f => f.endsWith('.md'))) {
|
|
471
|
+
permissions.agents.push({
|
|
472
|
+
name: file.replace('.md', ''),
|
|
473
|
+
source: 'Plugin',
|
|
474
|
+
plugin: plugin.name,
|
|
475
|
+
path: path.join(agentsDir, file)
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Load plugin commands
|
|
481
|
+
const commandsDir = path.join(plugin.path, 'commands');
|
|
482
|
+
if (await fs.pathExists(commandsDir)) {
|
|
483
|
+
const commandFiles = await fs.readdir(commandsDir);
|
|
484
|
+
for (const file of commandFiles.filter(f => f.endsWith('.md'))) {
|
|
485
|
+
permissions.commands.push({
|
|
486
|
+
name: file.replace('.md', ''),
|
|
487
|
+
source: 'Plugin',
|
|
488
|
+
plugin: plugin.name,
|
|
489
|
+
path: path.join(commandsDir, file)
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Load plugin hooks
|
|
495
|
+
const hooksFile = path.join(plugin.path, 'hooks', 'hooks.json');
|
|
496
|
+
if (await fs.pathExists(hooksFile)) {
|
|
497
|
+
const hooksData = JSON.parse(await fs.readFile(hooksFile, 'utf8'));
|
|
498
|
+
for (const [event, hooks] of Object.entries(hooksData.hooks || {})) {
|
|
499
|
+
for (const hook of hooks) {
|
|
500
|
+
permissions.hooks.push({
|
|
501
|
+
name: `${event} hook`,
|
|
502
|
+
event,
|
|
503
|
+
source: 'Plugin',
|
|
504
|
+
plugin: plugin.name,
|
|
505
|
+
config: hook
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Load plugin MCPs
|
|
512
|
+
const mcpFile = path.join(plugin.path, '.mcp.json');
|
|
513
|
+
if (await fs.pathExists(mcpFile)) {
|
|
514
|
+
const mcpData = JSON.parse(await fs.readFile(mcpFile, 'utf8'));
|
|
515
|
+
for (const [name, config] of Object.entries(mcpData.mcpServers || {})) {
|
|
516
|
+
permissions.mcps.push({
|
|
517
|
+
name,
|
|
518
|
+
source: 'Plugin',
|
|
519
|
+
plugin: plugin.name,
|
|
520
|
+
config
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
} catch (error) {
|
|
525
|
+
console.warn(chalk.yellow(`Warning: Error loading plugin permissions for ${plugin.name}`), error.message);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return permissions;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
setupWebServer() {
|
|
532
|
+
// Add CORS middleware
|
|
533
|
+
this.app.use((req, res, next) => {
|
|
534
|
+
res.header('Access-Control-Allow-Origin', '*');
|
|
535
|
+
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
536
|
+
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
|
|
537
|
+
|
|
538
|
+
if (req.method === 'OPTIONS') {
|
|
539
|
+
res.sendStatus(200);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
next();
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// Serve static files
|
|
547
|
+
this.app.use(express.static(path.join(__dirname, 'plugin-dashboard-web')));
|
|
548
|
+
|
|
549
|
+
// API endpoints - reload data on each request
|
|
550
|
+
this.app.get('/api/marketplaces', async (req, res) => {
|
|
551
|
+
try {
|
|
552
|
+
await this.loadPluginData();
|
|
553
|
+
res.json({
|
|
554
|
+
marketplaces: this.marketplaces || [],
|
|
555
|
+
count: (this.marketplaces || []).length,
|
|
556
|
+
timestamp: new Date().toISOString()
|
|
557
|
+
});
|
|
558
|
+
} catch (error) {
|
|
559
|
+
console.error('Error loading marketplaces:', error);
|
|
560
|
+
res.status(500).json({ error: 'Failed to load marketplaces' });
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
this.app.get('/api/plugins', async (req, res) => {
|
|
565
|
+
try {
|
|
566
|
+
await this.loadPluginData();
|
|
567
|
+
res.json({
|
|
568
|
+
plugins: this.plugins || [],
|
|
569
|
+
count: (this.plugins || []).length,
|
|
570
|
+
timestamp: new Date().toISOString()
|
|
571
|
+
});
|
|
572
|
+
} catch (error) {
|
|
573
|
+
console.error('Error loading plugins:', error);
|
|
574
|
+
res.status(500).json({ error: 'Failed to load plugins' });
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
this.app.get('/api/permissions', async (req, res) => {
|
|
579
|
+
try {
|
|
580
|
+
await this.loadPluginData();
|
|
581
|
+
res.json({
|
|
582
|
+
permissions: this.permissions || {},
|
|
583
|
+
counts: {
|
|
584
|
+
agents: (this.permissions?.agents || []).length,
|
|
585
|
+
commands: (this.permissions?.commands || []).length,
|
|
586
|
+
hooks: (this.permissions?.hooks || []).length,
|
|
587
|
+
mcps: (this.permissions?.mcps || []).length
|
|
588
|
+
},
|
|
589
|
+
timestamp: new Date().toISOString()
|
|
590
|
+
});
|
|
591
|
+
} catch (error) {
|
|
592
|
+
console.error('Error loading permissions:', error);
|
|
593
|
+
res.status(500).json({ error: 'Failed to load permissions' });
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
this.app.get('/api/summary', async (req, res) => {
|
|
598
|
+
try {
|
|
599
|
+
await this.loadPluginData();
|
|
600
|
+
res.json({
|
|
601
|
+
marketplaces: (this.marketplaces || []).length,
|
|
602
|
+
plugins: (this.plugins || []).length,
|
|
603
|
+
permissions: {
|
|
604
|
+
agents: (this.permissions?.agents || []).length,
|
|
605
|
+
commands: (this.permissions?.commands || []).length,
|
|
606
|
+
hooks: (this.permissions?.hooks || []).length,
|
|
607
|
+
mcps: (this.permissions?.mcps || []).length,
|
|
608
|
+
total: (this.permissions?.agents || []).length +
|
|
609
|
+
(this.permissions?.commands || []).length +
|
|
610
|
+
(this.permissions?.hooks || []).length +
|
|
611
|
+
(this.permissions?.mcps || []).length
|
|
612
|
+
},
|
|
613
|
+
timestamp: new Date().toISOString()
|
|
614
|
+
});
|
|
615
|
+
} catch (error) {
|
|
616
|
+
console.error('Error loading summary:', error);
|
|
617
|
+
res.status(500).json({ error: 'Failed to load summary' });
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
// Main route
|
|
622
|
+
this.app.get('/', (req, res) => {
|
|
623
|
+
res.sendFile(path.join(__dirname, 'plugin-dashboard-web', 'index.html'));
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
async startServer() {
|
|
628
|
+
return new Promise((resolve) => {
|
|
629
|
+
this.httpServer = this.app.listen(this.port, async () => {
|
|
630
|
+
console.log(chalk.green(`š Plugin dashboard started at http://localhost:${this.port}`));
|
|
631
|
+
resolve();
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async openBrowser() {
|
|
637
|
+
const url = `http://localhost:${this.port}`;
|
|
638
|
+
console.log(chalk.blue('š Opening browser to Plugin Dashboard...'));
|
|
639
|
+
|
|
640
|
+
try {
|
|
641
|
+
await open(url);
|
|
642
|
+
} catch (error) {
|
|
643
|
+
console.log(chalk.yellow('Could not open browser automatically. Please visit:'));
|
|
644
|
+
console.log(chalk.cyan(url));
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
stop() {
|
|
649
|
+
if (this.httpServer) {
|
|
650
|
+
this.httpServer.close();
|
|
651
|
+
}
|
|
652
|
+
console.log(chalk.yellow('Plugin dashboard stopped'));
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
async function runPluginDashboard(options = {}) {
|
|
657
|
+
console.log(chalk.blue('š Starting Claude Code Plugin Dashboard...'));
|
|
658
|
+
|
|
659
|
+
const dashboard = new PluginDashboard(options);
|
|
660
|
+
|
|
661
|
+
try {
|
|
662
|
+
await dashboard.initialize();
|
|
663
|
+
await dashboard.startServer();
|
|
664
|
+
await dashboard.openBrowser();
|
|
665
|
+
|
|
666
|
+
console.log(chalk.green('ā
Plugin dashboard is running!'));
|
|
667
|
+
console.log(chalk.cyan(`š Access at: http://localhost:${dashboard.port}`));
|
|
668
|
+
console.log(chalk.gray('Press Ctrl+C to stop the server'));
|
|
669
|
+
|
|
670
|
+
// Handle graceful shutdown
|
|
671
|
+
process.on('SIGINT', () => {
|
|
672
|
+
console.log(chalk.yellow('\nš Shutting down plugin dashboard...'));
|
|
673
|
+
dashboard.stop();
|
|
674
|
+
process.exit(0);
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
// Keep the process running
|
|
678
|
+
await new Promise(() => {});
|
|
679
|
+
|
|
680
|
+
} catch (error) {
|
|
681
|
+
console.error(chalk.red('ā Failed to start plugin dashboard:'), error.message);
|
|
682
|
+
process.exit(1);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
module.exports = {
|
|
687
|
+
runPluginDashboard,
|
|
688
|
+
PluginDashboard
|
|
689
|
+
};
|