claudeup 3.7.2 → 3.8.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.
Files changed (44) hide show
  1. package/package.json +1 -1
  2. package/src/data/settings-catalog.js +612 -0
  3. package/src/data/settings-catalog.ts +689 -0
  4. package/src/services/plugin-manager.js +2 -0
  5. package/src/services/plugin-manager.ts +3 -0
  6. package/src/services/profiles.js +161 -0
  7. package/src/services/profiles.ts +225 -0
  8. package/src/services/settings-manager.js +108 -0
  9. package/src/services/settings-manager.ts +140 -0
  10. package/src/types/index.ts +34 -0
  11. package/src/ui/App.js +17 -18
  12. package/src/ui/App.tsx +21 -23
  13. package/src/ui/components/TabBar.js +8 -8
  14. package/src/ui/components/TabBar.tsx +14 -19
  15. package/src/ui/components/layout/ScreenLayout.js +8 -14
  16. package/src/ui/components/layout/ScreenLayout.tsx +51 -58
  17. package/src/ui/components/modals/ModalContainer.js +43 -11
  18. package/src/ui/components/modals/ModalContainer.tsx +44 -12
  19. package/src/ui/components/modals/SelectModal.js +4 -18
  20. package/src/ui/components/modals/SelectModal.tsx +10 -21
  21. package/src/ui/screens/CliToolsScreen.js +2 -2
  22. package/src/ui/screens/CliToolsScreen.tsx +8 -8
  23. package/src/ui/screens/EnvVarsScreen.js +248 -116
  24. package/src/ui/screens/EnvVarsScreen.tsx +419 -184
  25. package/src/ui/screens/McpRegistryScreen.tsx +18 -6
  26. package/src/ui/screens/McpScreen.js +1 -1
  27. package/src/ui/screens/McpScreen.tsx +15 -5
  28. package/src/ui/screens/ModelSelectorScreen.js +3 -5
  29. package/src/ui/screens/ModelSelectorScreen.tsx +12 -16
  30. package/src/ui/screens/PluginsScreen.js +154 -66
  31. package/src/ui/screens/PluginsScreen.tsx +280 -97
  32. package/src/ui/screens/ProfilesScreen.js +255 -0
  33. package/src/ui/screens/ProfilesScreen.tsx +487 -0
  34. package/src/ui/screens/StatusLineScreen.js +2 -2
  35. package/src/ui/screens/StatusLineScreen.tsx +10 -12
  36. package/src/ui/screens/index.js +2 -2
  37. package/src/ui/screens/index.ts +2 -2
  38. package/src/ui/state/AppContext.js +2 -1
  39. package/src/ui/state/AppContext.tsx +2 -0
  40. package/src/ui/state/reducer.js +63 -19
  41. package/src/ui/state/reducer.ts +68 -19
  42. package/src/ui/state/types.ts +33 -14
  43. package/src/utils/clipboard.js +56 -0
  44. package/src/utils/clipboard.ts +58 -0
