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
@@ -0,0 +1,487 @@
1
+ import React, { useEffect, useCallback } from "react";
2
+ import { useApp, useModal } from "../state/AppContext.js";
3
+ import { useDimensions } from "../state/DimensionsContext.js";
4
+ import { useKeyboard } from "../hooks/useKeyboard.js";
5
+ import { ScreenLayout } from "../components/layout/index.js";
6
+ import { ScrollableList } from "../components/ScrollableList.js";
7
+ import {
8
+ listProfiles,
9
+ applyProfile,
10
+ renameProfile,
11
+ deleteProfile,
12
+ exportProfileToJson,
13
+ importProfileFromJson,
14
+ } from "../../services/profiles.js";
15
+ import {
16
+ writeClipboard,
17
+ readClipboard,
18
+ ClipboardUnavailableError,
19
+ } from "../../utils/clipboard.js";
20
+ import type { ProfileEntry } from "../../types/index.js";
21
+
22
+ export function ProfilesScreen() {
23
+ const { state, dispatch } = useApp();
24
+ const { profiles: profilesState } = state;
25
+ const modal = useModal();
26
+ const dimensions = useDimensions();
27
+
28
+ // Fetch data
29
+ const fetchData = useCallback(async () => {
30
+ dispatch({ type: "PROFILES_DATA_LOADING" });
31
+ try {
32
+ const entries = await listProfiles(state.projectPath);
33
+ dispatch({ type: "PROFILES_DATA_SUCCESS", profiles: entries });
34
+ } catch (error) {
35
+ dispatch({
36
+ type: "PROFILES_DATA_ERROR",
37
+ error: error instanceof Error ? error : new Error(String(error)),
38
+ });
39
+ }
40
+ }, [dispatch, state.projectPath]);
41
+
42
+ useEffect(() => {
43
+ fetchData();
44
+ }, [fetchData, state.dataRefreshVersion]);
45
+
46
+ const profileList =
47
+ profilesState.profiles.status === "success"
48
+ ? profilesState.profiles.data
49
+ : [];
50
+
51
+ const selectedProfile: ProfileEntry | undefined =
52
+ profileList[profilesState.selectedIndex];
53
+
54
+ // Keyboard handling
55
+ useKeyboard((event) => {
56
+ if (state.isSearching || state.modal) return;
57
+
58
+ if (event.name === "up" || event.name === "k") {
59
+ const newIndex = Math.max(0, profilesState.selectedIndex - 1);
60
+ dispatch({ type: "PROFILES_SELECT", index: newIndex });
61
+ } else if (event.name === "down" || event.name === "j") {
62
+ const newIndex = Math.min(
63
+ Math.max(0, profileList.length - 1),
64
+ profilesState.selectedIndex + 1,
65
+ );
66
+ dispatch({ type: "PROFILES_SELECT", index: newIndex });
67
+ } else if (event.name === "enter" || event.name === "a") {
68
+ handleApply();
69
+ } else if (event.name === "r") {
70
+ handleRename();
71
+ } else if (event.name === "d") {
72
+ handleDelete();
73
+ } else if (event.name === "c") {
74
+ handleCopy();
75
+ } else if (event.name === "i") {
76
+ handleImport();
77
+ }
78
+ });
79
+
80
+ const handleApply = async () => {
81
+ if (!selectedProfile) return;
82
+
83
+ const scopeChoice = await modal.select(
84
+ "Apply Profile",
85
+ `Apply "${selectedProfile.name}" to which scope?`,
86
+ [
87
+ { label: "User — ~/.claude/settings.json (global)", value: "user" },
88
+ {
89
+ label: "Project — .claude/settings.json (this project)",
90
+ value: "project",
91
+ },
92
+ ],
93
+ );
94
+ if (scopeChoice === null) return;
95
+
96
+ const targetScope = scopeChoice as "user" | "project";
97
+ const scopeLabel =
98
+ targetScope === "user"
99
+ ? "~/.claude/settings.json"
100
+ : ".claude/settings.json";
101
+
102
+ const pluginCount = Object.keys(selectedProfile.plugins).length;
103
+ const emptyWarning =
104
+ pluginCount === 0
105
+ ? "\n\nWARNING: This profile has no plugins — applying will disable all plugins."
106
+ : "";
107
+
108
+ const confirmed = await modal.confirm(
109
+ "Confirm Apply",
110
+ `This will REPLACE all enabledPlugins in ${scopeLabel}.${emptyWarning}\n\nContinue?`,
111
+ );
112
+ if (!confirmed) return;
113
+
114
+ modal.loading(`Applying "${selectedProfile.name}"...`);
115
+ try {
116
+ await applyProfile(
117
+ selectedProfile.id,
118
+ selectedProfile.scope,
119
+ targetScope,
120
+ state.projectPath,
121
+ );
122
+ modal.hideModal();
123
+ // Trigger PluginsScreen to refetch
124
+ dispatch({ type: "DATA_REFRESH_COMPLETE" });
125
+ await modal.message(
126
+ "Applied",
127
+ `Profile "${selectedProfile.name}" applied to ${scopeLabel}.`,
128
+ "success",
129
+ );
130
+ } catch (error) {
131
+ modal.hideModal();
132
+ await modal.message(
133
+ "Error",
134
+ `Failed to apply profile: ${error}`,
135
+ "error",
136
+ );
137
+ }
138
+ };
139
+
140
+ const handleRename = async () => {
141
+ if (!selectedProfile) return;
142
+
143
+ const newName = await modal.input(
144
+ "Rename Profile",
145
+ "New name:",
146
+ selectedProfile.name,
147
+ );
148
+ if (newName === null || !newName.trim()) return;
149
+
150
+ modal.loading("Renaming...");
151
+ try {
152
+ await renameProfile(
153
+ selectedProfile.id,
154
+ newName.trim(),
155
+ selectedProfile.scope,
156
+ state.projectPath,
157
+ );
158
+ modal.hideModal();
159
+ await fetchData();
160
+ await modal.message(
161
+ "Renamed",
162
+ `Profile renamed to "${newName.trim()}".`,
163
+ "success",
164
+ );
165
+ } catch (error) {
166
+ modal.hideModal();
167
+ await modal.message("Error", `Failed to rename: ${error}`, "error");
168
+ }
169
+ };
170
+
171
+ const handleDelete = async () => {
172
+ if (!selectedProfile) return;
173
+
174
+ const confirmed = await modal.confirm(
175
+ `Delete "${selectedProfile.name}"?`,
176
+ "This will permanently remove the profile.",
177
+ );
178
+ if (!confirmed) return;
179
+
180
+ modal.loading("Deleting...");
181
+ try {
182
+ await deleteProfile(
183
+ selectedProfile.id,
184
+ selectedProfile.scope,
185
+ state.projectPath,
186
+ );
187
+ modal.hideModal();
188
+ // Adjust selection if we deleted the last item
189
+ const newIndex = Math.max(
190
+ 0,
191
+ Math.min(profilesState.selectedIndex, profileList.length - 2),
192
+ );
193
+ dispatch({ type: "PROFILES_SELECT", index: newIndex });
194
+ await fetchData();
195
+ await modal.message("Deleted", "Profile deleted.", "success");
196
+ } catch (error) {
197
+ modal.hideModal();
198
+ await modal.message("Error", `Failed to delete: ${error}`, "error");
199
+ }
200
+ };
201
+
202
+ const handleCopy = async () => {
203
+ if (!selectedProfile) return;
204
+
205
+ modal.loading("Exporting...");
206
+ try {
207
+ const json = await exportProfileToJson(
208
+ selectedProfile.id,
209
+ selectedProfile.scope,
210
+ state.projectPath,
211
+ );
212
+ modal.hideModal();
213
+ try {
214
+ await writeClipboard(json);
215
+ await modal.message(
216
+ "Copied",
217
+ `Profile JSON copied to clipboard.\nShare it with teammates to import.`,
218
+ "success",
219
+ );
220
+ } catch (err) {
221
+ if (err instanceof ClipboardUnavailableError) {
222
+ // Fallback: show JSON in modal for manual copy
223
+ await modal.message("Profile JSON (copy manually)", json, "info");
224
+ } else {
225
+ throw err;
226
+ }
227
+ }
228
+ } catch (error) {
229
+ modal.hideModal();
230
+ await modal.message("Error", `Failed to export: ${error}`, "error");
231
+ }
232
+ };
233
+
234
+ const handleImport = async () => {
235
+ let json: string | null = null;
236
+
237
+ // Try to read from clipboard first
238
+ try {
239
+ json = await readClipboard();
240
+ } catch (err) {
241
+ if (err instanceof ClipboardUnavailableError) {
242
+ // Fallback: ask user to paste JSON manually
243
+ json = await modal.input("Import Profile", "Paste profile JSON:");
244
+ } else {
245
+ await modal.message("Error", `Clipboard error: ${err}`, "error");
246
+ return;
247
+ }
248
+ }
249
+
250
+ if (json === null || !json.trim()) return;
251
+
252
+ const scopeChoice = await modal.select(
253
+ "Import Profile",
254
+ "Save imported profile to which scope?",
255
+ [
256
+ { label: "User — ~/.claude/profiles.json (global)", value: "user" },
257
+ {
258
+ label: "Project — .claude/profiles.json (this project)",
259
+ value: "project",
260
+ },
261
+ ],
262
+ );
263
+ if (scopeChoice === null) return;
264
+
265
+ const targetScope = scopeChoice as "user" | "project";
266
+
267
+ modal.loading("Importing...");
268
+ try {
269
+ const id = await importProfileFromJson(
270
+ json,
271
+ targetScope,
272
+ state.projectPath,
273
+ );
274
+ modal.hideModal();
275
+ await fetchData();
276
+ await modal.message(
277
+ "Imported",
278
+ `Profile imported successfully (id: ${id}).`,
279
+ "success",
280
+ );
281
+ } catch (error) {
282
+ modal.hideModal();
283
+ await modal.message("Error", `Failed to import: ${error}`, "error");
284
+ }
285
+ };
286
+
287
+ const formatDate = (iso: string): string => {
288
+ try {
289
+ const d = new Date(iso);
290
+ return d.toLocaleDateString("en-US", {
291
+ month: "short",
292
+ day: "numeric",
293
+ year: "numeric",
294
+ });
295
+ } catch {
296
+ return iso;
297
+ }
298
+ };
299
+
300
+ const renderListItem = (
301
+ entry: ProfileEntry,
302
+ _idx: number,
303
+ isSelected: boolean,
304
+ ) => {
305
+ const pluginCount = Object.keys(entry.plugins).length;
306
+ const dateStr = formatDate(entry.updatedAt);
307
+ const scopeColor = entry.scope === "user" ? "cyan" : "green";
308
+ const scopeLabel = entry.scope === "user" ? "[user]" : "[proj]";
309
+
310
+ if (isSelected) {
311
+ return (
312
+ <text bg="magenta" fg="white">
313
+ {" "}
314
+ {scopeLabel} {entry.name} — {pluginCount} plugin
315
+ {pluginCount !== 1 ? "s" : ""} · {dateStr}{" "}
316
+ </text>
317
+ );
318
+ }
319
+
320
+ return (
321
+ <text>
322
+ <span fg={scopeColor}>{scopeLabel}</span>
323
+ <span> </span>
324
+ <span fg="white">{entry.name}</span>
325
+ <span fg="gray">
326
+ {" "}
327
+ — {pluginCount} plugin{pluginCount !== 1 ? "s" : ""} · {dateStr}
328
+ </span>
329
+ </text>
330
+ );
331
+ };
332
+
333
+ const renderDetail = () => {
334
+ if (profilesState.profiles.status === "loading") {
335
+ return <text fg="gray">Loading profiles...</text>;
336
+ }
337
+
338
+ if (profilesState.profiles.status === "error") {
339
+ return (
340
+ <text fg="red">Error: {profilesState.profiles.error.message}</text>
341
+ );
342
+ }
343
+
344
+ if (profileList.length === 0) {
345
+ return (
346
+ <box flexDirection="column">
347
+ <text fg="gray">No profiles yet.</text>
348
+ <box marginTop={1}>
349
+ <text fg="green">
350
+ Press 's' in the Plugins screen to save the current selection as a
351
+ profile.
352
+ </text>
353
+ </box>
354
+ </box>
355
+ );
356
+ }
357
+
358
+ if (!selectedProfile) {
359
+ return <text fg="gray">Select a profile to see details</text>;
360
+ }
361
+
362
+ const plugins = Object.keys(selectedProfile.plugins);
363
+ const scopeColor = selectedProfile.scope === "user" ? "cyan" : "green";
364
+ const scopeLabel =
365
+ selectedProfile.scope === "user"
366
+ ? "User (~/.claude/profiles.json)"
367
+ : "Project (.claude/profiles.json — committed to git)";
368
+
369
+ return (
370
+ <box flexDirection="column">
371
+ <text fg="cyan">
372
+ <strong>{selectedProfile.name}</strong>
373
+ </text>
374
+ <box marginTop={1}>
375
+ <text fg="gray">Scope: </text>
376
+ <text fg={scopeColor}>{scopeLabel}</text>
377
+ </box>
378
+ <box marginTop={1}>
379
+ <text fg="gray">
380
+ Created: {formatDate(selectedProfile.createdAt)} · Updated:{" "}
381
+ {formatDate(selectedProfile.updatedAt)}
382
+ </text>
383
+ </box>
384
+ <box marginTop={1} flexDirection="column">
385
+ <text fg="gray">
386
+ Plugins ({plugins.length}
387
+ {plugins.length === 0 ? " — applying will disable all plugins" : ""}
388
+ ):
389
+ </text>
390
+ {plugins.length === 0 ? (
391
+ <text fg="yellow"> (none)</text>
392
+ ) : (
393
+ plugins.map((p) => (
394
+ <box key={p}>
395
+ <text fg="white"> {p}</text>
396
+ </box>
397
+ ))
398
+ )}
399
+ </box>
400
+ <box marginTop={2} flexDirection="column">
401
+ <box>
402
+ <text bg="magenta" fg="white">
403
+ {" "}
404
+ Enter/a{" "}
405
+ </text>
406
+ <text fg="gray"> Apply profile</text>
407
+ </box>
408
+ <box marginTop={1}>
409
+ <text bg="#333333" fg="white">
410
+ {" "}
411
+ r{" "}
412
+ </text>
413
+ <text fg="gray"> Rename</text>
414
+ </box>
415
+ <box marginTop={1}>
416
+ <text bg="red" fg="white">
417
+ {" "}
418
+ d{" "}
419
+ </text>
420
+ <text fg="gray"> Delete</text>
421
+ </box>
422
+ <box marginTop={1}>
423
+ <text bg="blue" fg="white">
424
+ {" "}
425
+ c{" "}
426
+ </text>
427
+ <text fg="gray"> Copy JSON to clipboard</text>
428
+ </box>
429
+ <box marginTop={1}>
430
+ <text bg="green" fg="white">
431
+ {" "}
432
+ i{" "}
433
+ </text>
434
+ <text fg="gray"> Import from clipboard</text>
435
+ </box>
436
+ </box>
437
+ </box>
438
+ );
439
+ };
440
+
441
+ const profileCount = profileList.length;
442
+ const userCount = profileList.filter((p) => p.scope === "user").length;
443
+ const projCount = profileList.filter((p) => p.scope === "project").length;
444
+
445
+ const statusContent = (
446
+ <text>
447
+ <span fg="gray">Profiles: </span>
448
+ <span fg="cyan">{userCount} user</span>
449
+ <span fg="gray"> + </span>
450
+ <span fg="green">{projCount} project</span>
451
+ <span fg="gray"> = </span>
452
+ <span fg="white">{profileCount} total</span>
453
+ </text>
454
+ );
455
+
456
+ return (
457
+ <ScreenLayout
458
+ title="claudeup Plugin Profiles"
459
+ currentScreen="profiles"
460
+ statusLine={statusContent}
461
+ footerHints="↑↓:nav │ Enter/a:apply │ r:rename │ d:delete │ c:copy │ i:import"
462
+ listPanel={
463
+ profileList.length === 0 &&
464
+ profilesState.profiles.status !== "loading" ? (
465
+ <box flexDirection="column">
466
+ <text fg="gray">No profiles yet.</text>
467
+ <box marginTop={1}>
468
+ <text fg="green">
469
+ Go to Plugins (1) and press 's' to save a profile.
470
+ </text>
471
+ </box>
472
+ </box>
473
+ ) : (
474
+ <ScrollableList
475
+ items={profileList}
476
+ selectedIndex={profilesState.selectedIndex}
477
+ renderItem={renderListItem}
478
+ maxHeight={dimensions.listPanelHeight}
479
+ />
480
+ )
481
+ }
482
+ detailPanel={renderDetail()}
483
+ />
484
+ );
485
+ }
486
+
487
+ export default ProfilesScreen;
@@ -162,7 +162,7 @@ export function StatusLineScreen() {
162
162
  return (_jsx("box", { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: _jsx("text", { fg: "gray", children: "Select a theme to see preview" }) }));
163
163
  }
