claudeup 3.9.0 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/data/skill-repos.js +15 -15
- package/src/data/skill-repos.ts +15 -15
- package/src/services/skills-manager.js +124 -56
- package/src/services/skills-manager.ts +131 -58
- package/src/types/index.ts +4 -0
- package/src/ui/App.js +9 -9
- package/src/ui/App.tsx +9 -9
- package/src/ui/components/EmptyFilterState.js +4 -0
- package/src/ui/components/EmptyFilterState.tsx +27 -0
- package/src/ui/components/TabBar.js +4 -4
- package/src/ui/components/TabBar.tsx +4 -4
- package/src/ui/screens/PluginsScreen.js +32 -47
- package/src/ui/screens/PluginsScreen.tsx +39 -59
- package/src/ui/screens/SkillsScreen.js +248 -143
- package/src/ui/screens/SkillsScreen.tsx +316 -163
package/src/ui/App.js
CHANGED
|
@@ -92,24 +92,24 @@ function GlobalKeyHandler({ onDebugToggle, onExit, }) {
|
|
|
92
92
|
if (input === "1")
|
|
93
93
|
navigateToScreen("plugins");
|
|
94
94
|
else if (input === "2")
|
|
95
|
-
navigateToScreen("
|
|
95
|
+
navigateToScreen("skills");
|
|
96
96
|
else if (input === "3")
|
|
97
|
-
navigateToScreen("
|
|
97
|
+
navigateToScreen("mcp");
|
|
98
98
|
else if (input === "4")
|
|
99
|
-
navigateToScreen("
|
|
99
|
+
navigateToScreen("settings");
|
|
100
100
|
else if (input === "5")
|
|
101
101
|
navigateToScreen("profiles");
|
|
102
102
|
else if (input === "6")
|
|
103
|
-
navigateToScreen("
|
|
103
|
+
navigateToScreen("cli-tools");
|
|
104
104
|
// Tab navigation cycling
|
|
105
105
|
if (key.tab) {
|
|
106
106
|
const screens = [
|
|
107
107
|
"plugins",
|
|
108
|
+
"skills",
|
|
108
109
|
"mcp",
|
|
109
110
|
"settings",
|
|
110
|
-
"cli-tools",
|
|
111
111
|
"profiles",
|
|
112
|
-
"
|
|
112
|
+
"cli-tools",
|
|
113
113
|
];
|
|
114
114
|
const currentIndex = screens.indexOf(state.currentRoute.screen);
|
|
115
115
|
if (currentIndex !== -1) {
|
|
@@ -144,9 +144,9 @@ function GlobalKeyHandler({ onDebugToggle, onExit, }) {
|
|
|
144
144
|
? This help
|
|
145
145
|
|
|
146
146
|
Quick Navigation
|
|
147
|
-
1 Plugins 4
|
|
148
|
-
2
|
|
149
|
-
3
|
|
147
|
+
1 Plugins 4 Settings
|
|
148
|
+
2 Skills 5 Profiles
|
|
149
|
+
3 MCP Servers 6 CLI Tools
|
|
150
150
|
|
|
151
151
|
Plugin Actions
|
|
152
152
|
u Update d Uninstall
|
package/src/ui/App.tsx
CHANGED
|
@@ -124,21 +124,21 @@ function GlobalKeyHandler({
|
|
|
124
124
|
|
|
125
125
|
if (isTopLevel) {
|
|
126
126
|
if (input === "1") navigateToScreen("plugins");
|
|
127
|
-
else if (input === "2") navigateToScreen("
|
|
128
|
-
else if (input === "3") navigateToScreen("
|
|
129
|
-
else if (input === "4") navigateToScreen("
|
|
127
|
+
else if (input === "2") navigateToScreen("skills");
|
|
128
|
+
else if (input === "3") navigateToScreen("mcp");
|
|
129
|
+
else if (input === "4") navigateToScreen("settings");
|
|
130
130
|
else if (input === "5") navigateToScreen("profiles");
|
|
131
|
-
else if (input === "6") navigateToScreen("
|
|
131
|
+
else if (input === "6") navigateToScreen("cli-tools");
|
|
132
132
|
|
|
133
133
|
// Tab navigation cycling
|
|
134
134
|
if (key.tab) {
|
|
135
135
|
const screens: Screen[] = [
|
|
136
136
|
"plugins",
|
|
137
|
+
"skills",
|
|
137
138
|
"mcp",
|
|
138
139
|
"settings",
|
|
139
|
-
"cli-tools",
|
|
140
140
|
"profiles",
|
|
141
|
-
"
|
|
141
|
+
"cli-tools",
|
|
142
142
|
];
|
|
143
143
|
const currentIndex = screens.indexOf(
|
|
144
144
|
state.currentRoute.screen as Screen,
|
|
@@ -177,9 +177,9 @@ function GlobalKeyHandler({
|
|
|
177
177
|
? This help
|
|
178
178
|
|
|
179
179
|
Quick Navigation
|
|
180
|
-
1 Plugins 4
|
|
181
|
-
2
|
|
182
|
-
3
|
|
180
|
+
1 Plugins 4 Settings
|
|
181
|
+
2 Skills 5 Profiles
|
|
182
|
+
3 MCP Servers 6 CLI Tools
|
|
183
183
|
|
|
184
184
|
Plugin Actions
|
|
185
185
|
u Update d Uninstall
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "@opentui/react/jsx-runtime";
|
|
2
|
+
export function EmptyFilterState({ query, entityName = "items", }) {
|
|
3
|
+
return (_jsxs("box", { flexDirection: "column", marginTop: 2, paddingLeft: 2, paddingRight: 2, children: [_jsxs("text", { fg: "yellow", children: ["No ", entityName, " found for \"", query, "\""] }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: "Try a different search term, or if you think" }) }), _jsx("text", { fg: "gray", children: "this is a bug, report it at:" }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "#5c9aff", children: "github.com/MadAppGang/magus/issues" }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: "Press Esc to clear the filter." }) })] }));
|
|
4
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
interface EmptyFilterStateProps {
|
|
4
|
+
query: string;
|
|
5
|
+
entityName?: string; // "plugins", "skills", "MCP servers"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function EmptyFilterState({
|
|
9
|
+
query,
|
|
10
|
+
entityName = "items",
|
|
11
|
+
}: EmptyFilterStateProps) {
|
|
12
|
+
return (
|
|
13
|
+
<box flexDirection="column" marginTop={2} paddingLeft={2} paddingRight={2}>
|
|
14
|
+
<text fg="yellow">No {entityName} found for "{query}"</text>
|
|
15
|
+
<box marginTop={1}>
|
|
16
|
+
<text fg="gray">Try a different search term, or if you think</text>
|
|
17
|
+
</box>
|
|
18
|
+
<text fg="gray">this is a bug, report it at:</text>
|
|
19
|
+
<box marginTop={1}>
|
|
20
|
+
<text fg="#5c9aff">github.com/MadAppGang/magus/issues</text>
|
|
21
|
+
</box>
|
|
22
|
+
<box marginTop={1}>
|
|
23
|
+
<text fg="gray">Press Esc to clear the filter.</text>
|
|
24
|
+
</box>
|
|
25
|
+
</box>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -2,11 +2,11 @@ import { jsxs as _jsxs, jsx as _jsx } from "@opentui/react/jsx-runtime";
|
|
|
2
2
|
import { useKeyboardHandler } from "../hooks/useKeyboardHandler";
|
|
3
3
|
const TABS = [
|
|
4
4
|
{ key: "1", label: "Plugins", screen: "plugins" },
|
|
5
|
-
{ key: "2", label: "
|
|
6
|
-
{ key: "3", label: "
|
|
7
|
-
{ key: "4", label: "
|
|
5
|
+
{ key: "2", label: "Skills", screen: "skills" },
|
|
6
|
+
{ key: "3", label: "MCP", screen: "mcp" },
|
|
7
|
+
{ key: "4", label: "Settings", screen: "settings" },
|
|
8
8
|
{ key: "5", label: "Profiles", screen: "profiles" },
|
|
9
|
-
{ key: "6", label: "
|
|
9
|
+
{ key: "6", label: "CLI", screen: "cli-tools" },
|
|
10
10
|
];
|
|
11
11
|
export function TabBar({ currentScreen, onTabChange }) {
|
|
12
12
|
// Handle number key shortcuts (1-5)
|
|
@@ -10,11 +10,11 @@ interface Tab {
|
|
|
10
10
|
|
|
11
11
|
const TABS: Tab[] = [
|
|
12
12
|
{ key: "1", label: "Plugins", screen: "plugins" },
|
|
13
|
-
{ key: "2", label: "
|
|
14
|
-
{ key: "3", label: "
|
|
15
|
-
{ key: "4", label: "
|
|
13
|
+
{ key: "2", label: "Skills", screen: "skills" },
|
|
14
|
+
{ key: "3", label: "MCP", screen: "mcp" },
|
|
15
|
+
{ key: "4", label: "Settings", screen: "settings" },
|
|
16
16
|
{ key: "5", label: "Profiles", screen: "profiles" },
|
|
17
|
-
{ key: "6", label: "
|
|
17
|
+
{ key: "6", label: "CLI", screen: "cli-tools" },
|
|
18
18
|
];
|
|
19
19
|
|
|
20
20
|
interface TabBarProps {
|
|
@@ -6,6 +6,7 @@ import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
|
6
6
|
import { ScreenLayout } from "../components/layout/index.js";
|
|
7
7
|
import { CategoryHeader } from "../components/CategoryHeader.js";
|
|
8
8
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
9
|
+
import { EmptyFilterState } from "../components/EmptyFilterState.js";
|
|
9
10
|
import { fuzzyFilter, highlightMatches } from "../../utils/fuzzy-search.js";
|
|
10
11
|
import { getAllMarketplaces } from "../../data/marketplaces.js";
|
|
11
12
|
import { getAvailablePlugins, refreshAllMarketplaces, clearMarketplaceCache, getLocalMarketplacesInfo, } from "../../services/plugin-manager.js";
|
|
@@ -175,31 +176,17 @@ export function PluginsScreen() {
|
|
|
175
176
|
}
|
|
176
177
|
return;
|
|
177
178
|
}
|
|
178
|
-
// Navigation — always works
|
|
179
|
+
// Navigation — always works; exits search mode on navigate
|
|
179
180
|
if (event.name === "up" || event.name === "k") {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
dispatch({
|
|
183
|
-
type: "PLUGINS_SET_SEARCH",
|
|
184
|
-
query: pluginsState.searchQuery + event.name,
|
|
185
|
-
});
|
|
186
|
-
dispatch({ type: "PLUGINS_SELECT", index: 0 });
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
181
|
+
if (isSearchActive)
|
|
182
|
+
dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
189
183
|
const newIndex = Math.max(0, pluginsState.selectedIndex - 1);
|
|
190
184
|
dispatch({ type: "PLUGINS_SELECT", index: newIndex });
|
|
191
185
|
return;
|
|
192
186
|
}
|
|
193
187
|
if (event.name === "down" || event.name === "j") {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
dispatch({
|
|
197
|
-
type: "PLUGINS_SET_SEARCH",
|
|
198
|
-
query: pluginsState.searchQuery + event.name,
|
|
199
|
-
});
|
|
200
|
-
dispatch({ type: "PLUGINS_SELECT", index: 0 });
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
188
|
+
if (isSearchActive)
|
|
189
|
+
dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
203
190
|
const newIndex = Math.min(selectableItems.length - 1, pluginsState.selectedIndex + 1);
|
|
204
191
|
dispatch({ type: "PLUGINS_SELECT", index: newIndex });
|
|
205
192
|
return;
|
|
@@ -229,9 +216,9 @@ export function PluginsScreen() {
|
|
|
229
216
|
}
|
|
230
217
|
return;
|
|
231
218
|
}
|
|
232
|
-
// When
|
|
233
|
-
//
|
|
234
|
-
if (
|
|
219
|
+
// When actively typing in search, letters go to the query
|
|
220
|
+
// After Enter (isSearchActive=false, hasQuery=true), shortcuts resume
|
|
221
|
+
if (isSearchActive) {
|
|
235
222
|
if (event.name.length === 1 && !event.ctrl && !event.meta && !/[0-9]/.test(event.name)) {
|
|
236
223
|
dispatch({
|
|
237
224
|
type: "PLUGINS_SET_SEARCH",
|
|
@@ -241,7 +228,7 @@ export function PluginsScreen() {
|
|
|
241
228
|
}
|
|
242
229
|
return;
|
|
243
230
|
}
|
|
244
|
-
//
|
|
231
|
+
// Action shortcuts work when not actively typing (even with filter visible)
|
|
245
232
|
// Start explicit search mode with /
|
|
246
233
|
if (event.name === "/") {
|
|
247
234
|
dispatch({ type: "SET_SEARCHING", isSearching: true });
|
|
@@ -264,18 +251,11 @@ export function PluginsScreen() {
|
|
|
264
251
|
handleUpdate();
|
|
265
252
|
else if (event.name === "a")
|
|
266
253
|
handleUpdateAll();
|
|
267
|
-
else if (event.name === "d")
|
|
268
|
-
handleUninstall();
|
|
269
254
|
else if (event.name === "s")
|
|
270
255
|
handleSaveAsProfile();
|
|
271
|
-
//
|
|
272
|
-
else if (event.name
|
|
256
|
+
// "/" to enter search mode
|
|
257
|
+
else if (event.name === "/") {
|
|
273
258
|
dispatch({ type: "SET_SEARCHING", isSearching: true });
|
|
274
|
-
dispatch({
|
|
275
|
-
type: "PLUGINS_SET_SEARCH",
|
|
276
|
-
query: event.name,
|
|
277
|
-
});
|
|
278
|
-
dispatch({ type: "PLUGINS_SELECT", index: 0 });
|
|
279
259
|
}
|
|
280
260
|
});
|
|
281
261
|
// Handle actions
|
|
@@ -639,7 +619,7 @@ export function PluginsScreen() {
|
|
|
639
619
|
const plugin = item.plugin;
|
|
640
620
|
const latestVersion = plugin.version || "0.0.0";
|
|
641
621
|
const scopeLabel = scope === "user" ? "User" : scope === "project" ? "Project" : "Local";
|
|
642
|
-
// Check if installed in this scope
|
|
622
|
+
// Check if installed in this specific scope
|
|
643
623
|
const scopeData = scope === "user"
|
|
644
624
|
? plugin.userScope
|
|
645
625
|
: scope === "project"
|
|
@@ -647,12 +627,16 @@ export function PluginsScreen() {
|
|
|
647
627
|
: plugin.localScope;
|
|
648
628
|
const isInstalledInScope = scopeData?.enabled;
|
|
649
629
|
const installedVersion = scopeData?.version;
|
|
630
|
+
// Also check if installed in ANY scope (for the toggle behavior)
|
|
631
|
+
const isInstalledAnywhere = plugin.userScope?.enabled || plugin.projectScope?.enabled || plugin.localScope?.enabled;
|
|
650
632
|
// Check if this scope has an update available
|
|
651
633
|
const hasUpdateInScope = isInstalledInScope &&
|
|
652
634
|
installedVersion &&
|
|
653
635
|
latestVersion !== "0.0.0" &&
|
|
654
636
|
installedVersion !== latestVersion;
|
|
655
|
-
// Determine action:
|
|
637
|
+
// Determine action: if installed in this scope → uninstall
|
|
638
|
+
// If installed anywhere else but not this scope → uninstall from detected scope
|
|
639
|
+
// Otherwise → install
|
|
656
640
|
let action;
|
|
657
641
|
if (isInstalledInScope && hasUpdateInScope) {
|
|
658
642
|
action = "update";
|
|
@@ -660,9 +644,13 @@ export function PluginsScreen() {
|
|
|
660
644
|
else if (isInstalledInScope) {
|
|
661
645
|
action = "uninstall";
|
|
662
646
|
}
|
|
663
|
-
else {
|
|
647
|
+
else if (!isInstalledAnywhere) {
|
|
664
648
|
action = "install";
|
|
665
649
|
}
|
|
650
|
+
else {
|
|
651
|
+
// Installed in a different scope — uninstall from the scope it's actually in
|
|
652
|
+
action = "uninstall";
|
|
653
|
+
}
|
|
666
654
|
const actionLabel = action === "update"
|
|
667
655
|
? `Updating ${scopeLabel}`
|
|
668
656
|
: action === "install"
|
|
@@ -819,18 +807,15 @@ export function PluginsScreen() {
|
|
|
819
807
|
const plugin = item.plugin;
|
|
820
808
|
let statusIcon = "○";
|
|
821
809
|
let statusColor = "gray";
|
|
810
|
+
const isAnyScope = plugin.userScope?.enabled || plugin.projectScope?.enabled || plugin.localScope?.enabled;
|
|
822
811
|
if (plugin.isOrphaned) {
|
|
823
812
|
statusIcon = "x";
|
|
824
813
|
statusColor = "red";
|
|
825
814
|
}
|
|
826
|
-
else if (
|
|
815
|
+
else if (isAnyScope) {
|
|
827
816
|
statusIcon = "●";
|
|
828
817
|
statusColor = "green";
|
|
829
818
|
}
|
|
830
|
-
else if (plugin.installedVersion) {
|
|
831
|
-
statusIcon = "●";
|
|
832
|
-
statusColor = "yellow";
|
|
833
|
-
}
|
|
834
819
|
// Build version string
|
|
835
820
|
let versionStr = "";
|
|
836
821
|
if (plugin.isOrphaned) {
|
|
@@ -899,7 +884,7 @@ export function PluginsScreen() {
|
|
|
899
884
|
}
|
|
900
885
|
if (selectedItem.type === "plugin" && selectedItem.plugin) {
|
|
901
886
|
const plugin = selectedItem.plugin;
|
|
902
|
-
const isInstalled = plugin.enabled || plugin.
|
|
887
|
+
const isInstalled = plugin.userScope?.enabled || plugin.projectScope?.enabled || plugin.localScope?.enabled;
|
|
903
888
|
// Orphaned/deprecated plugin
|
|
904
889
|
if (plugin.isOrphaned) {
|
|
905
890
|
return (_jsxs("box", { flexDirection: "column", children: [_jsx("box", { justifyContent: "center", children: _jsx("text", { bg: "yellow", fg: "black", children: _jsxs("strong", { children: [" ", plugin.name, " \u2014 DEPRECATED "] }) }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "yellow", children: "This plugin is no longer in the marketplace." }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: "It was removed from the marketplace but still referenced in your settings. Press d to uninstall and clean up." }) }), isInstalled && (_jsx("box", { flexDirection: "column", marginTop: 2, children: _jsxs("box", { children: [_jsx("text", { bg: "red", fg: "white", children: " d " }), _jsx("text", { children: " Uninstall (recommended)" })] }) }))] }));
|
|
@@ -920,14 +905,14 @@ export function PluginsScreen() {
|
|
|
920
905
|
// Show version only if valid (not null, not 0.0.0)
|
|
921
906
|
const showVersion = plugin.version && plugin.version !== "0.0.0";
|
|
922
907
|
const showInstalledVersion = plugin.installedVersion && plugin.installedVersion !== "0.0.0";
|
|
923
|
-
return (_jsxs("box", { flexDirection: "column", children: [_jsx("box", { justifyContent: "center", children: _jsx("text", { bg: "magenta", fg: "white", children: _jsxs("strong", { children: [" ", plugin.name, plugin.hasUpdate ? " ⬆" : "", " "] }) }) }), _jsx("box", { marginTop: 1, children: isInstalled ? (_jsx("text", { fg:
|
|
924
|
-
plugin.installedVersion !== plugin.version && (_jsxs("span", { children: [" (v", plugin.installedVersion, " installed)"] }))] })), plugin.category && (_jsxs("text", { children: [_jsx("span", { children: "Category " }), _jsx("span", { fg: "magenta", children: plugin.category })] })), plugin.author && (_jsxs("text", { children: [_jsx("span", { children: "Author " }), _jsx("span", { children: plugin.author.name })] })), components.length > 0 && (_jsxs("text", { children: [_jsx("span", { children: "Contains " }), _jsx("span", { fg: "yellow", children: components.join(" · ") })] })), _jsxs("box", { flexDirection: "column", marginTop: 1, children: [_jsx("text", { children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsx("text", { children: _jsx("strong", { children: "Scopes:" }) }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { children: [_jsxs("span", { bg: "cyan", fg: "black", children: [" ", "u", " "] }), _jsx("span", { fg: plugin.userScope?.enabled ? "cyan" : "gray", children: plugin.userScope?.enabled ? " ● " : " ○ " }), _jsx("span", { fg: "cyan", children: "User" }), _jsx("span", { children: " global" }), plugin.userScope?.version && (_jsxs("span", { fg: "cyan", children: [" v", plugin.userScope.version] }))] }), _jsxs("text", { children: [_jsxs("span", { bg: "green", fg: "black", children: [" ", "p", " "] }), _jsx("span", { fg: plugin.projectScope?.enabled ? "green" : "gray", children: plugin.projectScope?.enabled ? " ● " : " ○ " }), _jsx("span", { fg: "green", children: "Project" }), _jsx("span", { children: " team" }), plugin.projectScope?.version && (_jsxs("span", { fg: "green", children: [" v", plugin.projectScope.version] }))] }), _jsxs("text", { children: [_jsxs("span", { bg: "yellow", fg: "black", children: [" ", "l", " "] }), _jsx("span", { fg: plugin.localScope?.enabled ? "yellow" : "gray", children: plugin.localScope?.enabled ? " ● " : " ○ " }), _jsx("span", { fg: "yellow", children: "Local" }), _jsx("span", { children: " private" }), plugin.localScope?.version && (_jsxs("span", { fg: "yellow", children: [" v", plugin.localScope.version] }))] })] })] }), isInstalled && (
|
|
908
|
+
return (_jsxs("box", { flexDirection: "column", children: [_jsx("box", { justifyContent: "center", children: _jsx("text", { bg: "magenta", fg: "white", children: _jsxs("strong", { children: [" ", plugin.name, plugin.hasUpdate ? " ⬆" : "", " "] }) }) }), _jsx("box", { marginTop: 1, children: isInstalled ? (_jsx("text", { fg: "green", children: "\u25CF Installed" })) : (_jsx("text", { fg: "gray", children: "\u25CB Not installed" })) }), _jsx("box", { marginTop: 1, marginBottom: 1, children: _jsx("text", { fg: "white", children: plugin.description }) }), showVersion && (_jsxs("text", { children: [_jsx("span", { children: "Version " }), _jsxs("span", { fg: "#5c9aff", children: ["v", plugin.version] }), showInstalledVersion &&
|
|
909
|
+
plugin.installedVersion !== plugin.version && (_jsxs("span", { children: [" (v", plugin.installedVersion, " installed)"] }))] })), plugin.category && (_jsxs("text", { children: [_jsx("span", { children: "Category " }), _jsx("span", { fg: "magenta", children: plugin.category })] })), plugin.author && (_jsxs("text", { children: [_jsx("span", { children: "Author " }), _jsx("span", { children: plugin.author.name })] })), components.length > 0 && (_jsxs("text", { children: [_jsx("span", { children: "Contains " }), _jsx("span", { fg: "yellow", children: components.join(" · ") })] })), _jsxs("box", { flexDirection: "column", marginTop: 1, children: [_jsx("text", { children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsx("text", { children: _jsx("strong", { children: "Scopes:" }) }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { children: [_jsxs("span", { bg: "cyan", fg: "black", children: [" ", "u", " "] }), _jsx("span", { fg: plugin.userScope?.enabled ? "cyan" : "gray", children: plugin.userScope?.enabled ? " ● " : " ○ " }), _jsx("span", { fg: "cyan", children: "User" }), _jsx("span", { children: " global" }), plugin.userScope?.version && (_jsxs("span", { fg: "cyan", children: [" v", plugin.userScope.version] }))] }), _jsxs("text", { children: [_jsxs("span", { bg: "green", fg: "black", children: [" ", "p", " "] }), _jsx("span", { fg: plugin.projectScope?.enabled ? "green" : "gray", children: plugin.projectScope?.enabled ? " ● " : " ○ " }), _jsx("span", { fg: "green", children: "Project" }), _jsx("span", { children: " team" }), plugin.projectScope?.version && (_jsxs("span", { fg: "green", children: [" v", plugin.projectScope.version] }))] }), _jsxs("text", { children: [_jsxs("span", { bg: "yellow", fg: "black", children: [" ", "l", " "] }), _jsx("span", { fg: plugin.localScope?.enabled ? "yellow" : "gray", children: plugin.localScope?.enabled ? " ● " : " ○ " }), _jsx("span", { fg: "yellow", children: "Local" }), _jsx("span", { children: " private" }), plugin.localScope?.version && (_jsxs("span", { fg: "yellow", children: [" v", plugin.localScope.version] }))] })] })] }), isInstalled && (_jsx("box", { flexDirection: "column", marginTop: 1, children: plugin.hasUpdate && (_jsxs("box", { children: [_jsxs("text", { bg: "magenta", fg: "white", children: [" ", "U", " "] }), _jsxs("text", { children: [" Update to v", plugin.version] })] })) }))] }));
|
|
925
910
|
}
|
|
926
911
|
return null;
|
|
927
912
|
};
|
|
928
|
-
const footerHints = isSearchActive
|
|
929
|
-
? "
|
|
930
|
-
: "u/p/l:
|
|
913
|
+
const footerHints = isSearchActive
|
|
914
|
+
? "type to filter │ Enter:done │ Esc:clear"
|
|
915
|
+
: "u/p/l:toggle │ U:update │ a:all │ s:profile │ /:search";
|
|
931
916
|
// Calculate status for subtitle
|
|
932
917
|
const scopeLabel = pluginsState.scope === "global" ? "Global" : "Project";
|
|
933
918
|
const plugins = pluginsState.plugins.status === "success" ? pluginsState.plugins.data : [];
|
|
@@ -940,6 +925,6 @@ export function PluginsScreen() {
|
|
|
940
925
|
isActive: isSearchActive,
|
|
941
926
|
query: pluginsState.searchQuery,
|
|
942
927
|
placeholder: searchPlaceholder,
|
|
943
|
-
}, footerHints: footerHints, listPanel: _jsx(ScrollableList, { items: selectableItems, selectedIndex: pluginsState.selectedIndex, renderItem: renderListItem, maxHeight: dimensions.listPanelHeight }), detailPanel: renderDetail() }));
|
|
928
|
+
}, footerHints: footerHints, listPanel: _jsxs("box", { flexDirection: "column", children: [_jsx(ScrollableList, { items: selectableItems, selectedIndex: pluginsState.selectedIndex, renderItem: renderListItem, maxHeight: dimensions.listPanelHeight }), pluginsState.searchQuery && selectableItems.length === 0 && (_jsx(EmptyFilterState, { query: pluginsState.searchQuery, entityName: "plugins" }))] }), detailPanel: renderDetail() }));
|
|
944
929
|
}
|
|
945
930
|
export default PluginsScreen;
|
|
@@ -5,6 +5,7 @@ import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
|
5
5
|
import { ScreenLayout } from "../components/layout/index.js";
|
|
6
6
|
import { CategoryHeader } from "../components/CategoryHeader.js";
|
|
7
7
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
8
|
+
import { EmptyFilterState } from "../components/EmptyFilterState.js";
|
|
8
9
|
import { fuzzyFilter, highlightMatches } from "../../utils/fuzzy-search.js";
|
|
9
10
|
import { getAllMarketplaces } from "../../data/marketplaces.js";
|
|
10
11
|
import {
|
|
@@ -240,31 +241,15 @@ export function PluginsScreen() {
|
|
|
240
241
|
return;
|
|
241
242
|
}
|
|
242
243
|
|
|
243
|
-
// Navigation — always works
|
|
244
|
+
// Navigation — always works; exits search mode on navigate
|
|
244
245
|
if (event.name === "up" || event.name === "k") {
|
|
245
|
-
|
|
246
|
-
if (event.name === "k" && (hasQuery || isSearchActive)) {
|
|
247
|
-
dispatch({
|
|
248
|
-
type: "PLUGINS_SET_SEARCH",
|
|
249
|
-
query: pluginsState.searchQuery + event.name,
|
|
250
|
-
});
|
|
251
|
-
dispatch({ type: "PLUGINS_SELECT", index: 0 });
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
246
|
+
if (isSearchActive) dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
254
247
|
const newIndex = Math.max(0, pluginsState.selectedIndex - 1);
|
|
255
248
|
dispatch({ type: "PLUGINS_SELECT", index: newIndex });
|
|
256
249
|
return;
|
|
257
250
|
}
|
|
258
251
|
if (event.name === "down" || event.name === "j") {
|
|
259
|
-
|
|
260
|
-
if (event.name === "j" && (hasQuery || isSearchActive)) {
|
|
261
|
-
dispatch({
|
|
262
|
-
type: "PLUGINS_SET_SEARCH",
|
|
263
|
-
query: pluginsState.searchQuery + event.name,
|
|
264
|
-
});
|
|
265
|
-
dispatch({ type: "PLUGINS_SELECT", index: 0 });
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
252
|
+
if (isSearchActive) dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
268
253
|
const newIndex = Math.min(
|
|
269
254
|
selectableItems.length - 1,
|
|
270
255
|
pluginsState.selectedIndex + 1,
|
|
@@ -302,9 +287,9 @@ export function PluginsScreen() {
|
|
|
302
287
|
return;
|
|
303
288
|
}
|
|
304
289
|
|
|
305
|
-
// When
|
|
306
|
-
//
|
|
307
|
-
if (
|
|
290
|
+
// When actively typing in search, letters go to the query
|
|
291
|
+
// After Enter (isSearchActive=false, hasQuery=true), shortcuts resume
|
|
292
|
+
if (isSearchActive) {
|
|
308
293
|
if (event.name.length === 1 && !event.ctrl && !event.meta && !/[0-9]/.test(event.name)) {
|
|
309
294
|
dispatch({
|
|
310
295
|
type: "PLUGINS_SET_SEARCH",
|
|
@@ -315,7 +300,7 @@ export function PluginsScreen() {
|
|
|
315
300
|
return;
|
|
316
301
|
}
|
|
317
302
|
|
|
318
|
-
//
|
|
303
|
+
// Action shortcuts work when not actively typing (even with filter visible)
|
|
319
304
|
|
|
320
305
|
// Start explicit search mode with /
|
|
321
306
|
if (event.name === "/") {
|
|
@@ -332,16 +317,10 @@ export function PluginsScreen() {
|
|
|
332
317
|
else if (event.name === "l") handleScopeToggle("local");
|
|
333
318
|
else if (event.name === "U") handleUpdate();
|
|
334
319
|
else if (event.name === "a") handleUpdateAll();
|
|
335
|
-
else if (event.name === "d") handleUninstall();
|
|
336
320
|
else if (event.name === "s") handleSaveAsProfile();
|
|
337
|
-
//
|
|
338
|
-
else if (event.name
|
|
321
|
+
// "/" to enter search mode
|
|
322
|
+
else if (event.name === "/") {
|
|
339
323
|
dispatch({ type: "SET_SEARCHING", isSearching: true });
|
|
340
|
-
dispatch({
|
|
341
|
-
type: "PLUGINS_SET_SEARCH",
|
|
342
|
-
query: event.name,
|
|
343
|
-
});
|
|
344
|
-
dispatch({ type: "PLUGINS_SELECT", index: 0 });
|
|
345
324
|
}
|
|
346
325
|
});
|
|
347
326
|
|
|
@@ -812,7 +791,7 @@ export function PluginsScreen() {
|
|
|
812
791
|
const scopeLabel =
|
|
813
792
|
scope === "user" ? "User" : scope === "project" ? "Project" : "Local";
|
|
814
793
|
|
|
815
|
-
// Check if installed in this scope
|
|
794
|
+
// Check if installed in this specific scope
|
|
816
795
|
const scopeData =
|
|
817
796
|
scope === "user"
|
|
818
797
|
? plugin.userScope
|
|
@@ -822,6 +801,9 @@ export function PluginsScreen() {
|
|
|
822
801
|
const isInstalledInScope = scopeData?.enabled;
|
|
823
802
|
const installedVersion = scopeData?.version;
|
|
824
803
|
|
|
804
|
+
// Also check if installed in ANY scope (for the toggle behavior)
|
|
805
|
+
const isInstalledAnywhere = plugin.userScope?.enabled || plugin.projectScope?.enabled || plugin.localScope?.enabled;
|
|
806
|
+
|
|
825
807
|
// Check if this scope has an update available
|
|
826
808
|
const hasUpdateInScope =
|
|
827
809
|
isInstalledInScope &&
|
|
@@ -829,14 +811,19 @@ export function PluginsScreen() {
|
|
|
829
811
|
latestVersion !== "0.0.0" &&
|
|
830
812
|
installedVersion !== latestVersion;
|
|
831
813
|
|
|
832
|
-
// Determine action:
|
|
814
|
+
// Determine action: if installed in this scope → uninstall
|
|
815
|
+
// If installed anywhere else but not this scope → uninstall from detected scope
|
|
816
|
+
// Otherwise → install
|
|
833
817
|
let action: "update" | "install" | "uninstall";
|
|
834
818
|
if (isInstalledInScope && hasUpdateInScope) {
|
|
835
819
|
action = "update";
|
|
836
820
|
} else if (isInstalledInScope) {
|
|
837
821
|
action = "uninstall";
|
|
838
|
-
} else {
|
|
822
|
+
} else if (!isInstalledAnywhere) {
|
|
839
823
|
action = "install";
|
|
824
|
+
} else {
|
|
825
|
+
// Installed in a different scope — uninstall from the scope it's actually in
|
|
826
|
+
action = "uninstall";
|
|
840
827
|
}
|
|
841
828
|
|
|
842
829
|
const actionLabel =
|
|
@@ -1066,15 +1053,13 @@ export function PluginsScreen() {
|
|
|
1066
1053
|
let statusIcon = "○";
|
|
1067
1054
|
let statusColor = "gray";
|
|
1068
1055
|
|
|
1056
|
+
const isAnyScope = plugin.userScope?.enabled || plugin.projectScope?.enabled || plugin.localScope?.enabled;
|
|
1069
1057
|
if (plugin.isOrphaned) {
|
|
1070
1058
|
statusIcon = "x";
|
|
1071
1059
|
statusColor = "red";
|
|
1072
|
-
} else if (
|
|
1060
|
+
} else if (isAnyScope) {
|
|
1073
1061
|
statusIcon = "●";
|
|
1074
1062
|
statusColor = "green";
|
|
1075
|
-
} else if (plugin.installedVersion) {
|
|
1076
|
-
statusIcon = "●";
|
|
1077
|
-
statusColor = "yellow";
|
|
1078
1063
|
}
|
|
1079
1064
|
|
|
1080
1065
|
// Build version string
|
|
@@ -1198,7 +1183,7 @@ export function PluginsScreen() {
|
|
|
1198
1183
|
|
|
1199
1184
|
if (selectedItem.type === "plugin" && selectedItem.plugin) {
|
|
1200
1185
|
const plugin = selectedItem.plugin;
|
|
1201
|
-
const isInstalled = plugin.enabled || plugin.
|
|
1186
|
+
const isInstalled = plugin.userScope?.enabled || plugin.projectScope?.enabled || plugin.localScope?.enabled;
|
|
1202
1187
|
|
|
1203
1188
|
// Orphaned/deprecated plugin
|
|
1204
1189
|
if (plugin.isOrphaned) {
|
|
@@ -1260,9 +1245,7 @@ export function PluginsScreen() {
|
|
|
1260
1245
|
{/* Status line */}
|
|
1261
1246
|
<box marginTop={1}>
|
|
1262
1247
|
{isInstalled ? (
|
|
1263
|
-
<text fg=
|
|
1264
|
-
{plugin.enabled ? "● Enabled" : "● Disabled"}
|
|
1265
|
-
</text>
|
|
1248
|
+
<text fg="green">● Installed</text>
|
|
1266
1249
|
) : (
|
|
1267
1250
|
<text fg="gray">○ Not installed</text>
|
|
1268
1251
|
)}
|
|
@@ -1367,13 +1350,6 @@ export function PluginsScreen() {
|
|
|
1367
1350
|
<text> Update to v{plugin.version}</text>
|
|
1368
1351
|
</box>
|
|
1369
1352
|
)}
|
|
1370
|
-
<box>
|
|
1371
|
-
<text bg="red" fg="white">
|
|
1372
|
-
{" "}
|
|
1373
|
-
d{" "}
|
|
1374
|
-
</text>
|
|
1375
|
-
<text> Uninstall</text>
|
|
1376
|
-
</box>
|
|
1377
1353
|
</box>
|
|
1378
1354
|
)}
|
|
1379
1355
|
</box>
|
|
@@ -1383,10 +1359,9 @@ export function PluginsScreen() {
|
|
|
1383
1359
|
return null;
|
|
1384
1360
|
};
|
|
1385
1361
|
|
|
1386
|
-
const footerHints =
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
: "u/p/l:scope │ U:update │ a:all │ d:remove │ s:profile │ type to search";
|
|
1362
|
+
const footerHints = isSearchActive
|
|
1363
|
+
? "type to filter │ Enter:done │ Esc:clear"
|
|
1364
|
+
: "u/p/l:toggle │ U:update │ a:all │ s:profile │ /:search";
|
|
1390
1365
|
|
|
1391
1366
|
// Calculate status for subtitle
|
|
1392
1367
|
const scopeLabel = pluginsState.scope === "global" ? "Global" : "Project";
|
|
@@ -1411,12 +1386,17 @@ export function PluginsScreen() {
|
|
|
1411
1386
|
}}
|
|
1412
1387
|
footerHints={footerHints}
|
|
1413
1388
|
listPanel={
|
|
1414
|
-
<
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1389
|
+
<box flexDirection="column">
|
|
1390
|
+
<ScrollableList
|
|
1391
|
+
items={selectableItems}
|
|
1392
|
+
selectedIndex={pluginsState.selectedIndex}
|
|
1393
|
+
renderItem={renderListItem}
|
|
1394
|
+
maxHeight={dimensions.listPanelHeight}
|
|
1395
|
+
/>
|
|
1396
|
+
{pluginsState.searchQuery && selectableItems.length === 0 && (
|
|
1397
|
+
<EmptyFilterState query={pluginsState.searchQuery} entityName="plugins" />
|
|
1398
|
+
)}
|
|
1399
|
+
</box>
|
|
1420
1400
|
}
|
|
1421
1401
|
detailPanel={renderDetail()}
|
|
1422
1402
|
/>
|