claudeup 3.17.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/data/predefined-profiles.js +69 -12
- package/src/data/predefined-profiles.ts +73 -14
- package/src/services/claude-cli.js +8 -1
- package/src/services/claude-cli.ts +10 -7
- package/src/services/plugin-manager.js +40 -4
- package/src/services/plugin-manager.ts +57 -6
- package/src/ui/adapters/pluginsAdapter.js +139 -0
- package/src/ui/adapters/pluginsAdapter.ts +202 -0
- package/src/ui/adapters/settingsAdapter.js +111 -0
- package/src/ui/adapters/settingsAdapter.ts +165 -0
- package/src/ui/components/ScrollableDetail.js +23 -0
- package/src/ui/components/ScrollableDetail.tsx +55 -0
- package/src/ui/components/ScrollableList.js +4 -4
- package/src/ui/components/ScrollableList.tsx +4 -4
- package/src/ui/components/SearchInput.js +2 -2
- package/src/ui/components/SearchInput.tsx +3 -3
- package/src/ui/components/StyledText.js +1 -1
- package/src/ui/components/StyledText.tsx +5 -1
- package/src/ui/components/layout/ProgressBar.js +1 -1
- package/src/ui/components/layout/ProgressBar.tsx +1 -5
- package/src/ui/components/layout/ScreenLayout.js +1 -1
- package/src/ui/components/layout/ScreenLayout.tsx +11 -8
- package/src/ui/components/modals/InputModal.tsx +1 -6
- package/src/ui/components/modals/LoadingModal.js +1 -1
- package/src/ui/components/modals/LoadingModal.tsx +1 -3
- package/src/ui/hooks/index.js +3 -3
- package/src/ui/hooks/index.ts +3 -3
- package/src/ui/hooks/useKeyboard.ts +1 -3
- package/src/ui/hooks/useKeyboardHandler.js +9 -9
- package/src/ui/hooks/useKeyboardHandler.ts +9 -9
- package/src/ui/renderers/cliToolRenderers.js +33 -0
- package/src/ui/renderers/cliToolRenderers.tsx +153 -0
- package/src/ui/renderers/mcpRenderers.js +26 -0
- package/src/ui/renderers/mcpRenderers.tsx +145 -0
- package/src/ui/renderers/pluginRenderers.js +124 -0
- package/src/ui/renderers/pluginRenderers.tsx +362 -0
- package/src/ui/renderers/profileRenderers.js +177 -0
- package/src/ui/renderers/profileRenderers.tsx +361 -0
- package/src/ui/renderers/settingsRenderers.js +69 -0
- package/src/ui/renderers/settingsRenderers.tsx +205 -0
- package/src/ui/screens/CliToolsScreen.js +14 -58
- package/src/ui/screens/CliToolsScreen.tsx +36 -196
- package/src/ui/screens/EnvVarsScreen.js +12 -168
- package/src/ui/screens/EnvVarsScreen.tsx +16 -327
- package/src/ui/screens/McpScreen.js +12 -62
- package/src/ui/screens/McpScreen.tsx +21 -190
- package/src/ui/screens/PluginsScreen.js +52 -425
- package/src/ui/screens/PluginsScreen.tsx +70 -758
- package/src/ui/screens/ProfilesScreen.js +32 -97
- package/src/ui/screens/ProfilesScreen.tsx +58 -328
- package/src/ui/screens/SkillsScreen.js +16 -16
- package/src/ui/screens/SkillsScreen.tsx +20 -23
|
@@ -9,24 +9,12 @@ import { listProfiles, applyProfile, renameProfile, deleteProfile, exportProfile
|
|
|
9
9
|
import { readSettings, writeSettings, } from "../../services/claude-settings.js";
|
|
10
10
|
import { writeClipboard, readClipboard, ClipboardUnavailableError, } from "../../utils/clipboard.js";
|
|
11
11
|
import { PREDEFINED_PROFILES, } from "../../data/predefined-profiles.js";
|
|
12
|
-
|
|
13
|
-
const predefined = PREDEFINED_PROFILES.map((p) => ({
|
|
14
|
-
kind: "predefined",
|
|
15
|
-
profile: p,
|
|
16
|
-
}));
|
|
17
|
-
const saved = profileList.map((e) => ({
|
|
18
|
-
kind: "saved",
|
|
19
|
-
entry: e,
|
|
20
|
-
}));
|
|
21
|
-
return [...predefined, ...saved];
|
|
22
|
-
}
|
|
23
|
-
// ─── Component ────────────────────────────────────────────────────────────────
|
|
12
|
+
import { buildProfileListItems, renderProfileRow, renderProfileDetail, } from "../renderers/profileRenderers.js";
|
|
24
13
|
export function ProfilesScreen() {
|
|
25
14
|
const { state, dispatch } = useApp();
|
|
26
15
|
const { profiles: profilesState } = state;
|
|
27
16
|
const modal = useModal();
|
|
28
17
|
const dimensions = useDimensions();
|
|
29
|
-
// Fetch data
|
|
30
18
|
const fetchData = useCallback(async () => {
|
|
31
19
|
dispatch({ type: "PROFILES_DATA_LOADING" });
|
|
32
20
|
try {
|
|
@@ -46,25 +34,36 @@ export function ProfilesScreen() {
|
|
|
46
34
|
const profileList = profilesState.profiles.status === "success"
|
|
47
35
|
? profilesState.profiles.data
|
|
48
36
|
: [];
|
|
49
|
-
const allItems =
|
|
37
|
+
const allItems = buildProfileListItems(profileList, PREDEFINED_PROFILES);
|
|
50
38
|
const selectedItem = allItems[profilesState.selectedIndex];
|
|
51
|
-
|
|
39
|
+
const isNavigable = (item) => item.kind !== "header";
|
|
52
40
|
useKeyboard((event) => {
|
|
53
41
|
if (state.isSearching || state.modal)
|
|
54
42
|
return;
|
|
55
43
|
if (event.name === "up" || event.name === "k") {
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
let newIndex = profilesState.selectedIndex - 1;
|
|
45
|
+
while (newIndex > 0 && !isNavigable(allItems[newIndex])) {
|
|
46
|
+
newIndex--;
|
|
47
|
+
}
|
|
48
|
+
if (newIndex >= 0 && isNavigable(allItems[newIndex])) {
|
|
49
|
+
dispatch({ type: "PROFILES_SELECT", index: newIndex });
|
|
50
|
+
}
|
|
58
51
|
}
|
|
59
52
|
else if (event.name === "down" || event.name === "j") {
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
let newIndex = profilesState.selectedIndex + 1;
|
|
54
|
+
while (newIndex < allItems.length - 1 &&
|
|
55
|
+
!isNavigable(allItems[newIndex])) {
|
|
56
|
+
newIndex++;
|
|
57
|
+
}
|
|
58
|
+
if (newIndex < allItems.length && isNavigable(allItems[newIndex])) {
|
|
59
|
+
dispatch({ type: "PROFILES_SELECT", index: newIndex });
|
|
60
|
+
}
|
|
62
61
|
}
|
|
63
62
|
else if (event.name === "enter" || event.name === "a") {
|
|
64
63
|
if (selectedItem?.kind === "predefined") {
|
|
65
64
|
void handleApplyPredefined(selectedItem.profile);
|
|
66
65
|
}
|
|
67
|
-
else {
|
|
66
|
+
else if (selectedItem?.kind === "saved") {
|
|
68
67
|
void handleApply();
|
|
69
68
|
}
|
|
70
69
|
}
|
|
@@ -84,7 +83,7 @@ export function ProfilesScreen() {
|
|
|
84
83
|
void handleImport();
|
|
85
84
|
}
|
|
86
85
|
});
|
|
87
|
-
// ───
|
|
86
|
+
// ─── Actions ──────────────────────────────────────────────────────────────
|
|
88
87
|
const handleApplyPredefined = async (profile) => {
|
|
89
88
|
const allPlugins = [
|
|
90
89
|
...profile.magusPlugins.map((p) => `${p}@magus`),
|
|
@@ -97,14 +96,12 @@ export function ProfilesScreen() {
|
|
|
97
96
|
modal.loading(`Applying "${profile.name}"...`);
|
|
98
97
|
try {
|
|
99
98
|
const settings = await readSettings(state.projectPath);
|
|
100
|
-
// Merge plugins (additive only)
|
|
101
99
|
settings.enabledPlugins = settings.enabledPlugins ?? {};
|
|
102
100
|
for (const plugin of allPlugins) {
|
|
103
101
|
if (!settings.enabledPlugins[plugin]) {
|
|
104
102
|
settings.enabledPlugins[plugin] = true;
|
|
105
103
|
}
|
|
106
104
|
}
|
|
107
|
-
// Merge top-level settings (additive — only set if not already set)
|
|
108
105
|
for (const [key, value] of Object.entries(profile.settings)) {
|
|
109
106
|
if (key === "env") {
|
|
110
107
|
const envMap = value;
|
|
@@ -134,7 +131,6 @@ export function ProfilesScreen() {
|
|
|
134
131
|
await modal.message("Error", `Failed to apply profile: ${error}`, "error");
|
|
135
132
|
}
|
|
136
133
|
};
|
|
137
|
-
// ─── Saved profile actions ────────────────────────────────────────────────
|
|
138
134
|
const handleApply = async () => {
|
|
139
135
|
if (selectedItem?.kind !== "saved")
|
|
140
136
|
return;
|
|
@@ -163,7 +159,6 @@ export function ProfilesScreen() {
|
|
|
163
159
|
try {
|
|
164
160
|
await applyProfile(selectedProfile.id, selectedProfile.scope, targetScope, state.projectPath);
|
|
165
161
|
modal.hideModal();
|
|
166
|
-
// Trigger PluginsScreen to refetch
|
|
167
162
|
dispatch({ type: "DATA_REFRESH_COMPLETE" });
|
|
168
163
|
await modal.message("Applied", `Profile "${selectedProfile.name}" applied to ${scopeLabel}.`, "success");
|
|
169
164
|
}
|
|
@@ -202,7 +197,6 @@ export function ProfilesScreen() {
|
|
|
202
197
|
try {
|
|
203
198
|
await deleteProfile(selectedProfile.id, selectedProfile.scope, state.projectPath);
|
|
204
199
|
modal.hideModal();
|
|
205
|
-
// Adjust selection if we deleted the last item
|
|
206
200
|
const newIndex = Math.max(0, Math.min(profilesState.selectedIndex, allItems.length - 2));
|
|
207
201
|
dispatch({ type: "PROFILES_SELECT", index: newIndex });
|
|
208
202
|
await fetchData();
|
|
@@ -227,7 +221,6 @@ export function ProfilesScreen() {
|
|
|
227
221
|
}
|
|
228
222
|
catch (err) {
|
|
229
223
|
if (err instanceof ClipboardUnavailableError) {
|
|
230
|
-
// Fallback: show JSON in modal for manual copy
|
|
231
224
|
await modal.message("Profile JSON (copy manually)", json, "info");
|
|
232
225
|
}
|
|
233
226
|
else {
|
|
@@ -242,13 +235,11 @@ export function ProfilesScreen() {
|
|
|
242
235
|
};
|
|
243
236
|
const handleImport = async () => {
|
|
244
237
|
let json = null;
|
|
245
|
-
// Try to read from clipboard first
|
|
246
238
|
try {
|
|
247
239
|
json = await readClipboard();
|
|
248
240
|
}
|
|
249
241
|
catch (err) {
|
|
250
242
|
if (err instanceof ClipboardUnavailableError) {
|
|
251
|
-
// Fallback: ask user to paste JSON manually
|
|
252
243
|
json = await modal.input("Import Profile", "Paste profile JSON:");
|
|
253
244
|
}
|
|
254
245
|
else {
|
|
@@ -280,77 +271,21 @@ export function ProfilesScreen() {
|
|
|
280
271
|
await modal.message("Error", `Failed to import: ${error}`, "error");
|
|
281
272
|
}
|
|
282
273
|
};
|
|
283
|
-
// ───
|
|
284
|
-
const formatDate = (iso) => {
|
|
285
|
-
try {
|
|
286
|
-
const d = new Date(iso);
|
|
287
|
-
return d.toLocaleDateString("en-US", {
|
|
288
|
-
month: "short",
|
|
289
|
-
day: "numeric",
|
|
290
|
-
year: "numeric",
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
catch {
|
|
294
|
-
return iso;
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
|
-
const renderListItem = (item, _idx, isSelected) => {
|
|
298
|
-
if (item.kind === "predefined") {
|
|
299
|
-
const { profile } = item;
|
|
300
|
-
const pluginCount = profile.magusPlugins.length + profile.anthropicPlugins.length;
|
|
301
|
-
const skillCount = profile.skills.length;
|
|
302
|
-
if (isSelected) {
|
|
303
|
-
return (_jsxs("text", { bg: "blue", fg: "white", children: [" ", profile.icon, " ", profile.name, " \u2014 ", pluginCount, " plugins \u00B7 ", skillCount, " ", "skill", skillCount !== 1 ? "s" : "", " "] }));
|
|
304
|
-
}
|
|
305
|
-
return (_jsxs("text", { children: [_jsx("span", { fg: "blue", children: "[preset]" }), _jsx("span", { children: " " }), _jsxs("span", { fg: "white", children: [profile.icon, " ", profile.name] }), _jsxs("span", { fg: "gray", children: [" ", "\u2014 ", pluginCount, " plugins \u00B7 ", skillCount, " skill", skillCount !== 1 ? "s" : ""] })] }));
|
|
306
|
-
}
|
|
307
|
-
// Saved profile
|
|
308
|
-
const { entry } = item;
|
|
309
|
-
const pluginCount = Object.keys(entry.plugins).length;
|
|
310
|
-
const dateStr = formatDate(entry.updatedAt);
|
|
311
|
-
const scopeColor = entry.scope === "user" ? "cyan" : "green";
|
|
312
|
-
const scopeLabel = entry.scope === "user" ? "[user]" : "[proj]";
|
|
313
|
-
if (isSelected) {
|
|
314
|
-
return (_jsxs("text", { bg: "magenta", fg: "white", children: [" ", scopeLabel, " ", entry.name, " \u2014 ", pluginCount, " plugin", pluginCount !== 1 ? "s" : "", " \u00B7 ", dateStr, " "] }));
|
|
315
|
-
}
|
|
316
|
-
return (_jsxs("text", { children: [_jsx("span", { fg: scopeColor, children: scopeLabel }), _jsx("span", { children: " " }), _jsx("span", { fg: "white", children: entry.name }), _jsxs("span", { fg: "gray", children: [" ", "\u2014 ", pluginCount, " plugin", pluginCount !== 1 ? "s" : "", " \u00B7 ", dateStr] })] }));
|
|
317
|
-
};
|
|
318
|
-
const renderDetail = () => {
|
|
319
|
-
if (profilesState.profiles.status === "loading") {
|
|
320
|
-
return _jsx("text", { fg: "gray", children: "Loading profiles..." });
|
|
321
|
-
}
|
|
322
|
-
if (profilesState.profiles.status === "error") {
|
|
323
|
-
return (_jsxs("text", { fg: "red", children: ["Error: ", profilesState.profiles.error.message] }));
|
|
324
|
-
}
|
|
325
|
-
if (!selectedItem) {
|
|
326
|
-
return _jsx("text", { fg: "gray", children: "Select a profile to see details" });
|
|
327
|
-
}
|
|
328
|
-
if (selectedItem.kind === "predefined") {
|
|
329
|
-
return renderPredefinedDetail(selectedItem.profile);
|
|
330
|
-
}
|
|
331
|
-
return renderSavedDetail(selectedItem.entry);
|
|
332
|
-
};
|
|
333
|
-
const renderPredefinedDetail = (profile) => {
|
|
334
|
-
const allPlugins = [
|
|
335
|
-
...profile.magusPlugins.map((p) => `${p}@magus`),
|
|
336
|
-
...profile.anthropicPlugins.map((p) => `${p}@claude-plugins-official`),
|
|
337
|
-
];
|
|
338
|
-
return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "blue", children: _jsxs("strong", { children: [profile.icon, " ", profile.name] }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: profile.description }) }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { fg: "gray", children: ["Magus plugins (", profile.magusPlugins.length, "):"] }), profile.magusPlugins.map((p) => (_jsx("box", { children: _jsxs("text", { fg: "cyan", children: [" ", p, "@magus"] }) }, p)))] }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { fg: "gray", children: ["Anthropic plugins (", profile.anthropicPlugins.length, "):"] }), profile.anthropicPlugins.map((p) => (_jsx("box", { children: _jsxs("text", { fg: "yellow", children: [" ", p, "@claude-plugins-official"] }) }, p)))] }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { fg: "gray", children: ["Skills (", profile.skills.length, "):"] }), profile.skills.map((s) => (_jsx("box", { children: _jsxs("text", { fg: "white", children: [" ", s] }) }, s)))] }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { fg: "gray", children: ["Settings (", Object.keys(profile.settings).length, "):"] }), Object.entries(profile.settings)
|
|
339
|
-
.filter(([k]) => k !== "env")
|
|
340
|
-
.map(([k, v]) => (_jsx("box", { children: _jsxs("text", { fg: "white", children: [" ", k, ": ", String(v)] }) }, k)))] }), _jsx("box", { marginTop: 2, flexDirection: "column", children: _jsxs("box", { children: [_jsxs("text", { bg: "blue", fg: "white", children: [" ", "Enter/a", " "] }), _jsxs("text", { fg: "gray", children: [" ", "Apply (merges ", allPlugins.length, " plugins into project settings)"] })] }) })] }));
|
|
341
|
-
};
|
|
342
|
-
const renderSavedDetail = (selectedProfile) => {
|
|
343
|
-
const plugins = Object.keys(selectedProfile.plugins);
|
|
344
|
-
const scopeColor = selectedProfile.scope === "user" ? "cyan" : "green";
|
|
345
|
-
const scopeLabel = selectedProfile.scope === "user"
|
|
346
|
-
? "User (~/.claude/profiles.json)"
|
|
347
|
-
: "Project (.claude/profiles.json — committed to git)";
|
|
348
|
-
return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "cyan", children: _jsx("strong", { children: selectedProfile.name }) }), _jsxs("box", { marginTop: 1, children: [_jsx("text", { fg: "gray", children: "Scope: " }), _jsx("text", { fg: scopeColor, children: scopeLabel })] }), _jsx("box", { marginTop: 1, children: _jsxs("text", { fg: "gray", children: ["Created: ", formatDate(selectedProfile.createdAt), " \u00B7 Updated:", " ", formatDate(selectedProfile.updatedAt)] }) }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { fg: "gray", children: ["Plugins (", plugins.length, plugins.length === 0 ? " — applying will disable all plugins" : "", "):"] }), plugins.length === 0 ? (_jsx("text", { fg: "yellow", children: " (none)" })) : (plugins.map((p) => (_jsx("box", { children: _jsxs("text", { fg: "white", children: [" ", p] }) }, p))))] }), _jsxs("box", { marginTop: 2, flexDirection: "column", children: [_jsxs("box", { children: [_jsxs("text", { bg: "magenta", fg: "white", children: [" ", "Enter/a", " "] }), _jsx("text", { fg: "gray", children: " Apply profile" })] }), _jsxs("box", { marginTop: 1, children: [_jsxs("text", { bg: "#333333", fg: "white", children: [" ", "r", " "] }), _jsx("text", { fg: "gray", children: " Rename" })] }), _jsxs("box", { marginTop: 1, children: [_jsxs("text", { bg: "red", fg: "white", children: [" ", "d", " "] }), _jsx("text", { fg: "gray", children: " Delete" })] }), _jsxs("box", { marginTop: 1, children: [_jsxs("text", { bg: "blue", fg: "white", children: [" ", "c", " "] }), _jsx("text", { fg: "gray", children: " Copy JSON to clipboard" })] }), _jsxs("box", { marginTop: 1, children: [_jsxs("text", { bg: "green", fg: "white", children: [" ", "i", " "] }), _jsx("text", { fg: "gray", children: " Import from clipboard" })] })] })] }));
|
|
349
|
-
};
|
|
274
|
+
// ─── Render ───────────────────────────────────────────────────────────────
|
|
350
275
|
const profileCount = profileList.length;
|
|
351
276
|
const userCount = profileList.filter((p) => p.scope === "user").length;
|
|
352
277
|
const projCount = profileList.filter((p) => p.scope === "project").length;
|
|
353
278
|
const statusContent = (_jsxs("text", { children: [_jsxs("span", { fg: "blue", children: [PREDEFINED_PROFILES.length, " presets"] }), _jsx("span", { fg: "gray", children: " + " }), _jsxs("span", { fg: "cyan", children: [userCount, " user"] }), _jsx("span", { fg: "gray", children: " + " }), _jsxs("span", { fg: "green", children: [projCount, " project"] }), _jsx("span", { fg: "gray", children: " = " }), _jsxs("span", { fg: "white", children: [PREDEFINED_PROFILES.length + profileCount, " total"] })] }));
|
|
354
|
-
|
|
279
|
+
const firstNavigableIndex = allItems.findIndex(isNavigable);
|
|
280
|
+
const effectiveIndex = profilesState.selectedIndex === 0 && selectedItem?.kind === "header"
|
|
281
|
+
? firstNavigableIndex
|
|
282
|
+
: profilesState.selectedIndex;
|
|
283
|
+
const loadingStatus = profilesState.profiles.status === "loading"
|
|
284
|
+
? true
|
|
285
|
+
: false;
|
|
286
|
+
const errorMessage = profilesState.profiles.status === "error"
|
|
287
|
+
? profilesState.profiles.error.message
|
|
288
|
+
: undefined;
|
|
289
|
+
return (_jsx(ScreenLayout, { title: "claudeup Plugin Profiles", currentScreen: "profiles", statusLine: statusContent, footerHints: "\u2191\u2193:nav \u2502 Enter/a:apply \u2502 r:rename \u2502 d:delete \u2502 c:copy \u2502 i:import", listPanel: _jsx(ScrollableList, { items: allItems, selectedIndex: effectiveIndex, renderItem: renderProfileRow, maxHeight: dimensions.listPanelHeight }), detailPanel: renderProfileDetail(allItems[effectiveIndex], loadingStatus, errorMessage) }));
|
|
355
290
|
}
|
|
356
291
|
export default ProfilesScreen;
|