claudeup 4.35.1 → 4.37.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 (72) hide show
  1. package/package.json +4 -4
  2. package/src/__tests__/catalog-cache-store.test.ts +271 -0
  3. package/src/__tests__/catalog-notice.test.ts +155 -0
  4. package/src/__tests__/github-budget.test.ts +200 -0
  5. package/src/__tests__/plugin-manager-fallback.test.ts +200 -8
  6. package/src/__tests__/scope-squares.test.tsx +165 -0
  7. package/src/__tests__/theme-adaptive-colors.test.ts +307 -0
  8. package/src/__tests__/uppercase-keybindings.test.ts +101 -0
  9. package/src/main.tsx +21 -5
  10. package/src/opentui.d.ts +21 -12
  11. package/src/services/catalog-cache-store.ts +218 -0
  12. package/src/services/github-budget.ts +274 -0
  13. package/src/services/marketplace-catalog-git.ts +170 -0
  14. package/src/services/marketplace-catalog.ts +95 -0
  15. package/src/services/marketplace-fetcher.ts +310 -87
  16. package/src/services/plugin-manager.ts +103 -92
  17. package/src/ui/App.tsx +19 -12
  18. package/src/ui/adapters/catalogNotice.ts +122 -0
  19. package/src/ui/adapters/pluginsAdapter.ts +174 -168
  20. package/src/ui/adapters/settingsAdapter.ts +119 -116
  21. package/src/ui/adapters/skillsAdapter.ts +203 -196
  22. package/src/ui/components/CategoryHeader.tsx +9 -8
  23. package/src/ui/components/EmptyFilterState.tsx +10 -5
  24. package/src/ui/components/FlagDetailEditor.tsx +0 -0
  25. package/src/ui/components/ScopeIndicator.tsx +10 -6
  26. package/src/ui/components/ScrollableList.tsx +3 -2
  27. package/src/ui/components/SearchInput.tsx +2 -1
  28. package/src/ui/components/StyledText.tsx +5 -4
  29. package/src/ui/components/TabBar.tsx +4 -3
  30. package/src/ui/components/layout/FooterHints.tsx +37 -30
  31. package/src/ui/components/layout/Panel.tsx +6 -5
  32. package/src/ui/components/layout/ProgressBar.tsx +7 -6
  33. package/src/ui/components/layout/ScopeTabs.tsx +6 -5
  34. package/src/ui/components/layout/ScreenLayout.tsx +46 -26
  35. package/src/ui/components/layout/index.ts +3 -3
  36. package/src/ui/components/modals/ConfirmModal.tsx +12 -11
  37. package/src/ui/components/modals/InputModal.tsx +14 -6
  38. package/src/ui/components/modals/LoadingModal.tsx +6 -5
  39. package/src/ui/components/modals/MessageModal.tsx +9 -8
  40. package/src/ui/components/modals/SelectModal.tsx +11 -7
  41. package/src/ui/components/modals/VersionMismatchModal.tsx +14 -16
  42. package/src/ui/components/primitives/ActionHints.tsx +26 -26
  43. package/src/ui/components/primitives/DetailSection.tsx +13 -12
  44. package/src/ui/components/primitives/KeyValueLine.tsx +9 -8
  45. package/src/ui/components/primitives/ListCategoryRow.tsx +25 -27
  46. package/src/ui/components/primitives/MetaText.tsx +3 -3
  47. package/src/ui/components/primitives/ScopeDetail.tsx +48 -48
  48. package/src/ui/components/primitives/ScopeSquares.tsx +47 -22
  49. package/src/ui/components/primitives/SelectableRow.tsx +22 -16
  50. package/src/ui/hooks/useGitignoreModal.ts +78 -74
  51. package/src/ui/registry.ts +11 -11
  52. package/src/ui/renderers/cliToolRenderers.tsx +260 -203
  53. package/src/ui/renderers/gitignoreRenderers.tsx +43 -42
  54. package/src/ui/renderers/mcpRenderers.tsx +121 -117
  55. package/src/ui/renderers/pluginRenderers.tsx +566 -471
  56. package/src/ui/renderers/profileRenderers.tsx +346 -300
  57. package/src/ui/renderers/settingsRenderers.tsx +183 -176
  58. package/src/ui/renderers/skillRenderers.tsx +410 -326
  59. package/src/ui/screens/AliasScreen.tsx +1336 -1309
  60. package/src/ui/screens/CliToolsScreen.tsx +92 -40
  61. package/src/ui/screens/EnvVarsScreen.tsx +19 -13
  62. package/src/ui/screens/GitignoreScreen.tsx +510 -493
  63. package/src/ui/screens/McpRegistryScreen.tsx +28 -21
  64. package/src/ui/screens/McpScreen.tsx +12 -3
  65. package/src/ui/screens/PluginsScreen.tsx +152 -33
  66. package/src/ui/screens/ProfilesScreen.tsx +39 -23
  67. package/src/ui/screens/SkillsScreen.tsx +832 -688
  68. package/src/ui/state/reducer.ts +11 -2
  69. package/src/ui/state/types.ts +16 -1
  70. package/src/ui/theme-mode.ts +73 -0
  71. package/src/ui/theme.ts +147 -53
  72. package/src/utils/config-dir.ts +47 -0
