@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/app.js ADDED
@@ -0,0 +1,481 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Main App component for Claude Code Plugin Dashboard
4
+ * Interactive TUI to browse and manage Claude Code plugins
5
+ */
6
+ import { useState, useEffect, useReducer } from 'react';
7
+ import { Box, Text, useInput, useApp } from 'ink';
8
+ import TabBar, { getNextTab } from './components/TabBar.js';
9
+ import KeyHints from './components/KeyHints.js';
10
+ import DiscoverTab from './tabs/DiscoverTab.js';
11
+ import InstalledTab from './tabs/InstalledTab.js';
12
+ import MarketplacesTab from './tabs/MarketplacesTab.js';
13
+ import ErrorsTab from './tabs/ErrorsTab.js';
14
+ import { loadAllPlugins, loadMarketplaces, searchPlugins, sortPlugins, } from './services/pluginService.js';
15
+ import { togglePlugin } from './services/settingsService.js';
16
+ import { installPlugin, uninstallPlugin, } from './services/pluginActionsService.js';
17
+ import ConfirmDialog from './components/ConfirmDialog.js';
18
+ /**
19
+ * Initial application state
20
+ */
21
+ const initialState = {
22
+ activeTab: 'discover',
23
+ plugins: [],
24
+ marketplaces: [],
25
+ errors: [],
26
+ selectedIndex: 0,
27
+ searchQuery: '',
28
+ sortBy: 'installs',
29
+ sortOrder: 'desc',
30
+ loading: true,
31
+ error: null,
32
+ message: null,
33
+ operation: 'idle',
34
+ operationPluginId: null,
35
+ confirmUninstall: false,
36
+ };
37
+ /**
38
+ * State reducer for application state management
39
+ */
40
+ function appReducer(state, action) {
41
+ switch (action.type) {
42
+ case 'SET_TAB':
43
+ return {
44
+ ...state,
45
+ activeTab: action.payload,
46
+ selectedIndex: 0,
47
+ searchQuery: '',
48
+ message: null,
49
+ };
50
+ case 'NEXT_TAB':
51
+ return {
52
+ ...state,
53
+ activeTab: getNextTab(state.activeTab, 'next'),
54
+ selectedIndex: 0,
55
+ searchQuery: '',
56
+ message: null,
57
+ };
58
+ case 'PREV_TAB':
59
+ return {
60
+ ...state,
61
+ activeTab: getNextTab(state.activeTab, 'prev'),
62
+ selectedIndex: 0,
63
+ searchQuery: '',
64
+ message: null,
65
+ };
66
+ case 'SET_PLUGINS':
67
+ return {
68
+ ...state,
69
+ plugins: action.payload,
70
+ loading: false,
71
+ };
72
+ case 'SET_MARKETPLACES':
73
+ return {
74
+ ...state,
75
+ marketplaces: action.payload,
76
+ };
77
+ case 'SET_ERRORS':
78
+ return {
79
+ ...state,
80
+ errors: action.payload,
81
+ };
82
+ case 'SET_SELECTED_INDEX':
83
+ return {
84
+ ...state,
85
+ selectedIndex: action.payload,
86
+ message: null,
87
+ };
88
+ case 'MOVE_SELECTION': {
89
+ const items = getItemsForTab(state);
90
+ const maxIndex = Math.max(0, items.length - 1);
91
+ if (items.length === 0)
92
+ return state;
93
+ const newIndex = action.payload === 'up'
94
+ ? Math.max(0, state.selectedIndex - 1)
95
+ : Math.min(maxIndex, state.selectedIndex + 1);
96
+ return {
97
+ ...state,
98
+ selectedIndex: newIndex,
99
+ message: null,
100
+ };
101
+ }
102
+ case 'SET_SEARCH_QUERY':
103
+ return {
104
+ ...state,
105
+ searchQuery: action.payload,
106
+ selectedIndex: 0,
107
+ };
108
+ case 'SET_SORT':
109
+ return {
110
+ ...state,
111
+ sortBy: action.payload.by,
112
+ sortOrder: action.payload.order,
113
+ selectedIndex: 0,
114
+ };
115
+ case 'TOGGLE_PLUGIN_ENABLED': {
116
+ const pluginId = action.payload;
117
+ const updatedPlugins = state.plugins.map((p) => {
118
+ if (p.id === pluginId) {
119
+ return { ...p, isEnabled: !p.isEnabled };
120
+ }
121
+ return p;
122
+ });
123
+ return {
124
+ ...state,
125
+ plugins: updatedPlugins,
126
+ };
127
+ }
128
+ case 'UPDATE_PLUGIN': {
129
+ const updatedPlugins = state.plugins.map((p) => {
130
+ if (p.id === action.payload.id) {
131
+ return action.payload;
132
+ }
133
+ return p;
134
+ });
135
+ return {
136
+ ...state,
137
+ plugins: updatedPlugins,
138
+ };
139
+ }
140
+ case 'SET_LOADING':
141
+ return {
142
+ ...state,
143
+ loading: action.payload,
144
+ };
145
+ case 'SET_ERROR':
146
+ return {
147
+ ...state,
148
+ error: action.payload,
149
+ loading: false,
150
+ };
151
+ case 'SET_MESSAGE':
152
+ return {
153
+ ...state,
154
+ message: action.payload,
155
+ };
156
+ case 'START_OPERATION':
157
+ return {
158
+ ...state,
159
+ operation: action.payload.operation,
160
+ operationPluginId: action.payload.pluginId,
161
+ message: action.payload.operation === 'installing'
162
+ ? `Installing ${action.payload.pluginId}...`
163
+ : `Uninstalling ${action.payload.pluginId}...`,
164
+ };
165
+ case 'END_OPERATION':
166
+ return {
167
+ ...state,
168
+ operation: 'idle',
169
+ operationPluginId: null,
170
+ };
171
+ case 'SHOW_CONFIRM_UNINSTALL':
172
+ return {
173
+ ...state,
174
+ confirmUninstall: true,
175
+ operationPluginId: action.payload,
176
+ };
177
+ case 'HIDE_CONFIRM_UNINSTALL':
178
+ return {
179
+ ...state,
180
+ confirmUninstall: false,
181
+ operationPluginId: null,
182
+ };
183
+ default:
184
+ return state;
185
+ }
186
+ }
187
+ /**
188
+ * Get items array for current tab
189
+ */
190
+ function getItemsForTab(state) {
191
+ switch (state.activeTab) {
192
+ case 'discover':
193
+ return getFilteredPlugins(state);
194
+ case 'installed':
195
+ return state.plugins.filter((p) => p.isInstalled);
196
+ case 'marketplaces':
197
+ return state.marketplaces;
198
+ case 'errors':
199
+ return state.errors;
200
+ default:
201
+ return [];
202
+ }
203
+ }
204
+ /**
205
+ * Get filtered and sorted plugins for discover tab
206
+ */
207
+ function getFilteredPlugins(state) {
208
+ let plugins = state.plugins;
209
+ // Apply search filter
210
+ if (state.searchQuery) {
211
+ plugins = searchPlugins(state.searchQuery, plugins);
212
+ }
213
+ // Apply sort
214
+ plugins = sortPlugins(plugins, state.sortBy, state.sortOrder);
215
+ return plugins;
216
+ }
217
+ /**
218
+ * Main App component
219
+ */
220
+ export default function App() {
221
+ const { exit } = useApp();
222
+ const [state, dispatch] = useReducer(appReducer, initialState);
223
+ const [isSearchMode, setIsSearchMode] = useState(false);
224
+ // Load data on mount
225
+ useEffect(() => {
226
+ try {
227
+ const plugins = loadAllPlugins();
228
+ const marketplaces = loadMarketplaces();
229
+ dispatch({ type: 'SET_PLUGINS', payload: plugins });
230
+ dispatch({ type: 'SET_MARKETPLACES', payload: marketplaces });
231
+ }
232
+ catch (error) {
233
+ dispatch({
234
+ type: 'SET_ERROR',
235
+ payload: error instanceof Error ? error.message : 'Failed to load data',
236
+ });
237
+ }
238
+ }, []);
239
+ /**
240
+ * Handle plugin installation
241
+ */
242
+ async function handleInstall(pluginId) {
243
+ dispatch({
244
+ type: 'START_OPERATION',
245
+ payload: { operation: 'installing', pluginId },
246
+ });
247
+ const result = await installPlugin(pluginId);
248
+ dispatch({ type: 'END_OPERATION' });
249
+ if (result.success) {
250
+ // Reload plugins to get fresh state
251
+ const plugins = loadAllPlugins();
252
+ dispatch({ type: 'SET_PLUGINS', payload: plugins });
253
+ dispatch({ type: 'SET_MESSAGE', payload: `✅ ${result.message}` });
254
+ }
255
+ else {
256
+ dispatch({
257
+ type: 'SET_MESSAGE',
258
+ payload: `❌ ${result.message}${result.error ? `: ${result.error}` : ''}`,
259
+ });
260
+ }
261
+ }
262
+ /**
263
+ * Handle plugin uninstallation
264
+ */
265
+ async function handleUninstall(pluginId) {
266
+ dispatch({
267
+ type: 'START_OPERATION',
268
+ payload: { operation: 'uninstalling', pluginId },
269
+ });
270
+ const result = await uninstallPlugin(pluginId);
271
+ dispatch({ type: 'END_OPERATION' });
272
+ if (result.success) {
273
+ // Reload plugins to get fresh state
274
+ const plugins = loadAllPlugins();
275
+ dispatch({ type: 'SET_PLUGINS', payload: plugins });
276
+ dispatch({ type: 'SET_MESSAGE', payload: `✅ ${result.message}` });
277
+ }
278
+ else {
279
+ dispatch({
280
+ type: 'SET_MESSAGE',
281
+ payload: `❌ ${result.message}${result.error ? `: ${result.error}` : ''}`,
282
+ });
283
+ }
284
+ }
285
+ // Keyboard input handling
286
+ useInput((input, key) => {
287
+ // Block all input during operations
288
+ if (state.operation !== 'idle') {
289
+ return;
290
+ }
291
+ // Handle confirmation dialog
292
+ if (state.confirmUninstall && state.operationPluginId) {
293
+ if (input === 'y' || input === 'Y') {
294
+ dispatch({ type: 'HIDE_CONFIRM_UNINSTALL' });
295
+ handleUninstall(state.operationPluginId);
296
+ return;
297
+ }
298
+ if (input === 'n' || input === 'N' || key.escape) {
299
+ dispatch({ type: 'HIDE_CONFIRM_UNINSTALL' });
300
+ dispatch({ type: 'SET_MESSAGE', payload: 'Uninstall cancelled' });
301
+ return;
302
+ }
303
+ return;
304
+ }
305
+ // Search mode input
306
+ if (isSearchMode) {
307
+ if (key.escape || key.return) {
308
+ setIsSearchMode(false);
309
+ return;
310
+ }
311
+ if (key.backspace || key.delete) {
312
+ dispatch({
313
+ type: 'SET_SEARCH_QUERY',
314
+ payload: state.searchQuery.slice(0, -1),
315
+ });
316
+ return;
317
+ }
318
+ if (input && input.length === 1 && !key.ctrl && !key.meta) {
319
+ dispatch({
320
+ type: 'SET_SEARCH_QUERY',
321
+ payload: state.searchQuery + input,
322
+ });
323
+ return;
324
+ }
325
+ return;
326
+ }
327
+ // Emacs-style navigation (Ctrl+P / Ctrl+N)
328
+ if (key.ctrl && input === 'p') {
329
+ dispatch({ type: 'MOVE_SELECTION', payload: 'up' });
330
+ return;
331
+ }
332
+ if (key.ctrl && input === 'n') {
333
+ dispatch({ type: 'MOVE_SELECTION', payload: 'down' });
334
+ return;
335
+ }
336
+ // Tab navigation
337
+ if (key.leftArrow) {
338
+ dispatch({ type: 'PREV_TAB' });
339
+ return;
340
+ }
341
+ if (key.rightArrow) {
342
+ dispatch({ type: 'NEXT_TAB' });
343
+ return;
344
+ }
345
+ if (key.tab) {
346
+ dispatch({ type: 'NEXT_TAB' });
347
+ return;
348
+ }
349
+ // List navigation
350
+ if (key.upArrow) {
351
+ dispatch({ type: 'MOVE_SELECTION', payload: 'up' });
352
+ return;
353
+ }
354
+ if (key.downArrow) {
355
+ dispatch({ type: 'MOVE_SELECTION', payload: 'down' });
356
+ return;
357
+ }
358
+ // Enter search mode
359
+ if (input === '/' && state.activeTab === 'discover') {
360
+ setIsSearchMode(true);
361
+ return;
362
+ }
363
+ // Toggle plugin (Space or Enter)
364
+ if ((input === ' ' || key.return) &&
365
+ (state.activeTab === 'discover' || state.activeTab === 'installed')) {
366
+ const items = state.activeTab === 'installed'
367
+ ? state.plugins.filter((p) => p.isInstalled)
368
+ : getFilteredPlugins(state);
369
+ const selectedPlugin = items[state.selectedIndex];
370
+ if (selectedPlugin && selectedPlugin.isInstalled) {
371
+ try {
372
+ const newState = togglePlugin(selectedPlugin.id);
373
+ dispatch({
374
+ type: 'TOGGLE_PLUGIN_ENABLED',
375
+ payload: selectedPlugin.id,
376
+ });
377
+ dispatch({
378
+ type: 'SET_MESSAGE',
379
+ payload: newState
380
+ ? `✅ ${selectedPlugin.name} enabled`
381
+ : `❌ ${selectedPlugin.name} disabled`,
382
+ });
383
+ }
384
+ catch (error) {
385
+ dispatch({
386
+ type: 'SET_MESSAGE',
387
+ payload: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
388
+ });
389
+ }
390
+ }
391
+ return;
392
+ }
393
+ // Cycle sort (s key)
394
+ if (input === 's' && state.activeTab === 'discover') {
395
+ const nextSort = {
396
+ installs: 'name',
397
+ name: 'date',
398
+ date: 'installs',
399
+ };
400
+ dispatch({
401
+ type: 'SET_SORT',
402
+ payload: { by: nextSort[state.sortBy], order: state.sortOrder },
403
+ });
404
+ return;
405
+ }
406
+ // Toggle sort order (S key)
407
+ if (input === 'S' && state.activeTab === 'discover') {
408
+ dispatch({
409
+ type: 'SET_SORT',
410
+ payload: {
411
+ by: state.sortBy,
412
+ order: state.sortOrder === 'asc' ? 'desc' : 'asc',
413
+ },
414
+ });
415
+ return;
416
+ }
417
+ // Clear search (Escape)
418
+ if (key.escape && state.searchQuery) {
419
+ dispatch({ type: 'SET_SEARCH_QUERY', payload: '' });
420
+ return;
421
+ }
422
+ // Install (i key) - only on discover/installed tabs
423
+ if (input === 'i' &&
424
+ (state.activeTab === 'discover' || state.activeTab === 'installed')) {
425
+ const items = state.activeTab === 'installed'
426
+ ? state.plugins.filter((p) => p.isInstalled)
427
+ : getFilteredPlugins(state);
428
+ const selectedPlugin = items[state.selectedIndex];
429
+ if (selectedPlugin && !selectedPlugin.isInstalled) {
430
+ handleInstall(selectedPlugin.id);
431
+ }
432
+ else if (selectedPlugin?.isInstalled) {
433
+ dispatch({
434
+ type: 'SET_MESSAGE',
435
+ payload: '⚠️ Plugin is already installed',
436
+ });
437
+ }
438
+ return;
439
+ }
440
+ // Uninstall (u key) - only on discover/installed tabs
441
+ if (input === 'u' &&
442
+ (state.activeTab === 'discover' || state.activeTab === 'installed')) {
443
+ const items = state.activeTab === 'installed'
444
+ ? state.plugins.filter((p) => p.isInstalled)
445
+ : getFilteredPlugins(state);
446
+ const selectedPlugin = items[state.selectedIndex];
447
+ if (selectedPlugin && selectedPlugin.isInstalled) {
448
+ dispatch({ type: 'SHOW_CONFIRM_UNINSTALL', payload: selectedPlugin.id });
449
+ }
450
+ else if (selectedPlugin && !selectedPlugin.isInstalled) {
451
+ dispatch({ type: 'SET_MESSAGE', payload: '⚠️ Plugin is not installed' });
452
+ }
453
+ return;
454
+ }
455
+ // Exit (q or Ctrl+C)
456
+ if (input === 'q' || (key.ctrl && input === 'c')) {
457
+ exit();
458
+ return;
459
+ }
460
+ });
461
+ // Loading state
462
+ if (state.loading) {
463
+ return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(Text, { children: "Loading plugins..." }) }));
464
+ }
465
+ // Error state
466
+ if (state.error) {
467
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Text, { color: "red", children: ["Error: ", state.error] }), _jsx(Text, { dimColor: true, children: "Press q to exit" })] }));
468
+ }
469
+ // Get filtered data for current tab
470
+ const filteredPlugins = getFilteredPlugins(state);
471
+ const installedPlugins = state.plugins.filter((p) => p.isInstalled);
472
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, gap: 2, children: [_jsx(Text, { bold: true, color: "magenta", children: "\u26A1 Claude Code Plugin Dashboard" }), _jsx(Box, { flexGrow: 1 }), _jsx(Text, { dimColor: true, children: "v0.1.0" })] }), _jsx(TabBar, { activeTab: state.activeTab }), _jsxs(Box, { flexGrow: 1, flexDirection: "column", children: [state.activeTab === 'discover' && (_jsx(DiscoverTab, { plugins: filteredPlugins, selectedIndex: state.selectedIndex, searchQuery: state.searchQuery, sortBy: state.sortBy, sortOrder: state.sortOrder, isSearchMode: isSearchMode })), state.activeTab === 'installed' && (_jsx(InstalledTab, { plugins: installedPlugins, selectedIndex: state.selectedIndex })), state.activeTab === 'marketplaces' && (_jsx(MarketplacesTab, { marketplaces: state.marketplaces, selectedIndex: state.selectedIndex })), state.activeTab === 'errors' && (_jsx(ErrorsTab, { errors: state.errors, selectedIndex: state.selectedIndex }))] }), state.confirmUninstall && state.operationPluginId && (_jsx(ConfirmDialog, { message: `Uninstall ${state.operationPluginId}?` })), state.message && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: state.message }) })), _jsx(KeyHints, { extraHints: state.activeTab === 'discover' || state.activeTab === 'installed'
473
+ ? [
474
+ { key: 'i', action: 'install' },
475
+ { key: 'u', action: 'uninstall' },
476
+ ...(state.activeTab === 'discover'
477
+ ? [{ key: 's', action: 'sort' }]
478
+ : []),
479
+ ]
480
+ : undefined })] }));
481
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI entry point for Claude Code Plugin Dashboard
4
+ * Supports both interactive (TUI) and non-interactive (command) modes
5
+ *
6
+ * Usage:
7
+ * claude-plugin-dashboard # Interactive mode
8
+ * claude-plugin-dashboard status # Show summary
9
+ * claude-plugin-dashboard list # List all plugins
10
+ * claude-plugin-dashboard list --installed # List installed plugins
11
+ * claude-plugin-dashboard enable <plugin-id> # Enable plugin
12
+ * claude-plugin-dashboard disable <plugin-id># Disable plugin
13
+ * claude-plugin-dashboard toggle <plugin-id> # Toggle plugin
14
+ * claude-plugin-dashboard help # Show help
15
+ */
16
+ export {};