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
|
@@ -6,21 +6,19 @@ import { useDimensions } from "../state/DimensionsContext.js";
|
|
|
6
6
|
import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
7
7
|
import { ScreenLayout } from "../components/layout/index.js";
|
|
8
8
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
9
|
-
import { cliTools
|
|
9
|
+
import { cliTools } from "../../data/cli-tools.js";
|
|
10
|
+
import {
|
|
11
|
+
renderCliToolRow,
|
|
12
|
+
renderCliToolDetail,
|
|
13
|
+
type CliToolStatus,
|
|
14
|
+
} from "../renderers/cliToolRenderers.js";
|
|
10
15
|
|
|
11
16
|
const execAsync = promisify(exec);
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
tool: CliTool;
|
|
15
|
-
installed: boolean;
|
|
16
|
-
installedVersion?: string;
|
|
17
|
-
latestVersion?: string;
|
|
18
|
-
hasUpdate?: boolean;
|
|
19
|
-
checking: boolean;
|
|
20
|
-
}
|
|
18
|
+
// ─── Version helpers ───────────────────────────────────────────────────────────
|
|
21
19
|
|
|
22
20
|
// Session-level cache
|
|
23
|
-
let cachedToolStatuses:
|
|
21
|
+
let cachedToolStatuses: CliToolStatus[] | null = null;
|
|
24
22
|
let cacheInitialized = false;
|
|
25
23
|
|
|
26
24
|
function clearCliToolsCache(): void {
|
|
@@ -46,7 +44,9 @@ async function isInstalledViaHomebrew(toolName: string): Promise<boolean> {
|
|
|
46
44
|
}
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
async function getInstalledVersion(
|
|
47
|
+
async function getInstalledVersion(
|
|
48
|
+
tool: import("../../data/cli-tools.js").CliTool,
|
|
49
|
+
): Promise<string | undefined> {
|
|
50
50
|
try {
|
|
51
51
|
const { stdout } = await execAsync(tool.checkCommand, { timeout: 5000 });
|
|
52
52
|
return parseVersion(stdout.trim());
|
|
@@ -55,9 +55,7 @@ async function getInstalledVersion(tool: CliTool): Promise<string | undefined> {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
async function getLatestNpmVersion(
|
|
59
|
-
packageName: string,
|
|
60
|
-
): Promise<string | undefined> {
|
|
58
|
+
async function getLatestNpmVersion(packageName: string): Promise<string | undefined> {
|
|
61
59
|
try {
|
|
62
60
|
const { stdout } = await execAsync(
|
|
63
61
|
`npm view ${packageName} version 2>/dev/null`,
|
|
@@ -69,16 +67,11 @@ async function getLatestNpmVersion(
|
|
|
69
67
|
}
|
|
70
68
|
}
|
|
71
69
|
|
|
72
|
-
async function getLatestPipVersion(
|
|
73
|
-
packageName: string,
|
|
74
|
-
): Promise<string | undefined> {
|
|
70
|
+
async function getLatestPipVersion(packageName: string): Promise<string | undefined> {
|
|
75
71
|
try {
|
|
76
72
|
const { stdout } = await execAsync(
|
|
77
73
|
`pip index versions ${packageName} 2>/dev/null | head -1`,
|
|
78
|
-
{
|
|
79
|
-
timeout: 10000,
|
|
80
|
-
shell: "/bin/bash",
|
|
81
|
-
},
|
|
74
|
+
{ timeout: 10000, shell: "/bin/bash" },
|
|
82
75
|
);
|
|
83
76
|
const match = stdout.trim().match(/\(([^)]+)\)/);
|
|
84
77
|
return match ? match[1] : undefined;
|
|
@@ -87,18 +80,17 @@ async function getLatestPipVersion(
|
|
|
87
80
|
}
|
|
88
81
|
}
|
|
89
82
|
|
|
90
|
-
async function getLatestVersion(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
83
|
+
async function getLatestVersion(
|
|
84
|
+
tool: import("../../data/cli-tools.js").CliTool,
|
|
85
|
+
): Promise<string | undefined> {
|
|
86
|
+
return tool.packageManager === "npm"
|
|
87
|
+
? getLatestNpmVersion(tool.packageName)
|
|
88
|
+
: getLatestPipVersion(tool.packageName);
|
|
96
89
|
}
|
|
97
90
|
|
|
98
91
|
function compareVersions(v1: string, v2: string): number {
|
|
99
92
|
const parts1 = v1.split(/[-.]/).map((p) => parseInt(p, 10) || 0);
|
|
100
93
|
const parts2 = v2.split(/[-.]/).map((p) => parseInt(p, 10) || 0);
|
|
101
|
-
|
|
102
94
|
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
103
95
|
const p1 = parts1[i] || 0;
|
|
104
96
|
const p2 = parts2[i] || 0;
|
|
@@ -108,13 +100,15 @@ function compareVersions(v1: string, v2: string): number {
|
|
|
108
100
|
return 0;
|
|
109
101
|
}
|
|
110
102
|
|
|
103
|
+
// ─── Component ─────────────────────────────────────────────────────────────────
|
|
104
|
+
|
|
111
105
|
export function CliToolsScreen() {
|
|
112
106
|
const { state, dispatch } = useApp();
|
|
113
107
|
const { cliTools: cliToolsState } = state;
|
|
114
108
|
const modal = useModal();
|
|
115
109
|
const dimensions = useDimensions();
|
|
116
110
|
|
|
117
|
-
const [toolStatuses, setToolStatuses] = useState<
|
|
111
|
+
const [toolStatuses, setToolStatuses] = useState<CliToolStatus[]>(() => {
|
|
118
112
|
return (
|
|
119
113
|
cachedToolStatuses ||
|
|
120
114
|
cliTools.map((tool) => ({
|
|
@@ -129,12 +123,11 @@ export function CliToolsScreen() {
|
|
|
129
123
|
const statusesRef = useRef(toolStatuses);
|
|
130
124
|
statusesRef.current = toolStatuses;
|
|
131
125
|
|
|
132
|
-
// Update single tool status
|
|
133
126
|
const updateToolStatus = useCallback(
|
|
134
|
-
(index: number, updates: Partial<
|
|
127
|
+
(index: number, updates: Partial<CliToolStatus>) => {
|
|
135
128
|
setToolStatuses((prev) => {
|
|
136
129
|
const newStatuses = [...prev];
|
|
137
|
-
newStatuses[index] = { ...newStatuses[index]
|
|
130
|
+
newStatuses[index] = { ...newStatuses[index]!, ...updates };
|
|
138
131
|
cachedToolStatuses = newStatuses;
|
|
139
132
|
return newStatuses;
|
|
140
133
|
});
|
|
@@ -142,11 +135,9 @@ export function CliToolsScreen() {
|
|
|
142
135
|
[],
|
|
143
136
|
);
|
|
144
137
|
|
|
145
|
-
// Fetch all version info
|
|
146
138
|
const fetchVersionInfo = useCallback(async () => {
|
|
147
|
-
// Check installed versions
|
|
148
139
|
for (let i = 0; i < cliTools.length; i++) {
|
|
149
|
-
const tool = cliTools[i]
|
|
140
|
+
const tool = cliTools[i]!;
|
|
150
141
|
getInstalledVersion(tool).then((version) => {
|
|
151
142
|
updateToolStatus(i, {
|
|
152
143
|
installedVersion: version,
|
|
@@ -155,7 +146,7 @@ export function CliToolsScreen() {
|
|
|
155
146
|
});
|
|
156
147
|
|
|
157
148
|
getLatestVersion(tool).then((latest) => {
|
|
158
|
-
const current = statusesRef.current[i]
|
|
149
|
+
const current = statusesRef.current[i]!;
|
|
159
150
|
updateToolStatus(i, {
|
|
160
151
|
latestVersion: latest,
|
|
161
152
|
checking: false,
|
|
@@ -176,7 +167,6 @@ export function CliToolsScreen() {
|
|
|
176
167
|
}
|
|
177
168
|
}, [fetchVersionInfo, toolStatuses]);
|
|
178
169
|
|
|
179
|
-
// Keyboard handling
|
|
180
170
|
useKeyboard((event) => {
|
|
181
171
|
if (state.isSearching || state.modal) return;
|
|
182
172
|
|
|
@@ -217,27 +207,19 @@ export function CliToolsScreen() {
|
|
|
217
207
|
if (!status) return;
|
|
218
208
|
|
|
219
209
|
const { tool, installed, hasUpdate } = status;
|
|
220
|
-
const action = !installed
|
|
221
|
-
? "Installing"
|
|
222
|
-
: hasUpdate
|
|
223
|
-
? "Updating"
|
|
224
|
-
: "Reinstalling";
|
|
210
|
+
const action = !installed ? "Installing" : hasUpdate ? "Updating" : "Reinstalling";
|
|
225
211
|
|
|
226
212
|
const viaHomebrew = installed
|
|
227
213
|
? await isInstalledViaHomebrew(tool.name)
|
|
228
214
|
: false;
|
|
229
215
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
} else {
|
|
236
|
-
command = tool.installCommand;
|
|
237
|
-
}
|
|
216
|
+
const command = !installed
|
|
217
|
+
? tool.installCommand
|
|
218
|
+
: viaHomebrew
|
|
219
|
+
? `brew upgrade ${tool.name}`
|
|
220
|
+
: tool.installCommand;
|
|
238
221
|
|
|
239
222
|
modal.loading(`${action} ${tool.displayName}...`);
|
|
240
|
-
|
|
241
223
|
try {
|
|
242
224
|
execSync(command, {
|
|
243
225
|
encoding: "utf-8",
|
|
@@ -259,11 +241,7 @@ export function CliToolsScreen() {
|
|
|
259
241
|
const handleUpdateAll = async () => {
|
|
260
242
|
const updatable = toolStatuses.filter((s) => s.hasUpdate);
|
|
261
243
|
if (updatable.length === 0) {
|
|
262
|
-
await modal.message(
|
|
263
|
-
"Up to Date",
|
|
264
|
-
"All tools are already up to date.",
|
|
265
|
-
"info",
|
|
266
|
-
);
|
|
244
|
+
await modal.message("Up to Date", "All tools are already up to date.", "info");
|
|
267
245
|
return;
|
|
268
246
|
}
|
|
269
247
|
|
|
@@ -274,7 +252,6 @@ export function CliToolsScreen() {
|
|
|
274
252
|
const command = viaHomebrew
|
|
275
253
|
? `brew upgrade ${status.tool.name}`
|
|
276
254
|
: status.tool.installCommand;
|
|
277
|
-
|
|
278
255
|
try {
|
|
279
256
|
execSync(command, {
|
|
280
257
|
encoding: "utf-8",
|
|
@@ -290,145 +267,8 @@ export function CliToolsScreen() {
|
|
|
290
267
|
handleRefresh();
|
|
291
268
|
};
|
|
292
269
|
|
|
293
|
-
// Get selected item
|
|
294
270
|
const selectedStatus = toolStatuses[cliToolsState.selectedIndex];
|
|
295
271
|
|
|
296
|
-
const renderDetail = () => {
|
|
297
|
-
if (!selectedStatus) {
|
|
298
|
-
return (
|
|
299
|
-
<box
|
|
300
|
-
flexDirection="column"
|
|
301
|
-
alignItems="center"
|
|
302
|
-
justifyContent="center"
|
|
303
|
-
flexGrow={1}
|
|
304
|
-
>
|
|
305
|
-
<text fg="gray">Select a tool to see details</text>
|
|
306
|
-
</box>
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const {
|
|
311
|
-
tool,
|
|
312
|
-
installed,
|
|
313
|
-
installedVersion,
|
|
314
|
-
latestVersion,
|
|
315
|
-
hasUpdate,
|
|
316
|
-
checking,
|
|
317
|
-
} = selectedStatus;
|
|
318
|
-
|
|
319
|
-
return (
|
|
320
|
-
<box flexDirection="column">
|
|
321
|
-
<box marginBottom={1}>
|
|
322
|
-
<text fg="cyan">
|
|
323
|
-
<strong>⚙ {tool.displayName}</strong>
|
|
324
|
-
</text>
|
|
325
|
-
{hasUpdate && <text fg="yellow"> ⬆</text>}
|
|
326
|
-
</box>
|
|
327
|
-
|
|
328
|
-
<text fg="gray">{tool.description}</text>
|
|
329
|
-
|
|
330
|
-
<box marginTop={1} flexDirection="column">
|
|
331
|
-
<box>
|
|
332
|
-
<text fg="gray">Status </text>
|
|
333
|
-
{!installed ? (
|
|
334
|
-
<text fg="gray">○ Not installed</text>
|
|
335
|
-
) : checking ? (
|
|
336
|
-
<text fg="green">● Checking...</text>
|
|
337
|
-
) : hasUpdate ? (
|
|
338
|
-
<text fg="yellow">● Update available</text>
|
|
339
|
-
) : (
|
|
340
|
-
<text fg="green">● Up to date</text>
|
|
341
|
-
)}
|
|
342
|
-
</box>
|
|
343
|
-
{installedVersion && (
|
|
344
|
-
<box>
|
|
345
|
-
<text fg="gray">Installed </text>
|
|
346
|
-
<text fg="green">v{installedVersion}</text>
|
|
347
|
-
</box>
|
|
348
|
-
)}
|
|
349
|
-
{latestVersion && (
|
|
350
|
-
<box>
|
|
351
|
-
<text fg="gray">Latest </text>
|
|
352
|
-
<text fg="white">v{latestVersion}</text>
|
|
353
|
-
</box>
|
|
354
|
-
)}
|
|
355
|
-
<box>
|
|
356
|
-
<text fg="gray">Website </text>
|
|
357
|
-
<text fg="#5c9aff">{tool.website}</text>
|
|
358
|
-
</box>
|
|
359
|
-
</box>
|
|
360
|
-
|
|
361
|
-
<box marginTop={2}>
|
|
362
|
-
{!installed ? (
|
|
363
|
-
<box>
|
|
364
|
-
<text bg="green" fg="black">
|
|
365
|
-
{" "}
|
|
366
|
-
Enter{" "}
|
|
367
|
-
</text>
|
|
368
|
-
<text fg="gray"> Install</text>
|
|
369
|
-
</box>
|
|
370
|
-
) : hasUpdate ? (
|
|
371
|
-
<box>
|
|
372
|
-
<text bg="yellow" fg="black">
|
|
373
|
-
{" "}
|
|
374
|
-
Enter{" "}
|
|
375
|
-
</text>
|
|
376
|
-
<text fg="gray"> Update to v{latestVersion}</text>
|
|
377
|
-
</box>
|
|
378
|
-
) : (
|
|
379
|
-
<box>
|
|
380
|
-
<text bg="gray" fg="white">
|
|
381
|
-
{" "}
|
|
382
|
-
Enter{" "}
|
|
383
|
-
</text>
|
|
384
|
-
<text fg="gray"> Reinstall</text>
|
|
385
|
-
</box>
|
|
386
|
-
)}
|
|
387
|
-
</box>
|
|
388
|
-
</box>
|
|
389
|
-
);
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
const renderListItem = (
|
|
393
|
-
status: ToolStatus,
|
|
394
|
-
_idx: number,
|
|
395
|
-
isSelected: boolean,
|
|
396
|
-
) => {
|
|
397
|
-
const { tool, installed, installedVersion, hasUpdate, checking } = status;
|
|
398
|
-
|
|
399
|
-
let icon: string;
|
|
400
|
-
let iconColor: string;
|
|
401
|
-
|
|
402
|
-
if (!installed) {
|
|
403
|
-
icon = "○";
|
|
404
|
-
iconColor = "gray";
|
|
405
|
-
} else if (hasUpdate) {
|
|
406
|
-
icon = "⬆";
|
|
407
|
-
iconColor = "yellow";
|
|
408
|
-
} else {
|
|
409
|
-
icon = "●";
|
|
410
|
-
iconColor = "green";
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
const versionText = installedVersion ? `v${installedVersion}` : "";
|
|
414
|
-
|
|
415
|
-
return isSelected ? (
|
|
416
|
-
<text bg="magenta" fg="white">
|
|
417
|
-
{" "}
|
|
418
|
-
{icon} {tool.displayName} {versionText}
|
|
419
|
-
{checking ? "..." : ""}{" "}
|
|
420
|
-
</text>
|
|
421
|
-
) : (
|
|
422
|
-
<text>
|
|
423
|
-
<span fg={iconColor}>{icon}</span>
|
|
424
|
-
<span fg="white"> {tool.displayName}</span>
|
|
425
|
-
{versionText && <span fg="green"> {versionText}</span>}
|
|
426
|
-
{checking && <span fg="gray">...</span>}
|
|
427
|
-
</text>
|
|
428
|
-
);
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
// Calculate stats for status line
|
|
432
272
|
const installedCount = toolStatuses.filter((s) => s.installed).length;
|
|
433
273
|
const updateCount = toolStatuses.filter((s) => s.hasUpdate).length;
|
|
434
274
|
const statusContent = (
|
|
@@ -456,11 +296,11 @@ export function CliToolsScreen() {
|
|
|
456
296
|
<ScrollableList
|
|
457
297
|
items={toolStatuses}
|
|
458
298
|
selectedIndex={cliToolsState.selectedIndex}
|
|
459
|
-
renderItem={
|
|
299
|
+
renderItem={renderCliToolRow}
|
|
460
300
|
maxHeight={dimensions.listPanelHeight}
|
|
461
301
|
/>
|
|
462
302
|
}
|
|
463
|
-
detailPanel={
|
|
303
|
+
detailPanel={renderCliToolDetail(selectedStatus)}
|
|
464
304
|
/>
|
|
465
305
|
);
|
|
466
306
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
|
|
2
2
|
import { useEffect, useCallback, useMemo } from "react";
|
|
3
3
|
import { useApp, useModal } from "../state/AppContext.js";
|
|
4
4
|
import { useDimensions } from "../state/DimensionsContext.js";
|
|
@@ -7,88 +7,13 @@ import { ScreenLayout } from "../components/layout/index.js";
|
|
|
7
7
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
8
8
|
import { SETTINGS_CATALOG, } from "../../data/settings-catalog.js";
|
|
9
9
|
import { readAllSettingsBothScopes, writeSettingValue, } from "../../services/settings-manager.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
agents: "Agents & Teams",
|
|
13
|
-
models: "Models & Thinking",
|
|
14
|
-
workflow: "Workflow",
|
|
15
|
-
terminal: "Terminal & UI",
|
|
16
|
-
performance: "Performance",
|
|
17
|
-
advanced: "Advanced",
|
|
18
|
-
};
|
|
19
|
-
const CATEGORY_ORDER = [
|
|
20
|
-
"recommended",
|
|
21
|
-
"agents",
|
|
22
|
-
"models",
|
|
23
|
-
"workflow",
|
|
24
|
-
"terminal",
|
|
25
|
-
"performance",
|
|
26
|
-
"advanced",
|
|
27
|
-
];
|
|
28
|
-
/** Get the effective value (project overrides user) */
|
|
29
|
-
function getEffectiveValue(scoped) {
|
|
30
|
-
return scoped.project !== undefined ? scoped.project : scoped.user;
|
|
31
|
-
}
|
|
32
|
-
function buildListItems(values) {
|
|
33
|
-
const items = [];
|
|
34
|
-
for (const category of CATEGORY_ORDER) {
|
|
35
|
-
items.push({
|
|
36
|
-
id: `cat:${category}`,
|
|
37
|
-
type: "category",
|
|
38
|
-
label: CATEGORY_LABELS[category],
|
|
39
|
-
category,
|
|
40
|
-
isDefault: true,
|
|
41
|
-
});
|
|
42
|
-
const categorySettings = SETTINGS_CATALOG.filter((s) => s.category === category);
|
|
43
|
-
for (const setting of categorySettings) {
|
|
44
|
-
const scoped = values.get(setting.id) || {
|
|
45
|
-
user: undefined,
|
|
46
|
-
project: undefined,
|
|
47
|
-
};
|
|
48
|
-
const effective = getEffectiveValue(scoped);
|
|
49
|
-
items.push({
|
|
50
|
-
id: `setting:${setting.id}`,
|
|
51
|
-
type: "setting",
|
|
52
|
-
label: setting.name,
|
|
53
|
-
category,
|
|
54
|
-
setting,
|
|
55
|
-
scopedValues: scoped,
|
|
56
|
-
effectiveValue: effective,
|
|
57
|
-
isDefault: effective === undefined || effective === "",
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return items;
|
|
62
|
-
}
|
|
63
|
-
function formatValue(setting, value) {
|
|
64
|
-
if (value === undefined || value === "") {
|
|
65
|
-
if (setting.defaultValue !== undefined) {
|
|
66
|
-
return setting.type === "boolean"
|
|
67
|
-
? setting.defaultValue === "true"
|
|
68
|
-
? "on"
|
|
69
|
-
: "off"
|
|
70
|
-
: setting.defaultValue || "default";
|
|
71
|
-
}
|
|
72
|
-
return "—";
|
|
73
|
-
}
|
|
74
|
-
if (setting.type === "boolean") {
|
|
75
|
-
return value === "true" || value === "1" ? "on" : "off";
|
|
76
|
-
}
|
|
77
|
-
if (setting.type === "select" && setting.options) {
|
|
78
|
-
const opt = setting.options.find((o) => o.value === value);
|
|
79
|
-
return opt ? opt.label : value;
|
|
80
|
-
}
|
|
81
|
-
if (value.length > 20) {
|
|
82
|
-
return value.slice(0, 20) + "...";
|
|
83
|
-
}
|
|
84
|
-
return value;
|
|
85
|
-
}
|
|
10
|
+
import { buildSettingsBrowserItems, } from "../adapters/settingsAdapter.js";
|
|
11
|
+
import { renderSettingRow, renderSettingDetail } from "../renderers/settingsRenderers.js";
|
|
86
12
|
export function SettingsScreen() {
|
|
87
13
|
const { state, dispatch } = useApp();
|
|
88
14
|
const { settings } = state;
|
|
89
15
|
const modal = useModal();
|
|
90
16
|
const dimensions = useDimensions();
|
|
91
|
-
// Fetch data from both scopes
|
|
92
17
|
const fetchData = useCallback(async () => {
|
|
93
18
|
dispatch({ type: "SETTINGS_DATA_LOADING" });
|
|
94
19
|
try {
|
|
@@ -105,20 +30,17 @@ export function SettingsScreen() {
|
|
|
105
30
|
useEffect(() => {
|
|
106
31
|
fetchData();
|
|
107
32
|
}, [fetchData]);
|
|
108
|
-
// Build flat list items
|
|
109
33
|
const listItems = useMemo(() => {
|
|
110
34
|
if (settings.values.status !== "success")
|
|
111
35
|
return [];
|
|
112
|
-
return
|
|
36
|
+
return buildSettingsBrowserItems(settings.values.data);
|
|
113
37
|
}, [settings.values]);
|
|
114
|
-
const selectableItems = useMemo(() => listItems.filter((item) => item.type === "category" || item.type === "setting"), [listItems]);
|
|
115
|
-
// Change a setting in a specific scope
|
|
116
38
|
const handleScopeChange = async (scope) => {
|
|
117
|
-
const item =
|
|
118
|
-
if (!item || item.
|
|
39
|
+
const item = listItems[settings.selectedIndex];
|
|
40
|
+
if (!item || item.kind !== "setting")
|
|
119
41
|
return;
|
|
120
|
-
const setting = item
|
|
121
|
-
const currentValue = scope === "user" ?
|
|
42
|
+
const { setting, scopedValues } = item;
|
|
43
|
+
const currentValue = scope === "user" ? scopedValues.user : scopedValues.project;
|
|
122
44
|
if (setting.type === "boolean") {
|
|
123
45
|
const currentBool = currentValue === "true" ||
|
|
124
46
|
currentValue === "1" ||
|
|
@@ -137,9 +59,7 @@ export function SettingsScreen() {
|
|
|
137
59
|
label: o.label + (currentValue === o.value ? " (current)" : ""),
|
|
138
60
|
value: o.value,
|
|
139
61
|
}));
|
|
140
|
-
// Find current value index for pre-selection
|
|
141
62
|
const currentIndex = setting.options.findIndex((o) => o.value === currentValue);
|
|
142
|
-
// Add "clear" option to remove the setting
|
|
143
63
|
if (currentValue !== undefined) {
|
|
144
64
|
options.push({ label: "Clear (use default)", value: "__clear__" });
|
|
145
65
|
}
|
|
@@ -168,7 +88,6 @@ export function SettingsScreen() {
|
|
|
168
88
|
}
|
|
169
89
|
}
|
|
170
90
|
};
|
|
171
|
-
// Keyboard handling
|
|
172
91
|
useKeyboard((event) => {
|
|
173
92
|
if (state.isSearching || state.modal)
|
|
174
93
|
return;
|
|
@@ -177,7 +96,7 @@ export function SettingsScreen() {
|
|
|
177
96
|
dispatch({ type: "SETTINGS_SELECT", index: newIndex });
|
|
178
97
|
}
|
|
179
98
|
else if (event.name === "down" || event.name === "j") {
|
|
180
|
-
const newIndex = Math.min(Math.max(0,
|
|
99
|
+
const newIndex = Math.min(Math.max(0, listItems.length - 1), settings.selectedIndex + 1);
|
|
181
100
|
dispatch({ type: "SETTINGS_SELECT", index: newIndex });
|
|
182
101
|
}
|
|
183
102
|
else if (event.name === "u") {
|
|
@@ -187,40 +106,10 @@ export function SettingsScreen() {
|
|
|
187
106
|
handleScopeChange("project");
|
|
188
107
|
}
|
|
189
108
|
else if (event.name === "enter") {
|
|
190
|
-
// Enter defaults to project scope
|
|
191
109
|
handleScopeChange("project");
|
|
192
110
|
}
|
|
193
111
|
});
|
|
194
|
-
const selectedItem =
|
|
195
|
-
const renderListItem = (item, _idx, isSelected) => {
|
|
196
|
-
if (item.type === "category") {
|
|
197
|
-
const cat = item.category;
|
|
198
|
-
const catBg = cat === "recommended" ? "#2e7d32"
|
|
199
|
-
: cat === "agents" ? "#00838f"
|
|
200
|
-
: cat === "models" ? "#4527a0"
|
|
201
|
-
: cat === "workflow" ? "#1565c0"
|
|
202
|
-
: cat === "terminal" ? "#4e342e"
|
|
203
|
-
: cat === "performance" ? "#6a1b9a"
|
|
204
|
-
: "#e65100";
|
|
205
|
-
const star = cat === "recommended" ? "★ " : "";
|
|
206
|
-
if (isSelected) {
|
|
207
|
-
return (_jsx("text", { bg: "magenta", fg: "white", children: _jsxs("strong", { children: [" ", star, CATEGORY_LABELS[cat], " "] }) }));
|
|
208
|
-
}
|
|
209
|
-
return (_jsx("text", { bg: catBg, fg: "white", children: _jsxs("strong", { children: [" ", star, CATEGORY_LABELS[cat], " "] }) }));
|
|
210
|
-
}
|
|
211
|
-
if (item.type === "setting" && item.setting) {
|
|
212
|
-
const setting = item.setting;
|
|
213
|
-
const indicator = item.isDefault ? "○" : "●";
|
|
214
|
-
const indicatorColor = item.isDefault ? "gray" : "cyan";
|
|
215
|
-
const displayValue = formatValue(setting, item.effectiveValue);
|
|
216
|
-
const valueColor = item.isDefault ? "gray" : "green";
|
|
217
|
-
if (isSelected) {
|
|
218
|
-
return (_jsxs("text", { bg: "magenta", fg: "white", children: [" ", indicator, " ", setting.name.padEnd(28), displayValue, " "] }));
|
|
219
|
-
}
|
|
220
|
-
return (_jsxs("text", { children: [_jsxs("span", { fg: indicatorColor, children: [" ", indicator, " "] }), _jsx("span", { children: setting.name.padEnd(28) }), _jsx("span", { fg: valueColor, children: displayValue })] }));
|
|
221
|
-
}
|
|
222
|
-
return _jsx("text", { fg: "gray", children: item.label });
|
|
223
|
-
};
|
|
112
|
+
const selectedItem = listItems[settings.selectedIndex];
|
|
224
113
|
const renderDetail = () => {
|
|
225
114
|
if (settings.values.status === "loading") {
|
|
226
115
|
return _jsx("text", { fg: "gray", children: "Loading settings..." });
|
|
@@ -228,52 +117,7 @@ export function SettingsScreen() {
|
|
|
228
117
|
if (settings.values.status === "error") {
|
|
229
118
|
return _jsx("text", { fg: "red", children: "Failed to load settings" });
|
|
230
119
|
}
|
|
231
|
-
|
|
232
|
-
return _jsx("text", { fg: "gray", children: "Select a setting to see details" });
|
|
233
|
-
}
|
|
234
|
-
if (selectedItem.type === "category") {
|
|
235
|
-
const cat = selectedItem.category;
|
|
236
|
-
const catColor = cat === "recommended"
|
|
237
|
-
? "green"
|
|
238
|
-
: cat === "agents" || cat === "models"
|
|
239
|
-
? "cyan"
|
|
240
|
-
: cat === "workflow" || cat === "terminal"
|
|
241
|
-
? "blue"
|
|
242
|
-
: cat === "performance"
|
|
243
|
-
? "magentaBright"
|
|
244
|
-
: "yellow";
|
|
245
|
-
const descriptions = {
|
|
246
|
-
recommended: "Most impactful settings every user should know.",
|
|
247
|
-
agents: "Agent teams, task lists, and subagent configuration.",
|
|
248
|
-
models: "Model selection, extended thinking, and effort.",
|
|
249
|
-
workflow: "Git, plans, permissions, output style, and languages.",
|
|
250
|
-
terminal: "Shell, spinners, progress bars, voice, and UI behavior.",
|
|
251
|
-
performance: "Compaction, token limits, timeouts, and caching.",
|
|
252
|
-
advanced: "Telemetry, updates, debugging, and internal controls.",
|
|
253
|
-
};
|
|
254
|
-
return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: catColor, children: _jsx("strong", { children: CATEGORY_LABELS[cat] }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "gray", children: descriptions[cat] }) })] }));
|
|
255
|
-
}
|
|
256
|
-
if (selectedItem.type === "setting" && selectedItem.setting) {
|
|
257
|
-
const setting = selectedItem.setting;
|
|
258
|
-
const scoped = selectedItem.scopedValues || {
|
|
259
|
-
user: undefined,
|
|
260
|
-
project: undefined,
|
|
261
|
-
};
|
|
262
|
-
const storageDesc = setting.storage.type === "env"
|
|
263
|
-
? `env: ${setting.storage.key}`
|
|
264
|
-
: `settings.json: ${setting.storage.key}`;
|
|
265
|
-
const userValue = formatValue(setting, scoped.user);
|
|
266
|
-
const projectValue = formatValue(setting, scoped.project);
|
|
267
|
-
const userIsSet = scoped.user !== undefined && scoped.user !== "";
|
|
268
|
-
const projectIsSet = scoped.project !== undefined && scoped.project !== "";
|
|
269
|
-
const actionLabel = setting.type === "boolean"
|
|
270
|
-
? "toggle"
|
|
271
|
-
: setting.type === "select"
|
|
272
|
-
? "choose"
|
|
273
|
-
: "edit";
|
|
274
|
-
return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "cyan", children: _jsx("strong", { children: setting.name }) }), _jsx("box", { marginTop: 1, children: _jsx("text", { fg: "white", children: setting.description }) }), _jsx("box", { marginTop: 1, children: _jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Stored " }), _jsx("span", { fg: "#5c9aff", children: storageDesc })] }) }), setting.defaultValue !== undefined && (_jsx("box", { children: _jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Default " }), _jsx("span", { children: setting.defaultValue })] }) })), _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: userIsSet ? "cyan" : "gray", children: userIsSet ? " ● " : " ○ " }), _jsx("span", { fg: "cyan", children: "User" }), _jsx("span", { children: " global" }), _jsxs("span", { fg: userIsSet ? "cyan" : "gray", children: [" ", userValue] })] }), _jsxs("text", { children: [_jsxs("span", { bg: "green", fg: "black", children: [" ", "p", " "] }), _jsx("span", { fg: projectIsSet ? "green" : "gray", children: projectIsSet ? " ● " : " ○ " }), _jsx("span", { fg: "green", children: "Project" }), _jsx("span", { children: " team" }), _jsxs("span", { fg: projectIsSet ? "green" : "gray", children: [" ", projectValue] })] })] })] }), _jsx("box", { marginTop: 1, children: _jsxs("text", { fg: "gray", children: ["Press u/p to ", actionLabel, " in scope"] }) })] }));
|
|
275
|
-
}
|
|
276
|
-
return null;
|
|
120
|
+
return renderSettingDetail(selectedItem);
|
|
277
121
|
};
|
|
278
122
|
const totalSet = settings.values.status === "success"
|
|
279
123
|
? Array.from(settings.values.data.values()).filter((v) => v.user !== undefined || v.project !== undefined).length
|
|
@@ -281,6 +125,6 @@ export function SettingsScreen() {
|
|
|
281
125
|
const statusContent = (_jsxs("text", { children: [_jsx("span", { fg: "gray", children: "Settings: " }), _jsxs("span", { fg: "cyan", children: [totalSet, " configured"] }), _jsx("span", { fg: "gray", children: " \u2502 u:user p:project" })] }));
|
|
282
126
|
return (_jsx(ScreenLayout, { title: "claudeup Settings", currentScreen: "settings", statusLine: statusContent, footerHints: "\u2191\u2193:nav \u2502 u:user scope \u2502 p:project scope \u2502 Enter:project", listPanel: settings.values.status !== "success" ? (_jsx("text", { fg: "gray", children: settings.values.status === "loading"
|
|
283
127
|
? "Loading..."
|
|
284
|
-
: "Error loading settings" })) : (_jsx(ScrollableList, { items:
|
|
128
|
+
: "Error loading settings" })) : (_jsx(ScrollableList, { items: listItems, selectedIndex: settings.selectedIndex, renderItem: renderSettingRow, maxHeight: dimensions.listPanelHeight })), detailPanel: renderDetail() }));
|
|
285
129
|
}
|
|
286
130
|
export default SettingsScreen;
|