@laststance/claude-plugin-dashboard 0.2.3 → 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 (58) hide show
  1. package/README.md +7 -1
  2. package/dist/app.d.ts +7 -1
  3. package/dist/app.js +544 -262
  4. package/dist/cli.js +60 -67
  5. package/dist/components/ComponentBadges.d.ts +0 -9
  6. package/dist/components/ComponentBadges.js +0 -33
  7. package/dist/components/ComponentDetail.d.ts +32 -0
  8. package/dist/components/ComponentDetail.js +106 -0
  9. package/dist/components/ComponentList.d.ts +87 -0
  10. package/dist/components/ComponentList.js +287 -0
  11. package/dist/components/HelpOverlay.js +1 -0
  12. package/dist/components/KeyHints.d.ts +1 -0
  13. package/dist/components/KeyHints.js +33 -29
  14. package/dist/components/MarketplaceActionMenu.d.ts +41 -0
  15. package/dist/components/MarketplaceActionMenu.js +68 -0
  16. package/dist/components/MarketplaceDetail.d.ts +10 -3
  17. package/dist/components/MarketplaceDetail.js +10 -4
  18. package/dist/components/PluginDetail.d.ts +19 -3
  19. package/dist/components/PluginDetail.js +56 -6
  20. package/dist/components/PluginList.js +19 -7
  21. package/dist/services/componentService.d.ts +10 -31
  22. package/dist/services/componentService.js +19 -174
  23. package/dist/services/components/hookService.d.ts +17 -0
  24. package/dist/services/components/hookService.js +45 -0
  25. package/dist/services/components/index.d.ts +41 -0
  26. package/dist/services/components/index.js +126 -0
  27. package/dist/services/components/markdownService.d.ts +39 -0
  28. package/dist/services/components/markdownService.js +147 -0
  29. package/dist/services/components/serverService.d.ts +28 -0
  30. package/dist/services/components/serverService.js +69 -0
  31. package/dist/services/components/skillService.d.ts +48 -0
  32. package/dist/services/components/skillService.js +164 -0
  33. package/dist/services/components/utils.d.ts +23 -0
  34. package/dist/services/components/utils.js +42 -0
  35. package/dist/services/marketplaceActionsService.d.ts +17 -0
  36. package/dist/services/marketplaceActionsService.js +18 -0
  37. package/dist/services/pluginActionsService.d.ts +31 -2
  38. package/dist/services/pluginActionsService.js +65 -6
  39. package/dist/services/pluginService.js +78 -2
  40. package/dist/store/index.d.ts +46 -0
  41. package/dist/store/index.js +47 -0
  42. package/dist/store/slices/marketplaceSlice.d.ts +344 -0
  43. package/dist/store/slices/marketplaceSlice.js +152 -0
  44. package/dist/store/slices/pluginSlice.d.ts +1544 -0
  45. package/dist/store/slices/pluginSlice.js +191 -0
  46. package/dist/store/slices/uiSlice.d.ts +147 -0
  47. package/dist/store/slices/uiSlice.js +126 -0
  48. package/dist/tabs/DiscoverTab.d.ts +8 -2
  49. package/dist/tabs/DiscoverTab.js +2 -2
  50. package/dist/tabs/EnabledTab.d.ts +8 -2
  51. package/dist/tabs/EnabledTab.js +3 -3
  52. package/dist/tabs/ErrorsTab.js +1 -1
  53. package/dist/tabs/InstalledTab.d.ts +8 -2
  54. package/dist/tabs/InstalledTab.js +3 -3
  55. package/dist/tabs/MarketplacesTab.d.ts +15 -2
  56. package/dist/tabs/MarketplacesTab.js +13 -4
  57. package/dist/types/index.d.ts +157 -5
  58. package/package.json +10 -3
package/dist/cli.js CHANGED
@@ -14,12 +14,16 @@ import { jsx as _jsx } from "react/jsx-runtime";
14
14
  * claude-plugin-dashboard toggle <plugin-id> # Toggle plugin
15
15
  * claude-plugin-dashboard help # Show help
16
16
  */
17
- import { render } from 'ink';
17
+ import { withFullScreen } from 'fullscreen-ink';
18
+ import { match, P } from 'ts-pattern';
19
+ import { Provider } from 'react-redux';
18
20
  import App from './app.js';
21
+ import { store } from './store/index.js';
19
22
  import { loadAllPlugins, loadInstalledPlugins, loadMarketplaces, getPluginStatistics, getPluginById, } from './services/pluginService.js';
20
23
  import { enablePlugin, disablePlugin, togglePlugin, } from './services/settingsService.js';
