@laststance/claude-plugin-dashboard 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +235 -0
  3. package/dist/app.d.ts +8 -0
  4. package/dist/app.js +481 -0
  5. package/dist/cli.d.ts +16 -0
  6. package/dist/cli.js +316 -0
  7. package/dist/components/ConfirmDialog.d.ts +14 -0
  8. package/dist/components/ConfirmDialog.js +14 -0
  9. package/dist/components/KeyHints.d.ts +19 -0
  10. package/dist/components/KeyHints.js +23 -0
  11. package/dist/components/MarketplaceDetail.d.ts +15 -0
  12. package/dist/components/MarketplaceDetail.js +39 -0
  13. package/dist/components/MarketplaceList.d.ts +16 -0
  14. package/dist/components/MarketplaceList.js +32 -0
  15. package/dist/components/PluginDetail.d.ts +15 -0
  16. package/dist/components/PluginDetail.js +52 -0
  17. package/dist/components/PluginList.d.ts +19 -0
  18. package/dist/components/PluginList.js +54 -0
  19. package/dist/components/SearchInput.d.ts +16 -0
  20. package/dist/components/SearchInput.js +14 -0
  21. package/dist/components/SortDropdown.d.ts +21 -0
  22. package/dist/components/SortDropdown.js +29 -0
  23. package/dist/components/StatusIcon.d.ts +20 -0
  24. package/dist/components/StatusIcon.js +25 -0
  25. package/dist/components/TabBar.d.ts +24 -0
  26. package/dist/components/TabBar.js +38 -0
  27. package/dist/services/fileService.d.ts +41 -0
  28. package/dist/services/fileService.js +104 -0
  29. package/dist/services/pluginActionsService.d.ts +21 -0
  30. package/dist/services/pluginActionsService.js +65 -0
  31. package/dist/services/pluginService.d.ts +66 -0
  32. package/dist/services/pluginService.js +188 -0
  33. package/dist/services/settingsService.d.ts +82 -0
  34. package/dist/services/settingsService.js +117 -0
  35. package/dist/tabs/DiscoverTab.d.ts +26 -0
  36. package/dist/tabs/DiscoverTab.js +25 -0
  37. package/dist/tabs/ErrorsTab.d.ts +16 -0
  38. package/dist/tabs/ErrorsTab.js +39 -0
  39. package/dist/tabs/InstalledTab.d.ts +16 -0
  40. package/dist/tabs/InstalledTab.js +24 -0
  41. package/dist/tabs/MarketplacesTab.d.ts +16 -0
  42. package/dist/tabs/MarketplacesTab.js +21 -0
  43. package/dist/types/index.d.ts +250 -0
  44. package/dist/types/index.js +5 -0
  45. package/dist/utils/paths.d.ts +40 -0
  46. package/dist/utils/paths.js +50 -0
  47. package/package.json +60 -0
