claudeup 4.35.0 → 4.36.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 +4 -4
- package/src/__tests__/git-worktree.test.ts +108 -0
- package/src/__tests__/scope-squares.test.tsx +165 -0
- package/src/__tests__/theme-adaptive-colors.test.ts +307 -0
- package/src/__tests__/uppercase-keybindings.test.ts +101 -0
- package/src/__tests__/worktree-registry-resolution.test.ts +107 -0
- package/src/main.tsx +21 -5
- package/src/opentui.d.ts +21 -12
- package/src/services/claude-settings.ts +37 -7
- package/src/services/git-worktree.ts +129 -0
- package/src/services/plugin-manager.ts +10 -1
- package/src/ui/App.tsx +19 -12
- package/src/ui/adapters/pluginsAdapter.ts +174 -168
- package/src/ui/adapters/settingsAdapter.ts +119 -116
- package/src/ui/adapters/skillsAdapter.ts +203 -196
- package/src/ui/components/CategoryHeader.tsx +9 -8
- package/src/ui/components/EmptyFilterState.tsx +10 -5
- package/src/ui/components/FlagDetailEditor.tsx +0 -0
- package/src/ui/components/ScopeIndicator.tsx +10 -6
- package/src/ui/components/ScrollableList.tsx +3 -2
- package/src/ui/components/SearchInput.tsx +2 -1
- package/src/ui/components/StyledText.tsx +5 -4
- package/src/ui/components/TabBar.tsx +4 -3
- package/src/ui/components/layout/FooterHints.tsx +37 -30
- package/src/ui/components/layout/Panel.tsx +6 -5
- package/src/ui/components/layout/ProgressBar.tsx +7 -6
- package/src/ui/components/layout/ScopeTabs.tsx +6 -5
- package/src/ui/components/layout/ScreenLayout.tsx +27 -24
- package/src/ui/components/layout/index.ts +3 -3
- package/src/ui/components/modals/ConfirmModal.tsx +12 -11
- package/src/ui/components/modals/InputModal.tsx +14 -6
- package/src/ui/components/modals/LoadingModal.tsx +6 -5
- package/src/ui/components/modals/MessageModal.tsx +9 -8
- package/src/ui/components/modals/SelectModal.tsx +11 -7
- package/src/ui/components/modals/VersionMismatchModal.tsx +14 -16
- package/src/ui/components/primitives/ActionHints.tsx +26 -26
- package/src/ui/components/primitives/DetailSection.tsx +13 -12
- package/src/ui/components/primitives/KeyValueLine.tsx +9 -8
- package/src/ui/components/primitives/ListCategoryRow.tsx +25 -27
- package/src/ui/components/primitives/MetaText.tsx +3 -3
- package/src/ui/components/primitives/ScopeDetail.tsx +48 -48
- package/src/ui/components/primitives/ScopeSquares.tsx +47 -22
- package/src/ui/components/primitives/SelectableRow.tsx +22 -16
- package/src/ui/hooks/useGitignoreModal.ts +78 -74
- package/src/ui/registry.ts +11 -11
- package/src/ui/renderers/cliToolRenderers.tsx +260 -203
- package/src/ui/renderers/gitignoreRenderers.tsx +43 -42
- package/src/ui/renderers/mcpRenderers.tsx +121 -117
- package/src/ui/renderers/pluginRenderers.tsx +528 -471
- package/src/ui/renderers/profileRenderers.tsx +346 -300
- package/src/ui/renderers/settingsRenderers.tsx +183 -176
- package/src/ui/renderers/skillRenderers.tsx +410 -326
- package/src/ui/screens/AliasScreen.tsx +1336 -1309
- package/src/ui/screens/CliToolsScreen.tsx +92 -40
- package/src/ui/screens/EnvVarsScreen.tsx +19 -13
- package/src/ui/screens/GitignoreScreen.tsx +510 -493
- package/src/ui/screens/McpRegistryScreen.tsx +28 -21
- package/src/ui/screens/McpScreen.tsx +12 -3
- package/src/ui/screens/PluginsScreen.tsx +17 -7
- package/src/ui/screens/ProfilesScreen.tsx +39 -23
- package/src/ui/screens/SkillsScreen.tsx +832 -688
- package/src/ui/theme-mode.ts +73 -0
- package/src/ui/theme.ts +147 -53
|
@@ -8,6 +8,7 @@ import { searchMcpServers, formatDate } from "../../services/mcp-registry.js";
|
|
|
8
8
|
import { addMcpServer, setAllowMcp } from "../../services/claude-settings.js";
|
|
9
9
|
import type { McpRegistryServer, McpServerConfig } from "../../types/index.js";
|
|
10
10
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
11
|
+
import { theme } from "../theme.js";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Deduplicate servers by name, keeping only the latest version.
|
|
@@ -182,7 +183,9 @@ export function McpRegistryScreen() {
|
|
|
182
183
|
dispatch({ type: "MCPREGISTRY_SELECT", index: newIndex });
|
|
183
184
|
} else if (event.name === "l") {
|
|
184
185
|
navigateToScreen("mcp");
|
|
185
|
-
} else if (event.name === "
|
|
186
|
+
} else if (event.name === "r" && event.shift) {
|
|
187
|
+
// `name` is always the unshifted key, so `=== "R"` never matched and
|
|
188
|
+
// refresh silently did nothing.
|
|
186
189
|
loadServers(searchQuery);
|
|
187
190
|
} else if (event.name === "enter") {
|
|
188
191
|
handleInstall();
|
|
@@ -219,15 +222,17 @@ export function McpRegistryScreen() {
|
|
|
219
222
|
|
|
220
223
|
const renderDetail = () => {
|
|
221
224
|
if (isLoading) {
|
|
222
|
-
return <text fg=
|
|
225
|
+
return <text fg={theme.colors.muted}>Loading...</text>;
|
|
223
226
|
}
|
|
224
227
|
|
|
225
228
|
if (error) {
|
|
226
|
-
return <text fg=
|
|
229
|
+
return <text fg={theme.colors.danger}>Error: {error}</text>;
|
|
227
230
|
}
|
|
228
231
|
|
|
229
232
|
if (!selectedServer) {
|
|
230
|
-
return
|
|
233
|
+
return (
|
|
234
|
+
<text fg={theme.colors.muted}>Select a server to see details</text>
|
|
235
|
+
);
|
|
231
236
|
}
|
|
232
237
|
|
|
233
238
|
const dateDisplay = selectedServer.published_at
|
|
@@ -240,40 +245,42 @@ export function McpRegistryScreen() {
|
|
|
240
245
|
|
|
241
246
|
return (
|
|
242
247
|
<box flexDirection="column">
|
|
243
|
-
<text fg=
|
|
248
|
+
<text fg={theme.colors.accent}>
|
|
244
249
|
<strong>{selectedServer.name}</strong>
|
|
245
250
|
</text>
|
|
246
251
|
<box marginTop={1}>
|
|
247
|
-
<text>{selectedServer.short_description}</text>
|
|
252
|
+
<text fg={theme.colors.text}>{selectedServer.short_description}</text>
|
|
248
253
|
</box>
|
|
249
254
|
<box marginTop={1}>
|
|
250
|
-
<text>
|
|
255
|
+
<text fg={theme.colors.text}>
|
|
251
256
|
<strong>Version: </strong>
|
|
252
257
|
</text>
|
|
253
|
-
<text fg=
|
|
258
|
+
<text fg={theme.colors.success}>{versionDisplay}</text>
|
|
254
259
|
</box>
|
|
255
260
|
<box>
|
|
256
|
-
<text>
|
|
261
|
+
<text fg={theme.colors.text}>
|
|
257
262
|
<strong>Published: </strong>
|
|
258
263
|
</text>
|
|
259
|
-
<text fg=
|
|
264
|
+
<text fg={theme.colors.info}>{dateDisplay}</text>
|
|
260
265
|
</box>
|
|
261
266
|
<box marginTop={1} flexDirection="column">
|
|
262
|
-
<text>
|
|
267
|
+
<text fg={theme.colors.text}>
|
|
263
268
|
<strong>URL:</strong>
|
|
264
269
|
</text>
|
|
265
|
-
<text fg=
|
|
270
|
+
<text fg={theme.colors.info}>{selectedServer.url}</text>
|
|
266
271
|
</box>
|
|
267
272
|
{selectedServer.source_code_url && (
|
|
268
273
|
<box marginTop={1} flexDirection="column">
|
|
269
|
-
<text>
|
|
274
|
+
<text fg={theme.colors.text}>
|
|
270
275
|
<strong>Source:</strong>
|
|
271
276
|
</text>
|
|
272
|
-
<text fg=
|
|
277
|
+
<text fg={theme.colors.muted}>
|
|
278
|
+
{selectedServer.source_code_url}
|
|
279
|
+
</text>
|
|
273
280
|
</box>
|
|
274
281
|
)}
|
|
275
282
|
<box marginTop={1}>
|
|
276
|
-
<text fg=
|
|
283
|
+
<text fg={theme.colors.success}>Press Enter to install</text>
|
|
277
284
|
</box>
|
|
278
285
|
</box>
|
|
279
286
|
);
|
|
@@ -286,17 +293,17 @@ export function McpRegistryScreen() {
|
|
|
286
293
|
) => {
|
|
287
294
|
const version = server.version ? ` v${server.version}` : "";
|
|
288
295
|
return isSelected ? (
|
|
289
|
-
<text bg=
|
|
296
|
+
<text bg={theme.colors.accent} fg={theme.hints.fg}>
|
|
290
297
|
{" "}
|
|
291
298
|
{server.name}
|
|
292
299
|
{version}{" "}
|
|
293
300
|
</text>
|
|
294
301
|
) : (
|
|
295
|
-
<text>
|
|
302
|
+
<text fg={theme.colors.text}>
|
|
296
303
|
<span>
|
|
297
304
|
<strong>{server.name}</strong>
|
|
298
305
|
</span>
|
|
299
|
-
<span fg=
|
|
306
|
+
<span fg={theme.colors.success}>{version}</span>
|
|
300
307
|
</text>
|
|
301
308
|
);
|
|
302
309
|
};
|
|
@@ -333,11 +340,11 @@ export function McpRegistryScreen() {
|
|
|
333
340
|
footerHints={footerHints}
|
|
334
341
|
listPanel={
|
|
335
342
|
isLoading ? (
|
|
336
|
-
<text fg=
|
|
343
|
+
<text fg={theme.colors.muted}>Loading...</text>
|
|
337
344
|
) : error ? (
|
|
338
|
-
<text fg=
|
|
345
|
+
<text fg={theme.colors.danger}>Error: {error}</text>
|
|
339
346
|
) : servers.length === 0 ? (
|
|
340
|
-
<text fg=
|
|
347
|
+
<text fg={theme.colors.muted}>No servers found</text>
|
|
341
348
|
) : (
|
|
342
349
|
<ScrollableList
|
|
343
350
|
items={servers}
|
|
@@ -130,7 +130,11 @@ export function McpScreen() {
|
|
|
130
130
|
try {
|
|
131
131
|
await removeMcpServer(server.name, state.projectPath);
|
|
132
132
|
modal.hideModal();
|
|
133
|
-
await modal.message(
|
|
133
|
+
await modal.message(
|
|
134
|
+
"Removed",
|
|
135
|
+
`${server.name} has been removed.`,
|
|
136
|
+
"success",
|
|
137
|
+
);
|
|
134
138
|
fetchData();
|
|
135
139
|
} catch (error) {
|
|
136
140
|
modal.hideModal();
|
|
@@ -146,7 +150,8 @@ export function McpScreen() {
|
|
|
146
150
|
let config: McpServerConfig;
|
|
147
151
|
|
|
148
152
|
if (server.type === "http") {
|
|
149
|
-
if (!server.url)
|
|
153
|
+
if (!server.url)
|
|
154
|
+
throw new Error(`${server.name} is missing its HTTP URL`);
|
|
150
155
|
config = { type: "http", url: server.url };
|
|
151
156
|
} else {
|
|
152
157
|
if (!server.command) {
|
|
@@ -184,7 +189,11 @@ export function McpScreen() {
|
|
|
184
189
|
if (value === null) return;
|
|
185
190
|
|
|
186
191
|
if (field.required && !value) {
|
|
187
|
-
await modal.message(
|
|
192
|
+
await modal.message(
|
|
193
|
+
"Required Field",
|
|
194
|
+
`${field.label} is required.`,
|
|
195
|
+
"error",
|
|
196
|
+
);
|
|
188
197
|
return;
|
|
189
198
|
}
|
|
190
199
|
|
|
@@ -6,7 +6,10 @@ import { ScreenLayout } from "../components/layout/index.js";
|
|
|
6
6
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
7
7
|
import { EmptyFilterState } from "../components/EmptyFilterState.js";
|
|
8
8
|
import { fuzzyFilter } from "../../utils/fuzzy-search.js";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
defaultMarketplaces,
|
|
11
|
+
getAllMarketplaces,
|
|
12
|
+
} from "../../data/marketplaces.js";
|
|
10
13
|
import { clearContentDriftCache } from "../../services/content-drift.js";
|
|
11
14
|
import {
|
|
12
15
|
diffVersions,
|
|
@@ -63,6 +66,7 @@ import {
|
|
|
63
66
|
renderPluginRow,
|
|
64
67
|
renderPluginDetail,
|
|
65
68
|
} from "../renderers/pluginRenderers.js";
|
|
69
|
+
import { theme } from "../theme.js";
|
|
66
70
|
|
|
67
71
|
export function PluginsScreen() {
|
|
68
72
|
const { state, dispatch } = useApp();
|
|
@@ -297,11 +301,15 @@ export function PluginsScreen() {
|
|
|
297
301
|
if (event.name === "r") handleRefresh();
|
|
298
302
|
else if (event.name === "n") handleShowAddMarketplaceInstructions();
|
|
299
303
|
else if (event.name === "t") handleShowTeamConfigHelp();
|
|
304
|
+
// `name` is ALWAYS the unshifted key — Shift+U parses as
|
|
305
|
+
// {name:"u", shift:true, sequence:"U"}, so `name === "U"` can never be
|
|
306
|
+
// true. It sat below the plain "u" branch, so pressing Shift+U silently
|
|
307
|
+
// installed at user scope instead of updating.
|
|
308
|
+
else if (event.name === "u" && event.shift) handleUpdate();
|
|
300
309
|
else if (event.name === "u") handleScopeToggle("user");
|
|
301
310
|
else if (event.name === "p") handleScopeToggle("project");
|
|
302
311
|
else if (event.name === "l") handleScopeToggle("local");
|
|
303
312
|
else if (event.name === "d") handleRemoveDeprecated();
|
|
304
|
-
else if (event.name === "U") handleUpdate();
|
|
305
313
|
else if (event.name === "a") handleUpdateAll();
|
|
306
314
|
else if (event.name === "m") handleCheckMismatches();
|
|
307
315
|
else if (event.name === "s") handleSaveAsProfile();
|
|
@@ -993,7 +1001,9 @@ export function PluginsScreen() {
|
|
|
993
1001
|
// Reinstall under the new namespace in every scope it occupied, so
|
|
994
1002
|
// the migration does not quietly change where the plugin is enabled.
|
|
995
1003
|
const newId = `${plugin.name}@${migrateTo}`;
|
|
996
|
-
for (const scope of removedFrom.length > 0
|
|
1004
|
+
for (const scope of removedFrom.length > 0
|
|
1005
|
+
? removedFrom
|
|
1006
|
+
: ["project" as PluginScope]) {
|
|
997
1007
|
modal.loading(
|
|
998
1008
|
`Installing ${newId} (${scope})…\nclaude plugin install ${newId} --scope ${scope}`,
|
|
999
1009
|
);
|
|
@@ -1057,10 +1067,10 @@ export function PluginsScreen() {
|
|
|
1057
1067
|
) {
|
|
1058
1068
|
return (
|
|
1059
1069
|
<box flexDirection="column" paddingLeft={1} paddingRight={1}>
|
|
1060
|
-
<text fg=
|
|
1070
|
+
<text fg={theme.colors.accent}>
|
|
1061
1071
|
<strong>claudeup Plugins</strong>
|
|
1062
1072
|
</text>
|
|
1063
|
-
<text fg=
|
|
1073
|
+
<text fg={theme.colors.muted}>Loading...</text>
|
|
1064
1074
|
</box>
|
|
1065
1075
|
);
|
|
1066
1076
|
}
|
|
@@ -1071,10 +1081,10 @@ export function PluginsScreen() {
|
|
|
1071
1081
|
) {
|
|
1072
1082
|
return (
|
|
1073
1083
|
<box flexDirection="column" paddingLeft={1} paddingRight={1}>
|
|
1074
|
-
<text fg=
|
|
1084
|
+
<text fg={theme.colors.accent}>
|
|
1075
1085
|
<strong>claudeup Plugins</strong>
|
|
1076
1086
|
</text>
|
|
1077
|
-
<text fg=
|
|
1087
|
+
<text fg={theme.colors.danger}>Error loading data</text>
|
|
1078
1088
|
</box>
|
|
1079
1089
|
);
|
|
1080
1090
|
}
|
|
@@ -12,10 +12,7 @@ import {
|
|
|
12
12
|
exportProfileToJson,
|
|
13
13
|
importProfileFromJson,
|
|
14
14
|
} from "../../services/profiles.js";
|
|
15
|
-
import {
|
|
16
|
-
readSettings,
|
|
17
|
-
writeSettings,
|
|
18
|
-
} from "../../services/claude-settings.js";
|
|
15
|
+
import { readSettings, writeSettings } from "../../services/claude-settings.js";
|
|
19
16
|
import {
|
|
20
17
|
writeClipboard,
|
|
21
18
|
readClipboard,
|
|
@@ -32,6 +29,7 @@ import {
|
|
|
32
29
|
type ProfileListItem,
|
|
33
30
|
} from "../renderers/profileRenderers.js";
|
|
34
31
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
32
|
+
import { theme } from "../theme.js";
|
|
35
33
|
|
|
36
34
|
export function ProfilesScreen() {
|
|
37
35
|
const { state, dispatch } = useApp();
|
|
@@ -111,9 +109,7 @@ export function ProfilesScreen() {
|
|
|
111
109
|
const handleApplyPredefined = async (profile: PredefinedProfile) => {
|
|
112
110
|
const allPlugins = [
|
|
113
111
|
...profile.magusPlugins.map((p) => `${p}@magus`),
|
|
114
|
-
...profile.anthropicPlugins.map(
|
|
115
|
-
(p) => `${p}@claude-plugins-official`,
|
|
116
|
-
),
|
|
112
|
+
...profile.anthropicPlugins.map((p) => `${p}@claude-plugins-official`),
|
|
117
113
|
];
|
|
118
114
|
const settingsCount = Object.keys(profile.settings).length;
|
|
119
115
|
|
|
@@ -161,7 +157,11 @@ export function ProfilesScreen() {
|
|
|
161
157
|
);
|
|
162
158
|
} catch (error) {
|
|
163
159
|
modal.hideModal();
|
|
164
|
-
await modal.message(
|
|
160
|
+
await modal.message(
|
|
161
|
+
"Error",
|
|
162
|
+
`Failed to apply profile: ${error}`,
|
|
163
|
+
"error",
|
|
164
|
+
);
|
|
165
165
|
}
|
|
166
166
|
};
|
|
167
167
|
|
|
@@ -217,7 +217,11 @@ export function ProfilesScreen() {
|
|
|
217
217
|
);
|
|
218
218
|
} catch (error) {
|
|
219
219
|
modal.hideModal();
|
|
220
|
-
await modal.message(
|
|
220
|
+
await modal.message(
|
|
221
|
+
"Error",
|
|
222
|
+
`Failed to apply profile: ${error}`,
|
|
223
|
+
"error",
|
|
224
|
+
);
|
|
221
225
|
}
|
|
222
226
|
};
|
|
223
227
|
|
|
@@ -242,7 +246,11 @@ export function ProfilesScreen() {
|
|
|
242
246
|
);
|
|
243
247
|
modal.hideModal();
|
|
244
248
|
await fetchData();
|
|
245
|
-
await modal.message(
|
|
249
|
+
await modal.message(
|
|
250
|
+
"Renamed",
|
|
251
|
+
`Profile renamed to "${newName.trim()}".`,
|
|
252
|
+
"success",
|
|
253
|
+
);
|
|
246
254
|
} catch (error) {
|
|
247
255
|
modal.hideModal();
|
|
248
256
|
await modal.message("Error", `Failed to rename: ${error}`, "error");
|
|
@@ -345,10 +353,18 @@ export function ProfilesScreen() {
|
|
|
345
353
|
|
|
346
354
|
modal.loading("Importing...");
|
|
347
355
|
try {
|
|
348
|
-
const id = await importProfileFromJson(
|
|
356
|
+
const id = await importProfileFromJson(
|
|
357
|
+
json,
|
|
358
|
+
targetScope,
|
|
359
|
+
state.projectPath,
|
|
360
|
+
);
|
|
349
361
|
modal.hideModal();
|
|
350
362
|
await fetchData();
|
|
351
|
-
await modal.message(
|
|
363
|
+
await modal.message(
|
|
364
|
+
"Imported",
|
|
365
|
+
`Profile imported successfully (id: ${id}).`,
|
|
366
|
+
"success",
|
|
367
|
+
);
|
|
352
368
|
} catch (error) {
|
|
353
369
|
modal.hideModal();
|
|
354
370
|
await modal.message("Error", `Failed to import: ${error}`, "error");
|
|
@@ -362,14 +378,16 @@ export function ProfilesScreen() {
|
|
|
362
378
|
const projCount = profileList.filter((p) => p.scope === "project").length;
|
|
363
379
|
|
|
364
380
|
const statusContent = (
|
|
365
|
-
<text>
|
|
366
|
-
<span fg=
|
|
367
|
-
<span fg=
|
|
368
|
-
<span fg=
|
|
369
|
-
<span fg=
|
|
370
|
-
<span fg=
|
|
371
|
-
<span fg=
|
|
372
|
-
<span fg=
|
|
381
|
+
<text fg={theme.colors.text}>
|
|
382
|
+
<span fg={theme.colors.link}>{PREDEFINED_PROFILES.length} presets</span>
|
|
383
|
+
<span fg={theme.colors.muted}> + </span>
|
|
384
|
+
<span fg={theme.colors.info}>{userCount} user</span>
|
|
385
|
+
<span fg={theme.colors.muted}> + </span>
|
|
386
|
+
<span fg={theme.colors.success}>{projCount} project</span>
|
|
387
|
+
<span fg={theme.colors.muted}> = </span>
|
|
388
|
+
<span fg={theme.colors.text}>
|
|
389
|
+
{PREDEFINED_PROFILES.length + profileCount} total
|
|
390
|
+
</span>
|
|
373
391
|
</text>
|
|
374
392
|
);
|
|
375
393
|
|
|
@@ -380,9 +398,7 @@ export function ProfilesScreen() {
|
|
|
380
398
|
: profilesState.selectedIndex;
|
|
381
399
|
|
|
382
400
|
const loadingStatus =
|
|
383
|
-
profilesState.profiles.status === "loading"
|
|
384
|
-
? true
|
|
385
|
-
: false;
|
|
401
|
+
profilesState.profiles.status === "loading" ? true : false;
|
|
386
402
|
const errorMessage =
|
|
387
403
|
profilesState.profiles.status === "error"
|
|
388
404
|
? profilesState.profiles.error.message
|