claudeup 3.7.2 → 3.8.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/settings-catalog.js +612 -0
- package/src/data/settings-catalog.ts +689 -0
- package/src/services/plugin-manager.js +2 -0
- package/src/services/plugin-manager.ts +3 -0
- package/src/services/profiles.js +161 -0
- package/src/services/profiles.ts +225 -0
- package/src/services/settings-manager.js +108 -0
- package/src/services/settings-manager.ts +140 -0
- package/src/types/index.ts +34 -0
- package/src/ui/App.js +17 -18
- package/src/ui/App.tsx +21 -23
- package/src/ui/components/TabBar.js +8 -8
- package/src/ui/components/TabBar.tsx +14 -19
- package/src/ui/components/layout/ScreenLayout.js +8 -14
- package/src/ui/components/layout/ScreenLayout.tsx +51 -58
- package/src/ui/components/modals/ModalContainer.js +43 -11
- package/src/ui/components/modals/ModalContainer.tsx +44 -12
- package/src/ui/components/modals/SelectModal.js +4 -18
- package/src/ui/components/modals/SelectModal.tsx +10 -21
- package/src/ui/screens/CliToolsScreen.js +2 -2
- package/src/ui/screens/CliToolsScreen.tsx +8 -8
- package/src/ui/screens/EnvVarsScreen.js +248 -116
- package/src/ui/screens/EnvVarsScreen.tsx +419 -184
- package/src/ui/screens/McpRegistryScreen.tsx +18 -6
- package/src/ui/screens/McpScreen.js +1 -1
- package/src/ui/screens/McpScreen.tsx +15 -5
- package/src/ui/screens/ModelSelectorScreen.js +3 -5
- package/src/ui/screens/ModelSelectorScreen.tsx +12 -16
- package/src/ui/screens/PluginsScreen.js +154 -66
- package/src/ui/screens/PluginsScreen.tsx +280 -97
- package/src/ui/screens/ProfilesScreen.js +255 -0
- package/src/ui/screens/ProfilesScreen.tsx +487 -0
- package/src/ui/screens/StatusLineScreen.js +2 -2
- package/src/ui/screens/StatusLineScreen.tsx +10 -12
- package/src/ui/screens/index.js +2 -2
- package/src/ui/screens/index.ts +2 -2
- package/src/ui/state/AppContext.js +2 -1
- package/src/ui/state/AppContext.tsx +2 -0
- package/src/ui/state/reducer.js +63 -19
- package/src/ui/state/reducer.ts +68 -19
- package/src/ui/state/types.ts +33 -14
- package/src/utils/clipboard.js +56 -0
- package/src/utils/clipboard.ts +58 -0
|
@@ -239,25 +239,35 @@ export function McpRegistryScreen() {
|
|
|
239
239
|
|
|
240
240
|
return (
|
|
241
241
|
<box flexDirection="column">
|
|
242
|
-
<text fg="magenta"
|
|
242
|
+
<text fg="magenta">
|
|
243
|
+
<strong>{selectedServer.name}</strong>
|
|
244
|
+
</text>
|
|
243
245
|
<box marginTop={1}>
|
|
244
246
|
<text>{selectedServer.short_description}</text>
|
|
245
247
|
</box>
|
|
246
248
|
<box marginTop={1}>
|
|
247
|
-
<text
|
|
249
|
+
<text>
|
|
250
|
+
<strong>Version: </strong>
|
|
251
|
+
</text>
|
|
248
252
|
<text fg="green">{versionDisplay}</text>
|
|
249
253
|
</box>
|
|
250
254
|
<box>
|
|
251
|
-
<text
|
|
255
|
+
<text>
|
|
256
|
+
<strong>Published: </strong>
|
|
257
|
+
</text>
|
|
252
258
|
<text fg="cyan">{dateDisplay}</text>
|
|
253
259
|
</box>
|
|
254
260
|
<box marginTop={1} flexDirection="column">
|
|
255
|
-
<text
|
|
261
|
+
<text>
|
|
262
|
+
<strong>URL:</strong>
|
|
263
|
+
</text>
|
|
256
264
|
<text fg="cyan">{selectedServer.url}</text>
|
|
257
265
|
</box>
|
|
258
266
|
{selectedServer.source_code_url && (
|
|
259
267
|
<box marginTop={1} flexDirection="column">
|
|
260
|
-
<text
|
|
268
|
+
<text>
|
|
269
|
+
<strong>Source:</strong>
|
|
270
|
+
</text>
|
|
261
271
|
<text fg="gray">{selectedServer.source_code_url}</text>
|
|
262
272
|
</box>
|
|
263
273
|
)}
|
|
@@ -282,7 +292,9 @@ export function McpRegistryScreen() {
|
|
|
282
292
|
</text>
|
|
283
293
|
) : (
|
|
284
294
|
<text>
|
|
285
|
-
<span
|
|
295
|
+
<span>
|
|
296
|
+
<strong>{server.name}</strong>
|
|
297
|
+
</span>
|
|
286
298
|
<span fg="green">{version}</span>
|
|
287
299
|
</text>
|
|
288
300
|
);
|
|
@@ -198,7 +198,7 @@ export function McpScreen() {
|
|
|
198
198
|
const server = selectedItem.server;
|
|
199
199
|
const isInstalled = mcp.installedServers[server.name] !== undefined;
|
|
200
200
|
const isEnabled = mcp.installedServers[server.name] === true;
|
|
201
|
-
return (_jsxs("box", { flexDirection: "column", children: [_jsxs("box", { marginBottom: 1, children: [_jsx("text", { fg: "cyan", children: _jsxs("strong", { children: ["\u26A1 ", server.name] }) }), server.requiresConfig && _jsx("text", { fg: "yellow", children: " \u2699" })] }), _jsx("text", { fg: "gray", children: server.description }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("box", { children: [_jsx("text", { fg: "gray", children: "Status " }), isInstalled && isEnabled ? (_jsx("text", { fg: "green", children: "\u25CF Installed" })) : isInstalled ? (_jsx("text", { fg: "yellow", children: "\u25CF Disabled" })) : (_jsx("text", { fg: "gray", children: "\u25CB Not installed" }))] }), _jsxs("box", { children: [_jsx("text", { fg: "gray", children: "Type " }), _jsx("text", { fg: "white", children: server.type === "http" ? "HTTP" : "Command" })] }), server.type === "http" ? (_jsxs("box", { children: [_jsx("text", { fg: "gray", children: "URL " }), _jsx("text", { fg: "
|
|
201
|
+
return (_jsxs("box", { flexDirection: "column", children: [_jsxs("box", { marginBottom: 1, children: [_jsx("text", { fg: "cyan", children: _jsxs("strong", { children: ["\u26A1 ", server.name] }) }), server.requiresConfig && _jsx("text", { fg: "yellow", children: " \u2699" })] }), _jsx("text", { fg: "gray", children: server.description }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("box", { children: [_jsx("text", { fg: "gray", children: "Status " }), isInstalled && isEnabled ? (_jsx("text", { fg: "green", children: "\u25CF Installed" })) : isInstalled ? (_jsx("text", { fg: "yellow", children: "\u25CF Disabled" })) : (_jsx("text", { fg: "gray", children: "\u25CB Not installed" }))] }), _jsxs("box", { children: [_jsx("text", { fg: "gray", children: "Type " }), _jsx("text", { fg: "white", children: server.type === "http" ? "HTTP" : "Command" })] }), server.type === "http" ? (_jsxs("box", { children: [_jsx("text", { fg: "gray", children: "URL " }), _jsx("text", { fg: "#5c9aff", children: server.url })] })) : (_jsxs("box", { children: [_jsx("text", { fg: "gray", children: "Command " }), _jsx("text", { fg: "cyan", children: server.command })] })), server.requiresConfig && (_jsxs("box", { children: [_jsx("text", { fg: "gray", children: "Config " }), _jsxs("text", { fg: "yellow", children: [server.configFields?.length || 0, " fields required"] })] }))] }), _jsx("box", { marginTop: 2, children: isInstalled ? (_jsxs("box", { children: [_jsxs("text", { bg: "red", fg: "white", children: [" ", "Enter", " "] }), _jsx("text", { fg: "gray", children: " Remove server" })] })) : (_jsxs("box", { children: [_jsxs("text", { bg: "green", fg: "black", children: [" ", "Enter", " "] }), _jsx("text", { fg: "gray", children: " Install server" })] })) })] }));
|
|
202
202
|
};
|
|
203
203
|
const renderListItem = (item, _idx, isSelected) => {
|
|
204
204
|
// Category header
|
|
@@ -269,7 +269,9 @@ export function McpScreen() {
|
|
|
269
269
|
return (
|
|
270
270
|
<box flexDirection="column">
|
|
271
271
|
<box marginBottom={1}>
|
|
272
|
-
<text fg="cyan"
|
|
272
|
+
<text fg="cyan">
|
|
273
|
+
<strong>⚡ {server.name}</strong>
|
|
274
|
+
</text>
|
|
273
275
|
{server.requiresConfig && <text fg="yellow"> ⚙</text>}
|
|
274
276
|
</box>
|
|
275
277
|
|
|
@@ -295,7 +297,7 @@ export function McpScreen() {
|
|
|
295
297
|
{server.type === "http" ? (
|
|
296
298
|
<box>
|
|
297
299
|
<text fg="gray">URL </text>
|
|
298
|
-
<text fg="
|
|
300
|
+
<text fg="#5c9aff">{server.url}</text>
|
|
299
301
|
</box>
|
|
300
302
|
) : (
|
|
301
303
|
<box>
|
|
@@ -316,12 +318,18 @@ export function McpScreen() {
|
|
|
316
318
|
<box marginTop={2}>
|
|
317
319
|
{isInstalled ? (
|
|
318
320
|
<box>
|
|
319
|
-
<text bg="red" fg="white">
|
|
321
|
+
<text bg="red" fg="white">
|
|
322
|
+
{" "}
|
|
323
|
+
Enter{" "}
|
|
324
|
+
</text>
|
|
320
325
|
<text fg="gray"> Remove server</text>
|
|
321
326
|
</box>
|
|
322
327
|
) : (
|
|
323
328
|
<box>
|
|
324
|
-
<text bg="green" fg="black">
|
|
329
|
+
<text bg="green" fg="black">
|
|
330
|
+
{" "}
|
|
331
|
+
Enter{" "}
|
|
332
|
+
</text>
|
|
325
333
|
<text fg="gray"> Install server</text>
|
|
326
334
|
</box>
|
|
327
335
|
)}
|
|
@@ -338,7 +346,9 @@ export function McpScreen() {
|
|
|
338
346
|
// Category header
|
|
339
347
|
if (item.isCategory) {
|
|
340
348
|
return (
|
|
341
|
-
<text fg="magenta"
|
|
349
|
+
<text fg="magenta">
|
|
350
|
+
<strong>▸ {item.label}</strong>
|
|
351
|
+
</text>
|
|
342
352
|
);
|
|
343
353
|
}
|
|
344
354
|
|
|
@@ -243,9 +243,7 @@ export function ModelSelectorScreen() {
|
|
|
243
243
|
return;
|
|
244
244
|
}
|
|
245
245
|
// Regular text input (single character keys)
|
|
246
|
-
if (event.name.length === 1 &&
|
|
247
|
-
!event.ctrl &&
|
|
248
|
-
!event.shift) {
|
|
246
|
+
if (event.name.length === 1 && !event.ctrl && !event.shift) {
|
|
249
247
|
dispatch({
|
|
250
248
|
type: "MODEL_SELECTOR_SET_SEARCH",
|
|
251
249
|
query: modelSelector.searchQuery + event.name,
|
|
@@ -258,7 +256,7 @@ export function ModelSelectorScreen() {
|
|
|
258
256
|
if (item.type === "header") {
|
|
259
257
|
// Header style: "Name ----------------------------- Status"
|
|
260
258
|
const status = item.configured ? "✔ Configured" : "";
|
|
261
|
-
return (_jsxs("box", { margin: 0, children: [_jsxs("text", { fg: "gray", children: [item.label, " "] }), _jsx("text", { fg: "#888888", children: "\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF" }), status && _jsxs("text", { fg: "green", children: [" ", status] }), item.provider && _jsxs("text", { fg: "
|
|
259
|
+
return (_jsxs("box", { margin: 0, children: [_jsxs("text", { fg: "gray", children: [item.label, " "] }), _jsx("text", { fg: "#888888", children: "\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF" }), status && _jsxs("text", { fg: "green", children: [" ", status] }), item.provider && _jsxs("text", { fg: "#5c9aff", children: [" ", item.provider] })] }));
|
|
262
260
|
}
|
|
263
261
|
// Model item
|
|
264
262
|
// Matches highlighting
|
|
@@ -271,7 +269,7 @@ export function ModelSelectorScreen() {
|
|
|
271
269
|
: "cyan"
|
|
272
270
|
: isSelected
|
|
273
271
|
? "white"
|
|
274
|
-
: "white", children: seg.highlighted ? _jsx("u", { children: _jsx("strong", { children: seg.text }) }) : seg.text }, i)))
|
|
272
|
+
: "white", children: seg.highlighted ? (_jsx("u", { children: _jsx("strong", { children: seg.text }) })) : (seg.text) }, i)))
|
|
275
273
|
: item.label }));
|
|
276
274
|
if (isSelected) {
|
|
277
275
|
return (_jsxs("box", { overflow: "hidden", children: [_jsx("text", { bg: "magenta", fg: "white", children: " " }), _jsx("text", { bg: "magenta", fg: "white", children: _jsx(Label, {}) }), _jsx("box", { flexGrow: 1, children: _jsx("text", { bg: "magenta", children: " " }) }), item.provider && (_jsxs("text", { bg: "magenta", fg: "white", children: [item.provider, " "] }))] }));
|
|
@@ -282,11 +282,7 @@ export function ModelSelectorScreen() {
|
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
// Regular text input (single character keys)
|
|
285
|
-
if (
|
|
286
|
-
event.name.length === 1 &&
|
|
287
|
-
!event.ctrl &&
|
|
288
|
-
!event.shift
|
|
289
|
-
) {
|
|
285
|
+
if (event.name.length === 1 && !event.ctrl && !event.shift) {
|
|
290
286
|
dispatch({
|
|
291
287
|
type: "MODEL_SELECTOR_SET_SEARCH",
|
|
292
288
|
query: modelSelector.searchQuery + event.name,
|
|
@@ -304,11 +300,9 @@ export function ModelSelectorScreen() {
|
|
|
304
300
|
return (
|
|
305
301
|
<box margin={0}>
|
|
306
302
|
<text fg="gray">{item.label} </text>
|
|
307
|
-
<text fg="#888888">
|
|
308
|
-
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
|
|
309
|
-
</text>
|
|
303
|
+
<text fg="#888888">⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯</text>
|
|
310
304
|
{status && <text fg="green"> {status}</text>}
|
|
311
|
-
{item.provider && <text fg="
|
|
305
|
+
{item.provider && <text fg="#5c9aff"> {item.provider}</text>}
|
|
312
306
|
</box>
|
|
313
307
|
);
|
|
314
308
|
}
|
|
@@ -334,7 +328,13 @@ export function ModelSelectorScreen() {
|
|
|
334
328
|
: "white"
|
|
335
329
|
}
|
|
336
330
|
>
|
|
337
|
-
{seg.highlighted ?
|
|
331
|
+
{seg.highlighted ? (
|
|
332
|
+
<u>
|
|
333
|
+
<strong>{seg.text}</strong>
|
|
334
|
+
</u>
|
|
335
|
+
) : (
|
|
336
|
+
seg.text
|
|
337
|
+
)}
|
|
338
338
|
</text>
|
|
339
339
|
))
|
|
340
340
|
: item.label}
|
|
@@ -395,15 +395,11 @@ export function ModelSelectorScreen() {
|
|
|
395
395
|
<box flexDirection="row" justifyContent="space-between">
|
|
396
396
|
<box>
|
|
397
397
|
<text fg="#7e57c2">Switch Model </text>
|
|
398
|
-
<text
|
|
399
|
-
fg={modelSelector.taskSize === "large" ? "white" : "gray"}
|
|
400
|
-
>
|
|
398
|
+
<text fg={modelSelector.taskSize === "large" ? "white" : "gray"}>
|
|
401
399
|
{modelSelector.taskSize === "large" ? "◎" : "○"} Large Task
|
|
402
400
|
{" "}
|
|
403
401
|
</text>
|
|
404
|
-
<text
|
|
405
|
-
fg={modelSelector.taskSize === "small" ? "white" : "gray"}
|
|
406
|
-
>
|
|
402
|
+
<text fg={modelSelector.taskSize === "small" ? "white" : "gray"}>
|
|
407
403
|
{modelSelector.taskSize === "small" ? "◎" : "○"} Small Task
|
|
408
404
|
</text>
|
|
409
405
|
</box>
|
|
@@ -9,8 +9,9 @@ import { ScrollableList } from "../components/ScrollableList.js";
|
|
|
9
9
|
import { fuzzyFilter, highlightMatches } from "../../utils/fuzzy-search.js";
|
|
10
10
|
import { getAllMarketplaces } from "../../data/marketplaces.js";
|
|
11
11
|
import { getAvailablePlugins, refreshAllMarketplaces, clearMarketplaceCache, getLocalMarketplacesInfo, } from "../../services/plugin-manager.js";
|
|
12
|
-
import { setMcpEnvVar, getMcpEnvVars, saveGlobalInstalledPluginVersion, saveLocalInstalledPluginVersion, } from "../../services/claude-settings.js";
|
|
13
|
-
import {
|
|
12
|
+
import { setMcpEnvVar, getMcpEnvVars, readSettings, saveGlobalInstalledPluginVersion, saveLocalInstalledPluginVersion, } from "../../services/claude-settings.js";
|
|
13
|
+
import { saveProfile } from "../../services/profiles.js";
|
|
14
|
+
import { saveInstalledPluginVersion } from "../../services/plugin-manager.js";
|
|
14
15
|
import { installPlugin as cliInstallPlugin, uninstallPlugin as cliUninstallPlugin, updatePlugin as cliUpdatePlugin, } from "../../services/claude-cli.js";
|
|
15
16
|
import { getPluginEnvRequirements, getPluginSourcePath, } from "../../services/plugin-mcp-config.js";
|
|
16
17
|
import { getPluginSetupFromSource, checkMissingDeps, installPluginDeps, } from "../../services/plugin-setup.js";
|
|
@@ -146,50 +147,78 @@ export function PluginsScreen() {
|
|
|
146
147
|
const selectableItems = useMemo(() => {
|
|
147
148
|
return filteredItems.filter((item) => item.type === "plugin" || item.type === "category");
|
|
148
149
|
}, [filteredItems]);
|
|
149
|
-
// Keyboard handling
|
|
150
|
+
// Keyboard handling — inline search with live filtering
|
|
150
151
|
useKeyboard((event) => {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
if (state.modal)
|
|
153
|
+
return;
|
|
154
|
+
const hasQuery = pluginsState.searchQuery.length > 0;
|
|
155
|
+
// Escape: always clear search state fully
|
|
156
|
+
if (event.name === "escape") {
|
|
157
|
+
if (hasQuery || isSearchActive) {
|
|
155
158
|
dispatch({ type: "PLUGINS_SET_SEARCH", query: "" });
|
|
156
|
-
}
|
|
157
|
-
else if (event.name === "enter") {
|
|
158
159
|
dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
159
|
-
|
|
160
|
+
dispatch({ type: "PLUGINS_SELECT", index: 0 });
|
|
161
|
+
}
|
|
162
|
+
// Don't return — let GlobalKeyHandler handle Escape too (for quit)
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
// Backspace: remove last char from search query
|
|
166
|
+
if (event.name === "backspace" || event.name === "delete") {
|
|
167
|
+
if (hasQuery) {
|
|
168
|
+
const newQuery = pluginsState.searchQuery.slice(0, -1);
|
|
169
|
+
dispatch({ type: "PLUGINS_SET_SEARCH", query: newQuery });
|
|
170
|
+
dispatch({ type: "PLUGINS_SELECT", index: 0 });
|
|
171
|
+
// If query becomes empty, exit search mode
|
|
172
|
+
if (newQuery.length === 0) {
|
|
173
|
+
dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
174
|
+
}
|
|
160
175
|
}
|
|
161
|
-
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// Navigation — always works (even during search)
|
|
179
|
+
if (event.name === "up" || event.name === "k") {
|
|
180
|
+
// 'k' navigates when query is empty, otherwise appends to search
|
|
181
|
+
if (event.name === "k" && (hasQuery || isSearchActive)) {
|
|
162
182
|
dispatch({
|
|
163
183
|
type: "PLUGINS_SET_SEARCH",
|
|
164
|
-
query: pluginsState.searchQuery.
|
|
184
|
+
query: pluginsState.searchQuery + event.name,
|
|
165
185
|
});
|
|
186
|
+
dispatch({ type: "PLUGINS_SELECT", index: 0 });
|
|
187
|
+
return;
|
|
166
188
|
}
|
|
167
|
-
|
|
189
|
+
const newIndex = Math.max(0, pluginsState.selectedIndex - 1);
|
|
190
|
+
dispatch({ type: "PLUGINS_SELECT", index: newIndex });
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (event.name === "down" || event.name === "j") {
|
|
194
|
+
// 'j' navigates when query is empty, otherwise appends to search
|
|
195
|
+
if (event.name === "j" && (hasQuery || isSearchActive)) {
|
|
168
196
|
dispatch({
|
|
169
197
|
type: "PLUGINS_SET_SEARCH",
|
|
170
198
|
query: pluginsState.searchQuery + event.name,
|
|
171
199
|
});
|
|
200
|
+
dispatch({ type: "PLUGINS_SELECT", index: 0 });
|
|
201
|
+
return;
|
|
172
202
|
}
|
|
203
|
+
const newIndex = Math.min(selectableItems.length - 1, pluginsState.selectedIndex + 1);
|
|
204
|
+
dispatch({ type: "PLUGINS_SELECT", index: newIndex });
|
|
173
205
|
return;
|
|
174
206
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
207
|
+
// Enter — exit search mode (keep filter active) + select/install
|
|
208
|
+
if (event.name === "enter") {
|
|
209
|
+
if (isSearchActive) {
|
|
210
|
+
dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
211
|
+
// Keep the query — filter stays active, shortcuts resume
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
handleSelect();
|
|
180
215
|
return;
|
|
181
216
|
}
|
|
182
|
-
//
|
|
183
|
-
if (event.name === "
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
else if (event.name === "down" || event.name === "j") {
|
|
188
|
-
const newIndex = Math.min(selectableItems.length - 1, pluginsState.selectedIndex + 1);
|
|
189
|
-
dispatch({ type: "PLUGINS_SELECT", index: newIndex });
|
|
190
|
-
}
|
|
191
|
-
// Collapse/expand marketplace
|
|
192
|
-
else if ((event.name === "left" || event.name === "right" || event.name === "<" || event.name === ">") &&
|
|
217
|
+
// Collapse/expand marketplace — always works
|
|
218
|
+
if ((event.name === "left" ||
|
|
219
|
+
event.name === "right" ||
|
|
220
|
+
event.name === "<" ||
|
|
221
|
+
event.name === ">") &&
|
|
193
222
|
selectableItems[pluginsState.selectedIndex]?.marketplace) {
|
|
194
223
|
const item = selectableItems[pluginsState.selectedIndex];
|
|
195
224
|
if (item?.marketplace) {
|
|
@@ -198,44 +227,55 @@ export function PluginsScreen() {
|
|
|
198
227
|
name: item.marketplace.name,
|
|
199
228
|
});
|
|
200
229
|
}
|
|
230
|
+
return;
|
|
201
231
|
}
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
|
|
232
|
+
// When search query is non-empty, printable letters go to the query
|
|
233
|
+
// (shortcuts are suspended while filtering, digits skip to let tab nav work)
|
|
234
|
+
if (hasQuery || isSearchActive) {
|
|
235
|
+
if (event.name.length === 1 && !event.ctrl && !event.meta && !/[0-9]/.test(event.name)) {
|
|
236
|
+
dispatch({
|
|
237
|
+
type: "PLUGINS_SET_SEARCH",
|
|
238
|
+
query: pluginsState.searchQuery + event.name,
|
|
239
|
+
});
|
|
240
|
+
dispatch({ type: "PLUGINS_SELECT", index: 0 });
|
|
241
|
+
}
|
|
242
|
+
return;
|
|
205
243
|
}
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
244
|
+
// When search query is empty: action shortcuts work normally
|
|
245
|
+
// Start explicit search mode with /
|
|
246
|
+
if (event.name === "/") {
|
|
247
|
+
dispatch({ type: "SET_SEARCHING", isSearching: true });
|
|
248
|
+
return;
|
|
209
249
|
}
|
|
210
|
-
//
|
|
211
|
-
|
|
250
|
+
// Action shortcuts (only when query is empty)
|
|
251
|
+
if (event.name === "r")
|
|
252
|
+
handleRefresh();
|
|
253
|
+
else if (event.name === "n")
|
|
254
|
+
handleShowAddMarketplaceInstructions();
|
|
255
|
+
else if (event.name === "t")
|
|
212
256
|
handleShowTeamConfigHelp();
|
|
213
|
-
|
|
214
|
-
// Scope-specific toggle shortcuts (u/p/l)
|
|
215
|
-
else if (event.name === "u") {
|
|
257
|
+
else if (event.name === "u")
|
|
216
258
|
handleScopeToggle("user");
|
|
217
|
-
|
|
218
|
-
else if (event.name === "p") {
|
|
259
|
+
else if (event.name === "p")
|
|
219
260
|
handleScopeToggle("project");
|
|
220
|
-
|
|
221
|
-
else if (event.name === "l") {
|
|
261
|
+
else if (event.name === "l")
|
|
222
262
|
handleScopeToggle("local");
|
|
223
|
-
|
|
224
|
-
// Update plugin (Shift+U)
|
|
225
|
-
else if (event.name === "U") {
|
|
263
|
+
else if (event.name === "U")
|
|
226
264
|
handleUpdate();
|
|
227
|
-
|
|
228
|
-
// Update all
|
|
229
|
-
else if (event.name === "a") {
|
|
265
|
+
else if (event.name === "a")
|
|
230
266
|
handleUpdateAll();
|
|
231
|
-
|
|
232
|
-
// Delete/uninstall
|
|
233
|
-
else if (event.name === "d") {
|
|
267
|
+
else if (event.name === "d")
|
|
234
268
|
handleUninstall();
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
269
|
+
else if (event.name === "s")
|
|
270
|
+
handleSaveAsProfile();
|
|
271
|
+
// Any other printable letter: start inline search (skip digits — used for tab nav)
|
|
272
|
+
else if (event.name.length === 1 && !event.ctrl && !event.meta && !/[0-9]/.test(event.name)) {
|
|
273
|
+
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 });
|
|
239
279
|
}
|
|
240
280
|
});
|
|
241
281
|
// Handle actions
|
|
@@ -367,7 +407,8 @@ export function PluginsScreen() {
|
|
|
367
407
|
const hasMissing = (missing.pip?.length || 0) +
|
|
368
408
|
(missing.brew?.length || 0) +
|
|
369
409
|
(missing.npm?.length || 0) +
|
|
370
|
-
(missing.cargo?.length || 0) >
|
|
410
|
+
(missing.cargo?.length || 0) >
|
|
411
|
+
0;
|
|
371
412
|
if (!hasMissing)
|
|
372
413
|
return;
|
|
373
414
|
// Build description of what will be installed
|
|
@@ -654,6 +695,37 @@ export function PluginsScreen() {
|
|
|
654
695
|
await modal.message("Error", `Failed: ${error}`, "error");
|
|
655
696
|
}
|
|
656
697
|
};
|
|
698
|
+
const handleSaveAsProfile = async () => {
|
|
699
|
+
// Read current enabledPlugins from project settings
|
|
700
|
+
const settings = await readSettings(state.projectPath);
|
|
701
|
+
const enabledPlugins = settings.enabledPlugins ?? {};
|
|
702
|
+
const name = await modal.input("Save Profile", "Profile name:");
|
|
703
|
+
if (name === null || !name.trim())
|
|
704
|
+
return;
|
|
705
|
+
const scopeChoice = await modal.select("Save to scope", "Where should this profile be saved?", [
|
|
706
|
+
{
|
|
707
|
+
label: "User — ~/.claude/profiles.json (available everywhere)",
|
|
708
|
+
value: "user",
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
label: "Project — .claude/profiles.json (shared with team via git)",
|
|
712
|
+
value: "project",
|
|
713
|
+
},
|
|
714
|
+
]);
|
|
715
|
+
if (scopeChoice === null)
|
|
716
|
+
return;
|
|
717
|
+
const scope = scopeChoice;
|
|
718
|
+
modal.loading("Saving profile...");
|
|
719
|
+
try {
|
|
720
|
+
await saveProfile(name.trim(), enabledPlugins, scope, state.projectPath);
|
|
721
|
+
modal.hideModal();
|
|
722
|
+
await modal.message("Saved", `Profile "${name.trim()}" saved.\nPress 6 to manage profiles.`, "success");
|
|
723
|
+
}
|
|
724
|
+
catch (error) {
|
|
725
|
+
modal.hideModal();
|
|
726
|
+
await modal.message("Error", `Failed to save profile: ${error}`, "error");
|
|
727
|
+
}
|
|
728
|
+
};
|
|
657
729
|
const handleUninstall = async () => {
|
|
658
730
|
const item = selectableItems[pluginsState.selectedIndex];
|
|
659
731
|
if (!item || item.type !== "plugin" || !item.plugin)
|
|
@@ -747,7 +819,11 @@ export function PluginsScreen() {
|
|
|
747
819
|
const plugin = item.plugin;
|
|
748
820
|
let statusIcon = "○";
|
|
749
821
|
let statusColor = "gray";
|
|
750
|
-
if (plugin.
|
|
822
|
+
if (plugin.isOrphaned) {
|
|
823
|
+
statusIcon = "x";
|
|
824
|
+
statusColor = "red";
|
|
825
|
+
}
|
|
826
|
+
else if (plugin.enabled) {
|
|
751
827
|
statusIcon = "●";
|
|
752
828
|
statusColor = "green";
|
|
753
829
|
}
|
|
@@ -757,7 +833,10 @@ export function PluginsScreen() {
|
|
|
757
833
|
}
|
|
758
834
|
// Build version string
|
|
759
835
|
let versionStr = "";
|
|
760
|
-
if (plugin.
|
|
836
|
+
if (plugin.isOrphaned) {
|
|
837
|
+
versionStr = " deprecated";
|
|
838
|
+
}
|
|
839
|
+
else if (plugin.installedVersion && plugin.installedVersion !== "0.0.0") {
|
|
761
840
|
versionStr = ` v${plugin.installedVersion}`;
|
|
762
841
|
if (plugin.hasUpdate && plugin.version) {
|
|
763
842
|
versionStr += ` → v${plugin.version}`;
|
|
@@ -774,6 +853,11 @@ export function PluginsScreen() {
|
|
|
774
853
|
const displayName = segments
|
|
775
854
|
? segments.map((seg) => seg.text).join("")
|
|
776
855
|
: plugin.name;
|
|
856
|
+
if (plugin.isOrphaned) {
|
|
857
|
+
const ver = plugin.installedVersion && plugin.installedVersion !== "0.0.0"
|
|
858
|
+
? ` v${plugin.installedVersion}` : "";
|
|
859
|
+
return (_jsxs("text", { children: [_jsxs("span", { fg: "red", children: [" ", statusIcon, " "] }), _jsx("span", { fg: "gray", children: displayName }), ver && _jsx("span", { fg: "yellow", children: ver }), _jsx("span", { fg: "red", children: " deprecated" })] }));
|
|
860
|
+
}
|
|
777
861
|
return (_jsxs("text", { children: [_jsxs("span", { fg: statusColor, children: [" ", statusIcon, " "] }), _jsx("span", { children: displayName }), _jsx("span", { fg: plugin.hasUpdate ? "yellow" : "gray", children: versionStr })] }));
|
|
778
862
|
}
|
|
779
863
|
return _jsx("text", { fg: "gray", children: item.label });
|
|
@@ -811,11 +895,15 @@ export function PluginsScreen() {
|
|
|
811
895
|
actionHint = "Remove";
|
|
812
896
|
}
|
|
813
897
|
}
|
|
814
|
-
return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "cyan", children: _jsxs("strong", { children: [mp.displayName, getBadge()] }) }), _jsx("text", { fg: "gray", children: mp.description || "No description" }), _jsx("text", { fg: isEnabled ? "green" : "gray", children: isEnabled ? "● Added" : "○ Not added" }), _jsxs("text", { fg: "
|
|
898
|
+
return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "cyan", children: _jsxs("strong", { children: [mp.displayName, getBadge()] }) }), _jsx("text", { fg: "gray", children: mp.description || "No description" }), _jsx("text", { fg: isEnabled ? "green" : "gray", children: isEnabled ? "● Added" : "○ Not added" }), _jsxs("text", { fg: "#5c9aff", children: ["github.com/", mp.source.repo] }), _jsxs("text", { children: ["Plugins: ", selectedItem.pluginCount || 0] }), _jsxs("box", { marginTop: 1, children: [_jsxs("text", { bg: isEnabled ? "cyan" : "green", fg: "black", children: [" ", "Enter", " "] }), _jsxs("text", { fg: "gray", children: [" ", actionHint] })] }), isEnabled && (_jsx("box", { children: _jsx("text", { fg: "gray", children: "\u2190 \u2192 to expand/collapse" }) }))] }));
|
|
815
899
|
}
|
|
816
900
|
if (selectedItem.type === "plugin" && selectedItem.plugin) {
|
|
817
901
|
const plugin = selectedItem.plugin;
|
|
818
902
|
const isInstalled = plugin.enabled || plugin.installedVersion;
|
|
903
|
+
// Orphaned/deprecated plugin
|
|
904
|
+
if (plugin.isOrphaned) {
|
|
905
|
+
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)" })] }) }))] }));
|
|
906
|
+
}
|
|
819
907
|
// Build component counts
|
|
820
908
|
const components = [];
|
|
821
909
|
if (plugin.agents?.length)
|
|
@@ -832,14 +920,14 @@ export function PluginsScreen() {
|
|
|
832
920
|
// Show version only if valid (not null, not 0.0.0)
|
|
833
921
|
const showVersion = plugin.version && plugin.version !== "0.0.0";
|
|
834
922
|
const showInstalledVersion = plugin.installedVersion && plugin.installedVersion !== "0.0.0";
|
|
835
|
-
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: plugin.enabled ? "green" : "yellow", children: plugin.enabled ? "● Enabled" : "● Disabled" })) : (_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: "
|
|
836
|
-
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: [
|
|
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: plugin.enabled ? "green" : "yellow", children: plugin.enabled ? "● Enabled" : "● Disabled" })) : (_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 &&
|
|
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 && (_jsxs("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] })] })), _jsxs("box", { children: [_jsxs("text", { bg: "red", fg: "white", children: [" ", "d", " "] }), _jsx("text", { children: " Uninstall" })] })] }))] }));
|
|
837
925
|
}
|
|
838
926
|
return null;
|
|
839
927
|
};
|
|
840
|
-
const footerHints = isSearchActive
|
|
841
|
-
? "
|
|
842
|
-
: "u/p/l:scope │ U:update │ a:all │ d:remove │
|
|
928
|
+
const footerHints = isSearchActive || pluginsState.searchQuery
|
|
929
|
+
? "↑↓:nav │ Enter:select │ Esc:clear │ type to filter"
|
|
930
|
+
: "u/p/l:scope │ U:update │ a:all │ d:remove │ s:profile │ type to search";
|
|
843
931
|
// Calculate status for subtitle
|
|
844
932
|
const scopeLabel = pluginsState.scope === "global" ? "Global" : "Project";
|
|
845
933
|
const plugins = pluginsState.plugins.status === "success" ? pluginsState.plugins.data : [];
|