@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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown component detection and parsing service
|
|
3
|
+
* Handles commands/ and agents/ directory scanning
|
|
4
|
+
*/
|
|
5
|
+
import type { ComponentInfo, ComponentDetailedInfo } from '../../types/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Count .md files in a specific directory
|
|
8
|
+
* @param installPath - Plugin install path
|
|
9
|
+
* @param subdir - Subdirectory name ('commands' or 'agents')
|
|
10
|
+
* @returns Number of .md files
|
|
11
|
+
*/
|
|
12
|
+
export declare function countMarkdownFiles(installPath: string, subdir: string): number;
|
|
13
|
+
/**
|
|
14
|
+
* Get component details from .md files in a directory
|
|
15
|
+
* Uses filename (minus extension) as component name
|
|
16
|
+
* @param installPath - Plugin install path
|
|
17
|
+
* @param subdir - Subdirectory name ('commands' or 'agents')
|
|
18
|
+
* @param type - Component type
|
|
19
|
+
* @returns Array of ComponentInfo
|
|
20
|
+
*/
|
|
21
|
+
export declare function getMarkdownFileDetails(installPath: string, subdir: string, type: 'command' | 'agent'): ComponentInfo[];
|
|
22
|
+
/**
|
|
23
|
+
* Get detailed info for a command or agent markdown file
|
|
24
|
+
* @param installPath - Plugin install path
|
|
25
|
+
* @param componentName - Name of the component (without .md)
|
|
26
|
+
* @param type - Component type ('command' or 'agent')
|
|
27
|
+
* @returns ComponentDetailedInfo with full content
|
|
28
|
+
*/
|
|
29
|
+
export declare function getMarkdownComponentDetailedInfo(installPath: string, componentName: string, type: 'command' | 'agent'): ComponentDetailedInfo | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Parse first non-empty line of content as description
|
|
32
|
+
* Properly skips YAML frontmatter block and strips heading markers
|
|
33
|
+
* @param content - Markdown content string
|
|
34
|
+
* @returns First non-frontmatter, non-empty line or undefined
|
|
35
|
+
* @example
|
|
36
|
+
* parseFirstLineDescriptionFromContent("---\nname: test\n---\n# My Title\n")
|
|
37
|
+
* // => "My Title"
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseFirstLineDescriptionFromContent(content: string): string | undefined;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown component detection and parsing service
|
|
3
|
+
* Handles commands/ and agents/ directory scanning
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import { directoryExists, fileExists } from '../fileService.js';
|
|
8
|
+
/**
|
|
9
|
+
* Count .md files in a specific directory
|
|
10
|
+
* @param installPath - Plugin install path
|
|
11
|
+
* @param subdir - Subdirectory name ('commands' or 'agents')
|
|
12
|
+
* @returns Number of .md files
|
|
13
|
+
*/
|
|
14
|
+
export function countMarkdownFiles(installPath, subdir) {
|
|
15
|
+
const dirPath = path.join(installPath, subdir);
|
|
16
|
+
if (!directoryExists(dirPath)) {
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const files = fs.readdirSync(dirPath);
|
|
21
|
+
return files.filter((file) => file.endsWith('.md')).length;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get component details from .md files in a directory
|
|
29
|
+
* Uses filename (minus extension) as component name
|
|
30
|
+
* @param installPath - Plugin install path
|
|
31
|
+
* @param subdir - Subdirectory name ('commands' or 'agents')
|
|
32
|
+
* @param type - Component type
|
|
33
|
+
* @returns Array of ComponentInfo
|
|
34
|
+
*/
|
|
35
|
+
export function getMarkdownFileDetails(installPath, subdir, type) {
|
|
36
|
+
const dirPath = path.join(installPath, subdir);
|
|
37
|
+
if (!directoryExists(dirPath)) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const files = fs.readdirSync(dirPath);
|
|
42
|
+
return files
|
|
43
|
+
.filter((file) => file.endsWith('.md'))
|
|
44
|
+
.map((file) => {
|
|
45
|
+
const name = file.replace(/\.md$/, '');
|
|
46
|
+
const filePath = path.join(dirPath, file);
|
|
47
|
+
const description = parseFirstLineDescription(filePath);
|
|
48
|
+
return {
|
|
49
|
+
name,
|
|
50
|
+
description,
|
|
51
|
+
type,
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get detailed info for a command or agent markdown file
|
|
61
|
+
* @param installPath - Plugin install path
|
|
62
|
+
* @param componentName - Name of the component (without .md)
|
|
63
|
+
* @param type - Component type ('command' or 'agent')
|
|
64
|
+
* @returns ComponentDetailedInfo with full content
|
|
65
|
+
*/
|
|
66
|
+
export function getMarkdownComponentDetailedInfo(installPath, componentName, type) {
|
|
67
|
+
const subdir = type === 'command' ? 'commands' : 'agents';
|
|
68
|
+
const filePath = path.join(installPath, subdir, `${componentName}.md`);
|
|
69
|
+
if (!fileExists(filePath)) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
74
|
+
// Reuse content instead of reading file twice
|
|
75
|
+
const description = parseFirstLineDescriptionFromContent(content);
|
|
76
|
+
return {
|
|
77
|
+
name: componentName,
|
|
78
|
+
type,
|
|
79
|
+
description,
|
|
80
|
+
fullDescription: content,
|
|
81
|
+
filePath,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Parse first non-empty line of content as description
|
|
90
|
+
* Properly skips YAML frontmatter block and strips heading markers
|
|
91
|
+
* @param content - Markdown content string
|
|
92
|
+
* @returns First non-frontmatter, non-empty line or undefined
|
|
93
|
+
* @example
|
|
94
|
+
* parseFirstLineDescriptionFromContent("---\nname: test\n---\n# My Title\n")
|
|
95
|
+
* // => "My Title"
|
|
96
|
+
*/
|
|
97
|
+
export function parseFirstLineDescriptionFromContent(content) {
|
|
98
|
+
const lines = content.split('\n');
|
|
99
|
+
let inFrontmatter = false;
|
|
100
|
+
let frontmatterClosed = false;
|
|
101
|
+
for (const line of lines) {
|
|
102
|
+
const trimmed = line.trim();
|
|
103
|
+
// Detect frontmatter delimiter
|
|
104
|
+
if (trimmed === '---') {
|
|
105
|
+
if (!inFrontmatter && !frontmatterClosed) {
|
|
106
|
+
// Opening delimiter
|
|
107
|
+
inFrontmatter = true;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
else if (inFrontmatter) {
|
|
111
|
+
// Closing delimiter
|
|
112
|
+
inFrontmatter = false;
|
|
113
|
+
frontmatterClosed = true;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Skip lines inside frontmatter
|
|
118
|
+
if (inFrontmatter) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
// Skip empty lines
|
|
122
|
+
if (!trimmed) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
// Found first content line - remove heading markers and return
|
|
126
|
+
return trimmed.replace(/^#+\s*/, '');
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Parse first non-empty line of a markdown file as description
|
|
132
|
+
* Properly skips YAML frontmatter block and strips heading markers
|
|
133
|
+
* @param filePath - Path to markdown file
|
|
134
|
+
* @returns First non-frontmatter, non-empty line or undefined
|
|
135
|
+
* @example
|
|
136
|
+
* // File: "---\nname: test\n---\n# My Title\n"
|
|
137
|
+
* parseFirstLineDescription(path) // => "My Title"
|
|
138
|
+
*/
|
|
139
|
+
function parseFirstLineDescription(filePath) {
|
|
140
|
+
try {
|
|
141
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
142
|
+
return parseFirstLineDescriptionFromContent(content);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server component detection service
|
|
3
|
+
* Handles MCP and LSP server detection from configuration files
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Count MCP servers defined in plugin.json
|
|
7
|
+
* @param installPath - Plugin install path
|
|
8
|
+
* @returns Number of MCP server configurations
|
|
9
|
+
*/
|
|
10
|
+
export declare function countMcpServers(installPath: string): number;
|
|
11
|
+
/**
|
|
12
|
+
* Get MCP server names from plugin.json
|
|
13
|
+
* @param installPath - Plugin install path
|
|
14
|
+
* @returns Array of MCP server names
|
|
15
|
+
*/
|
|
16
|
+
export declare function getMcpServerNames(installPath: string): string[];
|
|
17
|
+
/**
|
|
18
|
+
* Count LSP servers defined in .lsp.json
|
|
19
|
+
* @param installPath - Plugin install path
|
|
20
|
+
* @returns Number of LSP server configurations
|
|
21
|
+
*/
|
|
22
|
+
export declare function countLspServers(installPath: string): number;
|
|
23
|
+
/**
|
|
24
|
+
* Get LSP server language IDs from .lsp.json
|
|
25
|
+
* @param installPath - Plugin install path
|
|
26
|
+
* @returns Array of language IDs
|
|
27
|
+
*/
|
|
28
|
+
export declare function getLspServerNames(installPath: string): string[];
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server component detection service
|
|
3
|
+
* Handles MCP and LSP server detection from configuration files
|
|
4
|
+
*/
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import { readJsonFile } from '../fileService.js';
|
|
7
|
+
/**
|
|
8
|
+
* Count MCP servers defined in plugin.json
|
|
9
|
+
* @param installPath - Plugin install path
|
|
10
|
+
* @returns Number of MCP server configurations
|
|
11
|
+
*/
|
|
12
|
+
export function countMcpServers(installPath) {
|
|
13
|
+
// Check both .claude-plugin/plugin.json and plugin.json at root
|
|
14
|
+
const pluginJsonPaths = [
|
|
15
|
+
path.join(installPath, '.claude-plugin', 'plugin.json'),
|
|
16
|
+
path.join(installPath, 'plugin.json'),
|
|
17
|
+
];
|
|
18
|
+
for (const pluginJsonPath of pluginJsonPaths) {
|
|
19
|
+
const pluginJson = readJsonFile(pluginJsonPath);
|
|
20
|
+
if (pluginJson?.mcpServers) {
|
|
21
|
+
return Object.keys(pluginJson.mcpServers).length;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get MCP server names from plugin.json
|
|
28
|
+
* @param installPath - Plugin install path
|
|
29
|
+
* @returns Array of MCP server names
|
|
30
|
+
*/
|
|
31
|
+
export function getMcpServerNames(installPath) {
|
|
32
|
+
const pluginJsonPaths = [
|
|
33
|
+
path.join(installPath, '.claude-plugin', 'plugin.json'),
|
|
34
|
+
path.join(installPath, 'plugin.json'),
|
|
35
|
+
];
|
|
36
|
+
for (const pluginJsonPath of pluginJsonPaths) {
|
|
37
|
+
const pluginJson = readJsonFile(pluginJsonPath);
|
|
38
|
+
if (pluginJson?.mcpServers) {
|
|
39
|
+
return Object.keys(pluginJson.mcpServers);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Count LSP servers defined in .lsp.json
|
|
46
|
+
* @param installPath - Plugin install path
|
|
47
|
+
* @returns Number of LSP server configurations
|
|
48
|
+
*/
|
|
49
|
+
export function countLspServers(installPath) {
|
|
50
|
+
const lspJsonPath = path.join(installPath, '.lsp.json');
|
|
51
|
+
const lspConfig = readJsonFile(lspJsonPath);
|
|
52
|
+
if (!lspConfig) {
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
return Object.keys(lspConfig).length;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get LSP server language IDs from .lsp.json
|
|
59
|
+
* @param installPath - Plugin install path
|
|
60
|
+
* @returns Array of language IDs
|
|
61
|
+
*/
|
|
62
|
+
export function getLspServerNames(installPath) {
|
|
63
|
+
const lspJsonPath = path.join(installPath, '.lsp.json');
|
|
64
|
+
const lspConfig = readJsonFile(lspJsonPath);
|
|
65
|
+
if (!lspConfig) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
return Object.keys(lspConfig);
|
|
69
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill component detection and parsing service
|
|
3
|
+
* Handles skills/ directory scanning and SKILL.md parsing
|
|
4
|
+
*/
|
|
5
|
+
import type { ComponentInfo, ComponentDetailedInfo } from '../../types/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Result of parsing full SKILL.md content
|
|
8
|
+
*/
|
|
9
|
+
interface SkillMdFullResult {
|
|
10
|
+
description?: string;
|
|
11
|
+
allowedTools?: string[];
|
|
12
|
+
fullDescription?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Count skill directories in the skills/ folder
|
|
16
|
+
* Skills are stored as subdirectories with SKILL.md files
|
|
17
|
+
* @param installPath - Plugin install path
|
|
18
|
+
* @returns Number of skill directories
|
|
19
|
+
*/
|
|
20
|
+
export declare function countSkills(installPath: string): number;
|
|
21
|
+
/**
|
|
22
|
+
* Get skill details from skills/ directory
|
|
23
|
+
* Reads directory names and parses SKILL.md frontmatter for descriptions
|
|
24
|
+
* @param installPath - Plugin install path
|
|
25
|
+
* @returns Array of ComponentInfo for each skill
|
|
26
|
+
*/
|
|
27
|
+
export declare function getSkillDetails(installPath: string): ComponentInfo[];
|
|
28
|
+
/**
|
|
29
|
+
* Parse full SKILL.md content including frontmatter and body
|
|
30
|
+
* Extracts description, allowed-tools, and full markdown body
|
|
31
|
+
* @param skillMdPath - Path to SKILL.md file
|
|
32
|
+
* @returns Parsed content or undefined if file doesn't exist
|
|
33
|
+
* @example
|
|
34
|
+
* parseSkillMdFull('/path/to/SKILL.md')
|
|
35
|
+
* // => { description: "...", allowedTools: ["Read", "Edit"], fullDescription: "..." }
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseSkillMdFull(skillMdPath: string): SkillMdFullResult | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Get detailed info for a specific skill component
|
|
40
|
+
* @param installPath - Plugin install path
|
|
41
|
+
* @param skillName - Name of the skill directory
|
|
42
|
+
* @returns ComponentDetailedInfo with full SKILL.md content
|
|
43
|
+
* @example
|
|
44
|
+
* getSkillDetailedInfo('/path/to/plugin', 'sentry-code-review')
|
|
45
|
+
* // => { name: 'sentry-code-review', type: 'skill', allowedTools: [...], ... }
|
|
46
|
+
*/
|
|
47
|
+
export declare function getSkillDetailedInfo(installPath: string, skillName: string): ComponentDetailedInfo | undefined;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill component detection and parsing service
|
|
3
|
+
* Handles skills/ directory scanning and SKILL.md parsing
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import { directoryExists, fileExists } from '../fileService.js';
|
|
8
|
+
/**
|
|
9
|
+
* Count skill directories in the skills/ folder
|
|
10
|
+
* Skills are stored as subdirectories with SKILL.md files
|
|
11
|
+
* @param installPath - Plugin install path
|
|
12
|
+
* @returns Number of skill directories
|
|
13
|
+
*/
|
|
14
|
+
export function countSkills(installPath) {
|
|
15
|
+
const skillsPath = path.join(installPath, 'skills');
|
|
16
|
+
if (!directoryExists(skillsPath)) {
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const entries = fs.readdirSync(skillsPath, { withFileTypes: true });
|
|
21
|
+
return entries.filter((entry) => entry.isDirectory()).length;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get skill details from skills/ directory
|
|
29
|
+
* Reads directory names and parses SKILL.md frontmatter for descriptions
|
|
30
|
+
* @param installPath - Plugin install path
|
|
31
|
+
* @returns Array of ComponentInfo for each skill
|
|
32
|
+
*/
|
|
33
|
+
export function getSkillDetails(installPath) {
|
|
34
|
+
const skillsPath = path.join(installPath, 'skills');
|
|
35
|
+
if (!directoryExists(skillsPath)) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const entries = fs.readdirSync(skillsPath, { withFileTypes: true });
|
|
40
|
+
return entries
|
|
41
|
+
.filter((entry) => entry.isDirectory())
|
|
42
|
+
.map((entry) => {
|
|
43
|
+
const skillMdPath = path.join(skillsPath, entry.name, 'SKILL.md');
|
|
44
|
+
const description = parseSkillMdDescription(skillMdPath);
|
|
45
|
+
return {
|
|
46
|
+
name: entry.name,
|
|
47
|
+
description,
|
|
48
|
+
type: 'skill',
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Parse SKILL.md frontmatter for description
|
|
58
|
+
* Looks for `description:` field in YAML frontmatter
|
|
59
|
+
* @param skillMdPath - Path to SKILL.md file
|
|
60
|
+
* @returns Description string or undefined
|
|
61
|
+
*/
|
|
62
|
+
function parseSkillMdDescription(skillMdPath) {
|
|
63
|
+
if (!fileExists(skillMdPath)) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const content = fs.readFileSync(skillMdPath, 'utf-8');
|
|
68
|
+
// Match YAML frontmatter description field
|
|
69
|
+
const match = content.match(/^---\n[\s\S]*?description:\s*["']?(.+?)["']?\s*\n[\s\S]*?---/m);
|
|
70
|
+
return match?.[1]?.trim();
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Parse full SKILL.md content including frontmatter and body
|
|
78
|
+
* Extracts description, allowed-tools, and full markdown body
|
|
79
|
+
* @param skillMdPath - Path to SKILL.md file
|
|
80
|
+
* @returns Parsed content or undefined if file doesn't exist
|
|
81
|
+
* @example
|
|
82
|
+
* parseSkillMdFull('/path/to/SKILL.md')
|
|
83
|
+
* // => { description: "...", allowedTools: ["Read", "Edit"], fullDescription: "..." }
|
|
84
|
+
*/
|
|
85
|
+
export function parseSkillMdFull(skillMdPath) {
|
|
86
|
+
if (!fileExists(skillMdPath)) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const content = fs.readFileSync(skillMdPath, 'utf-8');
|
|
91
|
+
const result = {};
|
|
92
|
+
// Check for frontmatter
|
|
93
|
+
if (!content.startsWith('---')) {
|
|
94
|
+
// No frontmatter, entire content is the body
|
|
95
|
+
result.fullDescription = content.trim();
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
// Find closing frontmatter delimiter
|
|
99
|
+
const endIndex = content.indexOf('---', 3);
|
|
100
|
+
if (endIndex === -1) {
|
|
101
|
+
// Malformed frontmatter
|
|
102
|
+
result.fullDescription = content.trim();
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
const frontmatter = content.substring(3, endIndex);
|
|
106
|
+
const body = content.substring(endIndex + 3).trim();
|
|
107
|
+
// Parse description from frontmatter
|
|
108
|
+
const descMatch = frontmatter.match(/description:\s*["']?(.+?)["']?\s*$/m);
|
|
109
|
+
if (descMatch?.[1]) {
|
|
110
|
+
result.description = descMatch[1].trim();
|
|
111
|
+
}
|
|
112
|
+
// Parse allowed-tools from frontmatter (can be array or comma-separated)
|
|
113
|
+
const toolsMatch = frontmatter.match(/allowed-tools:\s*\[([^\]]+)\]/m);
|
|
114
|
+
if (toolsMatch?.[1]) {
|
|
115
|
+
// Array format: allowed-tools: [Read, Edit, Write]
|
|
116
|
+
result.allowedTools = toolsMatch[1]
|
|
117
|
+
.split(',')
|
|
118
|
+
.map((t) => t.trim().replace(/["']/g, ''))
|
|
119
|
+
.filter(Boolean);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// Try single line format: allowed-tools: Read, Edit
|
|
123
|
+
const singleMatch = frontmatter.match(/allowed-tools:\s*(.+)$/m);
|
|
124
|
+
if (singleMatch?.[1]) {
|
|
125
|
+
result.allowedTools = singleMatch[1]
|
|
126
|
+
.split(',')
|
|
127
|
+
.map((t) => t.trim().replace(/["']/g, ''))
|
|
128
|
+
.filter(Boolean);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Body is the content after frontmatter
|
|
132
|
+
if (body) {
|
|
133
|
+
result.fullDescription = body;
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get detailed info for a specific skill component
|
|
143
|
+
* @param installPath - Plugin install path
|
|
144
|
+
* @param skillName - Name of the skill directory
|
|
145
|
+
* @returns ComponentDetailedInfo with full SKILL.md content
|
|
146
|
+
* @example
|
|
147
|
+
* getSkillDetailedInfo('/path/to/plugin', 'sentry-code-review')
|
|
148
|
+
* // => { name: 'sentry-code-review', type: 'skill', allowedTools: [...], ... }
|
|
149
|
+
*/
|
|
150
|
+
export function getSkillDetailedInfo(installPath, skillName) {
|
|
151
|
+
const skillMdPath = path.join(installPath, 'skills', skillName, 'SKILL.md');
|
|
152
|
+
if (!fileExists(skillMdPath)) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
const parsed = parseSkillMdFull(skillMdPath);
|
|
156
|
+
return {
|
|
157
|
+
name: skillName,
|
|
158
|
+
type: 'skill',
|
|
159
|
+
description: parsed?.description,
|
|
160
|
+
allowedTools: parsed?.allowedTools,
|
|
161
|
+
fullDescription: parsed?.fullDescription,
|
|
162
|
+
filePath: skillMdPath,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for plugin component operations
|
|
3
|
+
* Helper functions for checking and counting components
|
|
4
|
+
*/
|
|
5
|
+
import type { PluginComponents } from '../../types/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Check if a plugin has any components
|
|
8
|
+
* @param components - PluginComponents object
|
|
9
|
+
* @returns true if at least one component type is present
|
|
10
|
+
* @example
|
|
11
|
+
* hasAnyComponents({ skills: 2 }) // => true
|
|
12
|
+
* hasAnyComponents({}) // => false
|
|
13
|
+
* hasAnyComponents(undefined) // => false
|
|
14
|
+
*/
|
|
15
|
+
export declare function hasAnyComponents(components: PluginComponents | undefined): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Get total component count for a plugin
|
|
18
|
+
* @param components - PluginComponents object
|
|
19
|
+
* @returns Total number of components (hooks count as 1)
|
|
20
|
+
* @example
|
|
21
|
+
* getTotalComponentCount({ skills: 3, commands: 2, hooks: true }) // => 6
|
|
22
|
+
*/
|
|
23
|
+
export declare function getTotalComponentCount(components: PluginComponents | undefined): number;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for plugin component operations
|
|
3
|
+
* Helper functions for checking and counting components
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Check if a plugin has any components
|
|
7
|
+
* @param components - PluginComponents object
|
|
8
|
+
* @returns true if at least one component type is present
|
|
9
|
+
* @example
|
|
10
|
+
* hasAnyComponents({ skills: 2 }) // => true
|
|
11
|
+
* hasAnyComponents({}) // => false
|
|
12
|
+
* hasAnyComponents(undefined) // => false
|
|
13
|
+
*/
|
|
14
|
+
export function hasAnyComponents(components) {
|
|
15
|
+
if (!components) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return ((components.skills ?? 0) > 0 ||
|
|
19
|
+
(components.commands ?? 0) > 0 ||
|
|
20
|
+
(components.agents ?? 0) > 0 ||
|
|
21
|
+
components.hooks === true ||
|
|
22
|
+
(components.mcpServers ?? 0) > 0 ||
|
|
23
|
+
(components.lspServers ?? 0) > 0);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get total component count for a plugin
|
|
27
|
+
* @param components - PluginComponents object
|
|
28
|
+
* @returns Total number of components (hooks count as 1)
|
|
29
|
+
* @example
|
|
30
|
+
* getTotalComponentCount({ skills: 3, commands: 2, hooks: true }) // => 6
|
|
31
|
+
*/
|
|
32
|
+
export function getTotalComponentCount(components) {
|
|
33
|
+
if (!components) {
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
return ((components.skills ?? 0) +
|
|
37
|
+
(components.commands ?? 0) +
|
|
38
|
+
(components.agents ?? 0) +
|
|
39
|
+
(components.hooks ? 1 : 0) +
|
|
40
|
+
(components.mcpServers ?? 0) +
|
|
41
|
+
(components.lspServers ?? 0));
|
|
42
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Plugin actions service for install/uninstall operations
|
|
3
|
-
* Executes `claude plugin install/uninstall` as subprocess
|
|
2
|
+
* Plugin actions service for install/uninstall/update operations
|
|
3
|
+
* Executes `claude plugin install/uninstall/update` as subprocess
|
|
4
4
|
*/
|
|
5
5
|
export interface PluginActionResult {
|
|
6
6
|
success: boolean;
|
|
@@ -19,3 +19,32 @@ export declare function installPlugin(pluginId: string): Promise<PluginActionRes
|
|
|
19
19
|
* @returns Promise resolving to action result
|
|
20
20
|
*/
|
|
21
21
|
export declare function uninstallPlugin(pluginId: string): Promise<PluginActionResult>;
|
|
22
|
+
/**
|
|
23
|
+
* Update a plugin via Claude CLI
|
|
24
|
+
* @param pluginId - Plugin identifier
|
|
25
|
+
* @returns Promise resolving to action result
|
|
26
|
+
*/
|
|
27
|
+
export declare function updatePlugin(pluginId: string): Promise<PluginActionResult>;
|
|
28
|
+
/**
|
|
29
|
+
* Result of a bulk update operation
|
|
30
|
+
*/
|
|
31
|
+
export interface UpdateAllResult {
|
|
32
|
+
total: number;
|
|
33
|
+
succeeded: number;
|
|
34
|
+
failed: number;
|
|
35
|
+
results: Array<{
|
|
36
|
+
pluginId: string;
|
|
37
|
+
result: PluginActionResult;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Update all plugins sequentially
|
|
42
|
+
* @param pluginIds - Array of plugin identifiers to update
|
|
43
|
+
* @param onProgress - Optional callback for progress reporting
|
|
44
|
+
* @returns Promise resolving to bulk update result
|
|
45
|
+
* @example
|
|
46
|
+
* const result = await updateAllPlugins(['ctx7@official', 'sup@official'], (cur, total, id) => {
|
|
47
|
+
* console.log(`Updating (${cur}/${total}): ${id}...`)
|
|
48
|
+
* })
|
|
49
|
+
*/
|
|
50
|
+
export declare function updateAllPlugins(pluginIds: string[], onProgress?: (current: number, total: number, pluginId: string) => void): Promise<UpdateAllResult>;
|