claudeup 3.17.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/package.json +1 -1
  2. package/src/data/predefined-profiles.js +69 -12
  3. package/src/data/predefined-profiles.ts +73 -14
  4. package/src/services/claude-cli.js +8 -1
  5. package/src/services/claude-cli.ts +10 -7
  6. package/src/services/plugin-manager.js +40 -4
  7. package/src/services/plugin-manager.ts +57 -6
  8. package/src/ui/adapters/pluginsAdapter.js +139 -0
  9. package/src/ui/adapters/pluginsAdapter.ts +202 -0
  10. package/src/ui/adapters/settingsAdapter.js +111 -0
  11. package/src/ui/adapters/settingsAdapter.ts +165 -0
  12. package/src/ui/components/ScrollableDetail.js +23 -0
  13. package/src/ui/components/ScrollableDetail.tsx +55 -0
  14. package/src/ui/components/ScrollableList.js +4 -4
  15. package/src/ui/components/ScrollableList.tsx +4 -4
  16. package/src/ui/components/SearchInput.js +2 -2
  17. package/src/ui/components/SearchInput.tsx +3 -3
  18. package/src/ui/components/StyledText.js +1 -1
  19. package/src/ui/components/StyledText.tsx +5 -1
  20. package/src/ui/components/layout/ProgressBar.js +1 -1
  21. package/src/ui/components/layout/ProgressBar.tsx +1 -5
  22. package/src/ui/components/layout/ScreenLayout.js +1 -1
  23. package/src/ui/components/layout/ScreenLayout.tsx +11 -8
  24. package/src/ui/components/modals/InputModal.tsx +1 -6
  25. package/src/ui/components/modals/LoadingModal.js +1 -1
  26. package/src/ui/components/modals/LoadingModal.tsx +1 -3
  27. package/src/ui/hooks/index.js +3 -3
  28. package/src/ui/hooks/index.ts +3 -3
  29. package/src/ui/hooks/useKeyboard.ts +1 -3
  30. package/src/ui/hooks/useKeyboardHandler.js +9 -9
  31. package/src/ui/hooks/useKeyboardHandler.ts +9 -9
  32. package/src/ui/renderers/cliToolRenderers.js +33 -0
  33. package/src/ui/renderers/cliToolRenderers.tsx +153 -0
  34. package/src/ui/renderers/mcpRenderers.js +26 -0
  35. package/src/ui/renderers/mcpRenderers.tsx +145 -0
  36. package/src/ui/renderers/pluginRenderers.js +124 -0
  37. package/src/ui/renderers/pluginRenderers.tsx +362 -0
  38. package/src/ui/renderers/profileRenderers.js +177 -0
  39. package/src/ui/renderers/profileRenderers.tsx +361 -0
  40. package/src/ui/renderers/settingsRenderers.js +69 -0
  41. package/src/ui/renderers/settingsRenderers.tsx +205 -0
  42. package/src/ui/screens/CliToolsScreen.js +14 -58
  43. package/src/ui/screens/CliToolsScreen.tsx +36 -196
  44. package/src/ui/screens/EnvVarsScreen.js +12 -168
  45. package/src/ui/screens/EnvVarsScreen.tsx +16 -327
  46. package/src/ui/screens/McpScreen.js +12 -62
  47. package/src/ui/screens/McpScreen.tsx +21 -190
  48. package/src/ui/screens/PluginsScreen.js +52 -425
  49. package/src/ui/screens/PluginsScreen.tsx +70 -758
  50. package/src/ui/screens/ProfilesScreen.js +32 -97
  51. package/src/ui/screens/ProfilesScreen.tsx +58 -328
  52. package/src/ui/screens/SkillsScreen.js +16 -16
  53. package/src/ui/screens/SkillsScreen.tsx +20 -23
@@ -6,21 +6,19 @@ import { useDimensions } from "../state/DimensionsContext.js";
6
6
  import { useKeyboard } from "../hooks/useKeyboard.js";
7
7
  import { ScreenLayout } from "../components/layout/index.js";
8
8
  import { ScrollableList } from "../components/ScrollableList.js";
9
- import { cliTools, type CliTool } from "../../data/cli-tools.js";
9
+ import { cliTools } from "../../data/cli-tools.js";
10
+ import {
11
+ renderCliToolRow,
12
+ renderCliToolDetail,
13
+ type CliToolStatus,
14
+ } from "../renderers/cliToolRenderers.js";
10
15
 
11
16
  const execAsync = promisify(exec);
