claudeup 6.5.1 → 6.6.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 (78) hide show
  1. package/package.json +4 -4
  2. package/src/__tests__/active-closure.test.ts +184 -0
  3. package/src/__tests__/blackbox-active-closure.test.ts +119 -0
  4. package/src/__tests__/blackbox-convergence.test.ts +334 -0
  5. package/src/__tests__/blackbox-parity.test.ts +684 -0
  6. package/src/__tests__/blackbox-plugin-state.test.ts +523 -0
  7. package/src/__tests__/blackbox-tool-state.test.ts +669 -0
  8. package/src/__tests__/cli-apply-seams.test.ts +2 -0
  9. package/src/__tests__/cli-tool-commands.test.ts +1 -1
  10. package/src/__tests__/cli-tools-adapter.test.ts +294 -0
  11. package/src/__tests__/cli-update-view.test.ts +142 -2
  12. package/src/__tests__/content-drift.test.ts +60 -0
  13. package/src/__tests__/enabled-not-installed.test.ts +1 -2
  14. package/src/__tests__/fixtures/tool-probe/brew-list-versions-tmux.txt +2 -0
  15. package/src/__tests__/fixtures/tool-probe/brew-outdated-behind-homebrew-spec.txt +29 -0
  16. package/src/__tests__/fixtures/tool-probe/brew-outdated-tmux-current.txt +7 -0
  17. package/src/__tests__/fixtures/tool-probe/brew-outdated-unknown-formula.txt +6 -0
  18. package/src/__tests__/fixtures/tool-probe/go-version-m-tmux-mcp.txt +27 -0
  19. package/src/__tests__/fixtures/tool-probe/npm-dist-tags-missing-scoped.txt +4 -0
  20. package/src/__tests__/fixtures/tool-probe/npm-dist-tags-openai-codex.txt +4 -0
  21. package/src/__tests__/fixtures/tool-probe/pypi-cowsay.txt +3 -0
  22. package/src/__tests__/fixtures/tool-probe/uv-receipt-browser-use.toml +10 -0
  23. package/src/__tests__/fixtures/tool-probe/uv-receipt-cowsay-git.toml +8 -0
  24. package/src/__tests__/fixtures/tool-probe/uv-receipt-cowsay-pinned.toml +8 -0
  25. package/src/__tests__/fixtures/tool-probe/uv-receipt-graphifyy.toml +7 -0
  26. package/src/__tests__/fixtures/tool-probe/uv-receipt-mcp.toml +6 -0
  27. package/src/__tests__/fixtures/tool-probe/uv-receipt-serena-agent.toml +9 -0
  28. package/src/__tests__/fixtures/tool-probe/uv-tool-list-outdated-pinned.txt +5 -0
  29. package/src/__tests__/fixtures/tool-probe/uv-tool-list-outdated.txt +11 -0
  30. package/src/__tests__/fixtures/tool-probe/uv-tool-list.txt +22 -0
  31. package/src/__tests__/{gitignore-prerun.test.ts → gitignore-check.test.ts} +8 -6
  32. package/src/__tests__/plugin-cli-argv.test.ts +1 -1
  33. package/src/__tests__/plugin-row-text.test.ts +336 -0
  34. package/src/__tests__/scope-action.test.ts +69 -19
  35. package/src/__tests__/shell-script-callers.test.ts +8 -8
  36. package/src/__tests__/tool-probe.test.ts +1400 -0
  37. package/src/__tests__/update-apply.test.ts +283 -1
  38. package/src/__tests__/update-plan.test.ts +773 -31
  39. package/src/__tests__/version-unknown-state.test.ts +388 -0
  40. package/src/cli/router.ts +0 -5
  41. package/src/cli/update-view.ts +92 -12
  42. package/src/cli/update.ts +114 -38
  43. package/src/services/active-closure.ts +117 -0
  44. package/src/services/catalog-cache-store.ts +6 -2
  45. package/src/services/claude-settings.ts +1 -173
  46. package/src/services/{gitignore-prerun.ts → gitignore-check.ts} +14 -10
  47. package/src/services/gitignore-fixer.ts +1 -1
  48. package/src/services/marketplace-refresh.ts +30 -0
  49. package/src/services/plugin-manager.ts +200 -283
  50. package/src/services/resolver.ts +7 -2
  51. package/src/services/tool-probe.ts +1637 -0
  52. package/src/services/toolchain.ts +97 -0
  53. package/src/services/update-engine.ts +220 -32
  54. package/src/services/update-plan.ts +640 -160
  55. package/src/services/version-snapshot.ts +1 -1
  56. package/src/types/bun.d.ts +6 -0
  57. package/src/ui/App.tsx +2 -27
  58. package/src/ui/adapters/cliToolsAdapter.ts +176 -0
  59. package/src/ui/adapters/pluginsAdapter.ts +57 -0
  60. package/src/ui/components/modals/ModalContainer.tsx +0 -26
  61. package/src/ui/hooks/index.ts +0 -1
  62. package/src/ui/hooks/useGitignoreModal.ts +2 -3
  63. package/src/ui/renderers/cliToolRenderers.tsx +108 -67
  64. package/src/ui/renderers/pluginRenderers.tsx +157 -98
  65. package/src/ui/renderers/pluginRowText.ts +70 -0
  66. package/src/ui/renderers/pluginStatusText.ts +56 -0
  67. package/src/ui/screens/CliToolsScreen.tsx +154 -280
  68. package/src/ui/screens/PluginsScreen.tsx +156 -140
  69. package/src/ui/state/types.ts +0 -7
  70. package/src/__tests__/dual-write-prevention.test.ts +0 -380
  71. package/src/__tests__/gap-fill-versions.test.ts +0 -389
  72. package/src/__tests__/plugin-version-check.test.ts +0 -762
  73. package/src/cli/claude.ts +0 -21
  74. package/src/prerunner/index.ts +0 -500
  75. package/src/services/plugin-version-check.ts +0 -360
  76. package/src/services/update-cache.ts +0 -78
  77. package/src/ui/components/modals/VersionMismatchModal.tsx +0 -138
  78. package/src/ui/hooks/useMismatchModal.ts +0 -89
