claudeup 4.35.0 → 4.36.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 (63) hide show
  1. package/package.json +4 -4
  2. package/src/__tests__/git-worktree.test.ts +108 -0
  3. package/src/__tests__/scope-squares.test.tsx +165 -0
  4. package/src/__tests__/theme-adaptive-colors.test.ts +307 -0
  5. package/src/__tests__/uppercase-keybindings.test.ts +101 -0
  6. package/src/__tests__/worktree-registry-resolution.test.ts +107 -0
  7. package/src/main.tsx +21 -5
  8. package/src/opentui.d.ts +21 -12
  9. package/src/services/claude-settings.ts +37 -7
  10. package/src/services/git-worktree.ts +129 -0
  11. package/src/services/plugin-manager.ts +10 -1
  12. package/src/ui/App.tsx +19 -12
  13. package/src/ui/adapters/pluginsAdapter.ts +174 -168
  14. package/src/ui/adapters/settingsAdapter.ts +119 -116
  15. package/src/ui/adapters/skillsAdapter.ts +203 -196
  16. package/src/ui/components/CategoryHeader.tsx +9 -8
  17. package/src/ui/components/EmptyFilterState.tsx +10 -5
  18. package/src/ui/components/FlagDetailEditor.tsx +0 -0
  19. package/src/ui/components/ScopeIndicator.tsx +10 -6
  20. package/src/ui/components/ScrollableList.tsx +3 -2
  21. package/src/ui/components/SearchInput.tsx +2 -1
  22. package/src/ui/components/StyledText.tsx +5 -4
  23. package/src/ui/components/TabBar.tsx +4 -3
  24. package/src/ui/components/layout/FooterHints.tsx +37 -30
  25. package/src/ui/components/layout/Panel.tsx +6 -5
  26. package/src/ui/components/layout/ProgressBar.tsx +7 -6
  27. package/src/ui/components/layout/ScopeTabs.tsx +6 -5
  28. package/src/ui/components/layout/ScreenLayout.tsx +27 -24
  29. package/src/ui/components/layout/index.ts +3 -3
  30. package/src/ui/components/modals/ConfirmModal.tsx +12 -11
  31. package/src/ui/components/modals/InputModal.tsx +14 -6
  32. package/src/ui/components/modals/LoadingModal.tsx +6 -5
  33. package/src/ui/components/modals/MessageModal.tsx +9 -8
  34. package/src/ui/components/modals/SelectModal.tsx +11 -7
  35. package/src/ui/components/modals/VersionMismatchModal.tsx +14 -16
  36. package/src/ui/components/primitives/ActionHints.tsx +26 -26
  37. package/src/ui/components/primitives/DetailSection.tsx +13 -12
  38. package/src/ui/components/primitives/KeyValueLine.tsx +9 -8
  39. package/src/ui/components/primitives/ListCategoryRow.tsx +25 -27
  40. package/src/ui/components/primitives/MetaText.tsx +3 -3
  41. package/src/ui/components/primitives/ScopeDetail.tsx +48 -48
  42. package/src/ui/components/primitives/ScopeSquares.tsx +47 -22
  43. package/src/ui/components/primitives/SelectableRow.tsx +22 -16
  44. package/src/ui/hooks/useGitignoreModal.ts +78 -74
  45. package/src/ui/registry.ts +11 -11
  46. package/src/ui/renderers/cliToolRenderers.tsx +260 -203
  47. package/src/ui/renderers/gitignoreRenderers.tsx +43 -42
  48. package/src/ui/renderers/mcpRenderers.tsx +121 -117
  49. package/src/ui/renderers/pluginRenderers.tsx +528 -471
  50. package/src/ui/renderers/profileRenderers.tsx +346 -300
  51. package/src/ui/renderers/settingsRenderers.tsx +183 -176
  52. package/src/ui/renderers/skillRenderers.tsx +410 -326
  53. package/src/ui/screens/AliasScreen.tsx +1336 -1309
  54. package/src/ui/screens/CliToolsScreen.tsx +92 -40
  55. package/src/ui/screens/EnvVarsScreen.tsx +19 -13
  56. package/src/ui/screens/GitignoreScreen.tsx +510 -493
  57. package/src/ui/screens/McpRegistryScreen.tsx +28 -21
  58. package/src/ui/screens/McpScreen.tsx +12 -3
  59. package/src/ui/screens/PluginsScreen.tsx +17 -7
  60. package/src/ui/screens/ProfilesScreen.tsx +39 -23
  61. package/src/ui/screens/SkillsScreen.tsx +832 -688
  62. package/src/ui/theme-mode.ts +73 -0
  63. package/src/ui/theme.ts +147 -53