@@ -1,261 +1,496 @@
1
- import React, { useEffect, useCallback, useState } from "react";
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
- getMcpEnvVars,
9
- setMcpEnvVar,
10
- removeMcpEnvVar,
11
- } from "../../services/claude-settings.js";
12
-
13
- interface EnvVar {
14
- name: string;
15
- value: string;
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
- export function EnvVarsScreen() {
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 { envVars } = state;
125
+ const { settings } = state;
21
126
  const modal = useModal();
22
127
  const dimensions = useDimensions();
23
128
 
24
- const [envVarList, setEnvVarList] = useState<EnvVar[]>([]);
25
- const [isLoading, setIsLoading] = useState(true);
26
-
27
- // Fetch data
129
+ // Fetch data from both scopes
28
130
  const fetchData = useCallback(async () => {
29
- setIsLoading(true);
131
+ dispatch({ type: "SETTINGS_DATA_LOADING" });
30
132
  try {
31
- const vars = await getMcpEnvVars(state.projectPath);
32
- const list = Object.entries(vars).map(([name, value]) => ({
33
- name,
34
- value,
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
- setEnvVarList([]);
139
+ dispatch({
140
+ type: "SETTINGS_DATA_ERROR",
141
+ error: error instanceof Error ? error : new Error(String(error)),
142
+ });
39
143
  }
40
- setIsLoading(false);
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, envVars.selectedIndex - 1);
53
- dispatch({ type: "ENVVARS_SELECT", index: newIndex });
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, envVarList.length - 1),
57
- envVars.selectedIndex + 1,
243
+ Math.max(0, selectableItems.length - 1),
244
+ settings.selectedIndex + 1,
58
245
  );
59
- dispatch({ type: "ENVVARS_SELECT", index: newIndex });
60
- } else if (event.name === "a") {
61
- handleAdd();
62
- } else if (event.name === "e" || event.name === "enter") {
63
- handleEdit();
64
- } else if (event.name === "d") {
65
- handleDelete();
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 handleAdd = async () => {
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
- const value = await modal.input(`Set ${cleanName}`, "Value:");
89
- if (value === null) return;
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
- modal.loading(`Adding ${cleanName}...`);
92
- try {
93
- await setMcpEnvVar(cleanName, value, state.projectPath);
94
- modal.hideModal();
95
- await modal.message(
96
- "Added",
97
- `${cleanName} added.\nRestart Claude Code to apply.`,
98
- "success",
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
- const handleEdit = async () => {
108
- if (envVarList.length === 0) return;
109
- const envVar = envVarList[envVars.selectedIndex];
110
- if (!envVar) return;
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
- const newValue = await modal.input(
113
- `Edit ${envVar.name}`,
114
- "New value:",
115
- envVar.value,
116
- );
117
- if (newValue === null) return;
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
- modal.loading(`Updating ${envVar.name}...`);
120
- try {
121
- await setMcpEnvVar(envVar.name, newValue, state.projectPath);
122
- modal.hideModal();
123
- await modal.message(
124
- "Updated",
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
- const handleDelete = async () => {
136
- if (envVarList.length === 0) return;
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
- if (confirmed) {
146
- modal.loading(`Deleting ${envVar.name}...`);
147
- try {
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
- // Get selected item
160
- const selectedVar = envVarList[envVars.selectedIndex];
324
+ if (settings.values.status === "error") {
325
+ return <text fg="red">Failed to load settings</text>;
326
+ }
161
327
 
162
- const renderDetail = () => {
163
- if (isLoading) {
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 (envVarList.length === 0) {
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="gray">No environment variables configured.</text>
355
+ <text fg={catColor}>
356
+ <strong>{CATEGORY_LABELS[cat]}</strong>
357
+ </text>
171
358
  <box marginTop={1}>
172
- <text fg="green">Press 'a' to add a new variable</text>
359
+ <text fg="gray">{descriptions[cat]}</text>
173
360
  </box>
174
361
  </box>
175
362
  );
176
363
  }
177
364
 
178
- if (!selectedVar) {
179
- return <text fg="gray">Select a variable to see details</text>;
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
- return (
183
- <box flexDirection="column">
184
- <text fg="cyan">
185
- <strong>{selectedVar.name}</strong>
186
- </text>
187
- <box marginTop={1}>
188
- <text fg="gray">Value: </text>
189
- <text>
190
- {selectedVar.value.length > 50
191
- ? selectedVar.value.slice(0, 50) + "..."
192
- : selectedVar.value}
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
- </box>
195
- <box marginTop={2} flexDirection="column">
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 bg="red" fg="white">
205
- {" "}
206
- d{" "}
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
- <text fg="gray"> Delete variable</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>
209
454
  </box>
210
455
  </box>
211
- </box>
212
- );
213
- };
456
+ );
457
+ }
214
458
 
215
- const renderListItem = (
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
- <text fg="gray">Variables: </text>
240
- <text fg="cyan">{envVarList.length}</text>
241
- <text fg="gray"> │ Location: </text>
242
- <text fg="green">.claude/settings.local.json</text>
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 Environment Variables"
249
- currentScreen="env-vars"
479
+ title="claudeup Settings"
480
+ currentScreen="settings"
250
481
  statusLine={statusContent}
251
- footerHints="↑↓:nav │ Enter/e:edita:addd:delete"
482
+ footerHints="↑↓:nav │ u:user scope p:project scope Enter:project"
252
483
  listPanel={
253
- envVarList.length === 0 ? (
254
- <text fg="gray">No environment variables configured</text>
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={envVarList}
258
- selectedIndex={envVars.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 EnvVarsScreen;
504
+ export default SettingsScreen;