@@ -14,6 +14,7 @@ import {
14
14
  type InstallMethod,
15
15
  } from "../renderers/cliToolRenderers.js";
16
16
  import { ScrollableList } from "../components/ScrollableList.js";
17
+ import { theme } from "../theme.js";
17
18
 
18
19
  const execAsync = promisify(exec);
19
20
 
@@ -35,10 +36,17 @@ function parseVersion(versionOutput: string): string | undefined {
35
36
 
36
37
  function methodFromPath(binPath: string): InstallMethod | null {
37
38
  if (binPath.includes("/.bun/")) return "bun";
38
- if (binPath.includes("/homebrew/") || binPath.includes("/Cellar/")) return "brew";
39
- if (binPath.includes("/.local/share/claude") || binPath.includes("/.local/bin/claude")) return "npm";
40
- if (binPath.includes("/.nvm/") || binPath.includes("/node_modules/")) return "npm";
41
- if (binPath.includes("/.local/share/pnpm") || binPath.includes("/pnpm/")) return "pnpm";
39
+ if (binPath.includes("/homebrew/") || binPath.includes("/Cellar/"))
40
+ return "brew";
41
+ if (
42
+ binPath.includes("/.local/share/claude") ||
43
+ binPath.includes("/.local/bin/claude")
44
+ )
45
+ return "npm";
46
+ if (binPath.includes("/.nvm/") || binPath.includes("/node_modules/"))
47
+ return "npm";
48
+ if (binPath.includes("/.local/share/pnpm") || binPath.includes("/pnpm/"))
49
+ return "pnpm";
42
50
  if (binPath.includes("/.yarn/")) return "yarn";
43
51
  return null;
44
52
  }
@@ -50,7 +58,10 @@ interface InstallInfo {
50
58
  }
51
59
 
52
60
  /** Extract brew formula name from a Cellar symlink target like ../Cellar/gemini-cli/0.35.2/bin/gemini */
53
- function extractBrewFormula(binPath: string, linkTarget: string): string | undefined {
61
+ function extractBrewFormula(
62
+ binPath: string,
63
+ linkTarget: string,
64
+ ): string | undefined {
54
65
  // Try symlink target first: ../Cellar/{formula}/{version}/...
55
66
  const cellarMatch = linkTarget.match(/Cellar\/([^/]+)\//);
56
67
  if (cellarMatch) return cellarMatch[1];
@@ -70,8 +81,12 @@ async function detectInstallMethods(
70
81
  timeout: 3000,
71
82
  shell: "/bin/bash",
72
83
  });
73
- const paths = stdout.trim().split("\n").filter((p) => p && !p.includes("aliased"));
74
- if (paths.length === 0) return { primary: fallback as InstallMethod, all: [] };
84
+ const paths = stdout
85
+ .trim()
86
+ .split("\n")
87
+ .filter((p) => p && !p.includes("aliased"));
88
+ if (paths.length === 0)
89
+ return { primary: fallback as InstallMethod, all: [] };
75
90
 
76
91
  const methods: InstallMethod[] = [];
77
92
  let brewFormula: string | undefined;
@@ -100,38 +115,60 @@ async function detectInstallMethods(
100
115
  if (method && !methods.includes(method)) methods.push(method);
101
116
  }
102
117
 
103
- if (methods.length === 0) return { primary: fallback as InstallMethod, all: [] };
118
+ if (methods.length === 0)
119
+ return { primary: fallback as InstallMethod, all: [] };
104
120
  return { primary: methods[0]!, all: methods, brewFormula };
105
121
  } catch {
106
122
  return { primary: fallback as InstallMethod, all: [] };
107
123
  }
108
124
  }
109
125
 
110
- function getUninstallCommand(tool: import("../../data/cli-tools.js").CliTool, method: InstallMethod, brewFormula?: string): string {
126
+ function getUninstallCommand(
127
+ tool: import("../../data/cli-tools.js").CliTool,
128
+ method: InstallMethod,
129
+ brewFormula?: string,
130
+ ): string {
111
131
  switch (method) {
112
- case "bun": return `bun remove -g ${tool.packageName}`;
113
- case "npm": return `npm uninstall -g ${tool.packageName}`;
114
- case "pnpm": return `pnpm remove -g ${tool.packageName}`;
115
- case "yarn": return `yarn global remove ${tool.packageName}`;
116
- case "brew": return `brew uninstall ${brewFormula || tool.name}`;
117
- case "pip": return `pip uninstall -y ${tool.packageName}`;
118
- default: return "";
132
+ case "bun":
133
+ return `bun remove -g ${tool.packageName}`;
134
+ case "npm":
135
+ return `npm uninstall -g ${tool.packageName}`;
136
+ case "pnpm":
137
+ return `pnpm remove -g ${tool.packageName}`;
138
+ case "yarn":
139
+ return `yarn global remove ${tool.packageName}`;
140
+ case "brew":
141
+ return `brew uninstall ${brewFormula || tool.name}`;
142
+ case "pip":
143
+ return `pip uninstall -y ${tool.packageName}`;
144
+ default:
145
+ return "";
119
146
  }
120
147
  }
121
148
 
122
- function getUpdateCommand(tool: import("../../data/cli-tools.js").CliTool, method: InstallMethod, brewFormula?: string): string {
149
+ function getUpdateCommand(
150
+ tool: import("../../data/cli-tools.js").CliTool,
151
+ method: InstallMethod,
152
+ brewFormula?: string,
153
+ ): string {
123
154
  switch (method) {
124
- case "bun": return `bun install -g ${tool.packageName}`;
125
- case "npm": return `npm install -g ${tool.packageName}`;
126
- case "pnpm": return `pnpm install -g ${tool.packageName}`;
127
- case "yarn": return `yarn global add ${tool.packageName}`;
128
- case "brew": return `brew upgrade ${brewFormula || tool.name}`;
129
- case "pip": return tool.installCommand;
130
- default: return tool.installCommand;
155
+ case "bun":
156
+ return `bun install -g ${tool.packageName}`;
157
+ case "npm":
158
+ return `npm install -g ${tool.packageName}`;
159
+ case "pnpm":
160
+ return `pnpm install -g ${tool.packageName}`;
161
+ case "yarn":
162
+ return `yarn global add ${tool.packageName}`;
163
+ case "brew":
164
+ return `brew upgrade ${brewFormula || tool.name}`;
165
+ case "pip":
166
+ return tool.installCommand;
167
+ default:
168
+ return tool.installCommand;
131
169
  }
132
170
  }
133
171
 
134
-
135
172
  async function getInstalledVersion(
136
173
  tool: import("../../data/cli-tools.js").CliTool,
137
174
  ): Promise<string | undefined> {
@@ -143,7 +180,9 @@ async function getInstalledVersion(
143
180
  }
144
181
  }
145
182
 
146
- async function getLatestNpmVersion(packageName: string): Promise<string | undefined> {
183
+ async function getLatestNpmVersion(
184
+ packageName: string,
185
+ ): Promise<string | undefined> {
147
186
  try {
148
187
  const { stdout } = await execAsync(
149
188
  `npm view ${packageName} version 2>/dev/null`,
@@ -155,7 +194,9 @@ async function getLatestNpmVersion(packageName: string): Promise<string | undefi
155
194
  }
156
195
  }
157
196
 
158
- async function getLatestPipVersion(packageName: string): Promise<string | undefined> {
197
+ async function getLatestPipVersion(
198
+ packageName: string,
199
+ ): Promise<string | undefined> {
159
200
  try {
160
201
  const { stdout } = await execAsync(
161
202
  `pip index versions ${packageName} 2>/dev/null | head -1`,
@@ -247,12 +288,12 @@ export function CliToolsScreen() {
247
288
  latestVersion: latest,
248
289
  checking: false,
249
290
  hasUpdate:
250
- version && latest
251
- ? compareVersions(version, latest) < 0
252
- : false,
291
+ version && latest ? compareVersions(version, latest) < 0 : false,
253
292
  installMethod: version ? info.primary : undefined,
254
293
  allMethods: version && info.all.length > 1 ? info.all : undefined,
255
- updateCommand: version ? getUpdateCommand(tool, info.primary, info.brewFormula) : undefined,
294
+ updateCommand: version
295
+ ? getUpdateCommand(tool, info.primary, info.brewFormula)
296
+ : undefined,
256
297
  brewFormula: info.brewFormula,
257
298
  });
258
299
  });
@@ -304,7 +345,9 @@ export function CliToolsScreen() {
304
345
  fetchVersionInfo();
305
346
  };
306
347
 
307
- const runCommand = async (command: string): Promise<{ ok: boolean; error?: string }> => {
348
+ const runCommand = async (
349
+ command: string,
350
+ ): Promise<{ ok: boolean; error?: string }> => {
308
351
  try {
309
352
  await execAsync(command, {
310
353
  shell: "/bin/bash",
@@ -322,8 +365,13 @@ export function CliToolsScreen() {
322
365
  if (!status) return;
323
366
 
324
367
  const { tool, installed, hasUpdate, updateCommand } = status;
325
- const action = !installed ? "Installing" : hasUpdate ? "Updating" : "Reinstalling";
326
- const command = installed && updateCommand ? updateCommand : tool.installCommand;
368
+ const action = !installed
369
+ ? "Installing"
370
+ : hasUpdate
371
+ ? "Updating"
372
+ : "Reinstalling";
373
+ const command =
374
+ installed && updateCommand ? updateCommand : tool.installCommand;
327
375
 
328
376
  modal.loading(`${action} ${tool.displayName}...`);
329
377
  const result = await runCommand(command);
@@ -386,7 +434,11 @@ export function CliToolsScreen() {
386
434
  const handleUpdateAll = async () => {
387
435
  const updatable = toolStatuses.filter((s) => s.hasUpdate);
388
436
  if (updatable.length === 0) {
389
- await modal.message("Up to Date", "All tools are already up to date.", "info");
437
+ await modal.message(
438
+ "Up to Date",
439
+ "All tools are already up to date.",
440
+ "info",
441
+ );
390
442
  return;
391
443
  }
392
444
 
@@ -406,15 +458,15 @@ export function CliToolsScreen() {
406
458
  const installedCount = toolStatuses.filter((s) => s.installed).length;
407
459
  const updateCount = toolStatuses.filter((s) => s.hasUpdate).length;
408
460
  const statusContent = (
409
- <text>
410
- <span fg="gray">Installed: </span>
411
- <span fg="cyan">
461
+ <text fg={theme.colors.text}>
462
+ <span fg={theme.colors.muted}>Installed: </span>
463
+ <span fg={theme.colors.info}>
412
464
  {installedCount}/{toolStatuses.length}
413
465
  </span>
414
466
  {updateCount > 0 && (
415
467
  <>
416
- <span fg="gray"> │ Updates: </span>
417
- <span fg="yellow">{updateCount}</span>
468
+ <span fg={theme.colors.muted}> │ Updates: </span>
469
+ <span fg={theme.colors.warning}>{updateCount}</span>
418
470
  </>
419
471
  )}
420
472
  </text>
@@ -3,9 +3,7 @@ import { useApp, useModal } from "../state/AppContext.js";
3
3
  import { useDimensions } from "../state/DimensionsContext.js";
4
4
  import { useKeyboard } from "../hooks/useKeyboard.js";
5
5
  import { ScreenLayout } from "../components/layout/index.js";
6
- import {
7
- SETTINGS_CATALOG,
8
- } from "../../data/settings-catalog.js";
6
+ import { SETTINGS_CATALOG } from "../../data/settings-catalog.js";
9
7
  import {
10
8
  readAllSettingsBothScopes,
11
9
  writeSettingValue,
@@ -15,8 +13,12 @@ import {
15
13
  buildSettingsBrowserItems,
16
14
  type SettingsBrowserItem,
17
15
  } from "../adapters/settingsAdapter.js";
18
- import { renderSettingRow, renderSettingDetail } from "../renderers/settingsRenderers.js";
16
+ import {
17
+ renderSettingRow,
18
+ renderSettingDetail,
19
+ } from "../renderers/settingsRenderers.js";
19
20
  import { ScrollableList } from "../components/ScrollableList.js";
21
+ import { theme } from "../theme.js";
20
22
 
21
23
  export function SettingsScreen() {
22
24
  const { state, dispatch } = useApp();
@@ -28,9 +30,13 @@ export function SettingsScreen() {
28
30
  dispatch({ type: "SETTINGS_DATA_LOADING" });
29
31
  try {
30
32
  // Populate dynamic output style options from installed plugins
31
- const outputStyleSetting = SETTINGS_CATALOG.find((s) => s.id === "output-style");
33
+ const outputStyleSetting = SETTINGS_CATALOG.find(
34
+ (s) => s.id === "output-style",
35
+ );
32
36
  if (outputStyleSetting && outputStyleSetting.type === "select") {
33
- outputStyleSetting.options = await discoverOutputStyles(state.projectPath);
37
+ outputStyleSetting.options = await discoverOutputStyles(
38
+ state.projectPath,
39
+ );
34
40
  }
35
41
 
36
42
  const values = await readAllSettingsBothScopes(
@@ -157,10 +163,10 @@ export function SettingsScreen() {
157
163
 
158
164
  const renderDetail = () => {
159
165
  if (settings.values.status === "loading") {
160
- return <text fg="gray">Loading settings...</text>;
166
+ return <text fg={theme.colors.muted}>Loading settings...</text>;
161
167
  }
162
168
  if (settings.values.status === "error") {
163
- return <text fg="red">Failed to load settings</text>;
169
+ return <text fg={theme.colors.danger}>Failed to load settings</text>;
164
170
  }
165
171
  return renderSettingDetail(selectedItem);
166
172
  };
@@ -173,10 +179,10 @@ export function SettingsScreen() {
173
179
  : 0;
174
180
 
175
181
  const statusContent = (
176
- <text>
177
- <span fg="gray">Settings: </span>
178
- <span fg="cyan">{totalSet} configured</span>
179
- <span fg="gray"> │ u:user p:project</span>
182
+ <text fg={theme.colors.text}>
183
+ <span fg={theme.colors.muted}>Settings: </span>
184
+ <span fg={theme.colors.info}>{totalSet} configured</span>
185
+ <span fg={theme.colors.muted}> │ u:user p:project</span>
180
186
  </text>
181
187
  );
182
188
 
@@ -193,7 +199,7 @@ export function SettingsScreen() {
193
199
  ]}
194
200
  listPanel={
195
201
  settings.values.status !== "success" ? (
196
- <text fg="gray">
202
+ <text fg={theme.colors.muted}>
197
203
  {settings.values.status === "loading"
198
204
  ? "Loading..."
199
205
  : "Error loading settings"}