12
17
 
13
- interface ToolStatus {
14
- tool: CliTool;
15
- installed: boolean;
16
- installedVersion?: string;
17
- latestVersion?: string;
18
- hasUpdate?: boolean;
19
- checking: boolean;
20
- }
18
+ // ─── Version helpers ───────────────────────────────────────────────────────────
21
19
 
22
20
  // Session-level cache
23
- let cachedToolStatuses: ToolStatus[] | null = null;
21
+ let cachedToolStatuses: CliToolStatus[] | null = null;
24
22
  let cacheInitialized = false;
25
23
 
26
24
  function clearCliToolsCache(): void {
@@ -46,7 +44,9 @@ async function isInstalledViaHomebrew(toolName: string): Promise<boolean> {
46
44
  }
47
45
  }
48
46
 
49
- async function getInstalledVersion(tool: CliTool): Promise<string | undefined> {
47
+ async function getInstalledVersion(
48
+ tool: import("../../data/cli-tools.js").CliTool,
49
+ ): Promise<string | undefined> {
50
50
  try {
51
51
  const { stdout } = await execAsync(tool.checkCommand, { timeout: 5000 });
52
52
  return parseVersion(stdout.trim());
@@ -55,9 +55,7 @@ async function getInstalledVersion(tool: CliTool): Promise<string | undefined> {
55
55
  }
56
56
  }
57
57
 
58
- async function getLatestNpmVersion(
59
- packageName: string,
60
- ): Promise<string | undefined> {
58
+ async function getLatestNpmVersion(packageName: string): Promise<string | undefined> {
61
59
  try {
62
60
  const { stdout } = await execAsync(
63
61
  `npm view ${packageName} version 2>/dev/null`,
@@ -69,16 +67,11 @@ async function getLatestNpmVersion(
69
67
  }
70
68
  }
71
69
 
72
- async function getLatestPipVersion(
73
- packageName: string,
74
- ): Promise<string | undefined> {
70
+ async function getLatestPipVersion(packageName: string): Promise<string | undefined> {
75
71
  try {
76
72
  const { stdout } = await execAsync(
77
73
  `pip index versions ${packageName} 2>/dev/null | head -1`,
78
- {
79
- timeout: 10000,
80
- shell: "/bin/bash",
81
- },
74
+ { timeout: 10000, shell: "/bin/bash" },
82
75
  );
83
76
  const match = stdout.trim().match(/\(([^)]+)\)/);
84
77
  return match ? match[1] : undefined;
@@ -87,18 +80,17 @@ async function getLatestPipVersion(
87
80
  }
88
81
  }
89
82
 
90
- async function getLatestVersion(tool: CliTool): Promise<string | undefined> {
91
- if (tool.packageManager === "npm") {
92
- return getLatestNpmVersion(tool.packageName);
93
- } else {
94
- return getLatestPipVersion(tool.packageName);
95
- }
83
+ async function getLatestVersion(
84
+ tool: import("../../data/cli-tools.js").CliTool,
85
+ ): Promise<string | undefined> {
86
+ return tool.packageManager === "npm"
87
+ ? getLatestNpmVersion(tool.packageName)
88
+ : getLatestPipVersion(tool.packageName);
96
89
  }
97
90
 
98
91
  function compareVersions(v1: string, v2: string): number {
99
92
  const parts1 = v1.split(/[-.]/).map((p) => parseInt(p, 10) || 0);
100
93
  const parts2 = v2.split(/[-.]/).map((p) => parseInt(p, 10) || 0);
101
-
102
94
  for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
103
95
  const p1 = parts1[i] || 0;
104
96
  const p2 = parts2[i] || 0;
@@ -108,13 +100,15 @@ function compareVersions(v1: string, v2: string): number {
108
100
  return 0;
109
101
  }
110
102
 
103
+ // ─── Component ─────────────────────────────────────────────────────────────────
104
+
111
105
  export function CliToolsScreen() {
112
106
  const { state, dispatch } = useApp();
113
107
  const { cliTools: cliToolsState } = state;
114
108
  const modal = useModal();
115
109
  const dimensions = useDimensions();
116
110
 
117
- const [toolStatuses, setToolStatuses] = useState<ToolStatus[]>(() => {
111
+ const [toolStatuses, setToolStatuses] = useState<CliToolStatus[]>(() => {
118
112
  return (
119
113
  cachedToolStatuses ||
120
114
  cliTools.map((tool) => ({
@@ -129,12 +123,11 @@ export function CliToolsScreen() {
129
123
  const statusesRef = useRef(toolStatuses);
130
124
  statusesRef.current = toolStatuses;
131
125
 
132
- // Update single tool status
133
126
  const updateToolStatus = useCallback(
134
- (index: number, updates: Partial<ToolStatus>) => {
127
+ (index: number, updates: Partial<CliToolStatus>) => {
135
128
  setToolStatuses((prev) => {
136
129
  const newStatuses = [...prev];
137
- newStatuses[index] = { ...newStatuses[index], ...updates };
130
+ newStatuses[index] = { ...newStatuses[index]!, ...updates };
138
131
  cachedToolStatuses = newStatuses;
139
132
  return newStatuses;
140
133
  });
@@ -142,11 +135,9 @@ export function CliToolsScreen() {
142
135
  [],
143
136
  );
144
137
 
145
- // Fetch all version info
146
138
  const fetchVersionInfo = useCallback(async () => {
147
- // Check installed versions
148
139
  for (let i = 0; i < cliTools.length; i++) {
149
- const tool = cliTools[i];
140
+ const tool = cliTools[i]!;
150
141
  getInstalledVersion(tool).then((version) => {
151
142
  updateToolStatus(i, {
152
143
  installedVersion: version,
@@ -155,7 +146,7 @@ export function CliToolsScreen() {
155
146
  });
156
147
 
157
148
  getLatestVersion(tool).then((latest) => {
158
- const current = statusesRef.current[i];
149
+ const current = statusesRef.current[i]!;
159
150
  updateToolStatus(i, {
160
151
  latestVersion: latest,
161
152
  checking: false,
@@ -176,7 +167,6 @@ export function CliToolsScreen() {
176
167
  }
177
168
  }, [fetchVersionInfo, toolStatuses]);
178
169
 
179
- // Keyboard handling
180
170
  useKeyboard((event) => {
181
171
  if (state.isSearching || state.modal) return;
182
172
 
@@ -217,27 +207,19 @@ export function CliToolsScreen() {
217
207
  if (!status) return;
218
208
 
219
209
  const { tool, installed, hasUpdate } = status;
220
- const action = !installed
221
- ? "Installing"
222
- : hasUpdate
223
- ? "Updating"
224
- : "Reinstalling";
210
+ const action = !installed ? "Installing" : hasUpdate ? "Updating" : "Reinstalling";
225
211
 
226
212
  const viaHomebrew = installed
227
213
  ? await isInstalledViaHomebrew(tool.name)
228
214
  : false;
229
215
 
230
- let command: string;
231
- if (!installed) {
232
- command = tool.installCommand;
233
- } else if (viaHomebrew) {
234
- command = `brew upgrade ${tool.name}`;
235
- } else {
236
- command = tool.installCommand;
237
- }
216
+ const command = !installed
217
+ ? tool.installCommand
218
+ : viaHomebrew
219
+ ? `brew upgrade ${tool.name}`
220
+ : tool.installCommand;
238
221
 
239
222
  modal.loading(`${action} ${tool.displayName}...`);
240
-
241
223
  try {
242
224
  execSync(command, {
243
225
  encoding: "utf-8",
@@ -259,11 +241,7 @@ export function CliToolsScreen() {
259
241
  const handleUpdateAll = async () => {
260
242
  const updatable = toolStatuses.filter((s) => s.hasUpdate);
261
243
  if (updatable.length === 0) {
262
- await modal.message(
263
- "Up to Date",
264
- "All tools are already up to date.",
265
- "info",
266
- );
244
+ await modal.message("Up to Date", "All tools are already up to date.", "info");
267
245
  return;
268
246
  }
269
247
 
@@ -274,7 +252,6 @@ export function CliToolsScreen() {
274
252
  const command = viaHomebrew
275
253
  ? `brew upgrade ${status.tool.name}`
276
254
  : status.tool.installCommand;
277
-
278
255
  try {
279
256
  execSync(command, {
280
257
  encoding: "utf-8",
@@ -290,145 +267,8 @@ export function CliToolsScreen() {
290
267
  handleRefresh();
291
268
  };
292
269
 
293
- // Get selected item
294
270
  const selectedStatus = toolStatuses[cliToolsState.selectedIndex];
295
271
 
296
- const renderDetail = () => {
297
- if (!selectedStatus) {
298
- return (
299
- <box
300
- flexDirection="column"
301
- alignItems="center"
302
- justifyContent="center"
303
- flexGrow={1}
304
- >
305
- <text fg="gray">Select a tool to see details</text>
306
- </box>
307
- );
308
- }
309
-
310
- const {
311
- tool,
312
- installed,
313
- installedVersion,
314
- latestVersion,
315
- hasUpdate,
316
- checking,
317
- } = selectedStatus;
318
-
319
- return (
320
- <box flexDirection="column">
321
- <box marginBottom={1}>
322
- <text fg="cyan">
323
- <strong>⚙ {tool.displayName}</strong>
324
- </text>
325
- {hasUpdate && <text fg="yellow"> ⬆</text>}
326
- </box>
327
-
328
- <text fg="gray">{tool.description}</text>
329
-
330
- <box marginTop={1} flexDirection="column">
331
- <box>
332
- <text fg="gray">Status </text>
333
- {!installed ? (
334
- <text fg="gray">○ Not installed</text>
335
- ) : checking ? (
336
- <text fg="green">● Checking...</text>
337
- ) : hasUpdate ? (
338
- <text fg="yellow">● Update available</text>
339
- ) : (
340
- <text fg="green">● Up to date</text>
341
- )}
342
- </box>
343
- {installedVersion && (
344
- <box>
345
- <text fg="gray">Installed </text>
346
- <text fg="green">v{installedVersion}</text>
347
- </box>
348
- )}
349
- {latestVersion && (
350
- <box>
351
- <text fg="gray">Latest </text>
352
- <text fg="white">v{latestVersion}</text>
353
- </box>
354
- )}
355
- <box>
356
- <text fg="gray">Website </text>
357
- <text fg="#5c9aff">{tool.website}</text>
358
- </box>
359
- </box>
360
-
361
- <box marginTop={2}>
362
- {!installed ? (
363
- <box>
364
- <text bg="green" fg="black">
365
- {" "}
366
- Enter{" "}
367
- </text>
368
- <text fg="gray"> Install</text>
369
- </box>
370
- ) : hasUpdate ? (
371
- <box>
372
- <text bg="yellow" fg="black">
373
- {" "}
374
- Enter{" "}
375
- </text>
376
- <text fg="gray"> Update to v{latestVersion}</text>
377
- </box>
378
- ) : (
379
- <box>
380
- <text bg="gray" fg="white">
381
- {" "}
382
- Enter{" "}
383
- </text>
384
- <text fg="gray"> Reinstall</text>
385
- </box>
386
- )}
387
- </box>
388
- </box>
389
- );
390
- };
391
-
392
- const renderListItem = (
393
- status: ToolStatus,
394
- _idx: number,
395
- isSelected: boolean,
396
- ) => {
397
- const { tool, installed, installedVersion, hasUpdate, checking } = status;
398
-
399
- let icon: string;
400
- let iconColor: string;
401
-
402
- if (!installed) {
403
- icon = "○";
404
- iconColor = "gray";
405
- } else if (hasUpdate) {
406
- icon = "⬆";
407
- iconColor = "yellow";
408
- } else {
409
- icon = "●";
410
- iconColor = "green";
411
- }
412
-
413
- const versionText = installedVersion ? `v${installedVersion}` : "";
414
-
415
- return isSelected ? (
416
- <text bg="magenta" fg="white">
417
- {" "}
418
- {icon} {tool.displayName} {versionText}
419
- {checking ? "..." : ""}{" "}
420
- </text>
421
- ) : (
422
- <text>
423
- <span fg={iconColor}>{icon}</span>
424
- <span fg="white"> {tool.displayName}</span>
425
- {versionText && <span fg="green"> {versionText}</span>}
426
- {checking && <span fg="gray">...</span>}
427
- </text>
428
- );
429
- };
430
-
431
- // Calculate stats for status line
432
272
  const installedCount = toolStatuses.filter((s) => s.installed).length;
433
273
  const updateCount = toolStatuses.filter((s) => s.hasUpdate).length;
434
274
  const statusContent = (
@@ -456,11 +296,11 @@ export function CliToolsScreen() {
456
296
  <ScrollableList
457
297
  items={toolStatuses}
458
298
  selectedIndex={cliToolsState.selectedIndex}
459
- renderItem={renderListItem}
299
+ renderItem={renderCliToolRow}
460
300
  maxHeight={dimensions.listPanelHeight}
461
301
  />
462
302
  }
463
- detailPanel={renderDetail()}
303
+ detailPanel={renderCliToolDetail(selectedStatus)}
464
304
  />
465
305
  );
466
306
  }
@@ -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;