@@ -1,16 +1,16 @@
1
1
  import React from "react";
2
2
  import type { ItemRenderer } from "../registry.js";
3
3
  import type {
4
- SettingsBrowserItem,
5
- SettingsCategoryItem,
6
- SettingsSettingItem,
4
+ SettingsBrowserItem,
5
+ SettingsCategoryItem,
6
+ SettingsSettingItem,
7
7
  } from "../adapters/settingsAdapter.js";
8
8
  import {
9
- CATEGORY_LABELS,
10
- CATEGORY_BG,
11
- CATEGORY_COLOR,
12
- CATEGORY_DESCRIPTIONS,
13
- formatValue,
9
+ CATEGORY_LABELS,
10
+ CATEGORY_BG,
11
+ CATEGORY_COLOR,
12
+ CATEGORY_DESCRIPTIONS,
13
+ formatValue,
14
14
  } from "../adapters/settingsAdapter.js";
15
15
  import { SelectableRow, MetaText } from "../components/primitives/index.js";
16
16
  import { theme } from "../theme.js";
@@ -18,193 +18,200 @@ import { theme } from "../theme.js";
18
18
  // ─── Category renderer ─────────────────────────────────────────────────────────
19
19
 
20
20
  const categoryRenderer: ItemRenderer<SettingsCategoryItem> = {
21
- renderRow: ({ item, isSelected }) => {
22
- const star = item.category === "recommended" ? "★ " : "";
23
- const label = `${star}${CATEGORY_LABELS[item.category]}`;
24
- const bg = isSelected ? theme.selection.bg : CATEGORY_BG[item.category];
25
-
26
- return (
27
- <box width="100%">
28
- <text bg={bg} fg="white">
29
- <strong> {label} </strong>
30
- </text>
31
- </box>
32
- );
33
- },
34
-
35
- renderDetail: ({ item }) => {
36
- const cat = item.category;
37
- return (
38
- <box flexDirection="column">
39
- <text fg={CATEGORY_COLOR[cat]}>
40
- <strong>{CATEGORY_LABELS[cat]}</strong>
41
- </text>
42
- <box marginTop={1}>
43
- <text fg={theme.colors.muted}>{CATEGORY_DESCRIPTIONS[cat]}</text>
44
- </box>
45
- </box>
46
- );
47
- },
21
+ renderRow: ({ item, isSelected }) => {
22
+ const star = item.category === "recommended" ? "★ " : "";
23
+ const label = `${star}${CATEGORY_LABELS[item.category]}`;
24
+ const bg = isSelected ? theme.selection.bg : CATEGORY_BG[item.category];
25
+
26
+ return (
27
+ <box width="100%">
28
+ <text bg={bg} fg={theme.hints.fg}>
29
+ <strong> {label} </strong>
30
+ </text>
31
+ </box>
32
+ );
33
+ },
34
+
35
+ renderDetail: ({ item }) => {
36
+ const cat = item.category;
37
+ return (
38
+ <box flexDirection="column">
39
+ <text fg={CATEGORY_COLOR[cat]}>
40
+ <strong>{CATEGORY_LABELS[cat]}</strong>
41
+ </text>
42
+ <box marginTop={1}>
43
+ <text fg={theme.colors.muted}>{CATEGORY_DESCRIPTIONS[cat]}</text>
44
+ </box>
45
+ </box>
46
+ );
47
+ },
48
48
  };
