@laststance/claude-plugin-dashboard 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -32
- package/dist/app.d.ts +29 -0
- package/dist/app.js +496 -69
- package/dist/components/AddMarketplaceDialog.d.ts +20 -0
- package/dist/components/AddMarketplaceDialog.js +18 -0
- package/dist/components/ComponentBadges.d.ts +32 -0
- package/dist/components/ComponentBadges.js +82 -0
- package/dist/components/HelpOverlay.d.ts +15 -0
- package/dist/components/HelpOverlay.js +51 -0
- package/dist/components/KeyHints.d.ts +6 -3
- package/dist/components/KeyHints.js +39 -10
- package/dist/components/MarketplaceList.d.ts +4 -2
- package/dist/components/MarketplaceList.js +7 -3
- package/dist/components/PluginDetail.js +2 -1
- package/dist/components/PluginList.d.ts +29 -2
- package/dist/components/PluginList.js +26 -5
- package/dist/components/SearchInput.js +1 -1
- package/dist/components/TabBar.d.ts +5 -3
- package/dist/components/TabBar.js +20 -8
- package/dist/services/componentService.d.ts +35 -0
- package/dist/services/componentService.js +178 -0
- package/dist/services/marketplaceActionsService.d.ts +44 -0
- package/dist/services/marketplaceActionsService.js +92 -0
- package/dist/services/pluginService.d.ts +10 -0
- package/dist/services/pluginService.js +22 -0
- package/dist/tabs/DiscoverTab.d.ts +5 -3
- package/dist/tabs/DiscoverTab.js +3 -2
- package/dist/tabs/EnabledTab.d.ts +24 -0
- package/dist/tabs/EnabledTab.js +26 -0
- package/dist/tabs/InstalledTab.d.ts +10 -3
- package/dist/tabs/InstalledTab.js +14 -10
- package/dist/tabs/MarketplacesTab.d.ts +10 -3
- package/dist/tabs/MarketplacesTab.js +12 -3
- package/dist/types/index.d.ts +71 -1
- package/package.json +11 -3
package/dist/app.js
CHANGED
|
@@ -3,23 +3,29 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
* Main App component for Claude Code Plugin Dashboard
|
|
4
4
|
* Interactive TUI to browse and manage Claude Code plugins
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { useEffect, useReducer } from 'react';
|
|
7
7
|
import { Box, Text, useInput, useApp } from 'ink';
|
|
8
8
|
import TabBar, { getNextTab } from './components/TabBar.js';
|
|
9
9
|
import KeyHints from './components/KeyHints.js';
|
|
10
10
|
import DiscoverTab from './tabs/DiscoverTab.js';
|
|
11
|
+
import EnabledTab from './tabs/EnabledTab.js';
|
|
11
12
|
import InstalledTab from './tabs/InstalledTab.js';
|
|
12
13
|
import MarketplacesTab from './tabs/MarketplacesTab.js';
|
|
13
14
|
import ErrorsTab from './tabs/ErrorsTab.js';
|
|
14
|
-
import { loadAllPlugins, loadMarketplaces, searchPlugins, sortPlugins, } from './services/pluginService.js';
|
|
15
|
+
import { loadAllPlugins, loadMarketplaces, searchPlugins, searchMarketplaces, sortPlugins, } from './services/pluginService.js';
|
|
15
16
|
import { togglePlugin } from './services/settingsService.js';
|
|
16
17
|
import { installPlugin, uninstallPlugin, } from './services/pluginActionsService.js';
|
|
18
|
+
import { addMarketplace, removeMarketplace, updateMarketplace, } from './services/marketplaceActionsService.js';
|
|
19
|
+
import AddMarketplaceDialog from './components/AddMarketplaceDialog.js';
|
|
17
20
|
import ConfirmDialog from './components/ConfirmDialog.js';
|
|
21
|
+
import HelpOverlay from './components/HelpOverlay.js';
|
|
22
|
+
import packageJson from '../package.json' with { type: 'json' };
|
|
18
23
|
/**
|
|
19
24
|
* Initial application state
|
|
20
25
|
*/
|
|
21
|
-
const initialState = {
|
|
22
|
-
activeTab: '
|
|
26
|
+
export const initialState = {
|
|
27
|
+
activeTab: 'enabled',
|
|
28
|
+
focusZone: 'list',
|
|
23
29
|
plugins: [],
|
|
24
30
|
marketplaces: [],
|
|
25
31
|
errors: [],
|
|
@@ -33,16 +39,53 @@ const initialState = {
|
|
|
33
39
|
operation: 'idle',
|
|
34
40
|
operationPluginId: null,
|
|
35
41
|
confirmUninstall: false,
|
|
42
|
+
showHelp: false,
|
|
43
|
+
marketplaceOperation: 'idle',
|
|
44
|
+
operationMarketplaceId: null,
|
|
45
|
+
confirmRemoveMarketplace: false,
|
|
46
|
+
showAddMarketplaceDialog: false,
|
|
47
|
+
addMarketplaceError: null,
|
|
36
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* Get available focus zones for the current tab
|
|
51
|
+
* Errors tab has no search zone since it doesn't support filtering
|
|
52
|
+
* @param activeTab - The currently active tab
|
|
53
|
+
* @returns Array of available focus zones in navigation order
|
|
54
|
+
*/
|
|
55
|
+
export function getAvailableZones(activeTab) {
|
|
56
|
+
if (activeTab === 'errors') {
|
|
57
|
+
return ['tabbar', 'list'];
|
|
58
|
+
}
|
|
59
|
+
return ['tabbar', 'search', 'list'];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get display message for marketplace operation status
|
|
63
|
+
* @param operation - The marketplace operation type
|
|
64
|
+
* @param marketplaceId - Optional marketplace identifier
|
|
65
|
+
* @returns Display message for the operation
|
|
66
|
+
*/
|
|
67
|
+
function getMarketplaceOperationMessage(operation, marketplaceId) {
|
|
68
|
+
switch (operation) {
|
|
69
|
+
case 'adding':
|
|
70
|
+
return 'Adding marketplace...';
|
|
71
|
+
case 'removing':
|
|
72
|
+
return `Removing ${marketplaceId}...`;
|
|
73
|
+
case 'updating':
|
|
74
|
+
return `Updating ${marketplaceId || 'marketplaces'}...`;
|
|
75
|
+
default:
|
|
76
|
+
return '';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
37
79
|
/**
|
|
38
80
|
* State reducer for application state management
|
|
39
81
|
*/
|
|
40
|
-
function appReducer(state, action) {
|
|
82
|
+
export function appReducer(state, action) {
|
|
41
83
|
switch (action.type) {
|
|
42
84
|
case 'SET_TAB':
|
|
43
85
|
return {
|
|
44
86
|
...state,
|
|
45
87
|
activeTab: action.payload,
|
|
88
|
+
focusZone: 'list',
|
|
46
89
|
selectedIndex: 0,
|
|
47
90
|
searchQuery: '',
|
|
48
91
|
message: null,
|
|
@@ -51,6 +94,7 @@ function appReducer(state, action) {
|
|
|
51
94
|
return {
|
|
52
95
|
...state,
|
|
53
96
|
activeTab: getNextTab(state.activeTab, 'next'),
|
|
97
|
+
focusZone: 'list',
|
|
54
98
|
selectedIndex: 0,
|
|
55
99
|
searchQuery: '',
|
|
56
100
|
message: null,
|
|
@@ -59,10 +103,16 @@ function appReducer(state, action) {
|
|
|
59
103
|
return {
|
|
60
104
|
...state,
|
|
61
105
|
activeTab: getNextTab(state.activeTab, 'prev'),
|
|
106
|
+
focusZone: 'list',
|
|
62
107
|
selectedIndex: 0,
|
|
63
108
|
searchQuery: '',
|
|
64
109
|
message: null,
|
|
65
110
|
};
|
|
111
|
+
case 'SET_FOCUS_ZONE':
|
|
112
|
+
return {
|
|
113
|
+
...state,
|
|
114
|
+
focusZone: action.payload,
|
|
115
|
+
};
|
|
66
116
|
case 'SET_PLUGINS':
|
|
67
117
|
return {
|
|
68
118
|
...state,
|
|
@@ -180,19 +230,83 @@ function appReducer(state, action) {
|
|
|
180
230
|
confirmUninstall: false,
|
|
181
231
|
operationPluginId: null,
|
|
182
232
|
};
|
|
233
|
+
case 'TOGGLE_HELP':
|
|
234
|
+
return {
|
|
235
|
+
...state,
|
|
236
|
+
showHelp: !state.showHelp,
|
|
237
|
+
};
|
|
238
|
+
case 'SHOW_CONFIRM_REMOVE_MARKETPLACE':
|
|
239
|
+
return {
|
|
240
|
+
...state,
|
|
241
|
+
confirmRemoveMarketplace: true,
|
|
242
|
+
operationMarketplaceId: action.payload,
|
|
243
|
+
};
|
|
244
|
+
case 'HIDE_CONFIRM_REMOVE_MARKETPLACE':
|
|
245
|
+
return {
|
|
246
|
+
...state,
|
|
247
|
+
confirmRemoveMarketplace: false,
|
|
248
|
+
operationMarketplaceId: null,
|
|
249
|
+
};
|
|
250
|
+
case 'SHOW_ADD_MARKETPLACE_DIALOG':
|
|
251
|
+
return {
|
|
252
|
+
...state,
|
|
253
|
+
showAddMarketplaceDialog: true,
|
|
254
|
+
searchQuery: '', // Reuse searchQuery for dialog input
|
|
255
|
+
addMarketplaceError: null, // Clear previous error
|
|
256
|
+
};
|
|
257
|
+
case 'HIDE_ADD_MARKETPLACE_DIALOG':
|
|
258
|
+
return {
|
|
259
|
+
...state,
|
|
260
|
+
showAddMarketplaceDialog: false,
|
|
261
|
+
searchQuery: '',
|
|
262
|
+
addMarketplaceError: null,
|
|
263
|
+
};
|
|
264
|
+
case 'SET_ADD_MARKETPLACE_ERROR':
|
|
265
|
+
return {
|
|
266
|
+
...state,
|
|
267
|
+
addMarketplaceError: action.payload,
|
|
268
|
+
};
|
|
269
|
+
case 'START_MARKETPLACE_OPERATION':
|
|
270
|
+
return {
|
|
271
|
+
...state,
|
|
272
|
+
marketplaceOperation: action.payload.operation,
|
|
273
|
+
operationMarketplaceId: action.payload.marketplaceId ?? null,
|
|
274
|
+
message: getMarketplaceOperationMessage(action.payload.operation, action.payload.marketplaceId),
|
|
275
|
+
};
|
|
276
|
+
case 'END_MARKETPLACE_OPERATION':
|
|
277
|
+
return {
|
|
278
|
+
...state,
|
|
279
|
+
marketplaceOperation: 'idle',
|
|
280
|
+
operationMarketplaceId: null,
|
|
281
|
+
};
|
|
183
282
|
default:
|
|
184
283
|
return state;
|
|
185
284
|
}
|
|
186
285
|
}
|
|
187
286
|
/**
|
|
188
|
-
* Get items array for current tab
|
|
287
|
+
* Get items array for current tab with search filter applied
|
|
288
|
+
* @param state - Current app state
|
|
289
|
+
* @returns Filtered array of items for the active tab
|
|
290
|
+
* @example
|
|
291
|
+
* getItemsForTab({ activeTab: 'installed', searchQuery: 'su', plugins: [...] })
|
|
292
|
+
* // => Only installed plugins matching 'su'
|
|
189
293
|
*/
|
|
190
|
-
function getItemsForTab(state) {
|
|
294
|
+
export function getItemsForTab(state) {
|
|
191
295
|
switch (state.activeTab) {
|
|
296
|
+
case 'enabled': {
|
|
297
|
+
const enabledPlugins = state.plugins.filter((p) => p.isInstalled && p.isEnabled);
|
|
298
|
+
return state.searchQuery
|
|
299
|
+
? searchPlugins(state.searchQuery, enabledPlugins)
|
|
300
|
+
: enabledPlugins;
|
|
301
|
+
}
|
|
302
|
+
case 'installed': {
|
|
303
|
+
const installedPlugins = state.plugins.filter((p) => p.isInstalled);
|
|
304
|
+
return state.searchQuery
|
|
305
|
+
? searchPlugins(state.searchQuery, installedPlugins)
|
|
306
|
+
: installedPlugins;
|
|
307
|
+
}
|
|
192
308
|
case 'discover':
|
|
193
309
|
return getFilteredPlugins(state);
|
|
194
|
-
case 'installed':
|
|
195
|
-
return state.plugins.filter((p) => p.isInstalled);
|
|
196
310
|
case 'marketplaces':
|
|
197
311
|
return state.marketplaces;
|
|
198
312
|
case 'errors':
|
|
@@ -204,7 +318,7 @@ function getItemsForTab(state) {
|
|
|
204
318
|
/**
|
|
205
319
|
* Get filtered and sorted plugins for discover tab
|
|
206
320
|
*/
|
|
207
|
-
function getFilteredPlugins(state) {
|
|
321
|
+
export function getFilteredPlugins(state) {
|
|
208
322
|
let plugins = state.plugins;
|
|
209
323
|
// Apply search filter
|
|
210
324
|
if (state.searchQuery) {
|
|
@@ -220,7 +334,6 @@ function getFilteredPlugins(state) {
|
|
|
220
334
|
export default function App() {
|
|
221
335
|
const { exit } = useApp();
|
|
222
336
|
const [state, dispatch] = useReducer(appReducer, initialState);
|
|
223
|
-
const [isSearchMode, setIsSearchMode] = useState(false);
|
|
224
337
|
// Load data on mount
|
|
225
338
|
useEffect(() => {
|
|
226
339
|
try {
|
|
@@ -282,13 +395,123 @@ export default function App() {
|
|
|
282
395
|
});
|
|
283
396
|
}
|
|
284
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* Handle adding a new marketplace
|
|
400
|
+
* @param source - Marketplace source (e.g., "owner/repo", URL, or local path)
|
|
401
|
+
*/
|
|
402
|
+
async function handleAddMarketplace(source) {
|
|
403
|
+
dispatch({
|
|
404
|
+
type: 'START_MARKETPLACE_OPERATION',
|
|
405
|
+
payload: { operation: 'adding' },
|
|
406
|
+
});
|
|
407
|
+
const result = await addMarketplace(source);
|
|
408
|
+
dispatch({ type: 'END_MARKETPLACE_OPERATION' });
|
|
409
|
+
if (result.success) {
|
|
410
|
+
dispatch({ type: 'HIDE_ADD_MARKETPLACE_DIALOG' });
|
|
411
|
+
// Reload marketplaces to get fresh state
|
|
412
|
+
const marketplaces = loadMarketplaces();
|
|
413
|
+
dispatch({ type: 'SET_MARKETPLACES', payload: marketplaces });
|
|
414
|
+
// Also reload plugins as new marketplace may have plugins
|
|
415
|
+
const plugins = loadAllPlugins();
|
|
416
|
+
dispatch({ type: 'SET_PLUGINS', payload: plugins });
|
|
417
|
+
// Reset selection to avoid pointing to a different marketplace after re-sort
|
|
418
|
+
dispatch({ type: 'SET_SELECTED_INDEX', payload: 0 });
|
|
419
|
+
dispatch({ type: 'SET_MESSAGE', payload: `✅ ${result.message}` });
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
// Keep dialog open and show error inline
|
|
423
|
+
dispatch({
|
|
424
|
+
type: 'SET_ADD_MARKETPLACE_ERROR',
|
|
425
|
+
payload: result.error || result.message,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Handle removing a marketplace
|
|
431
|
+
* @param marketplaceId - Marketplace identifier to remove
|
|
432
|
+
*/
|
|
433
|
+
async function handleRemoveMarketplace(marketplaceId) {
|
|
434
|
+
dispatch({
|
|
435
|
+
type: 'START_MARKETPLACE_OPERATION',
|
|
436
|
+
payload: { operation: 'removing', marketplaceId },
|
|
437
|
+
});
|
|
438
|
+
const result = await removeMarketplace(marketplaceId);
|
|
439
|
+
dispatch({ type: 'END_MARKETPLACE_OPERATION' });
|
|
440
|
+
if (result.success) {
|
|
441
|
+
// Reload marketplaces to get fresh state
|
|
442
|
+
const marketplaces = loadMarketplaces();
|
|
443
|
+
dispatch({ type: 'SET_MARKETPLACES', payload: marketplaces });
|
|
444
|
+
// Also reload plugins as removed marketplace's plugins should be gone
|
|
445
|
+
const plugins = loadAllPlugins();
|
|
446
|
+
dispatch({ type: 'SET_PLUGINS', payload: plugins });
|
|
447
|
+
dispatch({ type: 'SET_MESSAGE', payload: `✅ ${result.message}` });
|
|
448
|
+
// Reset selection if needed
|
|
449
|
+
if (state.selectedIndex >= marketplaces.length) {
|
|
450
|
+
dispatch({
|
|
451
|
+
type: 'SET_SELECTED_INDEX',
|
|
452
|
+
payload: Math.max(0, marketplaces.length - 1),
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
dispatch({
|
|
458
|
+
type: 'SET_MESSAGE',
|
|
459
|
+
payload: `❌ ${result.message}${result.error ? `: ${result.error}` : ''}`,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Handle updating a marketplace (or all marketplaces)
|
|
465
|
+
* @param marketplaceId - Optional marketplace identifier. If omitted, updates all.
|
|
466
|
+
*/
|
|
467
|
+
async function handleUpdateMarketplace(marketplaceId) {
|
|
468
|
+
dispatch({
|
|
469
|
+
type: 'START_MARKETPLACE_OPERATION',
|
|
470
|
+
payload: { operation: 'updating', marketplaceId },
|
|
471
|
+
});
|
|
472
|
+
const result = await updateMarketplace(marketplaceId);
|
|
473
|
+
dispatch({ type: 'END_MARKETPLACE_OPERATION' });
|
|
474
|
+
if (result.success) {
|
|
475
|
+
// Reload marketplaces and plugins to get fresh state
|
|
476
|
+
const marketplaces = loadMarketplaces();
|
|
477
|
+
dispatch({ type: 'SET_MARKETPLACES', payload: marketplaces });
|
|
478
|
+
const plugins = loadAllPlugins();
|
|
479
|
+
dispatch({ type: 'SET_PLUGINS', payload: plugins });
|
|
480
|
+
// Reset selection to avoid pointing to a different marketplace after re-sort
|
|
481
|
+
dispatch({ type: 'SET_SELECTED_INDEX', payload: 0 });
|
|
482
|
+
dispatch({ type: 'SET_MESSAGE', payload: `✅ ${result.message}` });
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
dispatch({
|
|
486
|
+
type: 'SET_MESSAGE',
|
|
487
|
+
payload: `❌ ${result.message}${result.error ? `: ${result.error}` : ''}`,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
}
|
|
285
491
|
// Keyboard input handling
|
|
286
492
|
useInput((input, key) => {
|
|
287
|
-
// Block all input during operations
|
|
288
|
-
if (state.operation !== 'idle') {
|
|
493
|
+
// Block all input during operations (plugin or marketplace)
|
|
494
|
+
if (state.operation !== 'idle' || state.marketplaceOperation !== 'idle') {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
// Handle help overlay
|
|
498
|
+
if (state.showHelp) {
|
|
499
|
+
if (input === 'h' || key.escape) {
|
|
500
|
+
dispatch({ type: 'TOGGLE_HELP' });
|
|
501
|
+
}
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
// Exit (q or Ctrl+C) - Global handler, works in all focus zones
|
|
505
|
+
if (input === 'q' || (key.ctrl && input === 'c')) {
|
|
506
|
+
exit();
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
// Toggle help (h key)
|
|
510
|
+
if (input === 'h') {
|
|
511
|
+
dispatch({ type: 'TOGGLE_HELP' });
|
|
289
512
|
return;
|
|
290
513
|
}
|
|
291
|
-
// Handle confirmation dialog
|
|
514
|
+
// Handle plugin uninstall confirmation dialog
|
|
292
515
|
if (state.confirmUninstall && state.operationPluginId) {
|
|
293
516
|
if (input === 'y' || input === 'Y') {
|
|
294
517
|
dispatch({ type: 'HIDE_CONFIRM_UNINSTALL' });
|
|
@@ -302,12 +525,34 @@ export default function App() {
|
|
|
302
525
|
}
|
|
303
526
|
return;
|
|
304
527
|
}
|
|
305
|
-
//
|
|
306
|
-
if (
|
|
307
|
-
if (
|
|
308
|
-
|
|
528
|
+
// Handle marketplace remove confirmation dialog
|
|
529
|
+
if (state.confirmRemoveMarketplace && state.operationMarketplaceId) {
|
|
530
|
+
if (input === 'y' || input === 'Y') {
|
|
531
|
+
dispatch({ type: 'HIDE_CONFIRM_REMOVE_MARKETPLACE' });
|
|
532
|
+
handleRemoveMarketplace(state.operationMarketplaceId);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (input === 'n' || input === 'N' || key.escape) {
|
|
536
|
+
dispatch({ type: 'HIDE_CONFIRM_REMOVE_MARKETPLACE' });
|
|
537
|
+
dispatch({ type: 'SET_MESSAGE', payload: 'Remove cancelled' });
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
// Handle add marketplace dialog input
|
|
543
|
+
if (state.showAddMarketplaceDialog) {
|
|
544
|
+
// Submit on Enter
|
|
545
|
+
if (key.return && state.searchQuery.trim()) {
|
|
546
|
+
handleAddMarketplace(state.searchQuery.trim());
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
// Cancel on Escape
|
|
550
|
+
if (key.escape) {
|
|
551
|
+
dispatch({ type: 'HIDE_ADD_MARKETPLACE_DIALOG' });
|
|
552
|
+
dispatch({ type: 'SET_MESSAGE', payload: 'Add marketplace cancelled' });
|
|
309
553
|
return;
|
|
310
554
|
}
|
|
555
|
+
// Backspace
|
|
311
556
|
if (key.backspace || key.delete) {
|
|
312
557
|
dispatch({
|
|
313
558
|
type: 'SET_SEARCH_QUERY',
|
|
@@ -315,6 +560,7 @@ export default function App() {
|
|
|
315
560
|
});
|
|
316
561
|
return;
|
|
317
562
|
}
|
|
563
|
+
// Character input
|
|
318
564
|
if (input && input.length === 1 && !key.ctrl && !key.meta) {
|
|
319
565
|
dispatch({
|
|
320
566
|
type: 'SET_SEARCH_QUERY',
|
|
@@ -324,48 +570,147 @@ export default function App() {
|
|
|
324
570
|
}
|
|
325
571
|
return;
|
|
326
572
|
}
|
|
327
|
-
//
|
|
328
|
-
if (
|
|
329
|
-
|
|
573
|
+
// Search mode input (when focusZone is 'search')
|
|
574
|
+
if (state.focusZone === 'search') {
|
|
575
|
+
// Up arrow: move focus to tabbar
|
|
576
|
+
if (key.upArrow || (key.ctrl && input === 'p')) {
|
|
577
|
+
dispatch({ type: 'SET_FOCUS_ZONE', payload: 'tabbar' });
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
// Down arrow, Enter, or Escape: move focus to list
|
|
581
|
+
if (key.escape ||
|
|
582
|
+
key.return ||
|
|
583
|
+
key.downArrow ||
|
|
584
|
+
(key.ctrl && input === 'n')) {
|
|
585
|
+
dispatch({ type: 'SET_FOCUS_ZONE', payload: 'list' });
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (key.backspace || key.delete) {
|
|
589
|
+
dispatch({
|
|
590
|
+
type: 'SET_SEARCH_QUERY',
|
|
591
|
+
payload: state.searchQuery.slice(0, -1),
|
|
592
|
+
});
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (input && input.length === 1 && !key.ctrl && !key.meta) {
|
|
596
|
+
dispatch({
|
|
597
|
+
type: 'SET_SEARCH_QUERY',
|
|
598
|
+
payload: state.searchQuery + input,
|
|
599
|
+
});
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
330
602
|
return;
|
|
331
603
|
}
|
|
332
|
-
|
|
333
|
-
|
|
604
|
+
// TabBar focus zone navigation
|
|
605
|
+
if (state.focusZone === 'tabbar') {
|
|
606
|
+
// Down arrow: move to search (or list if no search)
|
|
607
|
+
if (key.downArrow || (key.ctrl && input === 'n')) {
|
|
608
|
+
const zones = getAvailableZones(state.activeTab);
|
|
609
|
+
dispatch({
|
|
610
|
+
type: 'SET_FOCUS_ZONE',
|
|
611
|
+
payload: zones.includes('search') ? 'search' : 'list',
|
|
612
|
+
});
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
// Left/Right arrows and Ctrl+B/F: tab switching (only in tabbar)
|
|
616
|
+
// Keep focus on tabbar after navigation
|
|
617
|
+
if (key.leftArrow || (key.ctrl && input === 'b')) {
|
|
618
|
+
dispatch({ type: 'PREV_TAB' });
|
|
619
|
+
dispatch({ type: 'SET_FOCUS_ZONE', payload: 'tabbar' });
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
if (key.rightArrow || (key.ctrl && input === 'f')) {
|
|
623
|
+
dispatch({ type: 'NEXT_TAB' });
|
|
624
|
+
dispatch({ type: 'SET_FOCUS_ZONE', payload: 'tabbar' });
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
// Tab key: next tab (resets focus to list)
|
|
628
|
+
if (key.tab) {
|
|
629
|
+
dispatch({ type: 'NEXT_TAB' });
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
334
632
|
return;
|
|
335
633
|
}
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
|
|
634
|
+
// List focus zone navigation (default zone)
|
|
635
|
+
// Up arrow: move up in list or focus search/tabbar at top
|
|
636
|
+
if (key.upArrow || (key.ctrl && input === 'p')) {
|
|
637
|
+
if (state.selectedIndex === 0) {
|
|
638
|
+
// At top of list: move focus to search (or tabbar if no search)
|
|
639
|
+
const zones = getAvailableZones(state.activeTab);
|
|
640
|
+
dispatch({
|
|
641
|
+
type: 'SET_FOCUS_ZONE',
|
|
642
|
+
payload: zones.includes('search') ? 'search' : 'tabbar',
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
dispatch({ type: 'MOVE_SELECTION', payload: 'up' });
|
|
647
|
+
}
|
|
339
648
|
return;
|
|
340
649
|
}
|
|
341
|
-
|
|
342
|
-
|
|
650
|
+
// Down arrow: move down in list
|
|
651
|
+
if (key.downArrow || (key.ctrl && input === 'n')) {
|
|
652
|
+
dispatch({ type: 'MOVE_SELECTION', payload: 'down' });
|
|
343
653
|
return;
|
|
344
654
|
}
|
|
655
|
+
// Tab key: next tab (from list zone)
|
|
345
656
|
if (key.tab) {
|
|
346
657
|
dispatch({ type: 'NEXT_TAB' });
|
|
347
658
|
return;
|
|
348
659
|
}
|
|
349
|
-
//
|
|
350
|
-
|
|
351
|
-
|
|
660
|
+
// Enter search mode (/ key on supported tabs)
|
|
661
|
+
const searchEnabledTabs = [
|
|
662
|
+
'enabled',
|
|
663
|
+
'installed',
|
|
664
|
+
'discover',
|
|
665
|
+
'marketplaces',
|
|
666
|
+
];
|
|
667
|
+
if (input === '/' && searchEnabledTabs.includes(state.activeTab)) {
|
|
668
|
+
dispatch({ type: 'SET_FOCUS_ZONE', payload: 'search' });
|
|
352
669
|
return;
|
|
353
670
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
671
|
+
// Enter key: Install (non-installed) or Toggle (installed)
|
|
672
|
+
if (key.return &&
|
|
673
|
+
(state.activeTab === 'enabled' ||
|
|
674
|
+
state.activeTab === 'installed' ||
|
|
675
|
+
state.activeTab === 'discover')) {
|
|
676
|
+
const items = getItemsForTab(state);
|
|
677
|
+
const selectedPlugin = items[state.selectedIndex];
|
|
678
|
+
if (selectedPlugin) {
|
|
679
|
+
if (!selectedPlugin.isInstalled) {
|
|
680
|
+
// Install non-installed plugin
|
|
681
|
+
handleInstall(selectedPlugin.id);
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
// Toggle installed plugin
|
|
685
|
+
try {
|
|
686
|
+
const newState = togglePlugin(selectedPlugin.id);
|
|
687
|
+
dispatch({
|
|
688
|
+
type: 'TOGGLE_PLUGIN_ENABLED',
|
|
689
|
+
payload: selectedPlugin.id,
|
|
690
|
+
});
|
|
691
|
+
dispatch({
|
|
692
|
+
type: 'SET_MESSAGE',
|
|
693
|
+
payload: newState
|
|
694
|
+
? `✅ ${selectedPlugin.name} enabled`
|
|
695
|
+
: `❌ ${selectedPlugin.name} disabled`,
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
catch (error) {
|
|
699
|
+
dispatch({
|
|
700
|
+
type: 'SET_MESSAGE',
|
|
701
|
+
payload: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
361
706
|
return;
|
|
362
707
|
}
|
|
363
|
-
// Toggle plugin (Space
|
|
364
|
-
if (
|
|
365
|
-
(state.activeTab === '
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
708
|
+
// Toggle plugin (Space only)
|
|
709
|
+
if (input === ' ' &&
|
|
710
|
+
(state.activeTab === 'enabled' ||
|
|
711
|
+
state.activeTab === 'installed' ||
|
|
712
|
+
state.activeTab === 'discover')) {
|
|
713
|
+
const items = getItemsForTab(state);
|
|
369
714
|
const selectedPlugin = items[state.selectedIndex];
|
|
370
715
|
if (selectedPlugin && selectedPlugin.isInstalled) {
|
|
371
716
|
try {
|
|
@@ -419,12 +764,43 @@ export default function App() {
|
|
|
419
764
|
dispatch({ type: 'SET_SEARCH_QUERY', payload: '' });
|
|
420
765
|
return;
|
|
421
766
|
}
|
|
422
|
-
//
|
|
767
|
+
// Marketplace-specific key bindings
|
|
768
|
+
if (state.activeTab === 'marketplaces' && state.focusZone === 'list') {
|
|
769
|
+
// Add marketplace (a key)
|
|
770
|
+
if (input === 'a') {
|
|
771
|
+
dispatch({ type: 'SHOW_ADD_MARKETPLACE_DIALOG' });
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
// Remove marketplace (d key or Backspace)
|
|
775
|
+
if (input === 'd' || key.backspace || key.delete) {
|
|
776
|
+
const selectedMarketplace = filteredMarketplaces[state.selectedIndex];
|
|
777
|
+
if (selectedMarketplace) {
|
|
778
|
+
dispatch({
|
|
779
|
+
type: 'SHOW_CONFIRM_REMOVE_MARKETPLACE',
|
|
780
|
+
payload: selectedMarketplace.id,
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
// Update marketplace (u key)
|
|
786
|
+
if (input === 'u') {
|
|
787
|
+
const selectedMarketplace = filteredMarketplaces[state.selectedIndex];
|
|
788
|
+
if (selectedMarketplace) {
|
|
789
|
+
handleUpdateMarketplace(selectedMarketplace.id);
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
// No marketplace selected, update all
|
|
793
|
+
handleUpdateMarketplace();
|
|
794
|
+
}
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
// Install (i key) - only on enabled/installed/discover tabs
|
|
423
799
|
if (input === 'i' &&
|
|
424
|
-
(state.activeTab === '
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
800
|
+
(state.activeTab === 'enabled' ||
|
|
801
|
+
state.activeTab === 'installed' ||
|
|
802
|
+
state.activeTab === 'discover')) {
|
|
803
|
+
const items = getItemsForTab(state);
|
|
428
804
|
const selectedPlugin = items[state.selectedIndex];
|
|
429
805
|
if (selectedPlugin && !selectedPlugin.isInstalled) {
|
|
430
806
|
handleInstall(selectedPlugin.id);
|
|
@@ -437,12 +813,12 @@ export default function App() {
|
|
|
437
813
|
}
|
|
438
814
|
return;
|
|
439
815
|
}
|
|
440
|
-
// Uninstall (u key) - only on
|
|
816
|
+
// Uninstall (u key) - only on enabled/installed/discover tabs
|
|
441
817
|
if (input === 'u' &&
|
|
442
|
-
(state.activeTab === '
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
818
|
+
(state.activeTab === 'enabled' ||
|
|
819
|
+
state.activeTab === 'installed' ||
|
|
820
|
+
state.activeTab === 'discover')) {
|
|
821
|
+
const items = getItemsForTab(state);
|
|
446
822
|
const selectedPlugin = items[state.selectedIndex];
|
|
447
823
|
if (selectedPlugin && selectedPlugin.isInstalled) {
|
|
448
824
|
dispatch({ type: 'SHOW_CONFIRM_UNINSTALL', payload: selectedPlugin.id });
|
|
@@ -452,11 +828,6 @@ export default function App() {
|
|
|
452
828
|
}
|
|
453
829
|
return;
|
|
454
830
|
}
|
|
455
|
-
// Exit (q or Ctrl+C)
|
|
456
|
-
if (input === 'q' || (key.ctrl && input === 'c')) {
|
|
457
|
-
exit();
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
831
|
});
|
|
461
832
|
// Loading state
|
|
462
833
|
if (state.loading) {
|
|
@@ -468,14 +839,70 @@ export default function App() {
|
|
|
468
839
|
}
|
|
469
840
|
// Get filtered data for current tab
|
|
470
841
|
const filteredPlugins = getFilteredPlugins(state);
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
842
|
+
// Apply search filter to enabled plugins
|
|
843
|
+
const enabledPluginsBase = state.plugins.filter((p) => p.isInstalled && p.isEnabled);
|
|
844
|
+
const enabledPlugins = state.searchQuery
|
|
845
|
+
? searchPlugins(state.searchQuery, enabledPluginsBase)
|
|
846
|
+
: enabledPluginsBase;
|
|
847
|
+
// Apply search filter to installed plugins
|
|
848
|
+
const installedPluginsBase = state.plugins.filter((p) => p.isInstalled);
|
|
849
|
+
const installedPlugins = state.searchQuery
|
|
850
|
+
? searchPlugins(state.searchQuery, installedPluginsBase)
|
|
851
|
+
: installedPluginsBase;
|
|
852
|
+
// Apply search filter to marketplaces
|
|
853
|
+
const filteredMarketplaces = state.searchQuery
|
|
854
|
+
? searchMarketplaces(state.searchQuery, state.marketplaces)
|
|
855
|
+
: state.marketplaces;
|
|
856
|
+
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 }), _jsxs(Text, { dimColor: true, children: ["v", packageJson.version] })] }), _jsx(TabBar, { activeTab: state.activeTab, isFocused: state.focusZone === 'tabbar' }), _jsxs(Box, { flexGrow: 1, flexDirection: "column", children: [state.activeTab === 'enabled' && (_jsx(EnabledTab, { plugins: enabledPlugins, selectedIndex: state.selectedIndex, searchQuery: state.searchQuery, focusZone: state.focusZone })), state.activeTab === 'installed' && (_jsx(InstalledTab, { plugins: installedPlugins, selectedIndex: state.selectedIndex, searchQuery: state.searchQuery, focusZone: state.focusZone })), state.activeTab === 'discover' && (_jsx(DiscoverTab, { plugins: filteredPlugins, selectedIndex: state.selectedIndex, searchQuery: state.searchQuery, sortBy: state.sortBy, sortOrder: state.sortOrder, focusZone: state.focusZone })), state.activeTab === 'marketplaces' && (_jsx(MarketplacesTab, { marketplaces: filteredMarketplaces, selectedIndex: state.selectedIndex, searchQuery: state.searchQuery, focusZone: state.focusZone })), state.activeTab === 'errors' && (_jsx(ErrorsTab, { errors: state.errors, selectedIndex: state.selectedIndex }))] }), state.confirmUninstall && state.operationPluginId && (_jsx(ConfirmDialog, { message: `Uninstall ${state.operationPluginId}?` })), state.confirmRemoveMarketplace && state.operationMarketplaceId && (_jsx(ConfirmDialog, { message: `Remove marketplace ${state.operationMarketplaceId}?` })), state.showAddMarketplaceDialog && (_jsx(AddMarketplaceDialog, { value: state.searchQuery, error: state.addMarketplaceError ?? undefined })), _jsx(HelpOverlay, { isVisible: state.showHelp }), state.message && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: state.message }) })), _jsx(KeyHints, { focusZone: state.focusZone, extraHints: (() => {
|
|
857
|
+
// Search mode - no extra hints (base hints cover it)
|
|
858
|
+
if (state.focusZone === 'search') {
|
|
859
|
+
return undefined;
|
|
860
|
+
}
|
|
861
|
+
// TabBar mode - no extra hints
|
|
862
|
+
if (state.focusZone === 'tabbar') {
|
|
863
|
+
return undefined;
|
|
864
|
+
}
|
|
865
|
+
// List mode - add tab-specific hints
|
|
866
|
+
// Plugin tabs hints (enabled, installed, discover)
|
|
867
|
+
if (state.activeTab === 'enabled' ||
|
|
868
|
+
state.activeTab === 'installed' ||
|
|
869
|
+
state.activeTab === 'discover') {
|
|
870
|
+
const hints = [
|
|
871
|
+
{ key: '/', action: 'search' },
|
|
872
|
+
{ key: 'i', action: 'install' },
|
|
873
|
+
{ key: 'u', action: 'uninstall' },
|
|
874
|
+
];
|
|
875
|
+
// Get selected plugin to determine Enter action
|
|
876
|
+
const items = state.activeTab === 'enabled'
|
|
877
|
+
? enabledPlugins
|
|
878
|
+
: state.activeTab === 'installed'
|
|
879
|
+
? installedPlugins
|
|
880
|
+
: filteredPlugins;
|
|
881
|
+
const selectedPlugin = items[state.selectedIndex];
|
|
882
|
+
// Add contextual Enter hint
|
|
883
|
+
if (selectedPlugin) {
|
|
884
|
+
if (!selectedPlugin.isInstalled) {
|
|
885
|
+
hints.push({ key: 'Enter', action: 'install' });
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
hints.push({ key: 'Enter', action: 'toggle' });
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
// Add sort hint for discover tab
|
|
892
|
+
if (state.activeTab === 'discover') {
|
|
893
|
+
hints.push({ key: 's', action: 'sort' });
|
|
894
|
+
}
|
|
895
|
+
return hints;
|
|
896
|
+
}
|
|
897
|
+
// Marketplaces tab hints
|
|
898
|
+
if (state.activeTab === 'marketplaces') {
|
|
899
|
+
return [
|
|
900
|
+
{ key: '/', action: 'search' },
|
|
901
|
+
{ key: 'a', action: 'add' },
|
|
902
|
+
{ key: 'd', action: 'remove' },
|
|
903
|
+
{ key: 'u', action: 'update' },
|
|
904
|
+
];
|
|
905
|
+
}
|
|
906
|
+
return undefined;
|
|
907
|
+
})() })] }));
|
|
481
908
|
}
|