@@ -2,12 +2,17 @@ import type React from "react";
2
2
  import type { CliTool } from "../../data/cli-tools.js";
3
3
  import {
4
4
  type InstallMethod,
5
- cliToolInstall,
6
5
  cliToolUninstall,
7
6
  } from "../../services/cli-tool-commands.js";
8
- import type { Command } from "../../utils/command-utils.js";
9
7
  import { formatCommand } from "../../utils/command-utils.js";
8
+ import {
9
+ type CliToolRow,
10
+ cliToolStatusText,
11
+ displayVersion,
12
+ isInstalled,
13
+ } from "../adapters/cliToolsAdapter.js";
10
14
  import { type UiColor, theme } from "../theme.js";
15
+ import type { StatusTone } from "./pluginStatusText.js";
11
16
 
12
17
  // ─── Helpers ────────────────────────────────────────────────────────────────
13
18
 
@@ -32,52 +37,58 @@ function uninstallHint(
32
37
  return cmd ? formatCommand(cmd) : "";
33
38
  }
34
39
 
35
- // ─── Status type ───────────────────────────────────────────────────────────────
36
-
37
- export interface CliToolStatus {
38
- tool: CliTool;
39
- installed: boolean;
40
- installedVersion?: string;
41
- latestVersion?: string;
42
- hasUpdate?: boolean;
43
- checking: boolean;
44
- installMethod?: InstallMethod;
45
- allMethods?: InstallMethod[];
46
- updateCommand?: Command;
47
- brewFormula?: string;
40
+ function toneColor(tone: StatusTone): UiColor {
41
+ switch (tone) {
42
+ case "warning":
43
+ return theme.colors.warning;
44
+ case "danger":
45
+ return theme.colors.danger;
46
+ case "success":
47
+ return theme.colors.success;
48
+ case "info":
49
+ return theme.colors.info;
50
+ case "muted":
51
+ return theme.colors.muted;
52
+ }
48
53
  }
49
54
 
50
55
  // ─── Row renderer ──────────────────────────────────────────────────────────────
51
56
 
52
57
  export function renderCliToolRow(
53
- status: CliToolStatus,
58
+ row: CliToolRow,
54
59
  _index: number,
55
60
  isSelected: boolean,
56
61
  ): React.ReactNode {
57
- const { tool, installed, installedVersion, hasUpdate, checking, allMethods } =
58
- status;
59
- const hasConflict = allMethods && allMethods.length > 1;
62
+ const { tool, status, install } = row;
63
+ const installed = isInstalled(status);
64
+ const allMethods = install?.all ?? [];
65
+ const hasConflict = allMethods.length > 1;
60
66
 
61
67
  let icon: string;
62
68
  let iconColor: UiColor;
63
-
64
69
  if (!installed) {
65
70
  icon = "○";
66
71
  iconColor = theme.colors.muted;
67
72
  } else if (hasConflict) {
68
73
  icon = "!";
69
74
  iconColor = theme.colors.danger;
70
- } else if (hasUpdate) {
75
+ } else if (status?.state === "upgrade") {
71
76
  icon = "*";
72
77
  iconColor = theme.colors.warning;
78
+ } else if (status?.state === "unknown") {
79
+ icon = "?";
80
+ iconColor = theme.colors.warning;
73
81
  } else {
74
82
  icon = "●";
75
83
  iconColor = theme.colors.success;
76
84
  }
77
85
 
78
- const versionText = installedVersion ? ` v${installedVersion}` : "";
86
+ const versionText = status?.installed
87
+ ? ` ${displayVersion(status.installed)}`
88
+ : "";
79
89
  const methodTag =
80
- installed && allMethods?.length ? ` ${allMethods.join("+")}` : "";
90
+ installed && allMethods.length ? ` ${allMethods.join("+")}` : "";
91
+ const { suffix, tone } = cliToolStatusText(row);
81
92
 
82
93
  if (isSelected) {
83
94
  return (
@@ -86,7 +97,7 @@ export function renderCliToolRow(
86
97
  {icon} {tool.displayName}
87
98
  {versionText}
88
99
  {methodTag}
89
- {checking ? " ..." : ""}{" "}
100
+ {suffix}{" "}
90
101
  </text>
91
102
  );
92
103
  }
@@ -103,7 +114,7 @@ export function renderCliToolRow(
103
114
  {methodTag}
104
115
  </span>
105
116
  ) : null}
106
- {checking ? <span fg={theme.colors.muted}>{" ..."}</span> : null}
117
+ {suffix ? <span fg={toneColor(tone)}>{suffix}</span> : null}
107
118
  </text>
108
119
  );
109
120
  }