49
49
 
50
50
  // ─── Setting renderer ──────────────────────────────────────────────────────────
51
51
 
52
52
  const settingRenderer: ItemRenderer<SettingsSettingItem> = {
53
- renderRow: ({ item, isSelected }) => {
54
- const { setting } = item;
55
- const indicator = item.isDefault ? "○" : "●";
56
- const displayValue = formatValue(setting, item.effectiveValue);
57
-
58
- if (isSelected) {
59
- return (
60
- <text bg={theme.selection.bg} fg={theme.selection.fg}>
61
- {" "}
62
- {indicator} {setting.name.padEnd(28)}
63
- {displayValue}{" "}
64
- </text>
65
- );
66
- }
67
-
68
- const indicatorColor = item.isDefault ? theme.colors.muted : theme.colors.info;
69
- const valueColor = item.isDefault ? theme.colors.muted : theme.colors.success;
70
-
71
- return (
72
- <text>
73
- <span fg={indicatorColor}> {indicator} </span>
74
- <span>{setting.name.padEnd(28)}</span>
75
- <span fg={valueColor}>{displayValue}</span>
76
- </text>
77
- );
78
- },
79
-
80
- renderDetail: ({ item }) => {
81
- const { setting } = item;
82
- const scoped = item.scopedValues;
83
- const storageDesc =
84
- setting.storage.type === "attribution"
85
- ? "settings.json: attribution"
86
- : setting.storage.type === "attribution-text"
87
- ? "settings.json: attribution"
88
- : setting.storage.type === "env"
89
- ? `env: ${setting.storage.key}`
90
- : `settings.json: ${setting.storage.key}`;
91
-
92
- const userValue = formatValue(setting, scoped.user);
93
- const projectValue = formatValue(setting, scoped.project);
94
- const userIsSet = scoped.user !== undefined && scoped.user !== "";
95
- const projectIsSet = scoped.project !== undefined && scoped.project !== "";
96
-
97
- const actionLabel =
98
- setting.type === "boolean"
99
- ? "toggle"
100
- : setting.type === "select"
101
- ? "choose"
102
- : "edit";
103
-
104
- return (
105
- <box flexDirection="column">
106
- <text fg={theme.colors.info}>
107
- <strong>{setting.name}</strong>
108
- </text>
109
- <box marginTop={1}>
110
- <text fg={theme.colors.text}>{setting.description}</text>
111
- </box>
112
-
113
- <box marginTop={1}>
114
- <text>
115
- <span fg={theme.colors.muted}>Stored </span>
116
- <span fg={theme.colors.link}>{storageDesc}</span>
117
- </text>
118
- </box>
119
- {setting.defaultValue !== undefined && (
120
- <box>
121
- <text>
122
- <span fg={theme.colors.muted}>Default </span>
123
- <span>{setting.defaultValue}</span>
124
- </text>
125
- </box>
126
- )}
127
-
128
- <box flexDirection="column" marginTop={1}>
129
- <text>{"────────────────────────"}</text>
130
- <text>
131
- <strong>Scopes:</strong>
132
- </text>
133
- <box marginTop={1} flexDirection="column">
134
- <text>
135
- <span bg={theme.scopes.user} fg="black">
136
- {" "}
137
- u{" "}
138
- </span>
139
- <span fg={userIsSet ? theme.scopes.user : theme.colors.muted}>
140
- {userIsSet ? " " : " ○ "}
141
- </span>
142
- <span fg={theme.scopes.user}>User</span>
143
- <span> global</span>
144
- <span fg={userIsSet ? theme.scopes.user : theme.colors.muted}>
145
- {" "}
146
- {userValue}
147
- </span>
148
- </text>
149
- <text>
150
- <span bg={theme.scopes.project} fg="black">
151
- {" "}
152
- p{" "}
153
- </span>
154
- <span
155
- fg={projectIsSet ? theme.scopes.project : theme.colors.muted}
156
- >
157
- {projectIsSet ? " ● " : " ○ "}
158
- </span>
159
- <span fg={theme.scopes.project}>Project</span>
160
- <span> team</span>
161
- <span
162
- fg={projectIsSet ? theme.scopes.project : theme.colors.muted}
163
- >
164
- {" "}
165
- {projectValue}
166
- </span>
167
- </text>
168
- </box>
169
- </box>
170
-
171
- <box marginTop={1}>
172
- <text fg={theme.colors.muted}>Press u/p to {actionLabel} in scope</text>
173
- </box>
174
- </box>
175
- );
176
- },
53
+ renderRow: ({ item, isSelected }) => {
54
+ const { setting } = item;
55
+ const indicator = item.isDefault ? "○" : "●";
56
+ const displayValue = formatValue(setting, item.effectiveValue);
57
+
58
+ if (isSelected) {
59
+ return (
60
+ <text bg={theme.selection.bg} fg={theme.selection.fg}>
61
+ {" "}
62
+ {indicator} {setting.name.padEnd(28)}
63
+ {displayValue}{" "}
64
+ </text>
65
+ );
66
+ }
67
+
68
+ const indicatorColor = item.isDefault
69
+ ? theme.colors.muted
70
+ : theme.colors.info;
71
+ const valueColor = item.isDefault
72
+ ? theme.colors.muted
73
+ : theme.colors.success;
74
+
75
+ return (
76
+ <text fg={theme.colors.text}>
77
+ <span fg={indicatorColor}> {indicator} </span>
78
+ <span>{setting.name.padEnd(28)}</span>
79
+ <span fg={valueColor}>{displayValue}</span>
80
+ </text>
81
+ );
82
+ },
83
+
84
+ renderDetail: ({ item }) => {
85
+ const { setting } = item;
86
+ const scoped = item.scopedValues;
87
+ const storageDesc =
88
+ setting.storage.type === "attribution"
89
+ ? "settings.json: attribution"
90
+ : setting.storage.type === "attribution-text"
91
+ ? "settings.json: attribution"
92
+ : setting.storage.type === "env"
93
+ ? `env: ${setting.storage.key}`
94
+ : `settings.json: ${setting.storage.key}`;
95
+
96
+ const userValue = formatValue(setting, scoped.user);
97
+ const projectValue = formatValue(setting, scoped.project);
98
+ const userIsSet = scoped.user !== undefined && scoped.user !== "";
99
+ const projectIsSet = scoped.project !== undefined && scoped.project !== "";
100
+
101
+ const actionLabel =
102
+ setting.type === "boolean"
103
+ ? "toggle"
104
+ : setting.type === "select"
105
+ ? "choose"
106
+ : "edit";
107
+
108
+ return (
109
+ <box flexDirection="column">
110
+ <text fg={theme.colors.info}>
111
+ <strong>{setting.name}</strong>
112
+ </text>
113
+ <box marginTop={1}>
114
+ <text fg={theme.colors.text}>{setting.description}</text>
115
+ </box>
116
+
117
+ <box marginTop={1}>
118
+ <text fg={theme.colors.text}>
119
+ <span fg={theme.colors.muted}>Stored </span>
120
+ <span fg={theme.colors.link}>{storageDesc}</span>
121
+ </text>
122
+ </box>
123
+ {setting.defaultValue !== undefined && (
124
+ <box>
125
+ <text fg={theme.colors.text}>
126
+ <span fg={theme.colors.muted}>Default </span>
127
+ <span>{setting.defaultValue}</span>
128
+ </text>
129
+ </box>
130
+ )}
131
+
132
+ <box flexDirection="column" marginTop={1}>
133
+ <text fg={theme.colors.text}>{"────────────────────────"}</text>
134
+ <text fg={theme.colors.text}>
135
+ <strong>Scopes:</strong>
136
+ </text>
137
+ <box marginTop={1} flexDirection="column">
138
+ <text fg={theme.colors.text}>
139
+ <span bg={theme.scopes.user} fg={theme.hints.fg}>
140
+ {" "}
141
+ u{" "}
142
+ </span>
143
+ <span fg={userIsSet ? theme.scopes.user : theme.colors.muted}>
144
+ {userIsSet ? " ● " : " ○ "}
145
+ </span>
146
+ <span fg={theme.scopes.user}>User</span>
147
+ <span> global</span>
148
+ <span fg={userIsSet ? theme.scopes.user : theme.colors.muted}>
149
+ {" "}
150
+ {userValue}
151
+ </span>
152
+ </text>
153
+ <text fg={theme.colors.text}>
154
+ <span bg={theme.scopes.project} fg={theme.hints.fg}>
155
+ {" "}
156
+ p{" "}
157
+ </span>
158
+ <span
159
+ fg={projectIsSet ? theme.scopes.project : theme.colors.muted}
160
+ >
161
+ {projectIsSet ? " ● " : " ○ "}
162
+ </span>
163
+ <span fg={theme.scopes.project}>Project</span>
164
+ <span> team</span>
165
+ <span
166
+ fg={projectIsSet ? theme.scopes.project : theme.colors.muted}
167
+ >
168
+ {" "}
169
+ {projectValue}
170
+ </span>
171
+ </text>
172
+ </box>
173
+ </box>
174
+
175
+ <box marginTop={1}>
176
+ <text fg={theme.colors.muted}>
177
+ Press u/p to {actionLabel} in scope
178
+ </text>
179
+ </box>
180
+ </box>
181
+ );
182
+ },
177
183
  };
