claudeup 3.16.0 → 4.0.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/predefined-profiles.js +191 -0
- package/src/data/predefined-profiles.ts +205 -0
- 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/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/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 +172 -0
- package/src/ui/renderers/profileRenderers.tsx +410 -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 +104 -68
- package/src/ui/screens/ProfilesScreen.tsx +147 -221
- package/src/ui/screens/SkillsScreen.js +16 -16
- package/src/ui/screens/SkillsScreen.tsx +20 -23
|
@@ -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;
|
|
@@ -6,119 +6,16 @@ import { ScreenLayout } from "../components/layout/index.js";
|
|
|
6
6
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
7
7
|
import {
|
|
8
8
|
SETTINGS_CATALOG,
|
|
9
|
-
type SettingCategory,
|
|
10
|
-
type SettingDefinition,
|
|
11
9
|
} from "../../data/settings-catalog.js";
|
|
12
10
|
import {
|
|
13
11
|
readAllSettingsBothScopes,
|
|
14
12
|
writeSettingValue,
|
|
15
|
-
type ScopedSettingValues,
|
|
16
13
|
} from "../../services/settings-manager.js";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
category?: SettingCategory;
|
|
23
|
-
setting?: SettingDefinition;
|
|
24
|
-
scopedValues?: ScopedSettingValues;
|
|
25
|
-
effectiveValue?: string;
|
|
26
|
-
isDefault: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const CATEGORY_LABELS: Record<SettingCategory, string> = {
|
|
30
|
-
recommended: "Recommended",
|
|
31
|
-
agents: "Agents & Teams",
|
|
32
|
-
models: "Models & Thinking",
|
|
33
|
-
workflow: "Workflow",
|
|
34
|
-
terminal: "Terminal & UI",
|
|
35
|
-
performance: "Performance",
|
|
36
|
-
advanced: "Advanced",
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const CATEGORY_ORDER: SettingCategory[] = [
|
|
40
|
-
"recommended",
|
|
41
|
-
"agents",
|
|
42
|
-
"models",
|
|
43
|
-
"workflow",
|
|
44
|
-
"terminal",
|
|
45
|
-
"performance",
|
|
46
|
-
"advanced",
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
/** Get the effective value (project overrides user) */
|
|
50
|
-
function getEffectiveValue(scoped: ScopedSettingValues): string | undefined {
|
|
51
|
-
return scoped.project !== undefined ? scoped.project : scoped.user;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function buildListItems(
|
|
55
|
-
values: Map<string, ScopedSettingValues>,
|
|
56
|
-
): SettingsListItem[] {
|
|
57
|
-
const items: SettingsListItem[] = [];
|
|
58
|
-
|
|
59
|
-
for (const category of CATEGORY_ORDER) {
|
|
60
|
-
items.push({
|
|
61
|
-
id: `cat:${category}`,
|
|
62
|
-
type: "category",
|
|
63
|
-
label: CATEGORY_LABELS[category],
|
|
64
|
-
category,
|
|
65
|
-
isDefault: true,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const categorySettings = SETTINGS_CATALOG.filter(
|
|
69
|
-
(s) => s.category === category,
|
|
70
|
-
);
|
|
71
|
-
for (const setting of categorySettings) {
|
|
72
|
-
const scoped = values.get(setting.id) || {
|
|
73
|
-
user: undefined,
|
|
74
|
-
project: undefined,
|
|
75
|
-
};
|
|
76
|
-
const effective = getEffectiveValue(scoped);
|
|
77
|
-
items.push({
|
|
78
|
-
id: `setting:${setting.id}`,
|
|
79
|
-
type: "setting",
|
|
80
|
-
label: setting.name,
|
|
81
|
-
category,
|
|
82
|
-
setting,
|
|
83
|
-
scopedValues: scoped,
|
|
84
|
-
effectiveValue: effective,
|
|
85
|
-
isDefault: effective === undefined || effective === "",
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return items;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function formatValue(
|
|
94
|
-
setting: SettingDefinition,
|
|
95
|
-
value: string | undefined,
|
|
96
|
-
): string {
|
|
97
|
-
if (value === undefined || value === "") {
|
|
98
|
-
if (setting.defaultValue !== undefined) {
|
|
99
|
-
return setting.type === "boolean"
|
|
100
|
-
? setting.defaultValue === "true"
|
|
101
|
-
? "on"
|
|
102
|
-
: "off"
|
|
103
|
-
: setting.defaultValue || "default";
|
|
104
|
-
}
|
|
105
|
-
return "—";
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (setting.type === "boolean") {
|
|
109
|
-
return value === "true" || value === "1" ? "on" : "off";
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (setting.type === "select" && setting.options) {
|
|
113
|
-
const opt = setting.options.find((o) => o.value === value);
|
|
114
|
-
return opt ? opt.label : value;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (value.length > 20) {
|
|
118
|
-
return value.slice(0, 20) + "...";
|
|
119
|
-
}
|
|
120
|
-
return value;
|
|
121
|
-
}
|
|
14
|
+
import {
|
|
15
|
+
buildSettingsBrowserItems,
|
|
16
|
+
type SettingsBrowserItem,
|
|
17
|
+
} from "../adapters/settingsAdapter.js";
|
|
18
|
+
import { renderSettingRow, renderSettingDetail } from "../renderers/settingsRenderers.js";
|
|
122
19
|
|
|
123
20
|
export function SettingsScreen() {
|
|
124
21
|
const { state, dispatch } = useApp();
|
|
@@ -126,7 +23,6 @@ export function SettingsScreen() {
|
|
|
126
23
|
const modal = useModal();
|
|
127
24
|
const dimensions = useDimensions();
|
|
128
25
|
|
|
129
|
-
// Fetch data from both scopes
|
|
130
26
|
const fetchData = useCallback(async () => {
|
|
131
27
|
dispatch({ type: "SETTINGS_DATA_LOADING" });
|
|
132
28
|
try {
|
|
@@ -147,28 +43,18 @@ export function SettingsScreen() {
|
|
|
147
43
|
fetchData();
|
|
148
44
|
}, [fetchData]);
|
|
149
45
|
|
|
150
|
-
|
|
151
|
-
const listItems = useMemo((): SettingsListItem[] => {
|
|
46
|
+
const listItems = useMemo((): SettingsBrowserItem[] => {
|
|
152
47
|
if (settings.values.status !== "success") return [];
|
|
153
|
-
return
|
|
48
|
+
return buildSettingsBrowserItems(settings.values.data);
|
|
154
49
|
}, [settings.values]);
|
|
155
50
|
|
|
156
|
-
const selectableItems = useMemo(
|
|
157
|
-
() =>
|
|
158
|
-
listItems.filter(
|
|
159
|
-
(item) => item.type === "category" || item.type === "setting",
|
|
160
|
-
),
|
|
161
|
-
[listItems],
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
// Change a setting in a specific scope
|
|
165
51
|
const handleScopeChange = async (scope: "user" | "project") => {
|
|
166
|
-
const item =
|
|
167
|
-
if (!item || item.
|
|
52
|
+
const item = listItems[settings.selectedIndex];
|
|
53
|
+
if (!item || item.kind !== "setting") return;
|
|
168
54
|
|
|
169
|
-
const setting = item
|
|
55
|
+
const { setting, scopedValues } = item;
|
|
170
56
|
const currentValue =
|
|
171
|
-
scope === "user" ?
|
|
57
|
+
scope === "user" ? scopedValues.user : scopedValues.project;
|
|
172
58
|
|
|
173
59
|
if (setting.type === "boolean") {
|
|
174
60
|
const currentBool =
|
|
@@ -187,11 +73,9 @@ export function SettingsScreen() {
|
|
|
187
73
|
label: o.label + (currentValue === o.value ? " (current)" : ""),
|
|
188
74
|
value: o.value,
|
|
189
75
|
}));
|
|
190
|
-
// Find current value index for pre-selection
|
|
191
76
|
const currentIndex = setting.options.findIndex(
|
|
192
77
|
(o) => o.value === currentValue,
|
|
193
78
|
);
|
|
194
|
-
// Add "clear" option to remove the setting
|
|
195
79
|
if (currentValue !== undefined) {
|
|
196
80
|
options.push({ label: "Clear (use default)", value: "__clear__" });
|
|
197
81
|
}
|
|
@@ -231,7 +115,6 @@ export function SettingsScreen() {
|
|
|
231
115
|
}
|
|
232
116
|
};
|
|
233
117
|
|
|
234
|
-
// Keyboard handling
|
|
235
118
|
useKeyboard((event) => {
|
|
236
119
|
if (state.isSearching || state.modal) return;
|
|
237
120
|
|
|
@@ -240,7 +123,7 @@ export function SettingsScreen() {
|
|
|
240
123
|
dispatch({ type: "SETTINGS_SELECT", index: newIndex });
|
|
241
124
|
} else if (event.name === "down" || event.name === "j") {
|
|
242
125
|
const newIndex = Math.min(
|
|
243
|
-
Math.max(0,
|
|
126
|
+
Math.max(0, listItems.length - 1),
|
|
244
127
|
settings.selectedIndex + 1,
|
|
245
128
|
);
|
|
246
129
|
dispatch({ type: "SETTINGS_SELECT", index: newIndex });
|
|
@@ -249,214 +132,20 @@ export function SettingsScreen() {
|
|
|
249
132
|
} else if (event.name === "p") {
|
|
250
133
|
handleScopeChange("project");
|
|
251
134
|
} else if (event.name === "enter") {
|
|
252
|
-
// Enter defaults to project scope
|
|
253
135
|
handleScopeChange("project");
|
|
254
136
|
}
|
|
255
137
|
});
|
|
256
138
|
|
|
257
|
-
const selectedItem =
|
|
258
|
-
|
|
259
|
-
const renderListItem = (
|
|
260
|
-
item: SettingsListItem,
|
|
261
|
-
_idx: number,
|
|
262
|
-
isSelected: boolean,
|
|
263
|
-
) => {
|
|
264
|
-
if (item.type === "category") {
|
|
265
|
-
const cat = item.category!;
|
|
266
|
-
const catBg =
|
|
267
|
-
cat === "recommended" ? "#2e7d32"
|
|
268
|
-
: cat === "agents" ? "#00838f"
|
|
269
|
-
: cat === "models" ? "#4527a0"
|
|
270
|
-
: cat === "workflow" ? "#1565c0"
|
|
271
|
-
: cat === "terminal" ? "#4e342e"
|
|
272
|
-
: cat === "performance" ? "#6a1b9a"
|
|
273
|
-
: "#e65100";
|
|
274
|
-
const star = cat === "recommended" ? "★ " : "";
|
|
275
|
-
|
|
276
|
-
if (isSelected) {
|
|
277
|
-
return (
|
|
278
|
-
<text bg="magenta" fg="white">
|
|
279
|
-
<strong> {star}{CATEGORY_LABELS[cat]} </strong>
|
|
280
|
-
</text>
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
return (
|
|
284
|
-
<text bg={catBg} fg="white">
|
|
285
|
-
<strong> {star}{CATEGORY_LABELS[cat]} </strong>
|
|
286
|
-
</text>
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (item.type === "setting" && item.setting) {
|
|
291
|
-
const setting = item.setting;
|
|
292
|
-
const indicator = item.isDefault ? "○" : "●";
|
|
293
|
-
const indicatorColor = item.isDefault ? "gray" : "cyan";
|
|
294
|
-
const displayValue = formatValue(setting, item.effectiveValue);
|
|
295
|
-
const valueColor = item.isDefault ? "gray" : "green";
|
|
296
|
-
|
|
297
|
-
if (isSelected) {
|
|
298
|
-
return (
|
|
299
|
-
<text bg="magenta" fg="white">
|
|
300
|
-
{" "}
|
|
301
|
-
{indicator} {setting.name.padEnd(28)}
|
|
302
|
-
{displayValue}{" "}
|
|
303
|
-
</text>
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return (
|
|
308
|
-
<text>
|
|
309
|
-
<span fg={indicatorColor}> {indicator} </span>
|
|
310
|
-
<span>{setting.name.padEnd(28)}</span>
|
|
311
|
-
<span fg={valueColor}>{displayValue}</span>
|
|
312
|
-
</text>
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return <text fg="gray">{item.label}</text>;
|
|
317
|
-
};
|
|
139
|
+
const selectedItem = listItems[settings.selectedIndex];
|
|
318
140
|
|
|
319
141
|
const renderDetail = () => {
|
|
320
142
|
if (settings.values.status === "loading") {
|
|
321
143
|
return <text fg="gray">Loading settings...</text>;
|
|
322
144
|
}
|
|
323
|
-
|
|
324
145
|
if (settings.values.status === "error") {
|
|
325
146
|
return <text fg="red">Failed to load settings</text>;
|
|
326
147
|
}
|
|
327
|
-
|
|
328
|
-
if (!selectedItem) {
|
|
329
|
-
return <text fg="gray">Select a setting to see details</text>;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if (selectedItem.type === "category") {
|
|
333
|
-
const cat = selectedItem.category!;
|
|
334
|
-
const catColor =
|
|
335
|
-
cat === "recommended"
|
|
336
|
-
? "green"
|
|
337
|
-
: cat === "agents" || cat === "models"
|
|
338
|
-
? "cyan"
|
|
339
|
-
: cat === "workflow" || cat === "terminal"
|
|
340
|
-
? "blue"
|
|
341
|
-
: cat === "performance"
|
|
342
|
-
? "magentaBright"
|
|
343
|
-
: "yellow";
|
|
344
|
-
const descriptions: Record<SettingCategory, string> = {
|
|
345
|
-
recommended: "Most impactful settings every user should know.",
|
|
346
|
-
agents: "Agent teams, task lists, and subagent configuration.",
|
|
347
|
-
models: "Model selection, extended thinking, and effort.",
|
|
348
|
-
workflow: "Git, plans, permissions, output style, and languages.",
|
|
349
|
-
terminal: "Shell, spinners, progress bars, voice, and UI behavior.",
|
|
350
|
-
performance: "Compaction, token limits, timeouts, and caching.",
|
|
351
|
-
advanced: "Telemetry, updates, debugging, and internal controls.",
|
|
352
|
-
};
|
|
353
|
-
return (
|
|
354
|
-
<box flexDirection="column">
|
|
355
|
-
<text fg={catColor}>
|
|
356
|
-
<strong>{CATEGORY_LABELS[cat]}</strong>
|
|
357
|
-
</text>
|
|
358
|
-
<box marginTop={1}>
|
|
359
|
-
<text fg="gray">{descriptions[cat]}</text>
|
|
360
|
-
</box>
|
|
361
|
-
</box>
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (selectedItem.type === "setting" && selectedItem.setting) {
|
|
366
|
-
const setting = selectedItem.setting;
|
|
367
|
-
const scoped = selectedItem.scopedValues || {
|
|
368
|
-
user: undefined,
|
|
369
|
-
project: undefined,
|
|
370
|
-
};
|
|
371
|
-
const storageDesc =
|
|
372
|
-
setting.storage.type === "env"
|
|
373
|
-
? `env: ${setting.storage.key}`
|
|
374
|
-
: `settings.json: ${setting.storage.key}`;
|
|
375
|
-
|
|
376
|
-
const userValue = formatValue(setting, scoped.user);
|
|
377
|
-
const projectValue = formatValue(setting, scoped.project);
|
|
378
|
-
const userIsSet = scoped.user !== undefined && scoped.user !== "";
|
|
379
|
-
const projectIsSet =
|
|
380
|
-
scoped.project !== undefined && scoped.project !== "";
|
|
381
|
-
|
|
382
|
-
const actionLabel =
|
|
383
|
-
setting.type === "boolean"
|
|
384
|
-
? "toggle"
|
|
385
|
-
: setting.type === "select"
|
|
386
|
-
? "choose"
|
|
387
|
-
: "edit";
|
|
388
|
-
|
|
389
|
-
return (
|
|
390
|
-
<box flexDirection="column">
|
|
391
|
-
<text fg="cyan">
|
|
392
|
-
<strong>{setting.name}</strong>
|
|
393
|
-
</text>
|
|
394
|
-
<box marginTop={1}>
|
|
395
|
-
<text fg="white">{setting.description}</text>
|
|
396
|
-
</box>
|
|
397
|
-
|
|
398
|
-
{/* Storage info */}
|
|
399
|
-
<box marginTop={1}>
|
|
400
|
-
<text>
|
|
401
|
-
<span fg="gray">Stored </span>
|
|
402
|
-
<span fg="#5c9aff">{storageDesc}</span>
|
|
403
|
-
</text>
|
|
404
|
-
</box>
|
|
405
|
-
{setting.defaultValue !== undefined && (
|
|
406
|
-
<box>
|
|
407
|
-
<text>
|
|
408
|
-
<span fg="gray">Default </span>
|
|
409
|
-
<span>{setting.defaultValue}</span>
|
|
410
|
-
</text>
|
|
411
|
-
</box>
|
|
412
|
-
)}
|
|
413
|
-
|
|
414
|
-
{/* Scopes — same pattern as Plugins */}
|
|
415
|
-
<box flexDirection="column" marginTop={1}>
|
|
416
|
-
<text>────────────────────────</text>
|
|
417
|
-
<text>
|
|
418
|
-
<strong>Scopes:</strong>
|
|
419
|
-
</text>
|
|
420
|
-
<box marginTop={1} flexDirection="column">
|
|
421
|
-
<text>
|
|
422
|
-
<span bg="cyan" fg="black">
|
|
423
|
-
{" "}
|
|
424
|
-
u{" "}
|
|
425
|
-
</span>
|
|
426
|
-
<span fg={userIsSet ? "cyan" : "gray"}>
|
|
427
|
-
{userIsSet ? " ● " : " ○ "}
|
|
428
|
-
</span>
|
|
429
|
-
<span fg="cyan">User</span>
|
|
430
|
-
<span> global</span>
|
|
431
|
-
<span fg={userIsSet ? "cyan" : "gray"}> {userValue}</span>
|
|
432
|
-
</text>
|
|
433
|
-
<text>
|
|
434
|
-
<span bg="green" fg="black">
|
|
435
|
-
{" "}
|
|
436
|
-
p{" "}
|
|
437
|
-
</span>
|
|
438
|
-
<span fg={projectIsSet ? "green" : "gray"}>
|
|
439
|
-
{projectIsSet ? " ● " : " ○ "}
|
|
440
|
-
</span>
|
|
441
|
-
<span fg="green">Project</span>
|
|
442
|
-
<span> team</span>
|
|
443
|
-
<span fg={projectIsSet ? "green" : "gray"}>
|
|
444
|
-
{" "}
|
|
445
|
-
{projectValue}
|
|
446
|
-
</span>
|
|
447
|
-
</text>
|
|
448
|
-
</box>
|
|
449
|
-
</box>
|
|
450
|
-
|
|
451
|
-
{/* Action hint */}
|
|
452
|
-
<box marginTop={1}>
|
|
453
|
-
<text fg="gray">Press u/p to {actionLabel} in scope</text>
|
|
454
|
-
</box>
|
|
455
|
-
</box>
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
return null;
|
|
148
|
+
return renderSettingDetail(selectedItem);
|
|
460
149
|
};
|
|
461
150
|
|
|
462
151
|
const totalSet =
|
|
@@ -489,9 +178,9 @@ export function SettingsScreen() {
|
|
|
489
178
|
</text>
|
|
490
179
|
) : (
|
|
491
180
|
<ScrollableList
|
|
492
|
-
items={
|
|
181
|
+
items={listItems}
|
|
493
182
|
selectedIndex={settings.selectedIndex}
|
|
494
|
-
renderItem={
|
|
183
|
+
renderItem={renderSettingRow}
|
|
495
184
|
maxHeight={dimensions.listPanelHeight}
|
|
496
185
|
/>
|
|
497
186
|
)
|