@@ -111,9 +122,9 @@ export function renderCliToolRow(
111
122
  // ─── Detail renderer ───────────────────────────────────────────────────────────
112
123
 
113
124
  export function renderCliToolDetail(
114
- status: CliToolStatus | undefined,
125
+ row: CliToolRow | undefined,
115
126
  ): React.ReactNode {
116
- if (!status) {
127
+ if (!row) {
117
128
  return (
118
129
  <box
119
130
  flexDirection="column"
@@ -126,20 +137,13 @@ export function renderCliToolDetail(
126
137
  );
127
138
  }
128
139
 
129
- const {
130
- tool,
131
- installed,
132
- installedVersion,
133
- latestVersion,
134
- hasUpdate,
135
- checking,
136
- installMethod,
137
- allMethods,
138
- updateCommand,
139
- brewFormula,
140
- } = status;
141
-
142
- const hasConflict = allMethods && allMethods.length > 1;
140
+ const { tool, status, checking, install, updateCommand, installCommand } =
141
+ row;
142
+ const installed = isInstalled(status);
143
+ const allMethods = install?.all ?? [];
144
+ const brewFormula = install?.brewFormula;
145
+ const hasConflict = allMethods.length > 1;
146
+ const state = status?.state;
143
147
 
144
148
  return (
145
149
  <box flexDirection="column">
@@ -150,7 +154,7 @@ export function renderCliToolDetail(
150
154
  {tool.displayName}
151
155
  </strong>
152
156
  </text>
153
- {hasUpdate ? <text fg={theme.colors.warning}> ⬆</text> : null}
157
+ {state === "upgrade" ? <text fg={theme.colors.warning}> ⬆</text> : null}
154
158
  {hasConflict ? <text fg={theme.colors.danger}> !</text> : null}
155
159
  </box>
156
160
 
@@ -159,30 +163,56 @@ export function renderCliToolDetail(
159
163
  <box marginTop={1} flexDirection="column">
160
164
  <box>
161
165
  <text fg={theme.colors.muted}>{"Status "}</text>
162
- {!installed ? (
166
+ {checking || !status ? (
167
+ <text fg={theme.colors.muted}>{"● Checking..."}</text>
168
+ ) : state === "install" ? (
163
169
  <text fg={theme.colors.muted}>{"○ Not installed"}</text>
164
- ) : checking ? (
165
- <text fg={theme.colors.success}>{"● Checking..."}</text>
166
- ) : hasUpdate ? (
170
+ ) : state === "upgrade" ? (
167
171
  <text fg={theme.colors.warning}>{"● Update available"}</text>
172
+ ) : state === "unknown" ? (
173
+ <text fg={theme.colors.warning}>{"? Unverified"}</text>
168
174
  ) : (
169
175
  <text fg={theme.colors.success}>{"● Up to date"}</text>
170
176
  )}
171
177
  </box>
172
- {installedVersion ? (
178
+ {state === "unknown" && status?.note ? (
179
+ <box>
180
+ <text fg={theme.colors.muted}>{"Why "}</text>
181
+ <text fg={theme.colors.warning}>{status.note}</text>
182
+ </box>
183
+ ) : null}
184
+ {/* An upgrade that also takes the tool over from another installer. */}
185
+ {status?.adopted ? (
186
+ <box>
187
+ <text fg={theme.colors.muted}>{"Note "}</text>
188
+ <text fg={theme.colors.warning}>{status.adopted}</text>
189
+ </box>
190
+ ) : null}
191
+ {status?.installed ? (
173
192
  <box>
174
193
  <text fg={theme.colors.muted}>{"Version "}</text>
175
194
  <text fg={theme.colors.text}>
176
- <span fg={theme.colors.success}>v{installedVersion}</span>
177
- {latestVersion && hasUpdate ? (
178
- <span fg={theme.colors.warning}> → v{latestVersion}</span>
195
+ <span fg={theme.colors.success}>
196
+ {displayVersion(status.installed)}
197
+ </span>
198
+ {state === "upgrade" && status.target ? (
199
+ <span fg={theme.colors.warning}>
200
+ {" "}
201
+ → {displayVersion(status.target)}
202
+ </span>
179
203
  ) : null}
180
204
  </text>
181
205
  </box>
182
- ) : latestVersion ? (
206
+ ) : status?.latest ? (
183
207
  <box>
184
208
  <text fg={theme.colors.muted}>{"Latest "}</text>
185
- <text fg={theme.colors.text}>v{latestVersion}</text>
209
+ <text fg={theme.colors.text}>{displayVersion(status.latest)}</text>
210
+ </box>
211
+ ) : null}
212
+ {status?.pin ? (
213
+ <box>
214
+ <text fg={theme.colors.muted}>{"Pinned "}</text>
215
+ <text fg={theme.colors.text}>{displayVersion(status.pin)}</text>
186
216
  </box>
187
217
  ) : null}
188
218
  {installed && updateCommand ? (
@@ -190,11 +220,11 @@ export function renderCliToolDetail(
190
220
  <text fg={theme.colors.muted}>{"Update "}</text>
191
221
  <text fg={theme.colors.accent}>{formatCommand(updateCommand)}</text>
192
222
  </box>
193
- ) : !installed ? (
223
+ ) : !installed && !checking ? (
194
224
  <box>
195
225
  <text fg={theme.colors.muted}>{"Install "}</text>
196
226
  <text fg={theme.colors.accent}>
197
- {formatCommand(cliToolInstall(tool))}
227
+ {formatCommand(installCommand)}
198
228
  </text>
199
229
  </box>
200
230
  ) : null}
@@ -250,33 +280,44 @@ export function renderCliToolDetail(
250
280
 
251
281
  {/* Actions */}
252
282
  <box marginTop={2} flexDirection="column">
253
- {!installed ? (
283
+ {checking || !status ? null : !installed ? (
254
284
  <box>
255
285
  <text bg={theme.colors.success} fg={theme.hints.fg}>
256
286
  {" "}
257
287
  Enter{" "}
258
288
  </text>
259
289
  <text fg={theme.colors.muted}> Install</text>
260
- <text fg={theme.colors.dim}>
261
- {" "}
262
- {formatCommand(cliToolInstall(tool))}
263
- </text>
290
+ <text fg={theme.colors.dim}> {formatCommand(installCommand)}</text>
264
291
  </box>
265
- ) : hasUpdate ? (
292
+ ) : state === "upgrade" ? (
266
293
  <box>
267
294
  <text bg={theme.colors.warning} fg={theme.hints.fg}>
268
295
  {" "}
269
296
  Enter{" "}
270
297
  </text>
271
- <text fg={theme.colors.muted}> Update to v{latestVersion}</text>
272
- </box>
273
- ) : (
274
- <box>
275
- <text bg={theme.colors.muted} fg={theme.hints.fg}>
298
+ <text fg={theme.colors.muted}>
276
299
  {" "}
277
- Enter{" "}
300
+ Update
301
+ {status.target ? ` to ${displayVersion(status.target)}` : ""}
278
302
  </text>
279
- <text fg={theme.colors.muted}> Reinstall</text>
303
+ </box>
304
+ ) : (
305
+ <box flexDirection="column">
306
+ <box>
307
+ <text bg={theme.colors.muted} fg={theme.hints.fg}>
308
+ {" "}
309
+ Enter{" "}
310
+ </text>
311
+ <text fg={theme.colors.muted}>
312
+ {state === "unknown" ? " Update anyway" : " Reinstall"}
313
+ </text>
314
+ </box>
315
+ {/* The status warns that this command may not replace the
316
+ running binary; the menu still offers it, as an explicit
317
+ user action, and says so beside it. */}
318
+ {status.reason === "not-managed" && status.note ? (
319
+ <text fg={theme.colors.warning}>{` ${status.note}`}</text>
320
+ ) : null}
280
321
  </box>
281
322
  )}
282
323
  </box>