178
184
 
179
185
  // ─── Dispatch helpers ──────────────────────────────────────────────────────────
180
186
 
181
187
  export const settingsRenderers: {
182
- category: ItemRenderer<SettingsCategoryItem>;
183
- setting: ItemRenderer<SettingsSettingItem>;
188
+ category: ItemRenderer<SettingsCategoryItem>;
189
+ setting: ItemRenderer<SettingsSettingItem>;
184
190
  } = {
185
- category: categoryRenderer,
186
- setting: settingRenderer,
191
+ category: categoryRenderer,
192
+ setting: settingRenderer,
187
193
  };
188
194
 
189
195
  export function renderSettingRow(
190
- item: SettingsBrowserItem,
191
- _index: number,
192
- isSelected: boolean,
196
+ item: SettingsBrowserItem,
197
+ _index: number,
198
+ isSelected: boolean,
193
199
  ): React.ReactNode {
194
- if (item.kind === "category") {
195
- return settingsRenderers.category.renderRow({ item, isSelected });
196
- }
197
- return settingsRenderers.setting.renderRow({ item, isSelected });
200
+ if (item.kind === "category") {
201
+ return settingsRenderers.category.renderRow({ item, isSelected });
202
+ }
203
+ return settingsRenderers.setting.renderRow({ item, isSelected });
198
204
  }
199
205
 
200
206
  export function renderSettingDetail(
201
- item: SettingsBrowserItem | undefined,
207
+ item: SettingsBrowserItem | undefined,
202
208
  ): React.ReactNode {
203
- if (!item) return <text fg={theme.colors.muted}>Select a setting to see details</text>;
204
- if (item.kind === "category") {
205
- return settingsRenderers.category.renderDetail({ item });
206
- }
207
- return settingsRenderers.setting.renderDetail({ item });
209
+ if (!item)
210
+ return <text fg={theme.colors.muted}>Select a setting to see details</text>;
211
+ if (item.kind === "category") {
212
+ return settingsRenderers.category.renderDetail({ item });
213
+ }
214
+ return settingsRenderers.setting.renderDetail({ item });
208
215
  }
209
216
 
210
217
  // Suppress unused import warning — MetaText may be used in future extensions