@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.
Files changed (47) hide show
  1. package/README.md +1 -0
  2. package/dist/app.js +346 -211
  3. package/dist/cli.js +3 -1
  4. package/dist/components/ComponentBadges.d.ts +0 -9
  5. package/dist/components/ComponentBadges.js +0 -33
  6. package/dist/components/ComponentDetail.d.ts +32 -0
  7. package/dist/components/ComponentDetail.js +106 -0
  8. package/dist/components/ComponentList.d.ts +36 -2
  9. package/dist/components/ComponentList.js +105 -11
  10. package/dist/components/HelpOverlay.js +1 -0
  11. package/dist/components/KeyHints.d.ts +1 -0
  12. package/dist/components/KeyHints.js +8 -1
  13. package/dist/components/PluginDetail.d.ts +16 -3
  14. package/dist/components/PluginDetail.js +29 -3
  15. package/dist/services/componentService.d.ts +10 -42
  16. package/dist/services/componentService.js +19 -412
  17. package/dist/services/components/hookService.d.ts +17 -0
  18. package/dist/services/components/hookService.js +45 -0
  19. package/dist/services/components/index.d.ts +41 -0
  20. package/dist/services/components/index.js +126 -0
  21. package/dist/services/components/markdownService.d.ts +39 -0
  22. package/dist/services/components/markdownService.js +147 -0
  23. package/dist/services/components/serverService.d.ts +28 -0
  24. package/dist/services/components/serverService.js +69 -0
  25. package/dist/services/components/skillService.d.ts +48 -0
  26. package/dist/services/components/skillService.js +164 -0
  27. package/dist/services/components/utils.d.ts +23 -0
  28. package/dist/services/components/utils.js +42 -0
  29. package/dist/services/pluginActionsService.d.ts +31 -2
  30. package/dist/services/pluginActionsService.js +65 -6
  31. package/dist/store/index.d.ts +46 -0
  32. package/dist/store/index.js +47 -0
  33. package/dist/store/slices/marketplaceSlice.d.ts +344 -0
  34. package/dist/store/slices/marketplaceSlice.js +152 -0
  35. package/dist/store/slices/pluginSlice.d.ts +1544 -0
  36. package/dist/store/slices/pluginSlice.js +191 -0
  37. package/dist/store/slices/uiSlice.d.ts +147 -0
  38. package/dist/store/slices/uiSlice.js +126 -0
  39. package/dist/tabs/DiscoverTab.d.ts +8 -2
  40. package/dist/tabs/DiscoverTab.js +2 -2
  41. package/dist/tabs/EnabledTab.d.ts +8 -2
  42. package/dist/tabs/EnabledTab.js +2 -2
  43. package/dist/tabs/ErrorsTab.js +1 -1
  44. package/dist/tabs/InstalledTab.d.ts +8 -2
  45. package/dist/tabs/InstalledTab.js +2 -2
  46. package/dist/types/index.d.ts +47 -4
  47. 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>;