21
24
  import { fileExists } from './services/fileService.js';
22
25
  import { PATHS } from './utils/paths.js';
26
+ import packageJson from '../package.json' with { type: 'json' };
23
27
  const args = process.argv.slice(2);
24
28
  const command = args[0];
25
29
  const subCommand = args[1];
@@ -243,66 +247,57 @@ function checkClaudeCodeInstalled() {
243
247
  if (command) {
244
248
  // Non-interactive mode
245
249
  checkClaudeCodeInstalled();
246
- switch (command) {
247
- case 'status':
248
- showStatus();
249
- break;
250
- case 'list':
251
- if (subCommand === '--installed' || args.includes('--installed')) {
252
- listPlugins({ installed: true });
253
- }
254
- else if (subCommand === '--marketplace' ||
255
- args.includes('--marketplace')) {
256
- const marketplaceIndex = args.indexOf('--marketplace');
257
- const marketplace = args[marketplaceIndex + 1];
258
- listPlugins({ marketplace });
259
- }
260
- else {
261
- listPlugins({});
262
- }
263
- break;
264
- case 'info':
265
- if (!subCommand) {
266
- console.error('Usage: claude-plugin-dashboard info <plugin-id>');
267
- process.exit(1);
268
- }
269
- showPluginInfo(subCommand);
270
- break;
271
- case 'enable':
272
- if (!subCommand) {
273
- console.error('Usage: claude-plugin-dashboard enable <plugin-id>');
274
- process.exit(1);
275
- }
276
- handleEnable(subCommand);
277
- break;
278
- case 'disable':
279
- if (!subCommand) {
280
- console.error('Usage: claude-plugin-dashboard disable <plugin-id>');
281
- process.exit(1);
282
- }
283
- handleDisable(subCommand);
284
- break;
285
- case 'toggle':
286
- if (!subCommand) {
287
- console.error('Usage: claude-plugin-dashboard toggle <plugin-id>');
288
- process.exit(1);
289
- }
290
- handleToggle(subCommand);
291
- break;
292
- case 'help':
293
- case '-h':
294
- case '--help':
295
- showHelp();
296
- break;
297
- case '-v':
298
- case '--version':
299
- console.log('claude-plugin-dashboard v0.1.0');
300
- break;
301
- default:
302
- console.error(`Unknown command: ${command}`);
303
- console.log('Run "claude-plugin-dashboard help" for usage information.');
250
+ match(command)
251
+ .with('status', () => showStatus())
252
+ .with('list', () => {
253
+ if (subCommand === '--installed' || args.includes('--installed')) {
254
+ listPlugins({ installed: true });
255
+ }
256
+ else if (subCommand === '--marketplace' ||
257
+ args.includes('--marketplace')) {
258
+ const marketplaceIndex = args.indexOf('--marketplace');
259
+ const marketplace = args[marketplaceIndex + 1];
260
+ listPlugins({ marketplace });
261
+ }
262
+ else {
263
+ listPlugins({});
264
+ }
265
+ })
266
+ .with('info', () => {
267
+ if (!subCommand) {
268
+ console.error('Usage: claude-plugin-dashboard info <plugin-id>');
304
269
  process.exit(1);
305
- }
270
+ }
271
+ showPluginInfo(subCommand);
272
+ })
273
+ .with('enable', () => {
274
+ if (!subCommand) {
275
+ console.error('Usage: claude-plugin-dashboard enable <plugin-id>');
276
+ process.exit(1);
277
+ }
278
+ handleEnable(subCommand);
279
+ })
280
+ .with('disable', () => {
281
+ if (!subCommand) {
282
+ console.error('Usage: claude-plugin-dashboard disable <plugin-id>');
283
+ process.exit(1);
284
+ }
285
+ handleDisable(subCommand);
286
+ })
287
+ .with('toggle', () => {
288
+ if (!subCommand) {
289
+ console.error('Usage: claude-plugin-dashboard toggle <plugin-id>');
290
+ process.exit(1);
291
+ }
292
+ handleToggle(subCommand);
293
+ })
294
+ .with(P.union('help', '-h', '--help'), () => showHelp())
295
+ .with(P.union('-v', '--version'), () => console.log(`claude-plugin-dashboard v${packageJson.version}`))
296
+ .otherwise((cmd) => {
297
+ console.error(`Unknown command: ${cmd}`);
298
+ console.log('Run "claude-plugin-dashboard help" for usage information.');
299
+ process.exit(1);
300
+ });
306
301
  }
307
302
  else {
308
303
  // Interactive mode
@@ -312,11 +307,9 @@ else {
312
307
  console.log('Use "claude-plugin-dashboard help" for non-interactive commands.');
313
308
  process.exit(1);
314
309
  }
315
- const instance = render(_jsx(App, {}));
316
- // Clear screen when app exits (q key or Ctrl+C)
317
- instance.waitUntilExit().then(() => {
318
- instance.clear();
319
- // Clear entire terminal screen and reset cursor to top-left
320
- process.stdout.write('\x1b[2J\x1b[H');
321
- });
310
+ // Use fullscreen-ink for alternate screen buffer management
311
+ // This prevents rendering artifacts when switching tabs
312
+ const ink = withFullScreen(_jsx(Provider, { store: store, children: _jsx(App, {}) }));
313
+ ink.start();
314
+ ink.waitUntilExit();
322
315
  }
@@ -21,12 +21,3 @@ export interface ComponentBadgesProps {
21
21
  * // Renders: Skills:5 Slash:2
22
22
  */
23
23
  export default function ComponentBadges({ components, }: ComponentBadgesProps): React.ReactNode;
24
- /**
25
- * Get a human-readable description of component types
26
- * @param components - PluginComponents object
27
- * @returns Formatted string describing components
28
- * @example
29
- * getComponentsDescription({ skills: 3, mcpServers: 1 })
30
- * // => "3 skills, 1 MCP server"
31
- */
32
- export declare function getComponentsDescription(components: PluginComponents | undefined): string;
@@ -48,36 +48,3 @@ export default function ComponentBadges({ components, }) {
48
48
  function Badge({ label, count, color, }) {
49
49
  return (_jsxs(Text, { children: [_jsx(Text, { color: color, bold: true, children: label }), count !== undefined && _jsxs(Text, { dimColor: true, children: [":", count] })] }));
50
50
  }
51
- /**
52
- * Get a human-readable description of component types
53
- * @param components - PluginComponents object
54
- * @returns Formatted string describing components
55
- * @example
56
- * getComponentsDescription({ skills: 3, mcpServers: 1 })
57
- * // => "3 skills, 1 MCP server"
58
- */
59
- export function getComponentsDescription(components) {
60
- if (!components) {
61
- return '';
62
- }
63
- const parts = [];
64
- if (components.skills) {
65
- parts.push(`${components.skills} skill${components.skills > 1 ? 's' : ''}`);
66
- }
67
- if (components.commands) {
68
- parts.push(`${components.commands} command${components.commands > 1 ? 's' : ''}`);
69
- }
70
- if (components.agents) {
71
- parts.push(`${components.agents} agent${components.agents > 1 ? 's' : ''}`);
72
- }
73
- if (components.hooks) {
74
- parts.push('hooks');
75
- }
76
- if (components.mcpServers) {
77
- parts.push(`${components.mcpServers} MCP server${components.mcpServers > 1 ? 's' : ''}`);
78
- }
79
- if (components.lspServers) {
80
- parts.push(`${components.lspServers} LSP server${components.lspServers > 1 ? 's' : ''}`);
81
- }
82
- return parts.join(', ');
83
- }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * ComponentDetail component
3
+ * Displays detailed information about a selected component
4
+ * Shows name, type, description, allowed tools, and full content
5
+ */
6
+ import type { ComponentDetailedInfo } from '../types/index.js';
7
+ /**
8
+ * Props for ComponentDetail
9
+ */
10
+ export interface ComponentDetailProps {
11
+ /** Detailed component info to display */
12
+ component: ComponentDetailedInfo | null;
13
+ /** Maximum height for the detail panel */
14
+ maxHeight?: number;
15
+ }
16
+ /**
17
+ * Displays detailed component information in a panel
18
+ * Used when user selects a component from the ComponentList
19
+ * Supports compact mode when maxHeight <= 4 (shows only name, type, description)
20
+ * @param props - ComponentDetailProps
21
+ * @returns React node
22
+ * @example
23
+ * <ComponentDetail
24
+ * component={{
25
+ * name: "sentry-code-review",
26
+ * type: "skill",
27
+ * description: "Analyze Sentry comments",
28
+ * allowedTools: ["Read", "Edit"]
29
+ * }}
30
+ * />
31
+ */
32
+ export default function ComponentDetail({ component, maxHeight, }: ComponentDetailProps): React.ReactNode;
@@ -0,0 +1,106 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * ComponentDetail component
4
+ * Displays detailed information about a selected component
5
+ * Shows name, type, description, allowed tools, and full content
6
+ */
7
+ import * as path from 'node:path';
8
+ import { Box, Text } from 'ink';
9
+ /**
10
+ * Type badge colors for visual distinction
11
+ */
12
+ const TYPE_COLORS = {
13
+ skill: 'magenta',
14
+ command: 'cyan',
15
+ agent: 'blue',
16
+ hook: 'yellow',
17
+ mcp: 'green',
18
+ lsp: 'blueBright',
19
+ };
20
+ /**
21
+ * Type labels for display
22
+ */
23
+ const TYPE_LABELS = {
24
+ skill: 'Skill',
25
+ command: 'Slash Command',
26
+ agent: 'Agent',
27
+ hook: 'Hook',
28
+ mcp: 'MCP Server',
29
+ lsp: 'LSP Server',
30
+ };
31
+ /**
32
+ * Compact mode threshold - show minimal info when height is small
33
+ */
34
+ const COMPACT_MODE_THRESHOLD = 4;
35
+ /**
36
+ * Displays detailed component information in a panel
37
+ * Used when user selects a component from the ComponentList
38
+ * Supports compact mode when maxHeight <= 4 (shows only name, type, description)
39
+ * @param props - ComponentDetailProps
40
+ * @returns React node
41
+ * @example
42
+ * <ComponentDetail
43
+ * component={{
44
+ * name: "sentry-code-review",
45
+ * type: "skill",
46
+ * description: "Analyze Sentry comments",
47
+ * allowedTools: ["Read", "Edit"]
48
+ * }}
49
+ * />
50
+ */
51
+ export default function ComponentDetail({ component, maxHeight = 10, }) {
52
+ // Safe content height calculation (at least 1 line)
53
+ const contentHeight = Math.max(1, maxHeight - 6);
54
+ const isCompact = maxHeight <= COMPACT_MODE_THRESHOLD;
55
+ if (!component) {
56
+ return (_jsx(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, height: maxHeight, children: _jsx(Text, { dimColor: true, children: "Select a component to view details" }) }));
57
+ }
58
+ const typeColor = TYPE_COLORS[component.type] || 'white';
59
+ const typeLabel = TYPE_LABELS[component.type] || component.type;
60
+ // Compact mode: minimal info only (name, type, description)
61
+ if (isCompact) {
62
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, height: maxHeight, overflow: "hidden", children: [_jsxs(Box, { height: 1, children: [_jsxs(Text, { bold: true, color: "white", children: ["\uD83D\uDCE6 ", truncateString(component.name, 20)] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: typeColor, bold: true, children: ["[", typeLabel, "]"] })] }), _jsx(Box, { height: 1, children: _jsx(Text, { wrap: "truncate", dimColor: true, children: component.description || 'No description' }) })] }));
63
+ }
64
+ // Full mode: show all details
65
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, height: maxHeight, overflow: "hidden", children: [_jsxs(Box, { height: 1, children: [_jsxs(Text, { bold: true, color: "white", children: ["\uD83D\uDCE6 ", component.name] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: typeColor, bold: true, children: ["[", typeLabel, "]"] })] }), component.description && (_jsx(Box, { height: 1, children: _jsx(Text, { wrap: "truncate", children: component.description }) })), component.allowedTools && component.allowedTools.length > 0 && (_jsxs(Box, { height: 1, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["Tools:", ' '] }), _jsx(Text, { dimColor: true, wrap: "truncate", children: component.allowedTools.join(', ') })] })), component.filePath && (_jsx(Box, { height: 1, children: _jsxs(Text, { color: "gray", children: ["\uD83D\uDCC4 ", shortenPath(component.filePath)] }) })), component.fullDescription && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: "\u2500\u2500 Content \u2500\u2500" }), _jsx(Box, { height: contentHeight, overflow: "hidden", children: _jsx(Text, { dimColor: true, wrap: "truncate", children: truncateContent(component.fullDescription, contentHeight) }) })] }))] }));
66
+ }
67
+ /**
68
+ * Truncate string to max length with ellipsis
69
+ * @param str - String to truncate
70
+ * @param maxLength - Maximum length
71
+ * @returns Truncated string
72
+ */
73
+ function truncateString(str, maxLength) {
74
+ if (str.length <= maxLength) {
75
+ return str;
76
+ }
77
+ return str.slice(0, maxLength - 1) + '…';
78
+ }
79
+ /**
80
+ * Shorten file path for display (cross-platform)
81
+ * Shows only the last few path components
82
+ * @param filePath - Full file path
83
+ * @returns Shortened path with forward slashes for consistent display
84
+ */
85
+ function shortenPath(filePath) {
86
+ const normalized = path.normalize(filePath);
87
+ const parts = normalized.split(path.sep);
88
+ // Show last 4 components: .../skills/component-name/SKILL.md
89
+ if (parts.length > 4) {
90
+ return '.../' + parts.slice(-4).join('/');
91
+ }
92
+ return parts.join('/');
93
+ }
94
+ /**
95
+ * Truncate multi-line content to fit within height
96
+ * @param content - Full content string
97
+ * @param maxLines - Maximum number of lines
98
+ * @returns Truncated content
99
+ */
100
+ function truncateContent(content, maxLines) {
101
+ const lines = content.split('\n');
102
+ if (lines.length <= maxLines) {
103
+ return content;
104
+ }
105
+ return lines.slice(0, maxLines).join('\n') + '\n...';
106
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * ComponentList component
3
+ * Displays detailed plugin components in a collapsible list format
4
+ * Shows component names when available, falls back to counts
5
+ *
6
+ * Data Source Architecture:
7
+ * - Installed plugins: Names + descriptions from file system scan
8
+ * - Not installed: Names only from marketplace JSON (if available)
9
+ * - Fallback: Count-only display from PluginComponents
10
+ */
11
+ import type { ComponentInfo, PluginComponents, PluginComponentsDetailed } from '../types/index.js';
12
+ /**
13
+ * Flattened component item for selection tracking
14
+ * Combines type and info for easy navigation
15
+ */
16
+ export interface FlatComponentItem {
17
+ /** Component info */
18
+ info: ComponentInfo;
19
+ /** Category label */
20
+ category: string;
21
+ /** Category color */
22
+ color: string;
23
+ }
24
+ /**
25
+ * Props for ComponentList
26
+ */
27
+ export interface ComponentListProps {
28
+ /** Component counts (backward compat fallback) */
29
+ components?: PluginComponents;
30
+ /** Detailed component info with names */
31
+ componentsDetailed?: PluginComponentsDetailed;
32
+ /** Maximum visible items per category (default: 3) */
33
+ maxItems?: number;
34
+ /** Whether this list is focused for selection */
35
+ isFocused?: boolean;
36
+ /** Currently selected index in flattened list */
37
+ selectedIndex?: number;
38
+ /** Callback when selection changes */
39
+ onSelect?: (item: FlatComponentItem, index: number) => void;
40
+ /** Number of visible items in virtual scroll viewport (default: 5) */
41
+ visibleCount?: number;
42
+ }
43
+ /**
44
+ * Flatten all components from detailed info into a single array for selection
45
+ * @param componentsDetailed - Detailed component info
46
+ * @returns Array of FlatComponentItem for navigation
47
+ * @example
48
+ * flattenComponents({ skills: [{ name: 'xlsx', type: 'skill' }] })
49
+ * // => [{ info: { name: 'xlsx', type: 'skill' }, category: 'Skills', color: 'magenta' }]
50
+ */
51
+ export declare function flattenComponents(componentsDetailed?: PluginComponentsDetailed): FlatComponentItem[];
52
+ /**
53
+ * Displays component details in a collapsible list
54
+ * Shows names when available, falls back to counts
55
+ * Supports selection mode when isFocused is true
56
+ * Uses virtual scrolling when focused to prevent layout overflow
57
+ * @param props - ComponentListProps
58
+ * @returns React node or null if no components
59
+ * @example
60
+ * <ComponentList
61
+ * componentsDetailed={{ skills: [{ name: 'xlsx', type: 'skill' }] }}
62
+ * maxItems={3}
63
+ * isFocused={true}
64
+ * selectedIndex={0}
65
+ * visibleCount={5}
66
+ * />
67
+ */
68
+ export default function ComponentList({ components, componentsDetailed, maxItems, isFocused, selectedIndex, visibleCount, }: ComponentListProps): React.ReactNode;
69
+ /**
70
+ * Check if PluginComponentsDetailed has any data
71
+ * @param detailed - Detailed components object
72
+ * @returns true if any category has items
73
+ * @example
74
+ * hasAnyDetailedComponents({ skills: [{ name: 'xlsx', type: 'skill' }] }) // => true
75
+ * hasAnyDetailedComponents({}) // => false
76
+ */
77
+ export declare function hasAnyDetailedComponents(detailed: PluginComponentsDetailed): boolean;
78
+ /**
79
+ * Check if PluginComponents has any count data
80
+ * @param components - Components counts object
81
+ * @returns true if any category has count > 0
82
+ * @example
83
+ * hasAnyCountComponents({ skills: 5 }) // => true
84
+ * hasAnyCountComponents({ hooks: true }) // => true
85
+ * hasAnyCountComponents({}) // => false
86
+ */
87
+ export declare function hasAnyCountComponents(components: PluginComponents): boolean;