claudeup 3.7.2 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/data/settings-catalog.js +612 -0
- package/src/data/settings-catalog.ts +689 -0
- package/src/data/skill-repos.js +86 -0
- package/src/data/skill-repos.ts +97 -0
- package/src/services/plugin-manager.js +2 -0
- package/src/services/plugin-manager.ts +3 -0
- package/src/services/profiles.js +161 -0
- package/src/services/profiles.ts +225 -0
- package/src/services/settings-manager.js +108 -0
- package/src/services/settings-manager.ts +140 -0
- package/src/services/skills-manager.js +239 -0
- package/src/services/skills-manager.ts +328 -0
- package/src/services/skillsmp-client.js +67 -0
- package/src/services/skillsmp-client.ts +89 -0
- package/src/types/index.ts +101 -1
- package/src/ui/App.js +23 -18
- package/src/ui/App.tsx +27 -23
- package/src/ui/components/TabBar.js +9 -8
- package/src/ui/components/TabBar.tsx +15 -19
- package/src/ui/components/layout/ScreenLayout.js +8 -14
- package/src/ui/components/layout/ScreenLayout.tsx +51 -58
- package/src/ui/components/modals/ModalContainer.js +43 -11
- package/src/ui/components/modals/ModalContainer.tsx +44 -12
- package/src/ui/components/modals/SelectModal.js +4 -18
- package/src/ui/components/modals/SelectModal.tsx +10 -21
- package/src/ui/screens/CliToolsScreen.js +2 -2
- package/src/ui/screens/CliToolsScreen.tsx +8 -8
- package/src/ui/screens/EnvVarsScreen.js +248 -116
- package/src/ui/screens/EnvVarsScreen.tsx +419 -184
- package/src/ui/screens/McpRegistryScreen.tsx +18 -6
- package/src/ui/screens/McpScreen.js +1 -1
- package/src/ui/screens/McpScreen.tsx +15 -5
- package/src/ui/screens/ModelSelectorScreen.js +3 -5
- package/src/ui/screens/ModelSelectorScreen.tsx +12 -16
- package/src/ui/screens/PluginsScreen.js +154 -66
- package/src/ui/screens/PluginsScreen.tsx +280 -97
- package/src/ui/screens/ProfilesScreen.js +255 -0
- package/src/ui/screens/ProfilesScreen.tsx +487 -0
- package/src/ui/screens/SkillsScreen.js +325 -0
- package/src/ui/screens/SkillsScreen.tsx +574 -0
- package/src/ui/screens/StatusLineScreen.js +2 -2
- package/src/ui/screens/StatusLineScreen.tsx +10 -12
- package/src/ui/screens/index.js +3 -2
- package/src/ui/screens/index.ts +3 -2
- package/src/ui/state/AppContext.js +2 -1
- package/src/ui/state/AppContext.tsx +2 -0
- package/src/ui/state/reducer.js +151 -19
- package/src/ui/state/reducer.ts +167 -19
- package/src/ui/state/types.ts +58 -14
- package/src/utils/clipboard.js +56 -0
- package/src/utils/clipboard.ts +58 -0
|
@@ -1,261 +1,496 @@
|
|
|
1
|
-
import React, { useEffect, useCallback,
|
|
1
|
+
import React, { useEffect, useCallback, useMemo } from "react";
|
|
2
2
|
import { useApp, useModal } from "../state/AppContext.js";
|
|
3
3
|
import { useDimensions } from "../state/DimensionsContext.js";
|
|
4
4
|
import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
5
5
|
import { ScreenLayout } from "../components/layout/index.js";
|
|
6
6
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from "../../
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
SETTINGS_CATALOG,
|
|
9
|
+
type SettingCategory,
|
|
10
|
+
type SettingDefinition,
|
|
11
|
+
} from "../../data/settings-catalog.js";
|
|
12
|
+
import {
|
|
13
|
+
readAllSettingsBothScopes,
|
|
14
|
+
writeSettingValue,
|
|
15
|
+
type ScopedSettingValues,
|
|
16
|
+
} from "../../services/settings-manager.js";
|
|
17
|
+
|
|
18
|
+
interface SettingsListItem {
|
|
19
|
+
id: string;
|
|
20
|
+
type: "category" | "setting";
|
|
21
|
+
label: string;
|
|
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;
|
|
16
91
|
}
|
|
17
92
|
|
|
18
|
-
|
|
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
|
+
}
|
|
122
|
+
|
|
123
|
+
export function SettingsScreen() {
|
|
19
124
|
const { state, dispatch } = useApp();
|
|
20
|
-
const {
|
|
125
|
+
const { settings } = state;
|
|
21
126
|
const modal = useModal();
|
|
22
127
|
const dimensions = useDimensions();
|
|
23
128
|
|
|
24
|
-
|
|
25
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
26
|
-
|
|
27
|
-
// Fetch data
|
|
129
|
+
// Fetch data from both scopes
|
|
28
130
|
const fetchData = useCallback(async () => {
|
|
29
|
-
|
|
131
|
+
dispatch({ type: "SETTINGS_DATA_LOADING" });
|
|
30
132
|
try {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
})
|
|
36
|
-
setEnvVarList(list);
|
|
133
|
+
const values = await readAllSettingsBothScopes(
|
|
134
|
+
SETTINGS_CATALOG,
|
|
135
|
+
state.projectPath,
|
|
136
|
+
);
|
|
137
|
+
dispatch({ type: "SETTINGS_DATA_SUCCESS", values });
|
|
37
138
|
} catch (error) {
|
|
38
|
-
|
|
139
|
+
dispatch({
|
|
140
|
+
type: "SETTINGS_DATA_ERROR",
|
|
141
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
142
|
+
});
|
|
39
143
|
}
|
|
40
|
-
|
|
41
|
-
}, [state.projectPath]);
|
|
144
|
+
}, [dispatch, state.projectPath]);
|
|
42
145
|
|
|
43
146
|
useEffect(() => {
|
|
44
147
|
fetchData();
|
|
45
148
|
}, [fetchData]);
|
|
46
149
|
|
|
150
|
+
// Build flat list items
|
|
151
|
+
const listItems = useMemo((): SettingsListItem[] => {
|
|
152
|
+
if (settings.values.status !== "success") return [];
|
|
153
|
+
return buildListItems(settings.values.data);
|
|
154
|
+
}, [settings.values]);
|
|
155
|
+
|
|
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
|
+
const handleScopeChange = async (scope: "user" | "project") => {
|
|
166
|
+
const item = selectableItems[settings.selectedIndex];
|
|
167
|
+
if (!item || item.type !== "setting" || !item.setting) return;
|
|
168
|
+
|
|
169
|
+
const setting = item.setting;
|
|
170
|
+
const currentValue =
|
|
171
|
+
scope === "user" ? item.scopedValues?.user : item.scopedValues?.project;
|
|
172
|
+
|
|
173
|
+
if (setting.type === "boolean") {
|
|
174
|
+
const currentBool =
|
|
175
|
+
currentValue === "true" ||
|
|
176
|
+
currentValue === "1" ||
|
|
177
|
+
(currentValue === undefined && setting.defaultValue === "true");
|
|
178
|
+
const newValue = currentBool ? "false" : "true";
|
|
179
|
+
try {
|
|
180
|
+
await writeSettingValue(setting, newValue, scope, state.projectPath);
|
|
181
|
+
await fetchData();
|
|
182
|
+
} catch (error) {
|
|
183
|
+
await modal.message("Error", `Failed to update: ${error}`, "error");
|
|
184
|
+
}
|
|
185
|
+
} else if (setting.type === "select" && setting.options) {
|
|
186
|
+
const options = setting.options.map((o) => ({
|
|
187
|
+
label: o.label + (currentValue === o.value ? " (current)" : ""),
|
|
188
|
+
value: o.value,
|
|
189
|
+
}));
|
|
190
|
+
// Find current value index for pre-selection
|
|
191
|
+
const currentIndex = setting.options.findIndex(
|
|
192
|
+
(o) => o.value === currentValue,
|
|
193
|
+
);
|
|
194
|
+
// Add "clear" option to remove the setting
|
|
195
|
+
if (currentValue !== undefined) {
|
|
196
|
+
options.push({ label: "Clear (use default)", value: "__clear__" });
|
|
197
|
+
}
|
|
198
|
+
const selected = await modal.select(
|
|
199
|
+
`${setting.name} — ${scope}`,
|
|
200
|
+
setting.description,
|
|
201
|
+
options,
|
|
202
|
+
currentIndex >= 0 ? currentIndex : undefined,
|
|
203
|
+
);
|
|
204
|
+
if (selected === null) return;
|
|
205
|
+
try {
|
|
206
|
+
const val =
|
|
207
|
+
selected === "__clear__" ? undefined : selected || undefined;
|
|
208
|
+
await writeSettingValue(setting, val, scope, state.projectPath);
|
|
209
|
+
await fetchData();
|
|
210
|
+
} catch (error) {
|
|
211
|
+
await modal.message("Error", `Failed to update: ${error}`, "error");
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
const newValue = await modal.input(
|
|
215
|
+
`${setting.name} — ${scope}`,
|
|
216
|
+
setting.description,
|
|
217
|
+
currentValue || "",
|
|
218
|
+
);
|
|
219
|
+
if (newValue === null) return;
|
|
220
|
+
try {
|
|
221
|
+
await writeSettingValue(
|
|
222
|
+
setting,
|
|
223
|
+
newValue || undefined,
|
|
224
|
+
scope,
|
|
225
|
+
state.projectPath,
|
|
226
|
+
);
|
|
227
|
+
await fetchData();
|
|
228
|
+
} catch (error) {
|
|
229
|
+
await modal.message("Error", `Failed to update: ${error}`, "error");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
47
234
|
// Keyboard handling
|
|
48
235
|
useKeyboard((event) => {
|
|
49
236
|
if (state.isSearching || state.modal) return;
|
|
50
237
|
|
|
51
238
|
if (event.name === "up" || event.name === "k") {
|
|
52
|
-
const newIndex = Math.max(0,
|
|
53
|
-
dispatch({ type: "
|
|
239
|
+
const newIndex = Math.max(0, settings.selectedIndex - 1);
|
|
240
|
+
dispatch({ type: "SETTINGS_SELECT", index: newIndex });
|
|
54
241
|
} else if (event.name === "down" || event.name === "j") {
|
|
55
242
|
const newIndex = Math.min(
|
|
56
|
-
Math.max(0,
|
|
57
|
-
|
|
243
|
+
Math.max(0, selectableItems.length - 1),
|
|
244
|
+
settings.selectedIndex + 1,
|
|
58
245
|
);
|
|
59
|
-
dispatch({ type: "
|
|
60
|
-
} else if (event.name === "
|
|
61
|
-
|
|
62
|
-
} else if (event.name === "
|
|
63
|
-
|
|
64
|
-
} else if (event.name === "
|
|
65
|
-
|
|
246
|
+
dispatch({ type: "SETTINGS_SELECT", index: newIndex });
|
|
247
|
+
} else if (event.name === "u") {
|
|
248
|
+
handleScopeChange("user");
|
|
249
|
+
} else if (event.name === "p") {
|
|
250
|
+
handleScopeChange("project");
|
|
251
|
+
} else if (event.name === "enter") {
|
|
252
|
+
// Enter defaults to project scope
|
|
253
|
+
handleScopeChange("project");
|
|
66
254
|
}
|
|
67
255
|
});
|
|
68
256
|
|
|
69
|
-
const
|
|
70
|
-
const varName = await modal.input("Add Variable", "Variable name:");
|
|
71
|
-
if (varName === null || !varName.trim()) return;
|
|
72
|
-
|
|
73
|
-
const cleanName = varName
|
|
74
|
-
.trim()
|
|
75
|
-
.toUpperCase()
|
|
76
|
-
.replace(/[^A-Z0-9_]/g, "_");
|
|
77
|
-
|
|
78
|
-
// Check if already exists
|
|
79
|
-
const existing = envVarList.find((v) => v.name === cleanName);
|
|
80
|
-
if (existing) {
|
|
81
|
-
const overwrite = await modal.confirm(
|
|
82
|
-
`${cleanName} exists`,
|
|
83
|
-
"Overwrite existing value?",
|
|
84
|
-
);
|
|
85
|
-
if (!overwrite) return;
|
|
86
|
-
}
|
|
257
|
+
const selectedItem = selectableItems[settings.selectedIndex];
|
|
87
258
|
|
|
88
|
-
|
|
89
|
-
|
|
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" ? "★ " : "";
|
|
90
275
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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>
|
|
99
287
|
);
|
|
100
|
-
fetchData();
|
|
101
|
-
} catch (error) {
|
|
102
|
-
modal.hideModal();
|
|
103
|
-
await modal.message("Error", `Failed to add: ${error}`, "error");
|
|
104
288
|
}
|
|
105
|
-
};
|
|
106
289
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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";
|
|
111
296
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
297
|
+
if (isSelected) {
|
|
298
|
+
return (
|
|
299
|
+
<text bg="magenta" fg="white">
|
|
300
|
+
{" "}
|
|
301
|
+
{indicator} {setting.name.padEnd(28)}
|
|
302
|
+
{displayValue}{" "}
|
|
303
|
+
</text>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
118
306
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
`${envVar.name} updated.\nRestart Claude Code to apply.`,
|
|
126
|
-
"success",
|
|
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>
|
|
127
313
|
);
|
|
128
|
-
fetchData();
|
|
129
|
-
} catch (error) {
|
|
130
|
-
modal.hideModal();
|
|
131
|
-
await modal.message("Error", `Failed to update: ${error}`, "error");
|
|
132
314
|
}
|
|
133
|
-
};
|
|
134
315
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const envVar = envVarList[envVars.selectedIndex];
|
|
138
|
-
if (!envVar) return;
|
|
139
|
-
|
|
140
|
-
const confirmed = await modal.confirm(
|
|
141
|
-
`Delete ${envVar.name}?`,
|
|
142
|
-
"This will remove the variable from configuration.",
|
|
143
|
-
);
|
|
316
|
+
return <text fg="gray">{item.label}</text>;
|
|
317
|
+
};
|
|
144
318
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
await removeMcpEnvVar(envVar.name, state.projectPath);
|
|
149
|
-
modal.hideModal();
|
|
150
|
-
await modal.message("Deleted", `${envVar.name} removed.`, "success");
|
|
151
|
-
fetchData();
|
|
152
|
-
} catch (error) {
|
|
153
|
-
modal.hideModal();
|
|
154
|
-
await modal.message("Error", `Failed to delete: ${error}`, "error");
|
|
155
|
-
}
|
|
319
|
+
const renderDetail = () => {
|
|
320
|
+
if (settings.values.status === "loading") {
|
|
321
|
+
return <text fg="gray">Loading settings...</text>;
|
|
156
322
|
}
|
|
157
|
-
};
|
|
158
323
|
|
|
159
|
-
|
|
160
|
-
|
|
324
|
+
if (settings.values.status === "error") {
|
|
325
|
+
return <text fg="red">Failed to load settings</text>;
|
|
326
|
+
}
|
|
161
327
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return <text fg="gray">Loading environment variables...</text>;
|
|
328
|
+
if (!selectedItem) {
|
|
329
|
+
return <text fg="gray">Select a setting to see details</text>;
|
|
165
330
|
}
|
|
166
331
|
|
|
167
|
-
if (
|
|
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
|
+
};
|
|
168
353
|
return (
|
|
169
354
|
<box flexDirection="column">
|
|
170
|
-
<text fg=
|
|
355
|
+
<text fg={catColor}>
|
|
356
|
+
<strong>{CATEGORY_LABELS[cat]}</strong>
|
|
357
|
+
</text>
|
|
171
358
|
<box marginTop={1}>
|
|
172
|
-
<text fg="
|
|
359
|
+
<text fg="gray">{descriptions[cat]}</text>
|
|
173
360
|
</box>
|
|
174
361
|
</box>
|
|
175
362
|
);
|
|
176
363
|
}
|
|
177
364
|
|
|
178
|
-
if (
|
|
179
|
-
|
|
180
|
-
|
|
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}`;
|
|
181
375
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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>
|
|
193
393
|
</text>
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
<box>
|
|
197
|
-
<text bg="magenta" fg="white">
|
|
198
|
-
{" "}
|
|
199
|
-
Enter{" "}
|
|
200
|
-
</text>
|
|
201
|
-
<text fg="gray"> Edit value</text>
|
|
394
|
+
<box marginTop={1}>
|
|
395
|
+
<text fg="white">{setting.description}</text>
|
|
202
396
|
</box>
|
|
397
|
+
|
|
398
|
+
{/* Storage info */}
|
|
203
399
|
<box marginTop={1}>
|
|
204
|
-
<text
|
|
205
|
-
|
|
206
|
-
|
|
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>
|
|
207
419
|
</text>
|
|
208
|
-
<
|
|
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>
|
|
209
454
|
</box>
|
|
210
455
|
</box>
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
};
|
|
456
|
+
);
|
|
457
|
+
}
|
|
214
458
|
|
|
215
|
-
|
|
216
|
-
envVar: EnvVar,
|
|
217
|
-
_idx: number,
|
|
218
|
-
isSelected: boolean,
|
|
219
|
-
) => {
|
|
220
|
-
const masked =
|
|
221
|
-
envVar.value.length > 20
|
|
222
|
-
? envVar.value.slice(0, 20) + "..."
|
|
223
|
-
: envVar.value;
|
|
224
|
-
return isSelected ? (
|
|
225
|
-
<text bg="magenta" fg="white">
|
|
226
|
-
{" "}
|
|
227
|
-
{envVar.name} = "{masked}"{" "}
|
|
228
|
-
</text>
|
|
229
|
-
) : (
|
|
230
|
-
<text>
|
|
231
|
-
<span fg="cyan">{envVar.name}</span>
|
|
232
|
-
<span fg="gray"> = "{masked}"</span>
|
|
233
|
-
</text>
|
|
234
|
-
);
|
|
459
|
+
return null;
|
|
235
460
|
};
|
|
236
461
|
|
|
462
|
+
const totalSet =
|
|
463
|
+
settings.values.status === "success"
|
|
464
|
+
? Array.from(settings.values.data.values()).filter(
|
|
465
|
+
(v) => v.user !== undefined || v.project !== undefined,
|
|
466
|
+
).length
|
|
467
|
+
: 0;
|
|
468
|
+
|
|
237
469
|
const statusContent = (
|
|
238
|
-
|
|
239
|
-
<
|
|
240
|
-
<
|
|
241
|
-
<
|
|
242
|
-
|
|
243
|
-
</>
|
|
470
|
+
<text>
|
|
471
|
+
<span fg="gray">Settings: </span>
|
|
472
|
+
<span fg="cyan">{totalSet} configured</span>
|
|
473
|
+
<span fg="gray"> │ u:user p:project</span>
|
|
474
|
+
</text>
|
|
244
475
|
);
|
|
245
476
|
|
|
246
477
|
return (
|
|
247
478
|
<ScreenLayout
|
|
248
|
-
title="claudeup
|
|
249
|
-
currentScreen="
|
|
479
|
+
title="claudeup Settings"
|
|
480
|
+
currentScreen="settings"
|
|
250
481
|
statusLine={statusContent}
|
|
251
|
-
footerHints="↑↓:nav │
|
|
482
|
+
footerHints="↑↓:nav │ u:user scope │ p:project scope │ Enter:project"
|
|
252
483
|
listPanel={
|
|
253
|
-
|
|
254
|
-
<text fg="gray">
|
|
484
|
+
settings.values.status !== "success" ? (
|
|
485
|
+
<text fg="gray">
|
|
486
|
+
{settings.values.status === "loading"
|
|
487
|
+
? "Loading..."
|
|
488
|
+
: "Error loading settings"}
|
|
489
|
+
</text>
|
|
255
490
|
) : (
|
|
256
491
|
<ScrollableList
|
|
257
|
-
items={
|
|
258
|
-
selectedIndex={
|
|
492
|
+
items={selectableItems}
|
|
493
|
+
selectedIndex={settings.selectedIndex}
|
|
259
494
|
renderItem={renderListItem}
|
|
260
495
|
maxHeight={dimensions.listPanelHeight}
|
|
261
496
|
/>
|
|
@@ -266,4 +501,4 @@ export function EnvVarsScreen() {
|
|
|
266
501
|
);
|
|
267
502
|
}
|
|
268
503
|
|
|
269
|
-
export default
|
|
504
|
+
export default SettingsScreen;
|