164
164
  if (selectedItem.isCustom) {
165
- return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "cyan", children: _jsx("strong", { children: "\u2728 Custom Status Line" }) }), _jsx("text", { fg: "gray", children: "Create your own unique status line!" }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsx("text", { fg: "yellow", children: _jsx("strong", { children: "Available variables:" }) }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { children: [_jsx("span", { fg: "green", children: _jsx("strong", { children: "{model}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Model name"] }), _jsxs("text", { children: [_jsx("span", { fg: "green", children: _jsx("strong", { children: "{model_short}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Short name"] }), _jsxs("text", { children: [_jsx("span", { fg: "yellow", children: _jsx("strong", { children: "{cost}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Session cost"] }), _jsxs("text", { children: [_jsx("span", { fg: "blue", children: _jsx("strong", { children: "{cwd}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Working directory"] }), _jsxs("text", { children: [_jsx("span", { fg: "magenta", children: _jsx("strong", { children: "{git_branch}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Git branch"] }), _jsxs("text", { children: [_jsx("span", { fg: "cyan", children: _jsx("strong", { children: "{input_tokens}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Input tokens"] }), _jsxs("text", { children: [_jsx("span", { fg: "cyan", children: _jsx("strong", { children: "{output_tokens}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Output tokens"] }), _jsxs("text", { children: [_jsx("span", { fg: "red", children: _jsx("strong", { children: "{session_duration}" }) }), _jsx("span", { fg: "gray", children: "\u2192" }), " Duration"] })] })] })] }));
165
+ return (_jsxs("box", { flexDirection: "column", children: [_jsx("text", { fg: "cyan", children: _jsx("strong", { children: "\u2728 Custom Status Line" }) }), _jsx("text", { fg: "gray", children: "Create your own unique status line!" }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsx("text", { fg: "yellow", children: _jsx("strong", { children: "Available variables:" }) }), _jsxs("box", { marginTop: 1, flexDirection: "column", children: [_jsxs("text", { children: [_jsx("span", { fg: "green", children: _jsx("strong", { children: "{model}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Model name"] }), _jsxs("text", { children: [_jsx("span", { fg: "green", children: _jsx("strong", { children: "{model_short}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Short name"] }), _jsxs("text", { children: [_jsx("span", { fg: "yellow", children: _jsx("strong", { children: "{cost}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Session cost"] }), _jsxs("text", { children: [_jsx("span", { fg: "#5c9aff", children: _jsx("strong", { children: "{cwd}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Working directory"] }), _jsxs("text", { children: [_jsx("span", { fg: "magenta", children: _jsx("strong", { children: "{git_branch}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Git branch"] }), _jsxs("text", { children: [_jsx("span", { fg: "cyan", children: _jsx("strong", { children: "{input_tokens}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Input tokens"] }), _jsxs("text", { children: [_jsx("span", { fg: "cyan", children: _jsx("strong", { children: "{output_tokens}" }) }), " ", _jsx("span", { fg: "gray", children: "\u2192" }), " Output tokens"] }), _jsxs("text", { children: [_jsx("span", { fg: "red", children: _jsx("strong", { children: "{session_duration}" }) }), _jsx("span", { fg: "gray", children: "\u2192" }), " Duration"] })] })] })] }));
166
166
  }
167
167
  if (selectedItem.preset) {
168
168
  const example = selectedItem.preset.template
@@ -183,7 +183,7 @@ export function StatusLineScreen() {
183
183
  return (_jsx("text", { fg: "magenta", children: _jsxs("strong", { children: ["\u25B8 ", item.label] }) }));
184
184
  }
185
185
  if (item.isCustom) {
186
- return isSelected ? (_jsx("text", { bg: "cyan", fg: "black", children: _jsxs("strong", { children: [" ", "\u2795 Custom Status Line", " "] }) })) : (_jsx("text", { fg: "cyan", children: _jsxs("strong", { children: [" ", "\u2795 Custom Status Line"] }) }));
186
+ return isSelected ? (_jsx("text", { bg: "cyan", fg: "black", children: _jsx("strong", { children: " \u2795 Custom Status Line " }) })) : (_jsx("text", { fg: "cyan", children: _jsxs("strong", { children: [" ", "\u2795 Custom Status Line"] }) }));
187
187
  }
188
188
  const currentForScope = getCurrentStatusLine();
189
189
  const isActive = item.preset && currentForScope === item.preset.template;
@@ -251,7 +251,7 @@ export function StatusLineScreen() {
251
251
  <span fg="gray">→</span> Session cost
252
252
  </text>
253
253
  <text>
254
- <span fg="blue">
254
+ <span fg="#5c9aff">
255
255
  <strong>{"{cwd}"}</strong>
256
256
  </span>{" "}
257
257
  <span fg="gray">→</span> Working directory
@@ -310,7 +310,8 @@ export function StatusLineScreen() {
310
310
  </text>
311
311
  <box
312
312
  marginTop={1}
313
- paddingLeft={1} paddingRight={1}
313
+ paddingLeft={1}
314
+ paddingRight={1}
314
315
  borderStyle="rounded"
315
316
  borderColor="green"
316
317
  >
@@ -336,7 +337,7 @@ export function StatusLineScreen() {
336
337
  ) => {
337
338
  if (item.isCategory) {
338
339
  return (
339
- <text fg="magenta" >
340
+ <text fg="magenta">
340
341
  <strong>▸ {item.label}</strong>
341
342
  </text>
342
343
  );
@@ -344,14 +345,11 @@ export function StatusLineScreen() {
344
345
 
345
346
  if (item.isCustom) {
346
347
  return isSelected ? (
347
- <text bg="cyan" fg="black" >
348
- <strong>
349
- {" "}
350
- ➕ Custom Status Line{" "}
351
- </strong>
348
+ <text bg="cyan" fg="black">
349
+ <strong> ➕ Custom Status Line </strong>
352
350
  </text>
353
351
  ) : (
354
- <text fg="cyan" >
352
+ <text fg="cyan">
355
353
  <strong>{" "}➕ Custom Status Line</strong>
356
354
  </text>
357
355
  );
@@ -361,12 +359,12 @@ export function StatusLineScreen() {
361
359
  const isActive = item.preset && currentForScope === item.preset.template;
362
360
 
363
361
  return isSelected ? (
364
- <text bg="magenta" fg="white" >
362
+ <text bg="magenta" fg="white">
365
363
  {" "}
366
364
  {isActive ? "●" : "○"} {item.preset?.name || ""}{" "}
367
365
  </text>
368
366
  ) : (
369
- <text fg={isActive ? "green" : "white"} >
367
+ <text fg={isActive ? "green" : "white"}>
370
368
  {" "}
371
369
  {isActive ? "●" : "○"} {item.preset?.name || ""}
372
370
  </text>
@@ -392,7 +390,7 @@ export function StatusLineScreen() {
392
390
  return (
393
391
  <ScreenLayout
394
392
  title="claudeup Status Line"
395
- currentScreen="statusline"
393
+ currentScreen={"statusline" as never}
396
394
  statusLine={statusContent}
397
395
  footerHints="↑↓:nav │ Enter:apply │ p:project │ g:global │ r:reset"
398
396
  listPanel={
@@ -1,7 +1,7 @@
1
1
  export { PluginsScreen } from "./PluginsScreen.js";
2
2
  export { McpScreen } from "./McpScreen.js";
3
3
  export { McpRegistryScreen } from "./McpRegistryScreen.js";
4
- export { StatusLineScreen } from "./StatusLineScreen.js";
5
- export { EnvVarsScreen } from "./EnvVarsScreen.js";
4
+ export { SettingsScreen } from "./EnvVarsScreen.js";
6
5
  export { CliToolsScreen } from "./CliToolsScreen.js";
7
6
  export { ModelSelectorScreen } from "./ModelSelectorScreen.js";
7
+ export { ProfilesScreen } from "./ProfilesScreen.js";
@@ -1,7 +1,7 @@
1
1
  export { PluginsScreen } from "./PluginsScreen.js";
2
2
  export { McpScreen } from "./McpScreen.js";
3
3
  export { McpRegistryScreen } from "./McpRegistryScreen.js";
4
- export { StatusLineScreen } from "./StatusLineScreen.js";
5
- export { EnvVarsScreen } from "./EnvVarsScreen.js";
4
+ export { SettingsScreen } from "./EnvVarsScreen.js";
6
5
  export { CliToolsScreen } from "./CliToolsScreen.js";
7
6
  export { ModelSelectorScreen } from "./ModelSelectorScreen.js";
7
+ export { ProfilesScreen } from "./ProfilesScreen.js";
@@ -91,7 +91,7 @@ export function useModal() {
91
91
  });
92
92
  });
93
93
  },
94
- select: (title, message, options) => {
94
+ select: (title, message, options, defaultIndex) => {
95
95
  return new Promise((resolve) => {
96
96
  dispatch({
97
97
  type: "SHOW_MODAL",
@@ -100,6 +100,7 @@ export function useModal() {
100
100
  title,
101
101
  message,
102
102
  options,
103
+ defaultIndex,
103
104
  onSelect: (value) => {
104
105
  dispatch({ type: "HIDE_MODAL" });
105
106
  resolve(value);
@@ -153,6 +153,7 @@ export function useModal() {
153
153
  title: string,
154
154
  message: string,
155
155
  options: { label: string; value: string; description?: string }[],
156
+ defaultIndex?: number,
156
157
  ): Promise<string | null> => {
157
158
  return new Promise((resolve) => {
158
159
  dispatch({
@@ -162,6 +163,7 @@ export function useModal() {
162
163
  title,
163
164
  message,
164
165
  options,
166
+ defaultIndex,
165
167
  onSelect: (value: string) => {
166
168
  dispatch({ type: "HIDE_MODAL" });
167
169
  resolve(value);