@laststance/claude-plugin-dashboard 0.3.0 → 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 +1 -0
- package/dist/app.js +346 -211
- package/dist/cli.js +3 -1
- 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 +36 -2
- package/dist/components/ComponentList.js +105 -11
- package/dist/components/HelpOverlay.js +1 -0
- package/dist/components/KeyHints.d.ts +1 -0
- package/dist/components/KeyHints.js +8 -1
- package/dist/components/PluginDetail.d.ts +16 -3
- package/dist/components/PluginDetail.js +29 -3
- package/dist/services/componentService.d.ts +10 -42
- package/dist/services/componentService.js +19 -412
- 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/pluginActionsService.d.ts +31 -2
- package/dist/services/pluginActionsService.js +65 -6
- 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 +2 -2
- package/dist/tabs/ErrorsTab.js +1 -1
- package/dist/tabs/InstalledTab.d.ts +8 -2
- package/dist/tabs/InstalledTab.js +2 -2
- package/dist/types/index.d.ts +47 -4
- package/package.json +7 -2
package/dist/app.js
CHANGED
|
@@ -3,7 +3,9 @@ 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';
|
|
8
10
|
import { match, P } from 'ts-pattern';
|
|
9
11
|
import TabBar, { getNextTab } from './components/TabBar.js';
|
|
@@ -14,12 +16,21 @@ import InstalledTab from './tabs/InstalledTab.js';
|
|
|
14
16
|
import MarketplacesTab from './tabs/MarketplacesTab.js';
|
|
15
17
|
import ErrorsTab from './tabs/ErrorsTab.js';
|
|
16
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';
|
|
17
21
|
import { togglePlugin } from './services/settingsService.js';
|
|
18
|
-
import { installPlugin, uninstallPlugin, } from './services/pluginActionsService.js';
|
|
22
|
+
import { installPlugin, uninstallPlugin, updateAllPlugins, } from './services/pluginActionsService.js';
|
|
19
23
|
import { addMarketplace, removeMarketplace, updateMarketplace, toggleAutoUpdate, } from './services/marketplaceActionsService.js';
|
|
20
24
|
import AddMarketplaceDialog from './components/AddMarketplaceDialog.js';
|
|
21
25
|
import ConfirmDialog from './components/ConfirmDialog.js';
|
|
22
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';
|
|
23
34
|
import packageJson from '../package.json' with { type: 'json' };
|
|
24
35
|
/**
|
|
25
36
|
* Initial application state
|
|
@@ -40,6 +51,8 @@ export const initialState = {
|
|
|
40
51
|
operation: 'idle',
|
|
41
52
|
operationPluginId: null,
|
|
42
53
|
confirmUninstall: false,
|
|
54
|
+
confirmUpdateAll: false,
|
|
55
|
+
updateProgress: null,
|
|
43
56
|
showHelp: false,
|
|
44
57
|
marketplaceOperation: 'idle',
|
|
45
58
|
operationMarketplaceId: null,
|
|
@@ -48,6 +61,7 @@ export const initialState = {
|
|
|
48
61
|
addMarketplaceError: null,
|
|
49
62
|
showMarketplaceActionMenu: false,
|
|
50
63
|
actionMenuSelectedIndex: 0,
|
|
64
|
+
selectedComponentIndex: 0,
|
|
51
65
|
};
|
|
52
66
|
/**
|
|
53
67
|
* Get available focus zones for the current tab
|
|
@@ -215,7 +229,9 @@ export function appReducer(state, action) {
|
|
|
215
229
|
operationPluginId: action.payload.pluginId,
|
|
216
230
|
message: action.payload.operation === 'installing'
|
|
217
231
|
? `Installing ${action.payload.pluginId}...`
|
|
218
|
-
:
|
|
232
|
+
: action.payload.operation === 'uninstalling'
|
|
233
|
+
? `Uninstalling ${action.payload.pluginId}...`
|
|
234
|
+
: `Updating plugins...`,
|
|
219
235
|
};
|
|
220
236
|
case 'END_OPERATION':
|
|
221
237
|
return {
|
|
@@ -311,6 +327,32 @@ export function appReducer(state, action) {
|
|
|
311
327
|
actionMenuSelectedIndex: newIndex,
|
|
312
328
|
};
|
|
313
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
|
+
};
|
|
314
356
|
default:
|
|
315
357
|
return state;
|
|
316
358
|
}
|
|
@@ -373,66 +415,110 @@ export function getFilteredMarketplaces(state) {
|
|
|
373
415
|
*/
|
|
374
416
|
export default function App() {
|
|
375
417
|
const { exit } = useApp();
|
|
376
|
-
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
|
+
};
|
|
377
452
|
// Load data on mount
|
|
378
453
|
useEffect(() => {
|
|
379
454
|
try {
|
|
380
455
|
const plugins = loadAllPlugins();
|
|
381
456
|
const marketplaces = loadMarketplaces();
|
|
382
|
-
dispatch(
|
|
383
|
-
dispatch(
|
|
457
|
+
dispatch(setPlugins(plugins));
|
|
458
|
+
dispatch(setMarketplaces(marketplaces));
|
|
384
459
|
}
|
|
385
460
|
catch (error) {
|
|
386
|
-
dispatch(
|
|
387
|
-
type: 'SET_ERROR',
|
|
388
|
-
payload: error instanceof Error ? error.message : 'Failed to load data',
|
|
389
|
-
});
|
|
461
|
+
dispatch(setError(error instanceof Error ? error.message : 'Failed to load data'));
|
|
390
462
|
}
|
|
391
463
|
}, []);
|
|
392
464
|
/**
|
|
393
465
|
* Handle plugin installation
|
|
394
466
|
*/
|
|
395
467
|
async function handleInstall(pluginId) {
|
|
396
|
-
dispatch({
|
|
397
|
-
type: 'START_OPERATION',
|
|
398
|
-
payload: { operation: 'installing', pluginId },
|
|
399
|
-
});
|
|
468
|
+
dispatch(startOperation({ operation: 'installing', pluginId }));
|
|
400
469
|
const result = await installPlugin(pluginId);
|
|
401
|
-
dispatch(
|
|
470
|
+
dispatch(endOperation());
|
|
402
471
|
if (result.success) {
|
|
403
472
|
// Reload plugins to get fresh state
|
|
404
473
|
const plugins = loadAllPlugins();
|
|
405
|
-
dispatch(
|
|
406
|
-
dispatch(
|
|
474
|
+
dispatch(setPlugins(plugins));
|
|
475
|
+
dispatch(setMessage(`✅ ${result.message}`));
|
|
407
476
|
}
|
|
408
477
|
else {
|
|
409
|
-
dispatch({
|
|
410
|
-
type: 'SET_MESSAGE',
|
|
411
|
-
payload: `❌ ${result.message}${result.error ? `: ${result.error}` : ''}`,
|
|
412
|
-
});
|
|
478
|
+
dispatch(setMessage(`❌ ${result.message}${result.error ? `: ${result.error}` : ''}`));
|
|
413
479
|
}
|
|
414
480
|
}
|
|
415
481
|
/**
|
|
416
482
|
* Handle plugin uninstallation
|
|
417
483
|
*/
|
|
418
484
|
async function handleUninstall(pluginId) {
|
|
419
|
-
dispatch({
|
|
420
|
-
type: 'START_OPERATION',
|
|
421
|
-
payload: { operation: 'uninstalling', pluginId },
|
|
422
|
-
});
|
|
485
|
+
dispatch(startOperation({ operation: 'uninstalling', pluginId }));
|
|
423
486
|
const result = await uninstallPlugin(pluginId);
|
|
424
|
-
dispatch(
|
|
487
|
+
dispatch(endOperation());
|
|
425
488
|
if (result.success) {
|
|
426
489
|
// Reload plugins to get fresh state
|
|
427
490
|
const plugins = loadAllPlugins();
|
|
428
|
-
dispatch(
|
|
429
|
-
dispatch(
|
|
491
|
+
dispatch(setPlugins(plugins));
|
|
492
|
+
dispatch(setMessage(`✅ ${result.message}`));
|
|
430
493
|
}
|
|
431
494
|
else {
|
|
432
|
-
dispatch({
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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)`));
|
|
436
522
|
}
|
|
437
523
|
}
|
|
438
524
|
/**
|
|
@@ -440,30 +526,24 @@ export default function App() {
|
|
|
440
526
|
* @param source - Marketplace source (e.g., "owner/repo", URL, or local path)
|
|
441
527
|
*/
|
|
442
528
|
async function handleAddMarketplace(source) {
|
|
443
|
-
dispatch({
|
|
444
|
-
type: 'START_MARKETPLACE_OPERATION',
|
|
445
|
-
payload: { operation: 'adding' },
|
|
446
|
-
});
|
|
529
|
+
dispatch(startMarketplaceOperation({ operation: 'adding' }));
|
|
447
530
|
const result = await addMarketplace(source);
|
|
448
|
-
dispatch(
|
|
531
|
+
dispatch(endMarketplaceOperation());
|
|
449
532
|
if (result.success) {
|
|
450
|
-
dispatch(
|
|
533
|
+
dispatch(hideAddMarketplaceDialog());
|
|
451
534
|
// Reload marketplaces to get fresh state
|
|
452
535
|
const marketplaces = loadMarketplaces();
|
|
453
|
-
dispatch(
|
|
536
|
+
dispatch(setMarketplaces(marketplaces));
|
|
454
537
|
// Also reload plugins as new marketplace may have plugins
|
|
455
538
|
const plugins = loadAllPlugins();
|
|
456
|
-
dispatch(
|
|
539
|
+
dispatch(setPlugins(plugins));
|
|
457
540
|
// Reset selection to avoid pointing to a different marketplace after re-sort
|
|
458
|
-
dispatch(
|
|
459
|
-
dispatch(
|
|
541
|
+
dispatch(setSelectedIndex(0));
|
|
542
|
+
dispatch(setMessage(`✅ ${result.message}`));
|
|
460
543
|
}
|
|
461
544
|
else {
|
|
462
545
|
// Keep dialog open and show error inline
|
|
463
|
-
dispatch(
|
|
464
|
-
type: 'SET_ADD_MARKETPLACE_ERROR',
|
|
465
|
-
payload: result.error || result.message,
|
|
466
|
-
});
|
|
546
|
+
dispatch(setAddMarketplaceError(result.error || result.message));
|
|
467
547
|
}
|
|
468
548
|
}
|
|
469
549
|
/**
|
|
@@ -471,33 +551,24 @@ export default function App() {
|
|
|
471
551
|
* @param marketplaceId - Marketplace identifier to remove
|
|
472
552
|
*/
|
|
473
553
|
async function handleRemoveMarketplace(marketplaceId) {
|
|
474
|
-
dispatch({
|
|
475
|
-
type: 'START_MARKETPLACE_OPERATION',
|
|
476
|
-
payload: { operation: 'removing', marketplaceId },
|
|
477
|
-
});
|
|
554
|
+
dispatch(startMarketplaceOperation({ operation: 'removing', marketplaceId }));
|
|
478
555
|
const result = await removeMarketplace(marketplaceId);
|
|
479
|
-
dispatch(
|
|
556
|
+
dispatch(endMarketplaceOperation());
|
|
480
557
|
if (result.success) {
|
|
481
558
|
// Reload marketplaces to get fresh state
|
|
482
559
|
const marketplaces = loadMarketplaces();
|
|
483
|
-
dispatch(
|
|
560
|
+
dispatch(setMarketplaces(marketplaces));
|
|
484
561
|
// Also reload plugins as removed marketplace's plugins should be gone
|
|
485
562
|
const plugins = loadAllPlugins();
|
|
486
|
-
dispatch(
|
|
487
|
-
dispatch(
|
|
563
|
+
dispatch(setPlugins(plugins));
|
|
564
|
+
dispatch(setMessage(`✅ ${result.message}`));
|
|
488
565
|
// Reset selection if needed
|
|
489
566
|
if (state.selectedIndex >= marketplaces.length) {
|
|
490
|
-
dispatch(
|
|
491
|
-
type: 'SET_SELECTED_INDEX',
|
|
492
|
-
payload: Math.max(0, marketplaces.length - 1),
|
|
493
|
-
});
|
|
567
|
+
dispatch(setSelectedIndex(Math.max(0, marketplaces.length - 1)));
|
|
494
568
|
}
|
|
495
569
|
}
|
|
496
570
|
else {
|
|
497
|
-
dispatch({
|
|
498
|
-
type: 'SET_MESSAGE',
|
|
499
|
-
payload: `❌ ${result.message}${result.error ? `: ${result.error}` : ''}`,
|
|
500
|
-
});
|
|
571
|
+
dispatch(setMessage(`❌ ${result.message}${result.error ? `: ${result.error}` : ''}`));
|
|
501
572
|
}
|
|
502
573
|
}
|
|
503
574
|
/**
|
|
@@ -505,27 +576,21 @@ export default function App() {
|
|
|
505
576
|
* @param marketplaceId - Optional marketplace identifier. If omitted, updates all.
|
|
506
577
|
*/
|
|
507
578
|
async function handleUpdateMarketplace(marketplaceId) {
|
|
508
|
-
dispatch({
|
|
509
|
-
type: 'START_MARKETPLACE_OPERATION',
|
|
510
|
-
payload: { operation: 'updating', marketplaceId },
|
|
511
|
-
});
|
|
579
|
+
dispatch(startMarketplaceOperation({ operation: 'updating', marketplaceId }));
|
|
512
580
|
const result = await updateMarketplace(marketplaceId);
|
|
513
|
-
dispatch(
|
|
581
|
+
dispatch(endMarketplaceOperation());
|
|
514
582
|
if (result.success) {
|
|
515
583
|
// Reload marketplaces and plugins to get fresh state
|
|
516
584
|
const marketplaces = loadMarketplaces();
|
|
517
|
-
dispatch(
|
|
585
|
+
dispatch(setMarketplaces(marketplaces));
|
|
518
586
|
const plugins = loadAllPlugins();
|
|
519
|
-
dispatch(
|
|
587
|
+
dispatch(setPlugins(plugins));
|
|
520
588
|
// Reset selection to avoid pointing to a different marketplace after re-sort
|
|
521
|
-
dispatch(
|
|
522
|
-
dispatch(
|
|
589
|
+
dispatch(setSelectedIndex(0));
|
|
590
|
+
dispatch(setMessage(`✅ ${result.message}`));
|
|
523
591
|
}
|
|
524
592
|
else {
|
|
525
|
-
dispatch({
|
|
526
|
-
type: 'SET_MESSAGE',
|
|
527
|
-
payload: `❌ ${result.message}${result.error ? `: ${result.error}` : ''}`,
|
|
528
|
-
});
|
|
593
|
+
dispatch(setMessage(`❌ ${result.message}${result.error ? `: ${result.error}` : ''}`));
|
|
529
594
|
}
|
|
530
595
|
}
|
|
531
596
|
/**
|
|
@@ -534,23 +599,17 @@ export default function App() {
|
|
|
534
599
|
* @param currentValue - Current auto-update state
|
|
535
600
|
*/
|
|
536
601
|
async function handleToggleAutoUpdate(marketplaceId, currentValue) {
|
|
537
|
-
dispatch({
|
|
538
|
-
type: 'START_MARKETPLACE_OPERATION',
|
|
539
|
-
payload: { operation: 'updating', marketplaceId },
|
|
540
|
-
});
|
|
602
|
+
dispatch(startMarketplaceOperation({ operation: 'updating', marketplaceId }));
|
|
541
603
|
const result = await toggleAutoUpdate(marketplaceId, currentValue);
|
|
542
|
-
dispatch(
|
|
604
|
+
dispatch(endMarketplaceOperation());
|
|
543
605
|
if (result.success) {
|
|
544
606
|
// Reload marketplaces to get fresh state
|
|
545
607
|
const marketplaces = loadMarketplaces();
|
|
546
|
-
dispatch(
|
|
547
|
-
dispatch(
|
|
608
|
+
dispatch(setMarketplaces(marketplaces));
|
|
609
|
+
dispatch(setMessage(`✅ ${result.message}`));
|
|
548
610
|
}
|
|
549
611
|
else {
|
|
550
|
-
dispatch({
|
|
551
|
-
type: 'SET_MESSAGE',
|
|
552
|
-
payload: `❌ ${result.message}${result.error ? `: ${result.error}` : ''}`,
|
|
553
|
-
});
|
|
612
|
+
dispatch(setMessage(`❌ ${result.message}${result.error ? `: ${result.error}` : ''}`));
|
|
554
613
|
}
|
|
555
614
|
}
|
|
556
615
|
/**
|
|
@@ -560,13 +619,10 @@ export default function App() {
|
|
|
560
619
|
*/
|
|
561
620
|
function handleBrowseMarketplacePlugins(marketplaceId) {
|
|
562
621
|
// Switch to Discover tab
|
|
563
|
-
dispatch(
|
|
622
|
+
dispatch(setTab('discover'));
|
|
564
623
|
// Set search query to filter by marketplace
|
|
565
|
-
dispatch(
|
|
566
|
-
dispatch({
|
|
567
|
-
type: 'SET_MESSAGE',
|
|
568
|
-
payload: `Browsing plugins from ${marketplaceId}`,
|
|
569
|
-
});
|
|
624
|
+
dispatch(setSearchQuery(marketplaceId));
|
|
625
|
+
dispatch(setMessage(`Browsing plugins from ${marketplaceId}`));
|
|
570
626
|
}
|
|
571
627
|
// Keyboard input handling
|
|
572
628
|
useInput((input, key) => {
|
|
@@ -577,7 +633,7 @@ export default function App() {
|
|
|
577
633
|
// Handle help overlay
|
|
578
634
|
if (state.showHelp) {
|
|
579
635
|
if (input === 'h' || key.escape) {
|
|
580
|
-
dispatch(
|
|
636
|
+
dispatch(toggleHelp());
|
|
581
637
|
}
|
|
582
638
|
return;
|
|
583
639
|
}
|
|
@@ -588,19 +644,33 @@ export default function App() {
|
|
|
588
644
|
}
|
|
589
645
|
// Toggle help (h key)
|
|
590
646
|
if (input === 'h') {
|
|
591
|
-
dispatch(
|
|
647
|
+
dispatch(toggleHelp());
|
|
592
648
|
return;
|
|
593
649
|
}
|
|
594
650
|
// Handle plugin uninstall confirmation dialog
|
|
595
651
|
if (state.confirmUninstall && state.operationPluginId) {
|
|
596
652
|
if (input === 'y' || input === 'Y' || key.return) {
|
|
597
|
-
dispatch(
|
|
653
|
+
dispatch(hideConfirmUninstall());
|
|
598
654
|
handleUninstall(state.operationPluginId);
|
|
599
655
|
return;
|
|
600
656
|
}
|
|
601
657
|
if (input === 'n' || input === 'N' || key.escape) {
|
|
602
|
-
dispatch(
|
|
603
|
-
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'));
|
|
604
674
|
return;
|
|
605
675
|
}
|
|
606
676
|
return;
|
|
@@ -608,13 +678,13 @@ export default function App() {
|
|
|
608
678
|
// Handle marketplace remove confirmation dialog
|
|
609
679
|
if (state.confirmRemoveMarketplace && state.operationMarketplaceId) {
|
|
610
680
|
if (input === 'y' || input === 'Y' || key.return) {
|
|
611
|
-
dispatch(
|
|
681
|
+
dispatch(hideConfirmRemoveMarketplace());
|
|
612
682
|
handleRemoveMarketplace(state.operationMarketplaceId);
|
|
613
683
|
return;
|
|
614
684
|
}
|
|
615
685
|
if (input === 'n' || input === 'N' || key.escape) {
|
|
616
|
-
dispatch(
|
|
617
|
-
dispatch(
|
|
686
|
+
dispatch(hideConfirmRemoveMarketplace());
|
|
687
|
+
dispatch(setMessage('Remove cancelled'));
|
|
618
688
|
return;
|
|
619
689
|
}
|
|
620
690
|
return;
|
|
@@ -628,24 +698,18 @@ export default function App() {
|
|
|
628
698
|
}
|
|
629
699
|
// Cancel on Escape
|
|
630
700
|
if (key.escape) {
|
|
631
|
-
dispatch(
|
|
632
|
-
dispatch(
|
|
701
|
+
dispatch(hideAddMarketplaceDialog());
|
|
702
|
+
dispatch(setMessage('Add marketplace cancelled'));
|
|
633
703
|
return;
|
|
634
704
|
}
|
|
635
705
|
// Backspace
|
|
636
706
|
if (key.backspace || key.delete) {
|
|
637
|
-
dispatch(
|
|
638
|
-
type: 'SET_SEARCH_QUERY',
|
|
639
|
-
payload: state.searchQuery.slice(0, -1),
|
|
640
|
-
});
|
|
707
|
+
dispatch(setSearchQuery(state.searchQuery.slice(0, -1)));
|
|
641
708
|
return;
|
|
642
709
|
}
|
|
643
710
|
// Character input
|
|
644
711
|
if (input && input.length === 1 && !key.ctrl && !key.meta) {
|
|
645
|
-
dispatch(
|
|
646
|
-
type: 'SET_SEARCH_QUERY',
|
|
647
|
-
payload: state.searchQuery + input,
|
|
648
|
-
});
|
|
712
|
+
dispatch(setSearchQuery(state.searchQuery + input));
|
|
649
713
|
return;
|
|
650
714
|
}
|
|
651
715
|
return;
|
|
@@ -654,21 +718,21 @@ export default function App() {
|
|
|
654
718
|
if (state.showMarketplaceActionMenu) {
|
|
655
719
|
const selectedMarketplace = getFilteredMarketplaces(state)[state.selectedIndex];
|
|
656
720
|
if (!selectedMarketplace) {
|
|
657
|
-
dispatch(
|
|
721
|
+
dispatch(hideMarketplaceActionMenu());
|
|
658
722
|
return;
|
|
659
723
|
}
|
|
660
724
|
// Up/Down arrow navigation
|
|
661
725
|
if (key.upArrow || (key.ctrl && input === 'p')) {
|
|
662
|
-
dispatch(
|
|
726
|
+
dispatch(moveActionMenuSelection('up'));
|
|
663
727
|
return;
|
|
664
728
|
}
|
|
665
729
|
if (key.downArrow || (key.ctrl && input === 'n')) {
|
|
666
|
-
dispatch(
|
|
730
|
+
dispatch(moveActionMenuSelection('down'));
|
|
667
731
|
return;
|
|
668
732
|
}
|
|
669
733
|
// Execute action on Enter
|
|
670
734
|
if (key.return) {
|
|
671
|
-
dispatch(
|
|
735
|
+
dispatch(hideMarketplaceActionMenu());
|
|
672
736
|
const actionIndex = state.actionMenuSelectedIndex;
|
|
673
737
|
if (actionIndex === 0) {
|
|
674
738
|
// Browse plugins
|
|
@@ -684,16 +748,13 @@ export default function App() {
|
|
|
684
748
|
}
|
|
685
749
|
else if (actionIndex === 3) {
|
|
686
750
|
// Remove marketplace (show confirmation)
|
|
687
|
-
dispatch(
|
|
688
|
-
type: 'SHOW_CONFIRM_REMOVE_MARKETPLACE',
|
|
689
|
-
payload: selectedMarketplace.id,
|
|
690
|
-
});
|
|
751
|
+
dispatch(showConfirmRemoveMarketplace(selectedMarketplace.id));
|
|
691
752
|
}
|
|
692
753
|
return;
|
|
693
754
|
}
|
|
694
755
|
// Close menu on Escape
|
|
695
756
|
if (key.escape) {
|
|
696
|
-
dispatch(
|
|
757
|
+
dispatch(hideMarketplaceActionMenu());
|
|
697
758
|
return;
|
|
698
759
|
}
|
|
699
760
|
return;
|
|
@@ -702,7 +763,7 @@ export default function App() {
|
|
|
702
763
|
if (state.focusZone === 'search') {
|
|
703
764
|
// Up arrow: move focus to tabbar
|
|
704
765
|
if (key.upArrow || (key.ctrl && input === 'p')) {
|
|
705
|
-
dispatch(
|
|
766
|
+
dispatch(setFocusZone('tabbar'));
|
|
706
767
|
return;
|
|
707
768
|
}
|
|
708
769
|
// Down arrow, Enter, or Escape: move focus to list
|
|
@@ -710,21 +771,15 @@ export default function App() {
|
|
|
710
771
|
key.return ||
|
|
711
772
|
key.downArrow ||
|
|
712
773
|
(key.ctrl && input === 'n')) {
|
|
713
|
-
dispatch(
|
|
774
|
+
dispatch(setFocusZone('list'));
|
|
714
775
|
return;
|
|
715
776
|
}
|
|
716
777
|
if (key.backspace || key.delete) {
|
|
717
|
-
dispatch(
|
|
718
|
-
type: 'SET_SEARCH_QUERY',
|
|
719
|
-
payload: state.searchQuery.slice(0, -1),
|
|
720
|
-
});
|
|
778
|
+
dispatch(setSearchQuery(state.searchQuery.slice(0, -1)));
|
|
721
779
|
return;
|
|
722
780
|
}
|
|
723
781
|
if (input && input.length === 1 && !key.ctrl && !key.meta) {
|
|
724
|
-
dispatch(
|
|
725
|
-
type: 'SET_SEARCH_QUERY',
|
|
726
|
-
payload: state.searchQuery + input,
|
|
727
|
-
});
|
|
782
|
+
dispatch(setSearchQuery(state.searchQuery + input));
|
|
728
783
|
return;
|
|
729
784
|
}
|
|
730
785
|
return;
|
|
@@ -734,27 +789,54 @@ export default function App() {
|
|
|
734
789
|
// Down arrow: move to search (or list if no search)
|
|
735
790
|
if (key.downArrow || (key.ctrl && input === 'n')) {
|
|
736
791
|
const zones = getAvailableZones(state.activeTab);
|
|
737
|
-
dispatch(
|
|
738
|
-
type: 'SET_FOCUS_ZONE',
|
|
739
|
-
payload: zones.includes('search') ? 'search' : 'list',
|
|
740
|
-
});
|
|
792
|
+
dispatch(setFocusZone(zones.includes('search') ? 'search' : 'list'));
|
|
741
793
|
return;
|
|
742
794
|
}
|
|
743
795
|
// Left/Right arrows and Ctrl+B/F: tab switching (only in tabbar)
|
|
744
796
|
// Keep focus on tabbar after navigation
|
|
745
797
|
if (key.leftArrow || (key.ctrl && input === 'b')) {
|
|
746
|
-
dispatch(
|
|
747
|
-
dispatch(
|
|
798
|
+
dispatch(prevTab());
|
|
799
|
+
dispatch(setFocusZone('tabbar'));
|
|
748
800
|
return;
|
|
749
801
|
}
|
|
750
802
|
if (key.rightArrow || (key.ctrl && input === 'f')) {
|
|
751
|
-
dispatch(
|
|
752
|
-
dispatch(
|
|
803
|
+
dispatch(nextTab());
|
|
804
|
+
dispatch(setFocusZone('tabbar'));
|
|
753
805
|
return;
|
|
754
806
|
}
|
|
755
807
|
// Tab key: next tab (resets focus to list)
|
|
756
808
|
if (key.tab) {
|
|
757
|
-
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
|
+
}));
|
|
758
840
|
return;
|
|
759
841
|
}
|
|
760
842
|
return;
|
|
@@ -765,24 +847,43 @@ export default function App() {
|
|
|
765
847
|
if (state.selectedIndex === 0) {
|
|
766
848
|
// At top of list: move focus to search (or tabbar if no search)
|
|
767
849
|
const zones = getAvailableZones(state.activeTab);
|
|
768
|
-
dispatch(
|
|
769
|
-
type: 'SET_FOCUS_ZONE',
|
|
770
|
-
payload: zones.includes('search') ? 'search' : 'tabbar',
|
|
771
|
-
});
|
|
850
|
+
dispatch(setFocusZone(zones.includes('search') ? 'search' : 'tabbar'));
|
|
772
851
|
}
|
|
773
852
|
else {
|
|
774
|
-
dispatch({
|
|
853
|
+
dispatch(moveSelection({
|
|
854
|
+
direction: 'up',
|
|
855
|
+
maxIndex: Math.max(0, getItemsForTab(state).length - 1),
|
|
856
|
+
}));
|
|
775
857
|
}
|
|
776
858
|
return;
|
|
777
859
|
}
|
|
778
860
|
// Down arrow: move down in list
|
|
779
861
|
if (key.downArrow || (key.ctrl && input === 'n')) {
|
|
780
|
-
dispatch({
|
|
862
|
+
dispatch(moveSelection({
|
|
863
|
+
direction: 'down',
|
|
864
|
+
maxIndex: Math.max(0, getItemsForTab(state).length - 1),
|
|
865
|
+
}));
|
|
781
866
|
return;
|
|
782
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
|
+
}
|
|
783
884
|
// Tab key: next tab (from list zone)
|
|
784
885
|
if (key.tab) {
|
|
785
|
-
dispatch(
|
|
886
|
+
dispatch(nextTab());
|
|
786
887
|
return;
|
|
787
888
|
}
|
|
788
889
|
// Enter search mode (/ key on supported tabs)
|
|
@@ -793,7 +894,7 @@ export default function App() {
|
|
|
793
894
|
'marketplaces',
|
|
794
895
|
];
|
|
795
896
|
if (input === '/' && searchEnabledTabs.includes(state.activeTab)) {
|
|
796
|
-
dispatch(
|
|
897
|
+
dispatch(setFocusZone('search'));
|
|
797
898
|
return;
|
|
798
899
|
}
|
|
799
900
|
// Enter key: Install (non-installed) or Toggle (installed)
|
|
@@ -812,22 +913,13 @@ export default function App() {
|
|
|
812
913
|
// Toggle installed plugin
|
|
813
914
|
try {
|
|
814
915
|
const newState = togglePlugin(selectedPlugin.id);
|
|
815
|
-
dispatch(
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
dispatch({
|
|
820
|
-
type: 'SET_MESSAGE',
|
|
821
|
-
payload: newState
|
|
822
|
-
? `✅ ${selectedPlugin.name} enabled`
|
|
823
|
-
: `❌ ${selectedPlugin.name} disabled`,
|
|
824
|
-
});
|
|
916
|
+
dispatch(togglePluginEnabled(selectedPlugin.id));
|
|
917
|
+
dispatch(setMessage(newState
|
|
918
|
+
? `✅ ${selectedPlugin.name} enabled`
|
|
919
|
+
: `❌ ${selectedPlugin.name} disabled`));
|
|
825
920
|
}
|
|
826
921
|
catch (error) {
|
|
827
|
-
dispatch({
|
|
828
|
-
type: 'SET_MESSAGE',
|
|
829
|
-
payload: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
830
|
-
});
|
|
922
|
+
dispatch(setMessage(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
831
923
|
}
|
|
832
924
|
}
|
|
833
925
|
}
|
|
@@ -843,22 +935,13 @@ export default function App() {
|
|
|
843
935
|
if (selectedPlugin && selectedPlugin.isInstalled) {
|
|
844
936
|
try {
|
|
845
937
|
const newState = togglePlugin(selectedPlugin.id);
|
|
846
|
-
dispatch(
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
dispatch({
|
|
851
|
-
type: 'SET_MESSAGE',
|
|
852
|
-
payload: newState
|
|
853
|
-
? `✅ ${selectedPlugin.name} enabled`
|
|
854
|
-
: `❌ ${selectedPlugin.name} disabled`,
|
|
855
|
-
});
|
|
938
|
+
dispatch(togglePluginEnabled(selectedPlugin.id));
|
|
939
|
+
dispatch(setMessage(newState
|
|
940
|
+
? `✅ ${selectedPlugin.name} enabled`
|
|
941
|
+
: `❌ ${selectedPlugin.name} disabled`));
|
|
856
942
|
}
|
|
857
943
|
catch (error) {
|
|
858
|
-
dispatch({
|
|
859
|
-
type: 'SET_MESSAGE',
|
|
860
|
-
payload: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
861
|
-
});
|
|
944
|
+
dispatch(setMessage(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
862
945
|
}
|
|
863
946
|
}
|
|
864
947
|
return;
|
|
@@ -870,26 +953,20 @@ export default function App() {
|
|
|
870
953
|
name: 'date',
|
|
871
954
|
date: 'installs',
|
|
872
955
|
};
|
|
873
|
-
dispatch({
|
|
874
|
-
type: 'SET_SORT',
|
|
875
|
-
payload: { by: nextSort[state.sortBy], order: state.sortOrder },
|
|
876
|
-
});
|
|
956
|
+
dispatch(setSort({ by: nextSort[state.sortBy], order: state.sortOrder }));
|
|
877
957
|
return;
|
|
878
958
|
}
|
|
879
959
|
// Toggle sort order (S key)
|
|
880
960
|
if (input === 'S' && state.activeTab === 'discover') {
|
|
881
|
-
dispatch({
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
order: state.sortOrder === 'asc' ? 'desc' : 'asc',
|
|
886
|
-
},
|
|
887
|
-
});
|
|
961
|
+
dispatch(setSort({
|
|
962
|
+
by: state.sortBy,
|
|
963
|
+
order: state.sortOrder === 'asc' ? 'desc' : 'asc',
|
|
964
|
+
}));
|
|
888
965
|
return;
|
|
889
966
|
}
|
|
890
967
|
// Clear search (Escape)
|
|
891
968
|
if (key.escape && state.searchQuery) {
|
|
892
|
-
dispatch(
|
|
969
|
+
dispatch(setSearchQuery(''));
|
|
893
970
|
return;
|
|
894
971
|
}
|
|
895
972
|
// Marketplace-specific key bindings
|
|
@@ -898,23 +975,20 @@ export default function App() {
|
|
|
898
975
|
if (key.return || input === 'm') {
|
|
899
976
|
const selectedMarketplace = filteredMarketplaces[state.selectedIndex];
|
|
900
977
|
if (selectedMarketplace) {
|
|
901
|
-
dispatch(
|
|
978
|
+
dispatch(showMarketplaceActionMenuAction());
|
|
902
979
|
}
|
|
903
980
|
return;
|
|
904
981
|
}
|
|
905
982
|
// Add marketplace (a key)
|
|
906
983
|
if (input === 'a') {
|
|
907
|
-
dispatch(
|
|
984
|
+
dispatch(showAddMarketplaceDialog());
|
|
908
985
|
return;
|
|
909
986
|
}
|
|
910
987
|
// Remove marketplace (d key or Backspace)
|
|
911
988
|
if (input === 'd' || key.backspace || key.delete) {
|
|
912
989
|
const selectedMarketplace = filteredMarketplaces[state.selectedIndex];
|
|
913
990
|
if (selectedMarketplace) {
|
|
914
|
-
dispatch(
|
|
915
|
-
type: 'SHOW_CONFIRM_REMOVE_MARKETPLACE',
|
|
916
|
-
payload: selectedMarketplace.id,
|
|
917
|
-
});
|
|
991
|
+
dispatch(showConfirmRemoveMarketplace(selectedMarketplace.id));
|
|
918
992
|
}
|
|
919
993
|
return;
|
|
920
994
|
}
|
|
@@ -942,10 +1016,19 @@ export default function App() {
|
|
|
942
1016
|
handleInstall(selectedPlugin.id);
|
|
943
1017
|
}
|
|
944
1018
|
else if (selectedPlugin?.isInstalled) {
|
|
945
|
-
dispatch(
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
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());
|
|
949
1032
|
}
|
|
950
1033
|
return;
|
|
951
1034
|
}
|
|
@@ -957,23 +1040,15 @@ export default function App() {
|
|
|
957
1040
|
const items = getItemsForTab(state);
|
|
958
1041
|
const selectedPlugin = items[state.selectedIndex];
|
|
959
1042
|
if (selectedPlugin && selectedPlugin.isInstalled) {
|
|
960
|
-
dispatch(
|
|
1043
|
+
dispatch(showConfirmUninstall(selectedPlugin.id));
|
|
961
1044
|
}
|
|
962
1045
|
else if (selectedPlugin && !selectedPlugin.isInstalled) {
|
|
963
|
-
dispatch(
|
|
1046
|
+
dispatch(setMessage('⚠️ Plugin is not installed'));
|
|
964
1047
|
}
|
|
965
1048
|
return;
|
|
966
1049
|
}
|
|
967
1050
|
});
|
|
968
|
-
//
|
|
969
|
-
if (state.loading) {
|
|
970
|
-
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(Text, { children: "Loading plugins..." }) }));
|
|
971
|
-
}
|
|
972
|
-
// Error state
|
|
973
|
-
if (state.error) {
|
|
974
|
-
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" })] }));
|
|
975
|
-
}
|
|
976
|
-
// Get filtered data for current tab
|
|
1051
|
+
// Get filtered data for current tab (must be before early returns for useMemo dependency)
|
|
977
1052
|
const filteredPlugins = getFilteredPlugins(state);
|
|
978
1053
|
// Apply search filter to enabled plugins
|
|
979
1054
|
const enabledPluginsBase = state.plugins.filter((p) => p.isInstalled && p.isEnabled);
|
|
@@ -989,13 +1064,71 @@ export default function App() {
|
|
|
989
1064
|
const filteredMarketplaces = state.searchQuery
|
|
990
1065
|
? searchMarketplaces(state.searchQuery, state.marketplaces)
|
|
991
1066
|
: state.marketplaces;
|
|
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
|
+
}
|
|
992
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)
|
|
993
|
-
.with('enabled', () => (_jsx(EnabledTab, { plugins: enabledPlugins, selectedIndex: state.selectedIndex, searchQuery: state.searchQuery, focusZone: state.focusZone }, "enabled")))
|
|
994
|
-
.with('installed', () => (_jsx(InstalledTab, { plugins: installedPlugins, selectedIndex: state.selectedIndex, searchQuery: state.searchQuery, focusZone: state.focusZone }, "installed")))
|
|
995
|
-
.with('discover', () => (_jsx(DiscoverTab, { plugins: filteredPlugins, selectedIndex: state.selectedIndex, searchQuery: state.searchQuery, sortBy: state.sortBy, sortOrder: state.sortOrder, focusZone: state.focusZone }, "discover")))
|
|
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")))
|
|
996
1127
|
.with('marketplaces', () => (_jsx(MarketplacesTab, { marketplaces: filteredMarketplaces, selectedIndex: state.selectedIndex, searchQuery: state.searchQuery, focusZone: state.focusZone, showActionMenu: state.showMarketplaceActionMenu, actionMenuSelectedIndex: state.actionMenuSelectedIndex }, "marketplaces")))
|
|
997
1128
|
.with('errors', () => (_jsx(ErrorsTab, { errors: state.errors, selectedIndex: state.selectedIndex }, "errors")))
|
|
998
|
-
.exhaustive() }), 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.
|
|
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])
|
|
999
1132
|
.with(['search', P._], () => undefined)
|
|
1000
1133
|
.with(['tabbar', P._], () => undefined)
|
|
1001
1134
|
.with(['list', 'enabled'], () => {
|
|
@@ -1003,6 +1136,7 @@ export default function App() {
|
|
|
1003
1136
|
{ key: '/', action: 'search' },
|
|
1004
1137
|
{ key: 'i', action: 'install' },
|
|
1005
1138
|
{ key: 'u', action: 'uninstall' },
|
|
1139
|
+
{ key: 'U', action: 'update all' },
|
|
1006
1140
|
];
|
|
1007
1141
|
const selectedPlugin = enabledPlugins[state.selectedIndex];
|
|
1008
1142
|
if (selectedPlugin) {
|
|
@@ -1018,6 +1152,7 @@ export default function App() {
|
|
|
1018
1152
|
{ key: '/', action: 'search' },
|
|
1019
1153
|
{ key: 'i', action: 'install' },
|
|
1020
1154
|
{ key: 'u', action: 'uninstall' },
|
|
1155
|
+
{ key: 'U', action: 'update all' },
|
|
1021
1156
|
];
|
|
1022
1157
|
const selectedPlugin = installedPlugins[state.selectedIndex];
|
|
1023
1158
|
if (selectedPlugin) {
|