package/dist/cli.js ADDED
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/env node
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /**
4
+ * CLI entry point for Claude Code Plugin Dashboard
5
+ * Supports both interactive (TUI) and non-interactive (command) modes
6
+ *
7
+ * Usage:
8
+ * claude-plugin-dashboard # Interactive mode
9
+ * claude-plugin-dashboard status # Show summary
10
+ * claude-plugin-dashboard list # List all plugins
11
+ * claude-plugin-dashboard list --installed # List installed plugins
12
+ * claude-plugin-dashboard enable <plugin-id> # Enable plugin
13
+ * claude-plugin-dashboard disable <plugin-id># Disable plugin
14
+ * claude-plugin-dashboard toggle <plugin-id> # Toggle plugin
15
+ * claude-plugin-dashboard help # Show help
16
+ */
17
+ import { render } from 'ink';
18
+ import App from './app.js';
19
+ import { loadAllPlugins, loadInstalledPlugins, loadMarketplaces, getPluginStatistics, getPluginById, } from './services/pluginService.js';
20
+ import { enablePlugin, disablePlugin, togglePlugin, } from './services/settingsService.js';
21
+ import { fileExists } from './services/fileService.js';
22
+ import { PATHS } from './utils/paths.js';
23
+ const args = process.argv.slice(2);
24
+ const command = args[0];
25
+ const subCommand = args[1];
26
+ /**
27
+ * Show status summary
28
+ */
29
+ function showStatus() {
30
+ try {
31
+ const stats = getPluginStatistics();
32
+ const marketplaces = loadMarketplaces();
33
+ console.log('');
34
+ console.log('⚡ Claude Code Plugin Dashboard');
35
+ console.log('');
36
+ console.log('📊 Summary:');
37
+ console.log(` Total plugins: ${stats.total}`);
38
+ console.log(` Installed: ${stats.installed}`);
39
+ console.log(` Enabled: ${stats.enabled}`);
40
+ console.log(` Marketplaces: ${marketplaces.length}`);
41
+ console.log('');
42
+ }
43
+ catch (error) {
44
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
45
+ process.exit(1);
46
+ }
47
+ }
48
+ /**
49
+ * List plugins
50
+ */
51
+ function listPlugins(options) {
52
+ try {
53
+ let plugins = options.installed ? loadInstalledPlugins() : loadAllPlugins();
54
+ if (options.marketplace) {
55
+ plugins = plugins.filter((p) => p.marketplace === options.marketplace);
56
+ }
57
+ if (plugins.length === 0) {
58
+ console.log('No plugins found');
59
+ return;
60
+ }
61
+ console.log('');
62
+ console.log(`Found ${plugins.length} plugins:`);
63
+ console.log('');
64
+ for (const plugin of plugins) {
65
+ const status = plugin.isInstalled ? (plugin.isEnabled ? '●' : '◐') : '○';
66
+ const statusColor = plugin.isInstalled
67
+ ? plugin.isEnabled
68
+ ? '\x1b[32m'
69
+ : '\x1b[33m'
70
+ : '\x1b[90m';
71
+ const reset = '\x1b[0m';
72
+ console.log(`${statusColor}${status}${reset} ${plugin.id}`);
73
+ console.log(` ${plugin.description.slice(0, 60)}${plugin.description.length > 60 ? '...' : ''}`);
74
+ }
75
+ console.log('');
76
+ }
77
+ catch (error) {
78
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
79
+ process.exit(1);
80
+ }
81
+ }
82
+ /**
83
+ * Enable a plugin
84
+ */
85
+ function handleEnable(pluginId) {
86
+ try {
87
+ const plugin = getPluginById(pluginId);
88
+ if (!plugin) {
89
+ console.error(`Plugin not found: ${pluginId}`);
90
+ process.exit(1);
91
+ }
92
+ if (!plugin.isInstalled) {
93
+ console.error(`Plugin not installed: ${pluginId}`);
94
+ console.log('Install it first with: /plugin install in Claude Code');
95
+ process.exit(1);
96
+ }
97
+ enablePlugin(pluginId);
98
+ console.log(`✅ ${plugin.name} enabled`);
99
+ }
100
+ catch (error) {
101
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
102
+ process.exit(1);
103
+ }
104
+ }
105
+ /**
106
+ * Disable a plugin
107
+ */
108
+ function handleDisable(pluginId) {
109
+ try {
110
+ const plugin = getPluginById(pluginId);
111
+ if (!plugin) {
112
+ console.error(`Plugin not found: ${pluginId}`);
113
+ process.exit(1);
114
+ }
115
+ disablePlugin(pluginId);
116
+ console.log(`❌ ${plugin.name} disabled`);
117
+ }
118
+ catch (error) {
119
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
120
+ process.exit(1);
121
+ }
122
+ }
123
+ /**
124
+ * Toggle a plugin
125
+ */
126
+ function handleToggle(pluginId) {
127
+ try {
128
+ const plugin = getPluginById(pluginId);
129
+ if (!plugin) {
130
+ console.error(`Plugin not found: ${pluginId}`);
131
+ process.exit(1);
132
+ }
133
+ if (!plugin.isInstalled) {
134
+ console.error(`Plugin not installed: ${pluginId}`);
135
+ console.log('Install it first with: /plugin install in Claude Code');
136
+ process.exit(1);
137
+ }
138
+ const newState = togglePlugin(pluginId);
139
+ console.log(`${newState ? '✅' : '❌'} ${plugin.name} ${newState ? 'enabled' : 'disabled'}`);
140
+ }
141
+ catch (error) {
142
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
143
+ process.exit(1);
144
+ }
145
+ }
146
+ /**
147
+ * Show plugin info
148
+ */
149
+ function showPluginInfo(pluginId) {
150
+ try {
151
+ const plugin = getPluginById(pluginId);
152
+ if (!plugin) {
153
+ console.error(`Plugin not found: ${pluginId}`);
154
+ process.exit(1);
155
+ }
156
+ console.log('');
157
+ console.log(`📦 ${plugin.name}`);
158
+ console.log('');
159
+ console.log(`ID: ${plugin.id}`);
160
+ console.log(`Marketplace: ${plugin.marketplace}`);
161
+ console.log(`Version: ${plugin.version}`);
162
+ console.log(`Installs: ${plugin.installCount.toLocaleString()}`);
163
+ console.log(`Status: ${plugin.isInstalled ? (plugin.isEnabled ? 'Installed & Enabled' : 'Installed & Disabled') : 'Not Installed'}`);
164
+ if (plugin.category)
165
+ console.log(`Category: ${plugin.category}`);
166
+ if (plugin.author)
167
+ console.log(`Author: ${plugin.author.name}`);
168
+ if (plugin.homepage)
169
+ console.log(`Homepage: ${plugin.homepage}`);
170
+ console.log('');
171
+ console.log('Description:');
172
+ console.log(` ${plugin.description}`);
173
+ console.log('');
174
+ }
175
+ catch (error) {
176
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
177
+ process.exit(1);
178
+ }
179
+ }
180
+ /**
181
+ * Show help
182
+ */
183
+ function showHelp() {
184
+ console.log(`
185
+ ⚡ Claude Code Plugin Dashboard
186
+
187
+ An interactive CLI tool to browse and manage Claude Code plugins.
188
+
189
+ USAGE:
190
+ claude-plugin-dashboard [command] [options]
191
+
192
+ COMMANDS:
193
+ (none) Open interactive dashboard
194
+ status Show summary statistics
195
+ list List all available plugins
196
+ list --installed List installed plugins only
197
+ list --marketplace <id> List plugins from specific marketplace
198
+ info <plugin-id> Show plugin details
199
+ enable <plugin-id> Enable an installed plugin
200
+ disable <plugin-id> Disable a plugin
201
+ toggle <plugin-id> Toggle plugin enabled state
202
+ help Show this help message
203
+
204
+ EXAMPLES:
205
+ claude-plugin-dashboard
206
+ claude-plugin-dashboard status
207
+ claude-plugin-dashboard list --installed
208
+ claude-plugin-dashboard info context7@claude-plugins-official
209
+ claude-plugin-dashboard toggle context7@claude-plugins-official
210
+
211
+ INTERACTIVE MODE:
212
+ ← → Switch tabs
213
+ ↑ ↓ Navigate list
214
+ ^P ^N Navigate list (Emacs-style)
215
+ i Install selected plugin
216
+ u Uninstall selected plugin
217
+ Space Toggle plugin enable/disable
218
+ / Search plugins
219
+ s Cycle sort options
220
+ q Quit
221
+
222
+ For more information, visit:
223
+ https://github.com/laststance/claude-code-plugin-dashboard
224
+ `);
225
+ }
226
+ /**
227
+ * Check if Claude Code is installed
228
+ */
229
+ function checkClaudeCodeInstalled() {
230
+ if (!fileExists(PATHS.settings)) {
231
+ console.error('');
232
+ console.error('❌ Claude Code not found');
233
+ console.error('');
234
+ console.error('This tool requires Claude Code to be installed.');
235
+ console.error('Expected settings file at: ' + PATHS.settings);
236
+ console.error('');
237
+ console.error('Install Claude Code: https://claude.ai/code');
238
+ console.error('');
239
+ process.exit(1);
240
+ }
241
+ }
242
+ // Main execution
243
+ if (command) {
244
+ // Non-interactive mode
245
+ 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.');
304
+ process.exit(1);
305
+ }
306
+ }
307
+ else {
308
+ // Interactive mode
309
+ checkClaudeCodeInstalled();
310
+ if (!process.stdin.isTTY) {
311
+ console.log('Interactive mode requires a TTY.');
312
+ console.log('Use "claude-plugin-dashboard help" for non-interactive commands.');
313
+ process.exit(1);
314
+ }
315
+ render(_jsx(App, {}));
316
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * ConfirmDialog component
3
+ * Displays a Y/N confirmation prompt for destructive actions
4
+ */
5
+ interface ConfirmDialogProps {
6
+ message: string;
7
+ }
8
+ /**
9
+ * A simple confirmation dialog that prompts Y/N
10
+ * @example
11
+ * <ConfirmDialog message="Uninstall plugin-name@marketplace?" />
12
+ */
13
+ export default function ConfirmDialog({ message }: ConfirmDialogProps): import("react/jsx-runtime").JSX.Element;
14
+ export {};
@@ -0,0 +1,14 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * ConfirmDialog component
4
+ * Displays a Y/N confirmation prompt for destructive actions
5
+ */
6
+ import { Box, Text } from 'ink';
7
+ /**
8
+ * A simple confirmation dialog that prompts Y/N
9
+ * @example
10
+ * <ConfirmDialog message="Uninstall plugin-name@marketplace?" />
11
+ */
12
+ export default function ConfirmDialog({ message }) {
13
+ return (_jsxs(Box, { marginTop: 1, paddingX: 2, paddingY: 1, borderStyle: "double", borderColor: "yellow", children: [_jsxs(Text, { children: [message, " "] }), _jsx(Text, { bold: true, color: "green", children: "Y" }), _jsx(Text, { dimColor: true, children: "/" }), _jsx(Text, { bold: true, color: "red", children: "N" })] }));
14
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * KeyHints component
3
+ * Displays keyboard shortcuts footer at the bottom of the dashboard
4
+ */
5
+ interface KeyHintsProps {
6
+ /** Additional context-specific hints */
7
+ extraHints?: Array<{
8
+ key: string;
9
+ action: string;
10
+ }>;
11
+ }
12
+ /**
13
+ * Displays keyboard shortcut hints in the footer
14
+ * @example
15
+ * <KeyHints />
16
+ * <KeyHints extraHints={[{ key: 'i', action: 'install' }]} />
17
+ */
18
+ export default function KeyHints({ extraHints }: KeyHintsProps): import("react/jsx-runtime").JSX.Element;
19
+ export {};
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * KeyHints component
4
+ * Displays keyboard shortcuts footer at the bottom of the dashboard
5
+ */
6
+ import { Box, Text } from 'ink';
7
+ /**
8
+ * Displays keyboard shortcut hints in the footer
9
+ * @example
10
+ * <KeyHints />
11
+ * <KeyHints extraHints={[{ key: 'i', action: 'install' }]} />
12
+ */
13
+ export default function KeyHints({ extraHints }) {
14
+ const baseHints = [
15
+ { key: '←/→', action: 'tabs' },
16
+ { key: '↑/↓', action: 'navigate' },
17
+ { key: 'Space', action: 'toggle' },
18
+ { key: '/', action: 'search' },
19
+ { key: 'q', action: 'quit' },
20
+ ];
21
+ const allHints = extraHints ? [...baseHints, ...extraHints] : baseHints;
22
+ return (_jsx(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, marginTop: 1, children: _jsx(Box, { gap: 2, flexWrap: "wrap", children: allHints.map((hint, index) => (_jsxs(Box, { gap: 1, children: [_jsx(Text, { bold: true, color: "white", children: hint.key }), _jsx(Text, { dimColor: true, children: hint.action })] }, index))) }) }));
23
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * MarketplaceDetail component
3
+ * Right panel showing marketplace information
4
+ */
5
+ import type { Marketplace } from '../types/index.js';
6
+ interface MarketplaceDetailProps {
7
+ marketplace: Marketplace | null;
8
+ }
9
+ /**
10
+ * Displays detailed information about a selected marketplace
11
+ * @example
12
+ * <MarketplaceDetail marketplace={selectedMarketplace} />
13
+ */
14
+ export default function MarketplaceDetail({ marketplace, }: MarketplaceDetailProps): import("react/jsx-runtime").JSX.Element;
15
+ export {};
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * MarketplaceDetail component
4
+ * Right panel showing marketplace information
5
+ */
6
+ import { Box, Text } from 'ink';
7
+ /**
8
+ * Displays detailed information about a selected marketplace
9
+ * @example
10
+ * <MarketplaceDetail marketplace={selectedMarketplace} />
11
+ */
12
+ export default function MarketplaceDetail({ marketplace, }) {
13
+ if (!marketplace) {
14
+ return (_jsx(Box, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "gray", children: _jsx(Text, { dimColor: true, children: "Select a marketplace to view details" }) }));
15
+ }
16
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: marketplace.name || marketplace.id }) }), _jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(DetailRow, { label: "ID", value: marketplace.id }), _jsx(DetailRow, { label: "Plugins", value: `${marketplace.pluginCount || 0}` }), _jsx(DetailRow, { label: "Source", value: marketplace.source.source }), marketplace.source.url && (_jsx(DetailRow, { label: "URL", value: marketplace.source.url })), marketplace.source.repo && (_jsx(DetailRow, { label: "Repo", value: marketplace.source.repo })), _jsx(DetailRow, { label: "Last Updated", value: formatDate(marketplace.lastUpdated) })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "gray", children: "Install Location:" }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: marketplace.installLocation })] })] }));
17
+ }
18
+ /**
19
+ * Single detail row with label and value
20
+ */
21
+ function DetailRow({ label, value }) {
22
+ return (_jsxs(Box, { gap: 1, children: [_jsxs(Text, { color: "gray", children: [label, ":"] }), _jsx(Text, { children: value })] }));
23
+ }
24
+ /**
25
+ * Format ISO date string to readable format
26
+ */
27
+ function formatDate(isoString) {
28
+ try {
29
+ const date = new Date(isoString);
30
+ return date.toLocaleDateString('en-US', {
31
+ year: 'numeric',
32
+ month: 'short',
33
+ day: 'numeric',
34
+ });
35
+ }
36
+ catch {
37
+ return isoString;
38
+ }
39
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * MarketplaceList component
3
+ * List of marketplace sources
4
+ */
5
+ import type { Marketplace } from '../types/index.js';
6
+ interface MarketplaceListProps {
7
+ marketplaces: Marketplace[];
8
+ selectedIndex: number;
9
+ }
10
+ /**
11
+ * Displays a list of marketplaces
12
+ * @example
13
+ * <MarketplaceList marketplaces={marketplaces} selectedIndex={0} />
14
+ */
15
+ export default function MarketplaceList({ marketplaces, selectedIndex, }: MarketplaceListProps): import("react/jsx-runtime").JSX.Element;
16
+ export {};
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * MarketplaceList component
4
+ * List of marketplace sources
5
+ */
6
+ import { Box, Text } from 'ink';
7
+ /**
8
+ * Displays a list of marketplaces
9
+ * @example
10
+ * <MarketplaceList marketplaces={marketplaces} selectedIndex={0} />
11
+ */
12
+ export default function MarketplaceList({ marketplaces, selectedIndex, }) {
13
+ if (marketplaces.length === 0) {
14
+ return (_jsx(Box, { padding: 1, children: _jsx(Text, { color: "gray", children: "No marketplaces found" }) }));
15
+ }
16
+ return (_jsx(Box, { flexDirection: "column", children: marketplaces.map((marketplace, index) => {
17
+ const isSelected = index === selectedIndex;
18
+ return (_jsxs(Box, { paddingX: 1, children: [_jsx(Box, { width: 2, children: isSelected ? _jsx(Text, { color: "cyan", children: '>' }) : _jsx(Text, { children: " " }) }), _jsxs(Box, { flexGrow: 1, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { bold: true, color: isSelected ? 'cyan' : 'white', children: marketplace.name || marketplace.id }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsxs(Text, { color: "gray", children: [marketplace.pluginCount || 0, " plugins"] })] }), _jsx(Text, { dimColor: true, wrap: "truncate", children: getSourceDisplay(marketplace) })] })] }, marketplace.id));
19
+ }) }));
20
+ }
21
+ /**
22
+ * Get display string for marketplace source
23
+ */
24
+ function getSourceDisplay(marketplace) {
25
+ if (marketplace.source.url) {
26
+ return marketplace.source.url;
27
+ }
28
+ if (marketplace.source.repo) {
29
+ return `github.com/${marketplace.source.repo}`;
30
+ }
31
+ return marketplace.source.source;
32
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * PluginDetail component
3
+ * Right panel showing detailed plugin information
4
+ */
5
+ import type { Plugin } from '../types/index.js';
6
+ interface PluginDetailProps {
7
+ plugin: Plugin | null;
8
+ }
9
+ /**
10
+ * Displays detailed information about a selected plugin
11
+ * @example
12
+ * <PluginDetail plugin={selectedPlugin} />
13
+ */
14
+ export default function PluginDetail({ plugin }: PluginDetailProps): import("react/jsx-runtime").JSX.Element;
15
+ export {};
@@ -0,0 +1,52 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * PluginDetail component
4
+ * Right panel showing detailed plugin information
5
+ */
6
+ import { Box, Text } from 'ink';
7
+ import StatusIcon from './StatusIcon.js';
8
+ /**
9
+ * Displays detailed information about a selected plugin
10
+ * @example
11
+ * <PluginDetail plugin={selectedPlugin} />
12
+ */
13
+ export default function PluginDetail({ plugin }) {
14
+ if (!plugin) {
15
+ return (_jsx(Box, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "gray", children: _jsx(Text, { dimColor: true, children: "Select a plugin to view details" }) }));
16
+ }
17
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "cyan", children: [_jsxs(Box, { marginBottom: 1, gap: 1, children: [_jsx(StatusIcon, { isInstalled: plugin.isInstalled, isEnabled: plugin.isEnabled }), _jsx(Text, { bold: true, color: "cyan", children: plugin.name })] }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { wrap: "wrap", children: plugin.description || 'No description available' }) }), _jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(DetailRow, { label: "Marketplace", value: plugin.marketplace }), _jsx(DetailRow, { label: "Version", value: plugin.version }), _jsx(DetailRow, { label: "Installs", value: formatCount(plugin.installCount) }), plugin.category && (_jsx(DetailRow, { label: "Category", value: plugin.category })), plugin.author && (_jsx(DetailRow, { label: "Author", value: plugin.author.name })), plugin.homepage && (_jsx(DetailRow, { label: "Homepage", value: plugin.homepage }))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { children: "Status:" }), plugin.isInstalled ? (plugin.isEnabled ? (_jsx(Text, { color: "green", bold: true, children: "Installed & Enabled" })) : (_jsx(Text, { color: "yellow", bold: true, children: "Installed & Disabled" }))) : (_jsx(Text, { color: "gray", children: "Not Installed" }))] }), plugin.installedAt && (_jsxs(Text, { dimColor: true, children: ["Installed: ", formatDate(plugin.installedAt)] }))] }), _jsx(Box, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "column", children: plugin.isInstalled ? (_jsx(Box, { flexDirection: "column", children: _jsxs(Text, { dimColor: true, children: [_jsx(Text, { bold: true, color: "white", children: "Space" }), ' ', plugin.isEnabled ? 'disable' : 'enable', " |", ' ', _jsx(Text, { bold: true, color: "white", children: "u" }), ' ', "uninstall"] }) })) : (_jsxs(Text, { dimColor: true, children: [_jsx(Text, { bold: true, color: "white", children: "i" }), ' ', "install"] })) })] }));
18
+ }
19
+ /**
20
+ * Single detail row with label and value
21
+ */
22
+ function DetailRow({ label, value }) {
23
+ return (_jsxs(Box, { gap: 1, children: [_jsxs(Text, { color: "gray", children: [label, ":"] }), _jsx(Text, { children: value })] }));
24
+ }
25
+ /**
26
+ * Format large numbers with K/M suffix
27
+ */
28
+ function formatCount(count) {
29
+ if (count >= 1000000) {
30
+ return `${(count / 1000000).toFixed(1)}M`;
31
+ }
32
+ if (count >= 1000) {
33
+ return `${(count / 1000).toFixed(1)}K`;
34
+ }
35
+ return count.toString();
36
+ }
37
+ /**
38
+ * Format ISO date string to readable format
39
+ */
40
+ function formatDate(isoString) {
41
+ try {
42
+ const date = new Date(isoString);
43
+ return date.toLocaleDateString('en-US', {
44
+ year: 'numeric',
45
+ month: 'short',
46
+ day: 'numeric',
47
+ });
48
+ }
49
+ catch {
50
+ return isoString;
51
+ }
52
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * PluginList component
3
+ * Scrollable list of plugins with selection highlighting
4
+ * Supports ↑ ↓ arrow key navigation
5
+ */
6
+ import type { Plugin } from '../types/index.js';
7
+ interface PluginListProps {
8
+ plugins: Plugin[];
9
+ selectedIndex: number;
10
+ /** Maximum visible items (for virtual scrolling) */
11
+ visibleCount?: number;
12
+ }
13
+ /**
14
+ * Scrollable plugin list with selection
15
+ * @example
16
+ * <PluginList plugins={plugins} selectedIndex={0} visibleCount={15} />
17
+ */
18
+ export default function PluginList({ plugins, selectedIndex, visibleCount, }: PluginListProps): import("react/jsx-runtime").JSX.Element;
19
+ export {};
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * PluginList component
4
+ * Scrollable list of plugins with selection highlighting
5
+ * Supports ↑ ↓ arrow key navigation
6
+ */
7
+ import { Box, Text } from 'ink';
8
+ import StatusIcon from './StatusIcon.js';
9
+ /**
10
+ * Scrollable plugin list with selection
11
+ * @example
12
+ * <PluginList plugins={plugins} selectedIndex={0} visibleCount={15} />
13
+ */
14
+ export default function PluginList({ plugins, selectedIndex, visibleCount = 15, }) {
15
+ if (plugins.length === 0) {
16
+ return (_jsx(Box, { padding: 1, children: _jsx(Text, { color: "gray", children: "No plugins found" }) }));
17
+ }
18
+ // Calculate visible window
19
+ const halfVisible = Math.floor(visibleCount / 2);
20
+ let startIndex = Math.max(0, selectedIndex - halfVisible);
21
+ const endIndex = Math.min(plugins.length, startIndex + visibleCount);
22
+ // Adjust start if we're near the end
23
+ if (endIndex - startIndex < visibleCount) {
24
+ startIndex = Math.max(0, endIndex - visibleCount);
25
+ }
26
+ const visiblePlugins = plugins.slice(startIndex, endIndex);
27
+ const hasMore = endIndex < plugins.length;
28
+ const hasPrevious = startIndex > 0;
29
+ return (_jsxs(Box, { flexDirection: "column", children: [hasPrevious && _jsxs(Text, { dimColor: true, children: ["\u2191 ", startIndex, " more above"] }), visiblePlugins.map((plugin, index) => {
30
+ const actualIndex = startIndex + index;
31
+ const isSelected = actualIndex === selectedIndex;
32
+ return (_jsxs(Box, { paddingX: 1, children: [_jsx(Box, { width: 2, children: isSelected ? _jsx(Text, { color: "cyan", children: '>' }) : _jsx(Text, { children: " " }) }), _jsx(Box, { width: 2, children: _jsx(StatusIcon, { isInstalled: plugin.isInstalled, isEnabled: plugin.isEnabled }) }), _jsxs(Box, { flexGrow: 1, flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { bold: true, color: isSelected ? 'cyan' : 'white', children: plugin.name }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { color: "gray", children: truncate(plugin.marketplace, 20) }), plugin.installCount > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: "\u00B7" }), _jsxs(Text, { color: "gray", children: [formatCount(plugin.installCount), " installs"] })] }))] }), _jsx(Text, { dimColor: true, wrap: "truncate", children: truncate(plugin.description, 60) })] })] }, plugin.id));
33
+ }), hasMore && (_jsxs(Text, { dimColor: true, children: ["\u2193 ", plugins.length - endIndex, " more below"] }))] }));
34
+ }
35
+ /**
36
+ * Truncate text to max length with ellipsis
37
+ */
38
+ function truncate(text, maxLength) {
39
+ if (text.length <= maxLength)
40
+ return text;
41
+ return text.slice(0, maxLength - 3) + '...';
42
+ }
43
+ /**
44
+ * Format large numbers with K/M suffix
45
+ */
46
+ function formatCount(count) {
47
+ if (count >= 1000000) {
48
+ return `${(count / 1000000).toFixed(1)}M`;
49
+ }
50
+ if (count >= 1000) {
51
+ return `${(count / 1000).toFixed(1)}K`;
52
+ }
53
+ return count.toString();
54
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * SearchInput component
3
+ * Filter/search box for the plugin list
4
+ */
5
+ interface SearchInputProps {
6
+ query: string;
7
+ isActive?: boolean;
8
+ placeholder?: string;
9
+ }
10
+ /**
11
+ * Search input display (read-only display, actual input handled by useInput)
12
+ * @example
13
+ * <SearchInput query={searchQuery} isActive={isSearchMode} />
14
+ */
15
+ export default function SearchInput({ query, isActive, placeholder, }: SearchInputProps): import("react/jsx-runtime").JSX.Element;
16
+ export {};