@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.
- package/README.md +7 -1
- package/dist/app.d.ts +7 -1
- package/dist/app.js +544 -262
- package/dist/cli.js +60 -67
- package/dist/components/ComponentBadges.d.ts +0 -9
- package/dist/components/ComponentBadges.js +0 -33
- package/dist/components/ComponentDetail.d.ts +32 -0
- package/dist/components/ComponentDetail.js +106 -0
- package/dist/components/ComponentList.d.ts +87 -0
- package/dist/components/ComponentList.js +287 -0
- package/dist/components/HelpOverlay.js +1 -0
- package/dist/components/KeyHints.d.ts +1 -0
- package/dist/components/KeyHints.js +33 -29
- package/dist/components/MarketplaceActionMenu.d.ts +41 -0
- package/dist/components/MarketplaceActionMenu.js +68 -0
- package/dist/components/MarketplaceDetail.d.ts +10 -3
- package/dist/components/MarketplaceDetail.js +10 -4
- package/dist/components/PluginDetail.d.ts +19 -3
- package/dist/components/PluginDetail.js +56 -6
- package/dist/components/PluginList.js +19 -7
- package/dist/services/componentService.d.ts +10 -31
- package/dist/services/componentService.js +19 -174
- package/dist/services/components/hookService.d.ts +17 -0
- package/dist/services/components/hookService.js +45 -0
- package/dist/services/components/index.d.ts +41 -0
- package/dist/services/components/index.js +126 -0
- package/dist/services/components/markdownService.d.ts +39 -0
- package/dist/services/components/markdownService.js +147 -0
- package/dist/services/components/serverService.d.ts +28 -0
- package/dist/services/components/serverService.js +69 -0
- package/dist/services/components/skillService.d.ts +48 -0
- package/dist/services/components/skillService.js +164 -0
- package/dist/services/components/utils.d.ts +23 -0
- package/dist/services/components/utils.js +42 -0
- package/dist/services/marketplaceActionsService.d.ts +17 -0
- package/dist/services/marketplaceActionsService.js +18 -0
- package/dist/services/pluginActionsService.d.ts +31 -2
- package/dist/services/pluginActionsService.js +65 -6
- package/dist/services/pluginService.js +78 -2
- package/dist/store/index.d.ts +46 -0
- package/dist/store/index.js +47 -0
- package/dist/store/slices/marketplaceSlice.d.ts +344 -0
- package/dist/store/slices/marketplaceSlice.js +152 -0
- package/dist/store/slices/pluginSlice.d.ts +1544 -0
- package/dist/store/slices/pluginSlice.js +191 -0
- package/dist/store/slices/uiSlice.d.ts +147 -0
- package/dist/store/slices/uiSlice.js +126 -0
- package/dist/tabs/DiscoverTab.d.ts +8 -2
- package/dist/tabs/DiscoverTab.js +2 -2
- package/dist/tabs/EnabledTab.d.ts +8 -2
- package/dist/tabs/EnabledTab.js +3 -3
- package/dist/tabs/ErrorsTab.js +1 -1
- package/dist/tabs/InstalledTab.d.ts +8 -2
- package/dist/tabs/InstalledTab.js +3 -3
- package/dist/tabs/MarketplacesTab.d.ts +15 -2
- package/dist/tabs/MarketplacesTab.js +13 -4
- package/dist/types/index.d.ts +157 -5
- package/package.json +10 -3
package/dist/app.js
CHANGED
|
@@ -3,8 +3,11 @@ 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 { useEffect,
|
|
6
|
+
import { useEffect, useMemo } from 'react';
|
|
7
|
+
import * as os from 'node:os';
|
|
8
|
+
import * as path from 'node:path';
|
|
7
9
|
import { Box, Text, useInput, useApp } from 'ink';
|
|
10
|
+
import { match, P } from 'ts-pattern';
|
|
8
11
|
import TabBar, { getNextTab } from './components/TabBar.js';
|
|
9
12
|
import KeyHints from './components/KeyHints.js';
|
|
10
13
|
import DiscoverTab from './tabs/DiscoverTab.js';
|
|
@@ -13,12 +16,21 @@ import InstalledTab from './tabs/InstalledTab.js';
|
|
|
13
16
|
import MarketplacesTab from './tabs/MarketplacesTab.js';
|
|
14
17
|
import ErrorsTab from './tabs/ErrorsTab.js';
|
|
15
18
|
import { loadAllPlugins, loadMarketplaces, searchPlugins, searchMarketplaces, sortPlugins, } from './services/pluginService.js';
|
|
19
|
+
import { getSkillDetailedInfo, getMarkdownComponentDetailedInfo, } from './services/componentService.js';
|
|
20
|
+
import { flattenComponents } from './components/ComponentList.js';
|
|
16
21
|
import { togglePlugin } from './services/settingsService.js';
|
|
17
|
-
import { installPlugin, uninstallPlugin, } from './services/pluginActionsService.js';
|
|
18
|
-
import { addMarketplace, removeMarketplace, updateMarketplace, } from './services/marketplaceActionsService.js';
|
|
22
|
+
import { installPlugin, uninstallPlugin, updateAllPlugins, } from './services/pluginActionsService.js';
|
|
23
|
+
import { addMarketplace, removeMarketplace, updateMarketplace, toggleAutoUpdate, } from './services/marketplaceActionsService.js';
|
|
19
24
|
import AddMarketplaceDialog from './components/AddMarketplaceDialog.js';
|
|
20
25
|
import ConfirmDialog from './components/ConfirmDialog.js';
|
|
21
26
|
import HelpOverlay from './components/HelpOverlay.js';
|
|
27
|
+
import { useAppDispatch, useAppSelector,
|
|
28
|
+
// UI actions
|
|
29
|
+
setTab, nextTab, prevTab, setFocusZone, toggleHelp, setSearchQuery, setSort, setMessage,
|
|
30
|
+
// Plugin actions
|
|
31
|
+
setPlugins, setError, setSelectedIndex, moveSelection, togglePluginEnabled, startOperation, endOperation, showConfirmUninstall, hideConfirmUninstall, showConfirmUpdateAll, hideConfirmUpdateAll, setUpdateProgress, clearUpdateProgress, moveComponentSelection, enterComponentMode, exitComponentMode,
|
|
32
|
+
// Marketplace actions
|
|
33
|
+
setMarketplaces, startMarketplaceOperation, endMarketplaceOperation, showConfirmRemoveMarketplace, hideConfirmRemoveMarketplace, showAddMarketplaceDialog, hideAddMarketplaceDialog, setAddMarketplaceError, showMarketplaceActionMenu as showMarketplaceActionMenuAction, hideMarketplaceActionMenu, moveActionMenuSelection, } from './store/index.js';
|
|
22
34
|
import packageJson from '../package.json' with { type: 'json' };
|
|
23
35
|
/**
|
|
24
36
|
* Initial application state
|
|
@@ -39,12 +51,17 @@ export const initialState = {
|
|
|
39
51
|
operation: 'idle',
|
|
40
52
|
operationPluginId: null,
|
|
41
53
|
confirmUninstall: false,
|
|
54
|
+
confirmUpdateAll: false,
|
|
55
|
+
updateProgress: null,
|
|
42
56
|
showHelp: false,
|
|
43
57
|
marketplaceOperation: 'idle',
|
|
44
58
|
operationMarketplaceId: null,
|
|
45
59
|
confirmRemoveMarketplace: false,
|
|
46
60
|
showAddMarketplaceDialog: false,
|
|
47
61
|
addMarketplaceError: null,
|
|
62
|
+
showMarketplaceActionMenu: false,
|
|
63
|
+
actionMenuSelectedIndex: 0,
|
|
64
|
+
selectedComponentIndex: 0,
|
|
48
65
|
};
|
|
49
66
|
/**
|
|
50
67
|
* Get available focus zones for the current tab
|
|
@@ -65,16 +82,12 @@ export function getAvailableZones(activeTab) {
|
|
|
65
82
|
* @returns Display message for the operation
|
|
66
83
|
*/
|
|
67
84
|
function getMarketplaceOperationMessage(operation, marketplaceId) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return `Updating ${marketplaceId || 'marketplaces'}...`;
|
|
75
|
-
default:
|
|
76
|
-
return '';
|
|
77
|
-
}
|
|
85
|
+
return match(operation)
|
|
86
|
+
.with('adding', () => 'Adding marketplace...')
|
|
87
|
+
.with('removing', () => `Removing ${marketplaceId}...`)
|
|
88
|
+
.with('updating', () => `Updating ${marketplaceId || 'marketplaces'}...`)
|
|
89
|
+
.with('idle', () => '')
|
|
90
|
+
.exhaustive();
|
|
78
91
|
}
|
|
79
92
|
/**
|
|
80
93
|
* State reducer for application state management
|
|
@@ -89,6 +102,8 @@ export function appReducer(state, action) {
|
|
|
89
102
|
selectedIndex: 0,
|
|
90
103
|
searchQuery: '',
|
|
91
104
|
message: null,
|
|
105
|
+
showMarketplaceActionMenu: false,
|
|
106
|
+
actionMenuSelectedIndex: 0,
|
|
92
107
|
};
|
|
93
108
|
case 'NEXT_TAB':
|
|
94
109
|
return {
|
|
@@ -98,6 +113,8 @@ export function appReducer(state, action) {
|
|
|
98
113
|
selectedIndex: 0,
|
|
99
114
|
searchQuery: '',
|
|
100
115
|
message: null,
|
|
116
|
+
showMarketplaceActionMenu: false,
|
|
117
|
+
actionMenuSelectedIndex: 0,
|
|
101
118
|
};
|
|
102
119
|
case 'PREV_TAB':
|
|
103
120
|
return {
|
|
@@ -107,6 +124,8 @@ export function appReducer(state, action) {
|
|
|
107
124
|
selectedIndex: 0,
|
|
108
125
|
searchQuery: '',
|
|
109
126
|
message: null,
|
|
127
|
+
showMarketplaceActionMenu: false,
|
|
128
|
+
actionMenuSelectedIndex: 0,
|
|
110
129
|
};
|
|
111
130
|
case 'SET_FOCUS_ZONE':
|
|
112
131
|
return {
|
|
@@ -210,7 +229,9 @@ export function appReducer(state, action) {
|
|
|
210
229
|
operationPluginId: action.payload.pluginId,
|
|
211
230
|
message: action.payload.operation === 'installing'
|
|
212
231
|
? `Installing ${action.payload.pluginId}...`
|
|
213
|
-
:
|
|
232
|
+
: action.payload.operation === 'uninstalling'
|
|
233
|
+
? `Uninstalling ${action.payload.pluginId}...`
|
|
234
|
+
: `Updating plugins...`,
|
|
214
235
|
};
|
|
215
236
|
case 'END_OPERATION':
|
|
216
237
|
return {
|
|
@@ -279,6 +300,59 @@ export function appReducer(state, action) {
|
|
|
279
300
|
marketplaceOperation: 'idle',
|
|
280
301
|
operationMarketplaceId: null,
|
|
281
302
|
};
|
|
303
|
+
case 'SHOW_MARKETPLACE_ACTION_MENU':
|
|
304
|
+
return {
|
|
305
|
+
...state,
|
|
306
|
+
showMarketplaceActionMenu: true,
|
|
307
|
+
actionMenuSelectedIndex: 0,
|
|
308
|
+
};
|
|
309
|
+
case 'HIDE_MARKETPLACE_ACTION_MENU':
|
|
310
|
+
return {
|
|
311
|
+
...state,
|
|
312
|
+
showMarketplaceActionMenu: false,
|
|
313
|
+
actionMenuSelectedIndex: 0,
|
|
314
|
+
};
|
|
315
|
+
case 'SET_ACTION_MENU_INDEX':
|
|
316
|
+
return {
|
|
317
|
+
...state,
|
|
318
|
+
actionMenuSelectedIndex: action.payload,
|
|
319
|
+
};
|
|
320
|
+
case 'MOVE_ACTION_MENU_SELECTION': {
|
|
321
|
+
const maxIndex = 3; // 4 actions: browse, update, autoUpdate, remove
|
|
322
|
+
const newIndex = action.payload === 'up'
|
|
323
|
+
? Math.max(0, state.actionMenuSelectedIndex - 1)
|
|
324
|
+
: Math.min(maxIndex, state.actionMenuSelectedIndex + 1);
|
|
325
|
+
return {
|
|
326
|
+
...state,
|
|
327
|
+
actionMenuSelectedIndex: newIndex,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
case 'SET_COMPONENT_INDEX':
|
|
331
|
+
return {
|
|
332
|
+
...state,
|
|
333
|
+
selectedComponentIndex: action.payload,
|
|
334
|
+
};
|
|
335
|
+
case 'MOVE_COMPONENT_SELECTION': {
|
|
336
|
+
const newIndex = action.payload === 'up'
|
|
337
|
+
? Math.max(0, state.selectedComponentIndex - 1)
|
|
338
|
+
: Math.min(action.maxIndex, state.selectedComponentIndex + 1);
|
|
339
|
+
return {
|
|
340
|
+
...state,
|
|
341
|
+
selectedComponentIndex: newIndex,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
case 'ENTER_COMPONENT_MODE':
|
|
345
|
+
return {
|
|
346
|
+
...state,
|
|
347
|
+
focusZone: 'components',
|
|
348
|
+
selectedComponentIndex: 0,
|
|
349
|
+
};
|
|
350
|
+
case 'EXIT_COMPONENT_MODE':
|
|
351
|
+
return {
|
|
352
|
+
...state,
|
|
353
|
+
focusZone: 'list',
|
|
354
|
+
selectedComponentIndex: 0,
|
|
355
|
+
};
|
|
282
356
|
default:
|
|
283
357
|
return state;
|
|
284
358
|
}
|
|
@@ -292,28 +366,23 @@ export function appReducer(state, action) {
|
|
|
292
366
|
* // => Only installed plugins matching 'su'
|
|
293
367
|
*/
|
|
294
368
|
export function getItemsForTab(state) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
case 'errors':
|
|
313
|
-
return state.errors;
|
|
314
|
-
default:
|
|
315
|
-
return [];
|
|
316
|
-
}
|
|
369
|
+
return match(state.activeTab)
|
|
370
|
+
.with('enabled', () => {
|
|
371
|
+
const enabledPlugins = state.plugins.filter((p) => p.isInstalled && p.isEnabled);
|
|
372
|
+
return state.searchQuery
|
|
373
|
+
? searchPlugins(state.searchQuery, enabledPlugins)
|
|
374
|
+
: enabledPlugins;
|
|
375
|
+
})
|
|
376
|
+
.with('installed', () => {
|
|
377
|
+
const installedPlugins = state.plugins.filter((p) => p.isInstalled);
|
|
378
|
+
return state.searchQuery
|
|
379
|
+
? searchPlugins(state.searchQuery, installedPlugins)
|
|
380
|
+
: installedPlugins;
|
|
381
|
+
})
|
|
382
|
+
.with('discover', () => getFilteredPlugins(state))
|
|
383
|
+
.with('marketplaces', () => state.marketplaces)
|
|
384
|
+
.with('errors', () => state.errors)
|
|
385
|
+
.otherwise(() => []);
|
|
317
386
|
}
|
|
318
387
|
/**
|
|
319
388
|
* Get filtered and sorted plugins for discover tab
|
|
@@ -328,71 +397,128 @@ export function getFilteredPlugins(state) {
|
|
|
328
397
|
plugins = sortPlugins(plugins, state.sortBy, state.sortOrder);
|
|
329
398
|
return plugins;
|
|
330
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* Get filtered marketplaces based on search query
|
|
402
|
+
* @param state - Current app state
|
|
403
|
+
* @returns Filtered array of marketplaces
|
|
404
|
+
*/
|
|
405
|
+
export function getFilteredMarketplaces(state) {
|
|
406
|
+
let marketplaces = state.marketplaces;
|
|
407
|
+
// Apply search filter
|
|
408
|
+
if (state.searchQuery) {
|
|
409
|
+
marketplaces = searchMarketplaces(state.searchQuery, marketplaces);
|
|
410
|
+
}
|
|
411
|
+
return marketplaces;
|
|
412
|
+
}
|
|
331
413
|
/**
|
|
332
414
|
* Main App component
|
|
333
415
|
*/
|
|
334
416
|
export default function App() {
|
|
335
417
|
const { exit } = useApp();
|
|
336
|
-
const
|
|
418
|
+
const dispatch = useAppDispatch();
|
|
419
|
+
// Select state from Redux slices
|
|
420
|
+
const ui = useAppSelector((s) => s.ui);
|
|
421
|
+
const pluginState = useAppSelector((s) => s.plugins);
|
|
422
|
+
const marketplaceState = useAppSelector((s) => s.marketplaces);
|
|
423
|
+
// Combine into AppState-compatible object for existing helper functions
|
|
424
|
+
const state = {
|
|
425
|
+
activeTab: ui.activeTab,
|
|
426
|
+
focusZone: ui.focusZone,
|
|
427
|
+
showHelp: ui.showHelp,
|
|
428
|
+
searchQuery: ui.searchQuery,
|
|
429
|
+
sortBy: ui.sortBy,
|
|
430
|
+
sortOrder: ui.sortOrder,
|
|
431
|
+
message: ui.message,
|
|
432
|
+
plugins: pluginState.plugins,
|
|
433
|
+
errors: pluginState.errors,
|
|
434
|
+
loading: pluginState.loading,
|
|
435
|
+
error: pluginState.error,
|
|
436
|
+
selectedIndex: pluginState.selectedIndex,
|
|
437
|
+
operation: pluginState.operation,
|
|
438
|
+
operationPluginId: pluginState.operationPluginId,
|
|
439
|
+
confirmUninstall: pluginState.confirmUninstall,
|
|
440
|
+
confirmUpdateAll: pluginState.confirmUpdateAll,
|
|
441
|
+
updateProgress: pluginState.updateProgress,
|
|
442
|
+
selectedComponentIndex: pluginState.selectedComponentIndex,
|
|
443
|
+
marketplaces: marketplaceState.marketplaces,
|
|
444
|
+
marketplaceOperation: marketplaceState.marketplaceOperation,
|
|
445
|
+
operationMarketplaceId: marketplaceState.operationMarketplaceId,
|
|
446
|
+
confirmRemoveMarketplace: marketplaceState.confirmRemoveMarketplace,
|
|
447
|
+
showAddMarketplaceDialog: marketplaceState.showAddMarketplaceDialog,
|
|
448
|
+
addMarketplaceError: marketplaceState.addMarketplaceError,
|
|
449
|
+
showMarketplaceActionMenu: marketplaceState.showMarketplaceActionMenu,
|
|
450
|
+
actionMenuSelectedIndex: marketplaceState.actionMenuSelectedIndex,
|
|
451
|
+
};
|
|
337
452
|
// Load data on mount
|
|
338
453
|
useEffect(() => {
|
|
339
454
|
try {
|
|
340
455
|
const plugins = loadAllPlugins();
|
|
341
456
|
const marketplaces = loadMarketplaces();
|
|
342
|
-
dispatch(
|
|
343
|
-
dispatch(
|
|
457
|
+
dispatch(setPlugins(plugins));
|
|
458
|
+
dispatch(setMarketplaces(marketplaces));
|
|
344
459
|
}
|
|
345
460
|
catch (error) {
|
|
346
|
-
dispatch(
|
|
347
|
-
type: 'SET_ERROR',
|
|
348
|
-
payload: error instanceof Error ? error.message : 'Failed to load data',
|
|
349
|
-
});
|
|
461
|
+
dispatch(setError(error instanceof Error ? error.message : 'Failed to load data'));
|
|
350
462
|
}
|
|
351
463
|
}, []);
|
|
352
464
|
/**
|
|
353
465
|
* Handle plugin installation
|
|
354
466
|
*/
|
|
355
467
|
async function handleInstall(pluginId) {
|
|
356
|
-
dispatch({
|
|
357
|
-
type: 'START_OPERATION',
|
|
358
|
-
payload: { operation: 'installing', pluginId },
|
|
359
|
-
});
|
|
468
|
+
dispatch(startOperation({ operation: 'installing', pluginId }));
|
|
360
469
|
const result = await installPlugin(pluginId);
|
|
361
|
-
dispatch(
|
|
470
|
+
dispatch(endOperation());
|
|
362
471
|
if (result.success) {
|
|
363
472
|
// Reload plugins to get fresh state
|
|
364
473
|
const plugins = loadAllPlugins();
|
|
365
|
-
dispatch(
|
|
366
|
-
dispatch(
|
|
474
|
+
dispatch(setPlugins(plugins));
|
|
475
|
+
dispatch(setMessage(`✅ ${result.message}`));
|
|
367
476
|
}
|
|
368
477
|
else {
|
|
369
|
-
dispatch({
|
|
370
|
-
type: 'SET_MESSAGE',
|
|
371
|
-
payload: `❌ ${result.message}${result.error ? `: ${result.error}` : ''}`,
|
|
372
|
-
});
|
|
478
|
+
dispatch(setMessage(`❌ ${result.message}${result.error ? `: ${result.error}` : ''}`));
|
|
373
479
|
}
|
|
374
480
|
}
|
|
375
481
|
/**
|
|
376
482
|
* Handle plugin uninstallation
|
|
377
483
|
*/
|
|
378
484
|
async function handleUninstall(pluginId) {
|
|
379
|
-
dispatch({
|
|
380
|
-
type: 'START_OPERATION',
|
|
381
|
-
payload: { operation: 'uninstalling', pluginId },
|
|
382
|
-
});
|
|
485
|
+
dispatch(startOperation({ operation: 'uninstalling', pluginId }));
|
|
383
486
|
const result = await uninstallPlugin(pluginId);
|
|
384
|
-
dispatch(
|
|
487
|
+
dispatch(endOperation());
|
|
385
488
|
if (result.success) {
|
|
386
489
|
// Reload plugins to get fresh state
|
|
387
490
|
const plugins = loadAllPlugins();
|
|
388
|
-
dispatch(
|
|
389
|
-
dispatch(
|
|
491
|
+
dispatch(setPlugins(plugins));
|
|
492
|
+
dispatch(setMessage(`✅ ${result.message}`));
|
|
390
493
|
}
|
|
391
494
|
else {
|
|
392
|
-
dispatch({
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
495
|
+
dispatch(setMessage(`❌ ${result.message}${result.error ? `: ${result.error}` : ''}`));
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Handle updating all installed plugins sequentially
|
|
500
|
+
*/
|
|
501
|
+
async function handleUpdateAllPlugins() {
|
|
502
|
+
const installed = state.plugins.filter((p) => p.isInstalled);
|
|
503
|
+
if (installed.length === 0) {
|
|
504
|
+
dispatch(setMessage('⚠️ No installed plugins to update'));
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
const pluginIds = installed.map((p) => p.id);
|
|
508
|
+
dispatch(startOperation({ operation: 'updating', pluginId: pluginIds[0] }));
|
|
509
|
+
const result = await updateAllPlugins(pluginIds, (current, total, pluginId) => {
|
|
510
|
+
dispatch(setUpdateProgress({ current, total, pluginId }));
|
|
511
|
+
});
|
|
512
|
+
dispatch(clearUpdateProgress());
|
|
513
|
+
dispatch(endOperation());
|
|
514
|
+
// Reload plugins to get fresh state
|
|
515
|
+
const plugins = loadAllPlugins();
|
|
516
|
+
dispatch(setPlugins(plugins));
|
|
517
|
+
if (result.failed === 0) {
|
|
518
|
+
dispatch(setMessage(`✅ Updated ${result.succeeded}/${result.total} plugins`));
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
dispatch(setMessage(`⚠️ Updated ${result.succeeded}/${result.total} (${result.failed} failed)`));
|
|
396
522
|
}
|
|
397
523
|
}
|
|
398
524
|
/**
|
|
@@ -400,30 +526,24 @@ export default function App() {
|
|
|
400
526
|
* @param source - Marketplace source (e.g., "owner/repo", URL, or local path)
|
|
401
527
|
*/
|
|
402
528
|
async function handleAddMarketplace(source) {
|
|
403
|
-
dispatch({
|
|
404
|
-
type: 'START_MARKETPLACE_OPERATION',
|
|
405
|
-
payload: { operation: 'adding' },
|
|
406
|
-
});
|
|
529
|
+
dispatch(startMarketplaceOperation({ operation: 'adding' }));
|
|
407
530
|
const result = await addMarketplace(source);
|
|
408
|
-
dispatch(
|
|
531
|
+
dispatch(endMarketplaceOperation());
|
|
409
532
|
if (result.success) {
|
|
410
|
-
dispatch(
|
|
533
|
+
dispatch(hideAddMarketplaceDialog());
|
|
411
534
|
// Reload marketplaces to get fresh state
|
|
412
535
|
const marketplaces = loadMarketplaces();
|
|
413
|
-
dispatch(
|
|
536
|
+
dispatch(setMarketplaces(marketplaces));
|
|
414
537
|
// Also reload plugins as new marketplace may have plugins
|
|
415
538
|
const plugins = loadAllPlugins();
|
|
416
|
-
dispatch(
|
|
539
|
+
dispatch(setPlugins(plugins));
|
|
417
540
|
// Reset selection to avoid pointing to a different marketplace after re-sort
|
|
418
|
-
dispatch(
|
|
419
|
-
dispatch(
|
|
541
|
+
dispatch(setSelectedIndex(0));
|
|
542
|
+
dispatch(setMessage(`✅ ${result.message}`));
|
|
420
543
|
}
|
|
421
544
|
else {
|
|
422
545
|
// Keep dialog open and show error inline
|
|
423
|
-
dispatch(
|
|
424
|
-
type: 'SET_ADD_MARKETPLACE_ERROR',
|
|
425
|
-
payload: result.error || result.message,
|
|
426
|
-
});
|
|
546
|
+
dispatch(setAddMarketplaceError(result.error || result.message));
|
|
427
547
|
}
|
|
428
548
|
}
|
|
429
549
|
/**
|
|
@@ -431,33 +551,24 @@ export default function App() {
|
|
|
431
551
|
* @param marketplaceId - Marketplace identifier to remove
|
|
432
552
|
*/
|
|
433
553
|
async function handleRemoveMarketplace(marketplaceId) {
|
|
434
|
-
dispatch({
|
|
435
|
-
type: 'START_MARKETPLACE_OPERATION',
|
|
436
|
-
payload: { operation: 'removing', marketplaceId },
|
|
437
|
-
});
|
|
554
|
+
dispatch(startMarketplaceOperation({ operation: 'removing', marketplaceId }));
|
|
438
555
|
const result = await removeMarketplace(marketplaceId);
|
|
439
|
-
dispatch(
|
|
556
|
+
dispatch(endMarketplaceOperation());
|
|
440
557
|
if (result.success) {
|
|
441
558
|
// Reload marketplaces to get fresh state
|
|
442
559
|
const marketplaces = loadMarketplaces();
|
|
443
|
-
dispatch(
|
|
560
|
+
dispatch(setMarketplaces(marketplaces));
|
|
444
561
|
// Also reload plugins as removed marketplace's plugins should be gone
|
|
445
562
|
const plugins = loadAllPlugins();
|
|
446
|
-
dispatch(
|
|
447
|
-
dispatch(
|
|
563
|
+
dispatch(setPlugins(plugins));
|
|
564
|
+
dispatch(setMessage(`✅ ${result.message}`));
|
|
448
565
|
// Reset selection if needed
|
|
449
566
|
if (state.selectedIndex >= marketplaces.length) {
|
|
450
|
-
dispatch(
|
|
451
|
-
type: 'SET_SELECTED_INDEX',
|
|
452
|
-
payload: Math.max(0, marketplaces.length - 1),
|
|
453
|
-
});
|
|
567
|
+
dispatch(setSelectedIndex(Math.max(0, marketplaces.length - 1)));
|
|
454
568
|
}
|
|
455
569
|
}
|
|
456
570
|
else {
|
|
457
|
-
dispatch({
|
|
458
|
-
type: 'SET_MESSAGE',
|
|
459
|
-
payload: `❌ ${result.message}${result.error ? `: ${result.error}` : ''}`,
|
|
460
|
-
});
|
|
571
|
+
dispatch(setMessage(`❌ ${result.message}${result.error ? `: ${result.error}` : ''}`));
|
|
461
572
|
}
|
|
462
573
|
}
|
|
463
574
|
/**
|
|
@@ -465,29 +576,54 @@ export default function App() {
|
|
|
465
576
|
* @param marketplaceId - Optional marketplace identifier. If omitted, updates all.
|
|
466
577
|
*/
|
|
467
578
|
async function handleUpdateMarketplace(marketplaceId) {
|
|
468
|
-
dispatch({
|
|
469
|
-
type: 'START_MARKETPLACE_OPERATION',
|
|
470
|
-
payload: { operation: 'updating', marketplaceId },
|
|
471
|
-
});
|
|
579
|
+
dispatch(startMarketplaceOperation({ operation: 'updating', marketplaceId }));
|
|
472
580
|
const result = await updateMarketplace(marketplaceId);
|
|
473
|
-
dispatch(
|
|
581
|
+
dispatch(endMarketplaceOperation());
|
|
474
582
|
if (result.success) {
|
|
475
583
|
// Reload marketplaces and plugins to get fresh state
|
|
476
584
|
const marketplaces = loadMarketplaces();
|
|
477
|
-
dispatch(
|
|
585
|
+
dispatch(setMarketplaces(marketplaces));
|
|
478
586
|
const plugins = loadAllPlugins();
|
|
479
|
-
dispatch(
|
|
587
|
+
dispatch(setPlugins(plugins));
|
|
480
588
|
// Reset selection to avoid pointing to a different marketplace after re-sort
|
|
481
|
-
dispatch(
|
|
482
|
-
dispatch(
|
|
589
|
+
dispatch(setSelectedIndex(0));
|
|
590
|
+
dispatch(setMessage(`✅ ${result.message}`));
|
|
483
591
|
}
|
|
484
592
|
else {
|
|
485
|
-
dispatch({
|
|
486
|
-
type: 'SET_MESSAGE',
|
|
487
|
-
payload: `❌ ${result.message}${result.error ? `: ${result.error}` : ''}`,
|
|
488
|
-
});
|
|
593
|
+
dispatch(setMessage(`❌ ${result.message}${result.error ? `: ${result.error}` : ''}`));
|
|
489
594
|
}
|
|
490
595
|
}
|
|
596
|
+
/**
|
|
597
|
+
* Handle toggling auto-update for a marketplace
|
|
598
|
+
* @param marketplaceId - ID of the marketplace
|
|
599
|
+
* @param currentValue - Current auto-update state
|
|
600
|
+
*/
|
|
601
|
+
async function handleToggleAutoUpdate(marketplaceId, currentValue) {
|
|
602
|
+
dispatch(startMarketplaceOperation({ operation: 'updating', marketplaceId }));
|
|
603
|
+
const result = await toggleAutoUpdate(marketplaceId, currentValue);
|
|
604
|
+
dispatch(endMarketplaceOperation());
|
|
605
|
+
if (result.success) {
|
|
606
|
+
// Reload marketplaces to get fresh state
|
|
607
|
+
const marketplaces = loadMarketplaces();
|
|
608
|
+
dispatch(setMarketplaces(marketplaces));
|
|
609
|
+
dispatch(setMessage(`✅ ${result.message}`));
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
dispatch(setMessage(`❌ ${result.message}${result.error ? `: ${result.error}` : ''}`));
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Handle browsing plugins for a specific marketplace
|
|
617
|
+
* Switches to Discover tab with marketplace filter applied
|
|
618
|
+
* @param marketplaceId - ID of the marketplace to browse
|
|
619
|
+
*/
|
|
620
|
+
function handleBrowseMarketplacePlugins(marketplaceId) {
|
|
621
|
+
// Switch to Discover tab
|
|
622
|
+
dispatch(setTab('discover'));
|
|
623
|
+
// Set search query to filter by marketplace
|
|
624
|
+
dispatch(setSearchQuery(marketplaceId));
|
|
625
|
+
dispatch(setMessage(`Browsing plugins from ${marketplaceId}`));
|
|
626
|
+
}
|
|
491
627
|
// Keyboard input handling
|
|
492
628
|
useInput((input, key) => {
|
|
493
629
|
// Block all input during operations (plugin or marketplace)
|
|
@@ -497,7 +633,7 @@ export default function App() {
|
|
|
497
633
|
// Handle help overlay
|
|
498
634
|
if (state.showHelp) {
|
|
499
635
|
if (input === 'h' || key.escape) {
|
|
500
|
-
dispatch(
|
|
636
|
+
dispatch(toggleHelp());
|
|
501
637
|
}
|
|
502
638
|
return;
|
|
503
639
|
}
|
|
@@ -508,19 +644,33 @@ export default function App() {
|
|
|
508
644
|
}
|
|
509
645
|
// Toggle help (h key)
|
|
510
646
|
if (input === 'h') {
|
|
511
|
-
dispatch(
|
|
647
|
+
dispatch(toggleHelp());
|
|
512
648
|
return;
|
|
513
649
|
}
|
|
514
650
|
// Handle plugin uninstall confirmation dialog
|
|
515
651
|
if (state.confirmUninstall && state.operationPluginId) {
|
|
516
652
|
if (input === 'y' || input === 'Y' || key.return) {
|
|
517
|
-
dispatch(
|
|
653
|
+
dispatch(hideConfirmUninstall());
|
|
518
654
|
handleUninstall(state.operationPluginId);
|
|
519
655
|
return;
|
|
520
656
|
}
|
|
521
657
|
if (input === 'n' || input === 'N' || key.escape) {
|
|
522
|
-
dispatch(
|
|
523
|
-
dispatch(
|
|
658
|
+
dispatch(hideConfirmUninstall());
|
|
659
|
+
dispatch(setMessage('Uninstall cancelled'));
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
// Handle update all confirmation dialog
|
|
665
|
+
if (state.confirmUpdateAll) {
|
|
666
|
+
if (input === 'y' || input === 'Y' || key.return) {
|
|
667
|
+
dispatch(hideConfirmUpdateAll());
|
|
668
|
+
handleUpdateAllPlugins();
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
if (input === 'n' || input === 'N' || key.escape) {
|
|
672
|
+
dispatch(hideConfirmUpdateAll());
|
|
673
|
+
dispatch(setMessage('Update cancelled'));
|
|
524
674
|
return;
|
|
525
675
|
}
|
|
526
676
|
return;
|
|
@@ -528,13 +678,13 @@ export default function App() {
|
|
|
528
678
|
// Handle marketplace remove confirmation dialog
|
|
529
679
|
if (state.confirmRemoveMarketplace && state.operationMarketplaceId) {
|
|
530
680
|
if (input === 'y' || input === 'Y' || key.return) {
|
|
531
|
-
dispatch(
|
|
681
|
+
dispatch(hideConfirmRemoveMarketplace());
|
|
532
682
|
handleRemoveMarketplace(state.operationMarketplaceId);
|
|
533
683
|
return;
|
|
534
684
|
}
|
|
535
685
|
if (input === 'n' || input === 'N' || key.escape) {
|
|
536
|
-
dispatch(
|
|
537
|
-
dispatch(
|
|
686
|
+
dispatch(hideConfirmRemoveMarketplace());
|
|
687
|
+
dispatch(setMessage('Remove cancelled'));
|
|
538
688
|
return;
|
|
539
689
|
}
|
|
540
690
|
return;
|
|
@@ -548,24 +698,63 @@ export default function App() {
|
|
|
548
698
|
}
|
|
549
699
|
// Cancel on Escape
|
|
550
700
|
if (key.escape) {
|
|
551
|
-
dispatch(
|
|
552
|
-
dispatch(
|
|
701
|
+
dispatch(hideAddMarketplaceDialog());
|
|
702
|
+
dispatch(setMessage('Add marketplace cancelled'));
|
|
553
703
|
return;
|
|
554
704
|
}
|
|
555
705
|
// Backspace
|
|
556
706
|
if (key.backspace || key.delete) {
|
|
557
|
-
dispatch(
|
|
558
|
-
type: 'SET_SEARCH_QUERY',
|
|
559
|
-
payload: state.searchQuery.slice(0, -1),
|
|
560
|
-
});
|
|
707
|
+
dispatch(setSearchQuery(state.searchQuery.slice(0, -1)));
|
|
561
708
|
return;
|
|
562
709
|
}
|
|
563
710
|
// Character input
|
|
564
711
|
if (input && input.length === 1 && !key.ctrl && !key.meta) {
|
|
565
|
-
dispatch(
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
712
|
+
dispatch(setSearchQuery(state.searchQuery + input));
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
// Handle marketplace action menu
|
|
718
|
+
if (state.showMarketplaceActionMenu) {
|
|
719
|
+
const selectedMarketplace = getFilteredMarketplaces(state)[state.selectedIndex];
|
|
720
|
+
if (!selectedMarketplace) {
|
|
721
|
+
dispatch(hideMarketplaceActionMenu());
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
// Up/Down arrow navigation
|
|
725
|
+
if (key.upArrow || (key.ctrl && input === 'p')) {
|
|
726
|
+
dispatch(moveActionMenuSelection('up'));
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
if (key.downArrow || (key.ctrl && input === 'n')) {
|
|
730
|
+
dispatch(moveActionMenuSelection('down'));
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
// Execute action on Enter
|
|
734
|
+
if (key.return) {
|
|
735
|
+
dispatch(hideMarketplaceActionMenu());
|
|
736
|
+
const actionIndex = state.actionMenuSelectedIndex;
|
|
737
|
+
if (actionIndex === 0) {
|
|
738
|
+
// Browse plugins
|
|
739
|
+
handleBrowseMarketplacePlugins(selectedMarketplace.id);
|
|
740
|
+
}
|
|
741
|
+
else if (actionIndex === 1) {
|
|
742
|
+
// Update marketplace
|
|
743
|
+
handleUpdateMarketplace(selectedMarketplace.id);
|
|
744
|
+
}
|
|
745
|
+
else if (actionIndex === 2) {
|
|
746
|
+
// Toggle auto-update
|
|
747
|
+
handleToggleAutoUpdate(selectedMarketplace.id, selectedMarketplace.autoUpdate ?? false);
|
|
748
|
+
}
|
|
749
|
+
else if (actionIndex === 3) {
|
|
750
|
+
// Remove marketplace (show confirmation)
|
|
751
|
+
dispatch(showConfirmRemoveMarketplace(selectedMarketplace.id));
|
|
752
|
+
}
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
// Close menu on Escape
|
|
756
|
+
if (key.escape) {
|
|
757
|
+
dispatch(hideMarketplaceActionMenu());
|
|
569
758
|
return;
|
|
570
759
|
}
|
|
571
760
|
return;
|
|
@@ -574,7 +763,7 @@ export default function App() {
|
|
|
574
763
|
if (state.focusZone === 'search') {
|
|
575
764
|
// Up arrow: move focus to tabbar
|
|
576
765
|
if (key.upArrow || (key.ctrl && input === 'p')) {
|
|
577
|
-
dispatch(
|
|
766
|
+
dispatch(setFocusZone('tabbar'));
|
|
578
767
|
return;
|
|
579
768
|
}
|
|
580
769
|
// Down arrow, Enter, or Escape: move focus to list
|
|
@@ -582,21 +771,15 @@ export default function App() {
|
|
|
582
771
|
key.return ||
|
|
583
772
|
key.downArrow ||
|
|
584
773
|
(key.ctrl && input === 'n')) {
|
|
585
|
-
dispatch(
|
|
774
|
+
dispatch(setFocusZone('list'));
|
|
586
775
|
return;
|
|
587
776
|
}
|
|
588
777
|
if (key.backspace || key.delete) {
|
|
589
|
-
dispatch(
|
|
590
|
-
type: 'SET_SEARCH_QUERY',
|
|
591
|
-
payload: state.searchQuery.slice(0, -1),
|
|
592
|
-
});
|
|
778
|
+
dispatch(setSearchQuery(state.searchQuery.slice(0, -1)));
|
|
593
779
|
return;
|
|
594
780
|
}
|
|
595
781
|
if (input && input.length === 1 && !key.ctrl && !key.meta) {
|
|
596
|
-
dispatch(
|
|
597
|
-
type: 'SET_SEARCH_QUERY',
|
|
598
|
-
payload: state.searchQuery + input,
|
|
599
|
-
});
|
|
782
|
+
dispatch(setSearchQuery(state.searchQuery + input));
|
|
600
783
|
return;
|
|
601
784
|
}
|
|
602
785
|
return;
|
|
@@ -606,27 +789,54 @@ export default function App() {
|
|
|
606
789
|
// Down arrow: move to search (or list if no search)
|
|
607
790
|
if (key.downArrow || (key.ctrl && input === 'n')) {
|
|
608
791
|
const zones = getAvailableZones(state.activeTab);
|
|
609
|
-
dispatch(
|
|
610
|
-
type: 'SET_FOCUS_ZONE',
|
|
611
|
-
payload: zones.includes('search') ? 'search' : 'list',
|
|
612
|
-
});
|
|
792
|
+
dispatch(setFocusZone(zones.includes('search') ? 'search' : 'list'));
|
|
613
793
|
return;
|
|
614
794
|
}
|
|
615
795
|
// Left/Right arrows and Ctrl+B/F: tab switching (only in tabbar)
|
|
616
796
|
// Keep focus on tabbar after navigation
|
|
617
797
|
if (key.leftArrow || (key.ctrl && input === 'b')) {
|
|
618
|
-
dispatch(
|
|
619
|
-
dispatch(
|
|
798
|
+
dispatch(prevTab());
|
|
799
|
+
dispatch(setFocusZone('tabbar'));
|
|
620
800
|
return;
|
|
621
801
|
}
|
|
622
802
|
if (key.rightArrow || (key.ctrl && input === 'f')) {
|
|
623
|
-
dispatch(
|
|
624
|
-
dispatch(
|
|
803
|
+
dispatch(nextTab());
|
|
804
|
+
dispatch(setFocusZone('tabbar'));
|
|
625
805
|
return;
|
|
626
806
|
}
|
|
627
807
|
// Tab key: next tab (resets focus to list)
|
|
628
808
|
if (key.tab) {
|
|
629
|
-
dispatch(
|
|
809
|
+
dispatch(nextTab());
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
// Component focus zone navigation
|
|
815
|
+
if (state.focusZone === 'components') {
|
|
816
|
+
// Get current plugin and its components
|
|
817
|
+
const items = getItemsForTab(state);
|
|
818
|
+
const selectedPlugin = items[state.selectedIndex];
|
|
819
|
+
const components = selectedPlugin
|
|
820
|
+
? flattenComponents(selectedPlugin.componentsDetailed)
|
|
821
|
+
: [];
|
|
822
|
+
// Left arrow: exit component mode, go back to list
|
|
823
|
+
if (key.leftArrow || (key.ctrl && input === 'b') || key.escape) {
|
|
824
|
+
dispatch(exitComponentMode());
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
// Up/Down: navigate components
|
|
828
|
+
if (key.upArrow || (key.ctrl && input === 'p')) {
|
|
829
|
+
dispatch(moveComponentSelection({
|
|
830
|
+
direction: 'up',
|
|
831
|
+
maxIndex: Math.max(0, components.length - 1),
|
|
832
|
+
}));
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
if (key.downArrow || (key.ctrl && input === 'n')) {
|
|
836
|
+
dispatch(moveComponentSelection({
|
|
837
|
+
direction: 'down',
|
|
838
|
+
maxIndex: Math.max(0, components.length - 1),
|
|
839
|
+
}));
|
|
630
840
|
return;
|
|
631
841
|
}
|
|
632
842
|
return;
|
|
@@ -637,24 +847,43 @@ export default function App() {
|
|
|
637
847
|
if (state.selectedIndex === 0) {
|
|
638
848
|
// At top of list: move focus to search (or tabbar if no search)
|
|
639
849
|
const zones = getAvailableZones(state.activeTab);
|
|
640
|
-
dispatch(
|
|
641
|
-
type: 'SET_FOCUS_ZONE',
|
|
642
|
-
payload: zones.includes('search') ? 'search' : 'tabbar',
|
|
643
|
-
});
|
|
850
|
+
dispatch(setFocusZone(zones.includes('search') ? 'search' : 'tabbar'));
|
|
644
851
|
}
|
|
645
852
|
else {
|
|
646
|
-
dispatch({
|
|
853
|
+
dispatch(moveSelection({
|
|
854
|
+
direction: 'up',
|
|
855
|
+
maxIndex: Math.max(0, getItemsForTab(state).length - 1),
|
|
856
|
+
}));
|
|
647
857
|
}
|
|
648
858
|
return;
|
|
649
859
|
}
|
|
650
860
|
// Down arrow: move down in list
|
|
651
861
|
if (key.downArrow || (key.ctrl && input === 'n')) {
|
|
652
|
-
dispatch({
|
|
862
|
+
dispatch(moveSelection({
|
|
863
|
+
direction: 'down',
|
|
864
|
+
maxIndex: Math.max(0, getItemsForTab(state).length - 1),
|
|
865
|
+
}));
|
|
653
866
|
return;
|
|
654
867
|
}
|
|
868
|
+
// Right arrow: enter component mode (on plugin tabs)
|
|
869
|
+
if ((key.rightArrow || (key.ctrl && input === 'f')) &&
|
|
870
|
+
state.focusZone === 'list' &&
|
|
871
|
+
(state.activeTab === 'enabled' ||
|
|
872
|
+
state.activeTab === 'installed' ||
|
|
873
|
+
state.activeTab === 'discover')) {
|
|
874
|
+
const items = getItemsForTab(state);
|
|
875
|
+
const selectedPlugin = items[state.selectedIndex];
|
|
876
|
+
if (selectedPlugin?.componentsDetailed) {
|
|
877
|
+
const components = flattenComponents(selectedPlugin.componentsDetailed);
|
|
878
|
+
if (components.length > 0) {
|
|
879
|
+
dispatch(enterComponentMode());
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
655
884
|
// Tab key: next tab (from list zone)
|
|
656
885
|
if (key.tab) {
|
|
657
|
-
dispatch(
|
|
886
|
+
dispatch(nextTab());
|
|
658
887
|
return;
|
|
659
888
|
}
|
|
660
889
|
// Enter search mode (/ key on supported tabs)
|
|
@@ -665,7 +894,7 @@ export default function App() {
|
|
|
665
894
|
'marketplaces',
|
|
666
895
|
];
|
|
667
896
|
if (input === '/' && searchEnabledTabs.includes(state.activeTab)) {
|
|
668
|
-
dispatch(
|
|
897
|
+
dispatch(setFocusZone('search'));
|
|
669
898
|
return;
|
|
670
899
|
}
|
|
671
900
|
// Enter key: Install (non-installed) or Toggle (installed)
|
|
@@ -684,22 +913,13 @@ export default function App() {
|
|
|
684
913
|
// Toggle installed plugin
|
|
685
914
|
try {
|
|
686
915
|
const newState = togglePlugin(selectedPlugin.id);
|
|
687
|
-
dispatch(
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
dispatch({
|
|
692
|
-
type: 'SET_MESSAGE',
|
|
693
|
-
payload: newState
|
|
694
|
-
? `✅ ${selectedPlugin.name} enabled`
|
|
695
|
-
: `❌ ${selectedPlugin.name} disabled`,
|
|
696
|
-
});
|
|
916
|
+
dispatch(togglePluginEnabled(selectedPlugin.id));
|
|
917
|
+
dispatch(setMessage(newState
|
|
918
|
+
? `✅ ${selectedPlugin.name} enabled`
|
|
919
|
+
: `❌ ${selectedPlugin.name} disabled`));
|
|
697
920
|
}
|
|
698
921
|
catch (error) {
|
|
699
|
-
dispatch({
|
|
700
|
-
type: 'SET_MESSAGE',
|
|
701
|
-
payload: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
702
|
-
});
|
|
922
|
+
dispatch(setMessage(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
703
923
|
}
|
|
704
924
|
}
|
|
705
925
|
}
|
|
@@ -715,22 +935,13 @@ export default function App() {
|
|
|
715
935
|
if (selectedPlugin && selectedPlugin.isInstalled) {
|
|
716
936
|
try {
|
|
717
937
|
const newState = togglePlugin(selectedPlugin.id);
|
|
718
|
-
dispatch(
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
dispatch({
|
|
723
|
-
type: 'SET_MESSAGE',
|
|
724
|
-
payload: newState
|
|
725
|
-
? `✅ ${selectedPlugin.name} enabled`
|
|
726
|
-
: `❌ ${selectedPlugin.name} disabled`,
|
|
727
|
-
});
|
|
938
|
+
dispatch(togglePluginEnabled(selectedPlugin.id));
|
|
939
|
+
dispatch(setMessage(newState
|
|
940
|
+
? `✅ ${selectedPlugin.name} enabled`
|
|
941
|
+
: `❌ ${selectedPlugin.name} disabled`));
|
|
728
942
|
}
|
|
729
943
|
catch (error) {
|
|
730
|
-
dispatch({
|
|
731
|
-
type: 'SET_MESSAGE',
|
|
732
|
-
payload: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
733
|
-
});
|
|
944
|
+
dispatch(setMessage(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
734
945
|
}
|
|
735
946
|
}
|
|
736
947
|
return;
|
|
@@ -742,43 +953,42 @@ export default function App() {
|
|
|
742
953
|
name: 'date',
|
|
743
954
|
date: 'installs',
|
|
744
955
|
};
|
|
745
|
-
dispatch({
|
|
746
|
-
type: 'SET_SORT',
|
|
747
|
-
payload: { by: nextSort[state.sortBy], order: state.sortOrder },
|
|
748
|
-
});
|
|
956
|
+
dispatch(setSort({ by: nextSort[state.sortBy], order: state.sortOrder }));
|
|
749
957
|
return;
|
|
750
958
|
}
|
|
751
959
|
// Toggle sort order (S key)
|
|
752
960
|
if (input === 'S' && state.activeTab === 'discover') {
|
|
753
|
-
dispatch({
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
order: state.sortOrder === 'asc' ? 'desc' : 'asc',
|
|
758
|
-
},
|
|
759
|
-
});
|
|
961
|
+
dispatch(setSort({
|
|
962
|
+
by: state.sortBy,
|
|
963
|
+
order: state.sortOrder === 'asc' ? 'desc' : 'asc',
|
|
964
|
+
}));
|
|
760
965
|
return;
|
|
761
966
|
}
|
|
762
967
|
// Clear search (Escape)
|
|
763
968
|
if (key.escape && state.searchQuery) {
|
|
764
|
-
dispatch(
|
|
969
|
+
dispatch(setSearchQuery(''));
|
|
765
970
|
return;
|
|
766
971
|
}
|
|
767
972
|
// Marketplace-specific key bindings
|
|
768
973
|
if (state.activeTab === 'marketplaces' && state.focusZone === 'list') {
|
|
974
|
+
// Open action menu (Enter or m key)
|
|
975
|
+
if (key.return || input === 'm') {
|
|
976
|
+
const selectedMarketplace = filteredMarketplaces[state.selectedIndex];
|
|
977
|
+
if (selectedMarketplace) {
|
|
978
|
+
dispatch(showMarketplaceActionMenuAction());
|
|
979
|
+
}
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
769
982
|
// Add marketplace (a key)
|
|
770
983
|
if (input === 'a') {
|
|
771
|
-
dispatch(
|
|
984
|
+
dispatch(showAddMarketplaceDialog());
|
|
772
985
|
return;
|
|
773
986
|
}
|
|
774
987
|
// Remove marketplace (d key or Backspace)
|
|
775
988
|
if (input === 'd' || key.backspace || key.delete) {
|
|
776
989
|
const selectedMarketplace = filteredMarketplaces[state.selectedIndex];
|
|
777
990
|
if (selectedMarketplace) {
|
|
778
|
-
dispatch(
|
|
779
|
-
type: 'SHOW_CONFIRM_REMOVE_MARKETPLACE',
|
|
780
|
-
payload: selectedMarketplace.id,
|
|
781
|
-
});
|
|
991
|
+
dispatch(showConfirmRemoveMarketplace(selectedMarketplace.id));
|
|
782
992
|
}
|
|
783
993
|
return;
|
|
784
994
|
}
|
|
@@ -806,10 +1016,19 @@ export default function App() {
|
|
|
806
1016
|
handleInstall(selectedPlugin.id);
|
|
807
1017
|
}
|
|
808
1018
|
else if (selectedPlugin?.isInstalled) {
|
|
809
|
-
dispatch(
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
1019
|
+
dispatch(setMessage('⚠️ Plugin is already installed'));
|
|
1020
|
+
}
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
// Update all plugins (U key = shift+u) - only on enabled/installed tabs
|
|
1024
|
+
if (input === 'U' &&
|
|
1025
|
+
(state.activeTab === 'enabled' || state.activeTab === 'installed')) {
|
|
1026
|
+
const installed = state.plugins.filter((p) => p.isInstalled);
|
|
1027
|
+
if (installed.length === 0) {
|
|
1028
|
+
dispatch(setMessage('⚠️ No installed plugins to update'));
|
|
1029
|
+
}
|
|
1030
|
+
else {
|
|
1031
|
+
dispatch(showConfirmUpdateAll());
|
|
813
1032
|
}
|
|
814
1033
|
return;
|
|
815
1034
|
}
|
|
@@ -821,23 +1040,15 @@ export default function App() {
|
|
|
821
1040
|
const items = getItemsForTab(state);
|
|
822
1041
|
const selectedPlugin = items[state.selectedIndex];
|
|
823
1042
|
if (selectedPlugin && selectedPlugin.isInstalled) {
|
|
824
|
-
dispatch(
|
|
1043
|
+
dispatch(showConfirmUninstall(selectedPlugin.id));
|
|
825
1044
|
}
|
|
826
1045
|
else if (selectedPlugin && !selectedPlugin.isInstalled) {
|
|
827
|
-
dispatch(
|
|
1046
|
+
dispatch(setMessage('⚠️ Plugin is not installed'));
|
|
828
1047
|
}
|
|
829
1048
|
return;
|
|
830
1049
|
}
|
|
831
1050
|
});
|
|
832
|
-
//
|
|
833
|
-
if (state.loading) {
|
|
834
|
-
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(Text, { children: "Loading plugins..." }) }));
|
|
835
|
-
}
|
|
836
|
-
// Error state
|
|
837
|
-
if (state.error) {
|
|
838
|
-
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" })] }));
|
|
839
|
-
}
|
|
840
|
-
// Get filtered data for current tab
|
|
1051
|
+
// Get filtered data for current tab (must be before early returns for useMemo dependency)
|
|
841
1052
|
const filteredPlugins = getFilteredPlugins(state);
|
|
842
1053
|
// Apply search filter to enabled plugins
|
|
843
1054
|
const enabledPluginsBase = state.plugins.filter((p) => p.isInstalled && p.isEnabled);
|
|
@@ -853,56 +1064,127 @@ export default function App() {
|
|
|
853
1064
|
const filteredMarketplaces = state.searchQuery
|
|
854
1065
|
? searchMarketplaces(state.searchQuery, state.marketplaces)
|
|
855
1066
|
: state.marketplaces;
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1067
|
+
// Get selected component detail for component focus mode (memoized)
|
|
1068
|
+
// Must be called before conditional returns to maintain hooks order
|
|
1069
|
+
const selectedComponentDetail = useMemo(() => {
|
|
1070
|
+
if (state.focusZone !== 'components')
|
|
1071
|
+
return null;
|
|
1072
|
+
// Get the appropriate plugins for current tab
|
|
1073
|
+
const currentPlugins = match(state.activeTab)
|
|
1074
|
+
.with('enabled', () => enabledPlugins)
|
|
1075
|
+
.with('installed', () => installedPlugins)
|
|
1076
|
+
.with('discover', () => filteredPlugins)
|
|
1077
|
+
.otherwise(() => []);
|
|
1078
|
+
const selectedPlugin = currentPlugins[state.selectedIndex];
|
|
1079
|
+
if (!selectedPlugin?.componentsDetailed)
|
|
1080
|
+
return null;
|
|
1081
|
+
const components = flattenComponents(selectedPlugin.componentsDetailed);
|
|
1082
|
+
const selectedComponent = components[state.selectedComponentIndex];
|
|
1083
|
+
if (!selectedComponent)
|
|
1084
|
+
return null;
|
|
1085
|
+
// Get install path for the plugin (cross-platform)
|
|
1086
|
+
const installPath = selectedPlugin.isInstalled
|
|
1087
|
+
? path.join(os.homedir(), '.claude', 'plugins', 'cache', selectedPlugin.marketplace ?? '', selectedPlugin.name, selectedPlugin.version ?? '')
|
|
1088
|
+
: null;
|
|
1089
|
+
if (!installPath)
|
|
1090
|
+
return null;
|
|
1091
|
+
// Fetch detailed info based on component type
|
|
1092
|
+
if (selectedComponent.info.type === 'skill') {
|
|
1093
|
+
return (getSkillDetailedInfo(installPath, selectedComponent.info.name) ?? null);
|
|
1094
|
+
}
|
|
1095
|
+
else if (selectedComponent.info.type === 'command' ||
|
|
1096
|
+
selectedComponent.info.type === 'agent') {
|
|
1097
|
+
return (getMarkdownComponentDetailedInfo(installPath, selectedComponent.info.name, selectedComponent.info.type) ?? null);
|
|
1098
|
+
}
|
|
1099
|
+
// For other types (mcp, lsp, hook), return basic info
|
|
1100
|
+
return {
|
|
1101
|
+
name: selectedComponent.info.name,
|
|
1102
|
+
type: selectedComponent.info.type,
|
|
1103
|
+
description: selectedComponent.info.description,
|
|
1104
|
+
};
|
|
1105
|
+
}, [
|
|
1106
|
+
state.focusZone,
|
|
1107
|
+
state.activeTab,
|
|
1108
|
+
state.selectedIndex,
|
|
1109
|
+
state.selectedComponentIndex,
|
|
1110
|
+
enabledPlugins,
|
|
1111
|
+
installedPlugins,
|
|
1112
|
+
filteredPlugins,
|
|
1113
|
+
]);
|
|
1114
|
+
const componentFocusMode = state.focusZone === 'components';
|
|
1115
|
+
// Loading state
|
|
1116
|
+
if (state.loading) {
|
|
1117
|
+
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(Text, { children: "Loading plugins..." }) }));
|
|
1118
|
+
}
|
|
1119
|
+
// Error state
|
|
1120
|
+
if (state.error) {
|
|
1121
|
+
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" })] }));
|
|
1122
|
+
}
|
|
1123
|
+
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' }), _jsx(Box, { flexGrow: 1, flexDirection: "column", children: match(state.activeTab)
|
|
1124
|
+
.with('enabled', () => (_jsx(EnabledTab, { plugins: enabledPlugins, selectedIndex: state.selectedIndex, searchQuery: state.searchQuery, focusZone: state.focusZone, componentFocusMode: componentFocusMode, selectedComponentIndex: state.selectedComponentIndex, selectedComponentDetail: selectedComponentDetail }, "enabled")))
|
|
1125
|
+
.with('installed', () => (_jsx(InstalledTab, { plugins: installedPlugins, selectedIndex: state.selectedIndex, searchQuery: state.searchQuery, focusZone: state.focusZone, componentFocusMode: componentFocusMode, selectedComponentIndex: state.selectedComponentIndex, selectedComponentDetail: selectedComponentDetail }, "installed")))
|
|
1126
|
+
.with('discover', () => (_jsx(DiscoverTab, { plugins: filteredPlugins, selectedIndex: state.selectedIndex, searchQuery: state.searchQuery, sortBy: state.sortBy, sortOrder: state.sortOrder, focusZone: state.focusZone, componentFocusMode: componentFocusMode, selectedComponentIndex: state.selectedComponentIndex, selectedComponentDetail: selectedComponentDetail }, "discover")))
|
|
1127
|
+
.with('marketplaces', () => (_jsx(MarketplacesTab, { marketplaces: filteredMarketplaces, selectedIndex: state.selectedIndex, searchQuery: state.searchQuery, focusZone: state.focusZone, showActionMenu: state.showMarketplaceActionMenu, actionMenuSelectedIndex: state.actionMenuSelectedIndex }, "marketplaces")))
|
|
1128
|
+
.with('errors', () => (_jsx(ErrorsTab, { errors: state.errors, selectedIndex: state.selectedIndex }, "errors")))
|
|
1129
|
+
.exhaustive() }), state.confirmUninstall && state.operationPluginId && (_jsx(ConfirmDialog, { message: `Uninstall ${state.operationPluginId}?` })), state.confirmUpdateAll && (_jsx(ConfirmDialog, { message: `Update all ${state.plugins.filter((p) => p.isInstalled).length} plugins?` })), 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 || state.updateProgress) && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: state.updateProgress
|
|
1130
|
+
? `Updating (${state.updateProgress.current}/${state.updateProgress.total}): ${state.updateProgress.pluginId}...`
|
|
1131
|
+
: state.message }) })), _jsx(KeyHints, { focusZone: state.focusZone, extraHints: match([state.focusZone, state.activeTab])
|
|
1132
|
+
.with(['search', P._], () => undefined)
|
|
1133
|
+
.with(['tabbar', P._], () => undefined)
|
|
1134
|
+
.with(['list', 'enabled'], () => {
|
|
1135
|
+
const hints = [
|
|
1136
|
+
{ key: '/', action: 'search' },
|
|
1137
|
+
{ key: 'i', action: 'install' },
|
|
1138
|
+
{ key: 'u', action: 'uninstall' },
|
|
1139
|
+
{ key: 'U', action: 'update all' },
|
|
1140
|
+
];
|
|
1141
|
+
const selectedPlugin = enabledPlugins[state.selectedIndex];
|
|
1142
|
+
if (selectedPlugin) {
|
|
1143
|
+
hints.push({
|
|
1144
|
+
key: 'Enter',
|
|
1145
|
+
action: selectedPlugin.isInstalled ? 'toggle' : 'install',
|
|
1146
|
+
});
|
|
864
1147
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
:
|
|
879
|
-
|
|
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;
|
|
1148
|
+
return hints;
|
|
1149
|
+
})
|
|
1150
|
+
.with(['list', 'installed'], () => {
|
|
1151
|
+
const hints = [
|
|
1152
|
+
{ key: '/', action: 'search' },
|
|
1153
|
+
{ key: 'i', action: 'install' },
|
|
1154
|
+
{ key: 'u', action: 'uninstall' },
|
|
1155
|
+
{ key: 'U', action: 'update all' },
|
|
1156
|
+
];
|
|
1157
|
+
const selectedPlugin = installedPlugins[state.selectedIndex];
|
|
1158
|
+
if (selectedPlugin) {
|
|
1159
|
+
hints.push({
|
|
1160
|
+
key: 'Enter',
|
|
1161
|
+
action: selectedPlugin.isInstalled ? 'toggle' : 'install',
|
|
1162
|
+
});
|
|
896
1163
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1164
|
+
return hints;
|
|
1165
|
+
})
|
|
1166
|
+
.with(['list', 'discover'], () => {
|
|
1167
|
+
const hints = [
|
|
1168
|
+
{ key: '/', action: 'search' },
|
|
1169
|
+
{ key: 'i', action: 'install' },
|
|
1170
|
+
{ key: 'u', action: 'uninstall' },
|
|
1171
|
+
];
|
|
1172
|
+
const selectedPlugin = filteredPlugins[state.selectedIndex];
|
|
1173
|
+
if (selectedPlugin) {
|
|
1174
|
+
hints.push({
|
|
1175
|
+
key: 'Enter',
|
|
1176
|
+
action: selectedPlugin.isInstalled ? 'toggle' : 'install',
|
|
1177
|
+
});
|
|
905
1178
|
}
|
|
906
|
-
|
|
907
|
-
|
|
1179
|
+
hints.push({ key: 's', action: 'sort' });
|
|
1180
|
+
return hints;
|
|
1181
|
+
})
|
|
1182
|
+
.with(['list', 'marketplaces'], () => [
|
|
1183
|
+
{ key: '/', action: 'search' },
|
|
1184
|
+
{ key: 'a', action: 'add' },
|
|
1185
|
+
{ key: 'd', action: 'remove' },
|
|
1186
|
+
{ key: 'u', action: 'update' },
|
|
1187
|
+
])
|
|
1188
|
+
.with(['list', 'errors'], () => undefined)
|
|
1189
|
+
.otherwise(() => undefined) })] }));
|
|
908
1190
|
}
|