@laststance/claude-plugin-dashboard 0.3.0 → 0.3.2
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 +1 -0
- package/dist/app.js +346 -211
- package/dist/cli.js +3 -1
- package/dist/components/ComponentBadges.d.ts +0 -9
- package/dist/components/ComponentBadges.js +0 -33
- package/dist/components/ComponentDetail.d.ts +32 -0
- package/dist/components/ComponentDetail.js +106 -0
- package/dist/components/ComponentList.d.ts +36 -2
- package/dist/components/ComponentList.js +105 -11
- package/dist/components/HelpOverlay.js +1 -0
- package/dist/components/KeyHints.d.ts +1 -0
- package/dist/components/KeyHints.js +8 -1
- package/dist/components/PluginDetail.d.ts +16 -3
- package/dist/components/PluginDetail.js +29 -3
- package/dist/services/componentService.d.ts +10 -42
- package/dist/services/componentService.js +19 -412
- package/dist/services/components/hookService.d.ts +17 -0
- package/dist/services/components/hookService.js +45 -0
- package/dist/services/components/index.d.ts +41 -0
- package/dist/services/components/index.js +126 -0
- package/dist/services/components/markdownService.d.ts +39 -0
- package/dist/services/components/markdownService.js +147 -0
- package/dist/services/components/serverService.d.ts +28 -0
- package/dist/services/components/serverService.js +69 -0
- package/dist/services/components/skillService.d.ts +48 -0
- package/dist/services/components/skillService.js +164 -0
- package/dist/services/components/utils.d.ts +23 -0
- package/dist/services/components/utils.js +42 -0
- package/dist/services/pluginActionsService.d.ts +31 -2
- package/dist/services/pluginActionsService.js +65 -6
- package/dist/store/index.d.ts +46 -0
- package/dist/store/index.js +47 -0
- package/dist/store/slices/marketplaceSlice.d.ts +344 -0
- package/dist/store/slices/marketplaceSlice.js +152 -0
- package/dist/store/slices/pluginSlice.d.ts +1544 -0
- package/dist/store/slices/pluginSlice.js +191 -0
- package/dist/store/slices/uiSlice.d.ts +147 -0
- package/dist/store/slices/uiSlice.js +126 -0
- package/dist/tabs/DiscoverTab.d.ts +8 -2
- package/dist/tabs/DiscoverTab.js +2 -2
- package/dist/tabs/EnabledTab.d.ts +8 -2
- package/dist/tabs/EnabledTab.js +2 -2
- package/dist/tabs/ErrorsTab.js +1 -1
- package/dist/tabs/InstalledTab.d.ts +8 -2
- package/dist/tabs/InstalledTab.js +2 -2
- package/dist/types/index.d.ts +47 -4
- package/package.json +7 -2
|
@@ -2,415 +2,22 @@
|
|
|
2
2
|
* Component service for detecting plugin component types
|
|
3
3
|
* Parses plugin.json and scans plugin directory structure to identify
|
|
4
4
|
* skills, commands, agents, hooks, MCP servers, and LSP servers
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// Detect skills (count directories in skills/ folder)
|
|
25
|
-
const skillsCount = countSkills(installPath);
|
|
26
|
-
if (skillsCount > 0) {
|
|
27
|
-
components.skills = skillsCount;
|
|
28
|
-
}
|
|
29
|
-
// Detect commands (legacy location, now unified with skills in Claude Code v2.1.3+)
|
|
30
|
-
const commandsCount = countMarkdownFiles(installPath, 'commands');
|
|
31
|
-
if (commandsCount > 0) {
|
|
32
|
-
components.commands = commandsCount;
|
|
33
|
-
}
|
|
34
|
-
// Detect agents (count .md files in agents/ folder)
|
|
35
|
-
const agentsCount = countMarkdownFiles(installPath, 'agents');
|
|
36
|
-
if (agentsCount > 0) {
|
|
37
|
-
components.agents = agentsCount;
|
|
38
|
-
}
|
|
39
|
-
// Detect hooks
|
|
40
|
-
const hasHooks = detectHooks(installPath);
|
|
41
|
-
if (hasHooks) {
|
|
42
|
-
components.hooks = true;
|
|
43
|
-
}
|
|
44
|
-
// Detect MCP servers from plugin.json
|
|
45
|
-
const mcpCount = countMcpServers(installPath);
|
|
46
|
-
if (mcpCount > 0) {
|
|
47
|
-
components.mcpServers = mcpCount;
|
|
48
|
-
}
|
|
49
|
-
// Detect LSP servers from .lsp.json
|
|
50
|
-
const lspCount = countLspServers(installPath);
|
|
51
|
-
if (lspCount > 0) {
|
|
52
|
-
components.lspServers = lspCount;
|
|
53
|
-
}
|
|
54
|
-
// Return undefined if no components detected
|
|
55
|
-
if (Object.keys(components).length === 0) {
|
|
56
|
-
return undefined;
|
|
57
|
-
}
|
|
58
|
-
return components;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Detect detailed components for an installed plugin
|
|
62
|
-
* Reads skills/, commands/, agents/ directories and parses plugin.json
|
|
63
|
-
* @param installPath - Absolute path to installed plugin directory
|
|
64
|
-
* @returns Detailed component info with names and descriptions
|
|
65
|
-
* - Returns undefined if path doesn't exist or has no components
|
|
66
|
-
* @example
|
|
67
|
-
* detectComponentsDetailed('/path/to/plugin')
|
|
68
|
-
* // => { skills: [{ name: 'xlsx', description: '...', type: 'skill' }] }
|
|
69
|
-
*/
|
|
70
|
-
export function detectComponentsDetailed(installPath) {
|
|
71
|
-
if (!directoryExists(installPath)) {
|
|
72
|
-
return undefined;
|
|
73
|
-
}
|
|
74
|
-
const detailed = {};
|
|
75
|
-
// Skills: Read directory names + SKILL.md frontmatter
|
|
76
|
-
const skills = getSkillDetails(installPath);
|
|
77
|
-
if (skills.length > 0) {
|
|
78
|
-
detailed.skills = skills;
|
|
79
|
-
}
|
|
80
|
-
// Commands: Read .md filenames
|
|
81
|
-
const commands = getMarkdownFileDetails(installPath, 'commands', 'command');
|
|
82
|
-
if (commands.length > 0) {
|
|
83
|
-
detailed.commands = commands;
|
|
84
|
-
}
|
|
85
|
-
// Agents: Read .md filenames
|
|
86
|
-
const agents = getMarkdownFileDetails(installPath, 'agents', 'agent');
|
|
87
|
-
if (agents.length > 0) {
|
|
88
|
-
detailed.agents = agents;
|
|
89
|
-
}
|
|
90
|
-
// Hooks: Read event names from hooks.json or hooks/ directory
|
|
91
|
-
const hooks = getHookNames(installPath);
|
|
92
|
-
if (hooks.length > 0) {
|
|
93
|
-
detailed.hooks = hooks;
|
|
94
|
-
}
|
|
95
|
-
// MCP Servers: Read plugin.json mcpServers keys
|
|
96
|
-
const mcpServers = getMcpServerNames(installPath);
|
|
97
|
-
if (mcpServers.length > 0) {
|
|
98
|
-
detailed.mcpServers = mcpServers;
|
|
99
|
-
}
|
|
100
|
-
// LSP Servers: Read .lsp.json keys
|
|
101
|
-
const lspServers = getLspServerNames(installPath);
|
|
102
|
-
if (lspServers.length > 0) {
|
|
103
|
-
detailed.lspServers = lspServers;
|
|
104
|
-
}
|
|
105
|
-
// Return undefined if no components detected
|
|
106
|
-
if (Object.keys(detailed).length === 0) {
|
|
107
|
-
return undefined;
|
|
108
|
-
}
|
|
109
|
-
return detailed;
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Get skill details from skills/ directory
|
|
113
|
-
* Reads directory names and parses SKILL.md frontmatter for descriptions
|
|
114
|
-
* @param installPath - Plugin install path
|
|
115
|
-
* @returns Array of ComponentInfo for each skill
|
|
116
|
-
*/
|
|
117
|
-
function getSkillDetails(installPath) {
|
|
118
|
-
const skillsPath = path.join(installPath, 'skills');
|
|
119
|
-
if (!directoryExists(skillsPath)) {
|
|
120
|
-
return [];
|
|
121
|
-
}
|
|
122
|
-
try {
|
|
123
|
-
const entries = fs.readdirSync(skillsPath, { withFileTypes: true });
|
|
124
|
-
return entries
|
|
125
|
-
.filter((entry) => entry.isDirectory())
|
|
126
|
-
.map((entry) => {
|
|
127
|
-
const skillMdPath = path.join(skillsPath, entry.name, 'SKILL.md');
|
|
128
|
-
const description = parseSkillMdDescription(skillMdPath);
|
|
129
|
-
return {
|
|
130
|
-
name: entry.name,
|
|
131
|
-
description,
|
|
132
|
-
type: 'skill',
|
|
133
|
-
};
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
return [];
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Parse SKILL.md frontmatter for description
|
|
142
|
-
* Looks for `description:` field in YAML frontmatter
|
|
143
|
-
* @param skillMdPath - Path to SKILL.md file
|
|
144
|
-
* @returns Description string or undefined
|
|
145
|
-
*/
|
|
146
|
-
function parseSkillMdDescription(skillMdPath) {
|
|
147
|
-
if (!fileExists(skillMdPath)) {
|
|
148
|
-
return undefined;
|
|
149
|
-
}
|
|
150
|
-
try {
|
|
151
|
-
const content = fs.readFileSync(skillMdPath, 'utf-8');
|
|
152
|
-
// Match YAML frontmatter description field
|
|
153
|
-
const match = content.match(/^---\n[\s\S]*?description:\s*["']?(.+?)["']?\s*\n[\s\S]*?---/m);
|
|
154
|
-
return match?.[1]?.trim();
|
|
155
|
-
}
|
|
156
|
-
catch {
|
|
157
|
-
return undefined;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Get component details from .md files in a directory
|
|
162
|
-
* Uses filename (minus extension) as component name
|
|
163
|
-
* @param installPath - Plugin install path
|
|
164
|
-
* @param subdir - Subdirectory name ('commands' or 'agents')
|
|
165
|
-
* @param type - Component type
|
|
166
|
-
* @returns Array of ComponentInfo
|
|
167
|
-
*/
|
|
168
|
-
function getMarkdownFileDetails(installPath, subdir, type) {
|
|
169
|
-
const dirPath = path.join(installPath, subdir);
|
|
170
|
-
if (!directoryExists(dirPath)) {
|
|
171
|
-
return [];
|
|
172
|
-
}
|
|
173
|
-
try {
|
|
174
|
-
const files = fs.readdirSync(dirPath);
|
|
175
|
-
return files
|
|
176
|
-
.filter((file) => file.endsWith('.md'))
|
|
177
|
-
.map((file) => {
|
|
178
|
-
const name = file.replace(/\.md$/, '');
|
|
179
|
-
const filePath = path.join(dirPath, file);
|
|
180
|
-
const description = parseFirstLineDescription(filePath);
|
|
181
|
-
return {
|
|
182
|
-
name,
|
|
183
|
-
description,
|
|
184
|
-
type,
|
|
185
|
-
};
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
catch {
|
|
189
|
-
return [];
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Parse first non-empty line of a markdown file as description
|
|
194
|
-
* Properly skips YAML frontmatter block and strips heading markers
|
|
195
|
-
* @param filePath - Path to markdown file
|
|
196
|
-
* @returns First non-frontmatter, non-empty line or undefined
|
|
197
|
-
* @example
|
|
198
|
-
* // File: "---\nname: test\n---\n# My Title\n"
|
|
199
|
-
* parseFirstLineDescription(path) // => "My Title"
|
|
200
|
-
*/
|
|
201
|
-
function parseFirstLineDescription(filePath) {
|
|
202
|
-
try {
|
|
203
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
204
|
-
const lines = content.split('\n');
|
|
205
|
-
let inFrontmatter = false;
|
|
206
|
-
let frontmatterClosed = false;
|
|
207
|
-
for (const line of lines) {
|
|
208
|
-
const trimmed = line.trim();
|
|
209
|
-
// Detect frontmatter delimiter
|
|
210
|
-
if (trimmed === '---') {
|
|
211
|
-
if (!inFrontmatter && !frontmatterClosed) {
|
|
212
|
-
// Opening delimiter
|
|
213
|
-
inFrontmatter = true;
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
else if (inFrontmatter) {
|
|
217
|
-
// Closing delimiter
|
|
218
|
-
inFrontmatter = false;
|
|
219
|
-
frontmatterClosed = true;
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
// Skip lines inside frontmatter
|
|
224
|
-
if (inFrontmatter) {
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
// Skip empty lines
|
|
228
|
-
if (!trimmed) {
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
// Found first content line - remove heading markers and return
|
|
232
|
-
return trimmed.replace(/^#+\s*/, '');
|
|
233
|
-
}
|
|
234
|
-
return undefined;
|
|
235
|
-
}
|
|
236
|
-
catch {
|
|
237
|
-
return undefined;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Get hook event names from hooks configuration
|
|
242
|
-
* @param installPath - Plugin install path
|
|
243
|
-
* @returns Array of hook event names
|
|
244
|
-
*/
|
|
245
|
-
function getHookNames(installPath) {
|
|
246
|
-
// Try hooks.json first
|
|
247
|
-
const hooksJsonPath = path.join(installPath, 'hooks.json');
|
|
248
|
-
const hooksJson = readJsonFile(hooksJsonPath);
|
|
249
|
-
if (hooksJson) {
|
|
250
|
-
return Object.keys(hooksJson);
|
|
251
|
-
}
|
|
252
|
-
// Try hooks/ directory
|
|
253
|
-
const hooksDir = path.join(installPath, 'hooks');
|
|
254
|
-
if (directoryExists(hooksDir)) {
|
|
255
|
-
try {
|
|
256
|
-
const files = fs.readdirSync(hooksDir);
|
|
257
|
-
return files
|
|
258
|
-
.filter((f) => f.endsWith('.json') || f.endsWith('.js'))
|
|
259
|
-
.map((f) => f.replace(/\.(json|js)$/, ''));
|
|
260
|
-
}
|
|
261
|
-
catch {
|
|
262
|
-
return [];
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
return [];
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Get MCP server names from plugin.json
|
|
269
|
-
* @param installPath - Plugin install path
|
|
270
|
-
* @returns Array of MCP server names
|
|
271
|
-
*/
|
|
272
|
-
function getMcpServerNames(installPath) {
|
|
273
|
-
const pluginJsonPaths = [
|
|
274
|
-
path.join(installPath, '.claude-plugin', 'plugin.json'),
|
|
275
|
-
path.join(installPath, 'plugin.json'),
|
|
276
|
-
];
|
|
277
|
-
for (const pluginJsonPath of pluginJsonPaths) {
|
|
278
|
-
const pluginJson = readJsonFile(pluginJsonPath);
|
|
279
|
-
if (pluginJson?.mcpServers) {
|
|
280
|
-
return Object.keys(pluginJson.mcpServers);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return [];
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* Get LSP server language IDs from .lsp.json
|
|
287
|
-
* @param installPath - Plugin install path
|
|
288
|
-
* @returns Array of language IDs
|
|
289
|
-
*/
|
|
290
|
-
function getLspServerNames(installPath) {
|
|
291
|
-
const lspJsonPath = path.join(installPath, '.lsp.json');
|
|
292
|
-
const lspConfig = readJsonFile(lspJsonPath);
|
|
293
|
-
if (!lspConfig) {
|
|
294
|
-
return [];
|
|
295
|
-
}
|
|
296
|
-
return Object.keys(lspConfig);
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* Count skill directories in the skills/ folder
|
|
300
|
-
* Skills are stored as subdirectories with SKILL.md files
|
|
301
|
-
* @param installPath - Plugin install path
|
|
302
|
-
* @returns Number of skill directories
|
|
303
|
-
*/
|
|
304
|
-
function countSkills(installPath) {
|
|
305
|
-
const skillsPath = path.join(installPath, 'skills');
|
|
306
|
-
if (!directoryExists(skillsPath)) {
|
|
307
|
-
return 0;
|
|
308
|
-
}
|
|
309
|
-
try {
|
|
310
|
-
const entries = fs.readdirSync(skillsPath, { withFileTypes: true });
|
|
311
|
-
return entries.filter((entry) => entry.isDirectory()).length;
|
|
312
|
-
}
|
|
313
|
-
catch {
|
|
314
|
-
return 0;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Count .md files in a specific directory
|
|
319
|
-
* @param installPath - Plugin install path
|
|
320
|
-
* @param subdir - Subdirectory name ('commands' or 'agents')
|
|
321
|
-
* @returns Number of .md files
|
|
322
|
-
*/
|
|
323
|
-
function countMarkdownFiles(installPath, subdir) {
|
|
324
|
-
const dirPath = path.join(installPath, subdir);
|
|
325
|
-
if (!directoryExists(dirPath)) {
|
|
326
|
-
return 0;
|
|
327
|
-
}
|
|
328
|
-
try {
|
|
329
|
-
const files = fs.readdirSync(dirPath);
|
|
330
|
-
return files.filter((file) => file.endsWith('.md')).length;
|
|
331
|
-
}
|
|
332
|
-
catch {
|
|
333
|
-
return 0;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* Detect if plugin has hooks configured
|
|
338
|
-
* Checks for hooks/ directory or hooks.json file
|
|
339
|
-
* @param installPath - Plugin install path
|
|
340
|
-
* @returns true if hooks are configured
|
|
341
|
-
*/
|
|
342
|
-
function detectHooks(installPath) {
|
|
343
|
-
const hooksDir = path.join(installPath, 'hooks');
|
|
344
|
-
const hooksJson = path.join(installPath, 'hooks.json');
|
|
345
|
-
return directoryExists(hooksDir) || fileExists(hooksJson);
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Count MCP servers defined in plugin.json
|
|
349
|
-
* @param installPath - Plugin install path
|
|
350
|
-
* @returns Number of MCP server configurations
|
|
351
|
-
*/
|
|
352
|
-
function countMcpServers(installPath) {
|
|
353
|
-
// Check both .claude-plugin/plugin.json and plugin.json at root
|
|
354
|
-
const pluginJsonPaths = [
|
|
355
|
-
path.join(installPath, '.claude-plugin', 'plugin.json'),
|
|
356
|
-
path.join(installPath, 'plugin.json'),
|
|
357
|
-
];
|
|
358
|
-
for (const pluginJsonPath of pluginJsonPaths) {
|
|
359
|
-
const pluginJson = readJsonFile(pluginJsonPath);
|
|
360
|
-
if (pluginJson?.mcpServers) {
|
|
361
|
-
return Object.keys(pluginJson.mcpServers).length;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
return 0;
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* Count LSP servers defined in .lsp.json
|
|
368
|
-
* @param installPath - Plugin install path
|
|
369
|
-
* @returns Number of LSP server configurations
|
|
370
|
-
*/
|
|
371
|
-
function countLspServers(installPath) {
|
|
372
|
-
const lspJsonPath = path.join(installPath, '.lsp.json');
|
|
373
|
-
const lspConfig = readJsonFile(lspJsonPath);
|
|
374
|
-
if (!lspConfig) {
|
|
375
|
-
return 0;
|
|
376
|
-
}
|
|
377
|
-
return Object.keys(lspConfig).length;
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* Check if a plugin has any components
|
|
381
|
-
* @param components - PluginComponents object
|
|
382
|
-
* @returns true if at least one component type is present
|
|
383
|
-
* @example
|
|
384
|
-
* hasAnyComponents({ skills: 2 }) // => true
|
|
385
|
-
* hasAnyComponents({}) // => false
|
|
386
|
-
* hasAnyComponents(undefined) // => false
|
|
387
|
-
*/
|
|
388
|
-
export function hasAnyComponents(components) {
|
|
389
|
-
if (!components) {
|
|
390
|
-
return false;
|
|
391
|
-
}
|
|
392
|
-
return ((components.skills ?? 0) > 0 ||
|
|
393
|
-
(components.commands ?? 0) > 0 ||
|
|
394
|
-
(components.agents ?? 0) > 0 ||
|
|
395
|
-
components.hooks === true ||
|
|
396
|
-
(components.mcpServers ?? 0) > 0 ||
|
|
397
|
-
(components.lspServers ?? 0) > 0);
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* Get total component count for a plugin
|
|
401
|
-
* @param components - PluginComponents object
|
|
402
|
-
* @returns Total number of components (hooks count as 1)
|
|
403
|
-
* @example
|
|
404
|
-
* getTotalComponentCount({ skills: 3, commands: 2, hooks: true }) // => 6
|
|
405
|
-
*/
|
|
406
|
-
export function getTotalComponentCount(components) {
|
|
407
|
-
if (!components) {
|
|
408
|
-
return 0;
|
|
409
|
-
}
|
|
410
|
-
return ((components.skills ?? 0) +
|
|
411
|
-
(components.commands ?? 0) +
|
|
412
|
-
(components.agents ?? 0) +
|
|
413
|
-
(components.hooks ? 1 : 0) +
|
|
414
|
-
(components.mcpServers ?? 0) +
|
|
415
|
-
(components.lspServers ?? 0));
|
|
416
|
-
}
|
|
5
|
+
*
|
|
6
|
+
* This is a facade module that re-exports from the components/ submodules.
|
|
7
|
+
* For implementation details, see:
|
|
8
|
+
* - components/skillService.ts - Skill detection and SKILL.md parsing
|
|
9
|
+
* - components/markdownService.ts - Command/Agent markdown parsing
|
|
10
|
+
* - components/hookService.ts - Hook detection
|
|
11
|
+
* - components/serverService.ts - MCP/LSP server detection
|
|
12
|
+
* - components/utils.ts - Utility functions
|
|
13
|
+
*/
|
|
14
|
+
// Re-export everything from the components module for backward compatibility
|
|
15
|
+
export {
|
|
16
|
+
// Main detection functions
|
|
17
|
+
detectPluginComponents, detectComponentsDetailed,
|
|
18
|
+
// Skill functions
|
|
19
|
+
parseSkillMdFull, getSkillDetailedInfo,
|
|
20
|
+
// Markdown functions
|
|
21
|
+
getMarkdownComponentDetailedInfo,
|
|
22
|
+
// Utility functions
|
|
23
|
+
hasAnyComponents, getTotalComponentCount, } from './components/index.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook component detection service
|
|
3
|
+
* Handles hooks/ directory and hooks.json scanning
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Detect if plugin has hooks configured
|
|
7
|
+
* Checks for hooks/ directory or hooks.json file
|
|
8
|
+
* @param installPath - Plugin install path
|
|
9
|
+
* @returns true if hooks are configured
|
|
10
|
+
*/
|
|
11
|
+
export declare function detectHooks(installPath: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Get hook event names from hooks configuration
|
|
14
|
+
* @param installPath - Plugin install path
|
|
15
|
+
* @returns Array of hook event names
|
|
16
|
+
*/
|
|
17
|
+
export declare function getHookNames(installPath: string): string[];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook component detection service
|
|
3
|
+
* Handles hooks/ directory and hooks.json scanning
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import { readJsonFile, directoryExists, fileExists } from '../fileService.js';
|
|
8
|
+
/**
|
|
9
|
+
* Detect if plugin has hooks configured
|
|
10
|
+
* Checks for hooks/ directory or hooks.json file
|
|
11
|
+
* @param installPath - Plugin install path
|
|
12
|
+
* @returns true if hooks are configured
|
|
13
|
+
*/
|
|
14
|
+
export function detectHooks(installPath) {
|
|
15
|
+
const hooksDir = path.join(installPath, 'hooks');
|
|
16
|
+
const hooksJson = path.join(installPath, 'hooks.json');
|
|
17
|
+
return directoryExists(hooksDir) || fileExists(hooksJson);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Get hook event names from hooks configuration
|
|
21
|
+
* @param installPath - Plugin install path
|
|
22
|
+
* @returns Array of hook event names
|
|
23
|
+
*/
|
|
24
|
+
export function getHookNames(installPath) {
|
|
25
|
+
// Try hooks.json first
|
|
26
|
+
const hooksJsonPath = path.join(installPath, 'hooks.json');
|
|
27
|
+
const hooksJson = readJsonFile(hooksJsonPath);
|
|
28
|
+
if (hooksJson) {
|
|
29
|
+
return Object.keys(hooksJson);
|
|
30
|
+
}
|
|
31
|
+
// Try hooks/ directory
|
|
32
|
+
const hooksDir = path.join(installPath, 'hooks');
|
|
33
|
+
if (directoryExists(hooksDir)) {
|
|
34
|
+
try {
|
|
35
|
+
const files = fs.readdirSync(hooksDir);
|
|
36
|
+
return files
|
|
37
|
+
.filter((f) => f.endsWith('.json') || f.endsWith('.js'))
|
|
38
|
+
.map((f) => f.replace(/\.(json|js)$/, ''));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component service module index
|
|
3
|
+
* Re-exports all component detection and parsing functions
|
|
4
|
+
*
|
|
5
|
+
* This module provides a facade for detecting plugin components:
|
|
6
|
+
* - Skills (skills/ directory with SKILL.md files)
|
|
7
|
+
* - Commands (commands/ directory with .md files)
|
|
8
|
+
* - Agents (agents/ directory with .md files)
|
|
9
|
+
* - Hooks (hooks/ directory or hooks.json)
|
|
10
|
+
* - MCP Servers (plugin.json mcpServers)
|
|
11
|
+
* - LSP Servers (.lsp.json)
|
|
12
|
+
*/
|
|
13
|
+
export { hasAnyComponents, getTotalComponentCount } from './utils.js';
|
|
14
|
+
export { countSkills, getSkillDetails, parseSkillMdFull, getSkillDetailedInfo, } from './skillService.js';
|
|
15
|
+
export { countMarkdownFiles, getMarkdownFileDetails, getMarkdownComponentDetailedInfo, parseFirstLineDescriptionFromContent, } from './markdownService.js';
|
|
16
|
+
export { detectHooks, getHookNames } from './hookService.js';
|
|
17
|
+
export { countMcpServers, getMcpServerNames, countLspServers, getLspServerNames, } from './serverService.js';
|
|
18
|
+
export type { ComponentInfo, ComponentDetailedInfo } from '../../types/index.js';
|
|
19
|
+
import type { PluginComponents, PluginComponentsDetailed } from '../../types/index.js';
|
|
20
|
+
/**
|
|
21
|
+
* Detect all component types for a plugin at the given install path
|
|
22
|
+
* @param installPath - Absolute path to the installed plugin directory
|
|
23
|
+
* @returns PluginComponents object with detected component counts
|
|
24
|
+
* - Returns undefined values for components that are not present
|
|
25
|
+
* - Returns counts > 0 for components that exist
|
|
26
|
+
* @example
|
|
27
|
+
* const components = detectPluginComponents('/path/to/plugin')
|
|
28
|
+
* // => { skills: 5, commands: 2, mcpServers: 1 }
|
|
29
|
+
*/
|
|
30
|
+
export declare function detectPluginComponents(installPath: string): PluginComponents | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Detect detailed components for an installed plugin
|
|
33
|
+
* Reads skills/, commands/, agents/ directories and parses plugin.json
|
|
34
|
+
* @param installPath - Absolute path to installed plugin directory
|
|
35
|
+
* @returns Detailed component info with names and descriptions
|
|
36
|
+
* - Returns undefined if path doesn't exist or has no components
|
|
37
|
+
* @example
|
|
38
|
+
* detectComponentsDetailed('/path/to/plugin')
|
|
39
|
+
* // => { skills: [{ name: 'xlsx', description: '...', type: 'skill' }] }
|
|
40
|
+
*/
|
|
41
|
+
export declare function detectComponentsDetailed(installPath: string): PluginComponentsDetailed | undefined;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component service module index
|
|
3
|
+
* Re-exports all component detection and parsing functions
|
|
4
|
+
*
|
|
5
|
+
* This module provides a facade for detecting plugin components:
|
|
6
|
+
* - Skills (skills/ directory with SKILL.md files)
|
|
7
|
+
* - Commands (commands/ directory with .md files)
|
|
8
|
+
* - Agents (agents/ directory with .md files)
|
|
9
|
+
* - Hooks (hooks/ directory or hooks.json)
|
|
10
|
+
* - MCP Servers (plugin.json mcpServers)
|
|
11
|
+
* - LSP Servers (.lsp.json)
|
|
12
|
+
*/
|
|
13
|
+
// Re-export all from submodules for backward compatibility
|
|
14
|
+
export { hasAnyComponents, getTotalComponentCount } from './utils.js';
|
|
15
|
+
export { countSkills, getSkillDetails, parseSkillMdFull, getSkillDetailedInfo, } from './skillService.js';
|
|
16
|
+
export { countMarkdownFiles, getMarkdownFileDetails, getMarkdownComponentDetailedInfo, parseFirstLineDescriptionFromContent, } from './markdownService.js';
|
|
17
|
+
export { detectHooks, getHookNames } from './hookService.js';
|
|
18
|
+
export { countMcpServers, getMcpServerNames, countLspServers, getLspServerNames, } from './serverService.js';
|
|
19
|
+
import { directoryExists } from '../fileService.js';
|
|
20
|
+
// Import from submodules for orchestration functions
|
|
21
|
+
import { countSkills, getSkillDetails } from './skillService.js';
|
|
22
|
+
import { countMarkdownFiles, getMarkdownFileDetails, } from './markdownService.js';
|
|
23
|
+
import { detectHooks, getHookNames } from './hookService.js';
|
|
24
|
+
import { countMcpServers, getMcpServerNames, countLspServers, getLspServerNames, } from './serverService.js';
|
|
25
|
+
/**
|
|
26
|
+
* Detect all component types for a plugin at the given install path
|
|
27
|
+
* @param installPath - Absolute path to the installed plugin directory
|
|
28
|
+
* @returns PluginComponents object with detected component counts
|
|
29
|
+
* - Returns undefined values for components that are not present
|
|
30
|
+
* - Returns counts > 0 for components that exist
|
|
31
|
+
* @example
|
|
32
|
+
* const components = detectPluginComponents('/path/to/plugin')
|
|
33
|
+
* // => { skills: 5, commands: 2, mcpServers: 1 }
|
|
34
|
+
*/
|
|
35
|
+
export function detectPluginComponents(installPath) {
|
|
36
|
+
if (!directoryExists(installPath)) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const components = {};
|
|
40
|
+
// Detect skills (count directories in skills/ folder)
|
|
41
|
+
const skillsCount = countSkills(installPath);
|
|
42
|
+
if (skillsCount > 0) {
|
|
43
|
+
components.skills = skillsCount;
|
|
44
|
+
}
|
|
45
|
+
// Detect commands (legacy location, now unified with skills in Claude Code v2.1.3+)
|
|
46
|
+
const commandsCount = countMarkdownFiles(installPath, 'commands');
|
|
47
|
+
if (commandsCount > 0) {
|
|
48
|
+
components.commands = commandsCount;
|
|
49
|
+
}
|
|
50
|
+
// Detect agents (count .md files in agents/ folder)
|
|
51
|
+
const agentsCount = countMarkdownFiles(installPath, 'agents');
|
|
52
|
+
if (agentsCount > 0) {
|
|
53
|
+
components.agents = agentsCount;
|
|
54
|
+
}
|
|
55
|
+
// Detect hooks
|
|
56
|
+
const hasHooks = detectHooks(installPath);
|
|
57
|
+
if (hasHooks) {
|
|
58
|
+
components.hooks = true;
|
|
59
|
+
}
|
|
60
|
+
// Detect MCP servers from plugin.json
|
|
61
|
+
const mcpCount = countMcpServers(installPath);
|
|
62
|
+
if (mcpCount > 0) {
|
|
63
|
+
components.mcpServers = mcpCount;
|
|
64
|
+
}
|
|
65
|
+
// Detect LSP servers from .lsp.json
|
|
66
|
+
const lspCount = countLspServers(installPath);
|
|
67
|
+
if (lspCount > 0) {
|
|
68
|
+
components.lspServers = lspCount;
|
|
69
|
+
}
|
|
70
|
+
// Return undefined if no components detected
|
|
71
|
+
if (Object.keys(components).length === 0) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
return components;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Detect detailed components for an installed plugin
|
|
78
|
+
* Reads skills/, commands/, agents/ directories and parses plugin.json
|
|
79
|
+
* @param installPath - Absolute path to installed plugin directory
|
|
80
|
+
* @returns Detailed component info with names and descriptions
|
|
81
|
+
* - Returns undefined if path doesn't exist or has no components
|
|
82
|
+
* @example
|
|
83
|
+
* detectComponentsDetailed('/path/to/plugin')
|
|
84
|
+
* // => { skills: [{ name: 'xlsx', description: '...', type: 'skill' }] }
|
|
85
|
+
*/
|
|
86
|
+
export function detectComponentsDetailed(installPath) {
|
|
87
|
+
if (!directoryExists(installPath)) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
const detailed = {};
|
|
91
|
+
// Skills: Read directory names + SKILL.md frontmatter
|
|
92
|
+
const skills = getSkillDetails(installPath);
|
|
93
|
+
if (skills.length > 0) {
|
|
94
|
+
detailed.skills = skills;
|
|
95
|
+
}
|
|
96
|
+
// Commands: Read .md filenames
|
|
97
|
+
const commands = getMarkdownFileDetails(installPath, 'commands', 'command');
|
|
98
|
+
if (commands.length > 0) {
|
|
99
|
+
detailed.commands = commands;
|
|
100
|
+
}
|
|
101
|
+
// Agents: Read .md filenames
|
|
102
|
+
const agents = getMarkdownFileDetails(installPath, 'agents', 'agent');
|
|
103
|
+
if (agents.length > 0) {
|
|
104
|
+
detailed.agents = agents;
|
|
105
|
+
}
|
|
106
|
+
// Hooks: Read event names from hooks.json or hooks/ directory
|
|
107
|
+
const hooks = getHookNames(installPath);
|
|
108
|
+
if (hooks.length > 0) {
|
|
109
|
+
detailed.hooks = hooks;
|
|
110
|
+
}
|
|
111
|
+
// MCP Servers: Read plugin.json mcpServers keys
|
|
112
|
+
const mcpServers = getMcpServerNames(installPath);
|
|
113
|
+
if (mcpServers.length > 0) {
|
|
114
|
+
detailed.mcpServers = mcpServers;
|
|
115
|
+
}
|
|
116
|
+
// LSP Servers: Read .lsp.json keys
|
|
117
|
+
const lspServers = getLspServerNames(installPath);
|
|
118
|
+
if (lspServers.length > 0) {
|
|
119
|
+
detailed.lspServers = lspServers;
|
|
120
|
+
}
|
|
121
|
+
// Return undefined if no components detected
|
|
122
|
+
if (Object.keys(detailed).length === 0) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
return detailed;
|
|
126
|
+
}
|