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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/src/data/predefined-profiles.js +191 -0
  3. package/src/data/predefined-profiles.ts +205 -0
  4. package/src/ui/adapters/pluginsAdapter.js +139 -0
  5. package/src/ui/adapters/pluginsAdapter.ts +202 -0
  6. package/src/ui/adapters/settingsAdapter.js +111 -0
  7. package/src/ui/adapters/settingsAdapter.ts +165 -0
  8. package/src/ui/components/ScrollableList.js +4 -4
  9. package/src/ui/components/ScrollableList.tsx +4 -4
  10. package/src/ui/components/SearchInput.js +2 -2
  11. package/src/ui/components/SearchInput.tsx +3 -3
  12. package/src/ui/components/StyledText.js +1 -1
  13. package/src/ui/components/StyledText.tsx +5 -1
  14. package/src/ui/components/layout/ProgressBar.js +1 -1
  15. package/src/ui/components/layout/ProgressBar.tsx +1 -5
  16. package/src/ui/components/modals/InputModal.tsx +1 -6
  17. package/src/ui/components/modals/LoadingModal.js +1 -1
  18. package/src/ui/components/modals/LoadingModal.tsx +1 -3
  19. package/src/ui/hooks/index.js +3 -3
  20. package/src/ui/hooks/index.ts +3 -3
  21. package/src/ui/hooks/useKeyboard.ts +1 -3
  22. package/src/ui/hooks/useKeyboardHandler.js +9 -9
  23. package/src/ui/hooks/useKeyboardHandler.ts +9 -9
  24. package/src/ui/renderers/cliToolRenderers.js +33 -0
  25. package/src/ui/renderers/cliToolRenderers.tsx +153 -0
  26. package/src/ui/renderers/mcpRenderers.js +26 -0
  27. package/src/ui/renderers/mcpRenderers.tsx +145 -0
  28. package/src/ui/renderers/pluginRenderers.js +124 -0
  29. package/src/ui/renderers/pluginRenderers.tsx +362 -0
  30. package/src/ui/renderers/profileRenderers.js +172 -0
  31. package/src/ui/renderers/profileRenderers.tsx +410 -0
  32. package/src/ui/renderers/settingsRenderers.js +69 -0
  33. package/src/ui/renderers/settingsRenderers.tsx +205 -0
  34. package/src/ui/screens/CliToolsScreen.js +14 -58
  35. package/src/ui/screens/CliToolsScreen.tsx +36 -196
  36. package/src/ui/screens/EnvVarsScreen.js +12 -168
  37. package/src/ui/screens/EnvVarsScreen.tsx +16 -327
  38. package/src/ui/screens/McpScreen.js +12 -62
  39. package/src/ui/screens/McpScreen.tsx +21 -190
  40. package/src/ui/screens/PluginsScreen.js +52 -425
  41. package/src/ui/screens/PluginsScreen.tsx +70 -758
  42. package/src/ui/screens/ProfilesScreen.js +104 -68
  43. package/src/ui/screens/ProfilesScreen.tsx +147 -221
  44. package/src/ui/screens/SkillsScreen.js +16 -16
  45. package/src/ui/screens/SkillsScreen.tsx +20 -23
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "@opentui/react/jsx-runtime";
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
- const CATEGORY_LABELS = {
11
- recommended: "Recommended",
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 buildListItems(settings.values.data);
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 = selectableItems[settings.selectedIndex];
118
- if (!item || item.type !== "setting" || !item.setting)
39
+ const item = listItems[settings.selectedIndex];
40
+ if (!item || item.kind !== "setting")
119
41
  return;
120
- const setting = item.setting;
121
- const currentValue = scope === "user" ? item.scopedValues?.user : item.scopedValues?.project;
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, selectableItems.length - 1), settings.selectedIndex + 1);
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 = selectableItems[settings.selectedIndex];
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
- if (!selectedItem) {
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: selectableItems, selectedIndex: settings.selectedIndex, renderItem: renderListItem, maxHeight: dimensions.listPanelHeight })), detailPanel: renderDetail() }));
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
- 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;
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
- // Build flat list items
151
- const listItems = useMemo((): SettingsListItem[] => {
46
+ const listItems = useMemo((): SettingsBrowserItem[] => {
152
47
  if (settings.values.status !== "success") return [];
153
- return buildListItems(settings.values.data);
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 = selectableItems[settings.selectedIndex];
167
- if (!item || item.type !== "setting" || !item.setting) return;
52
+ const item = listItems[settings.selectedIndex];
53
+ if (!item || item.kind !== "setting") return;
168
54
 
169
- const setting = item.setting;
55
+ const { setting, scopedValues } = item;
170
56
  const currentValue =
171
- scope === "user" ? item.scopedValues?.user : item.scopedValues?.project;
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, selectableItems.length - 1),
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 = selectableItems[settings.selectedIndex];
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={selectableItems}
181
+ items={listItems}
493
182
  selectedIndex={settings.selectedIndex}
494
- renderItem={renderListItem}
183
+ renderItem={renderSettingRow}
495
184
  maxHeight={dimensions.listPanelHeight}
496
185
  />
497
186
  )