claudeup 4.19.0 → 4.22.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 (180) hide show
  1. package/bin/claudeup.js +43 -19
  2. package/package.json +14 -19
  3. package/scripts/build-binaries.ts +77 -0
  4. package/scripts/check-optional-deps.ts +49 -0
  5. package/scripts/test-isolated.ts +43 -0
  6. package/src/__tests__/cli-router.test.ts +65 -0
  7. package/src/__tests__/conventions-integration.test.ts +745 -0
  8. package/src/__tests__/conventions-manager.test.ts +1172 -0
  9. package/src/__tests__/doctor-bins.test.ts +35 -0
  10. package/src/__tests__/doctor.test.ts +450 -0
  11. package/src/__tests__/dual-write-prevention.test.ts +14 -4
  12. package/src/__tests__/file-locking.test.ts +208 -0
  13. package/src/__tests__/gitignore-prerun.test.ts +1 -0
  14. package/src/__tests__/install-command.test.ts +81 -0
  15. package/src/__tests__/install-plan.test.ts +98 -0
  16. package/src/__tests__/manifest.test.ts +136 -0
  17. package/src/__tests__/marketplace-refresh.test.ts +267 -0
  18. package/src/__tests__/plugin-requires.test.ts +133 -0
  19. package/src/__tests__/plugin-setup.test.ts +7 -0
  20. package/src/__tests__/profile-command.test.ts +116 -0
  21. package/src/__tests__/profile-materializer.test.ts +82 -0
  22. package/src/__tests__/profile-sync.test.ts +136 -0
  23. package/src/__tests__/registry-version-resolution.test.ts +74 -0
  24. package/src/__tests__/resolve-executable.test.ts +42 -0
  25. package/src/__tests__/resolver.test.ts +218 -0
  26. package/src/__tests__/symlink-manager.test.ts +99 -0
  27. package/src/__tests__/toolchain.test.ts +56 -0
  28. package/src/cli/claude.ts +21 -0
  29. package/src/cli/doctor.ts +132 -0
  30. package/src/cli/install.ts +360 -0
  31. package/src/cli/profile.ts +166 -0
  32. package/src/cli/router.ts +107 -0
  33. package/src/cli/update.ts +85 -0
  34. package/src/data/cli-tools.ts +7 -7
  35. package/src/data/gitignore-defaults.ts +4 -0
  36. package/src/data/marketplaces.ts +12 -0
  37. package/src/data/predefined-profiles.ts +3 -4
  38. package/src/main.tsx +11 -154
  39. package/src/prerunner/index.ts +62 -27
  40. package/src/services/claude-cli.ts +19 -22
  41. package/src/services/claude-settings.ts +99 -26
  42. package/src/services/conventions-manager.ts +865 -0
  43. package/src/services/doctor-bins.ts +49 -0
  44. package/src/services/doctor.ts +509 -0
  45. package/src/services/install-plan.ts +107 -0
  46. package/src/services/manifest.ts +166 -0
  47. package/src/services/marketplace-refresh.ts +173 -0
  48. package/src/services/plugin-manager.ts +7 -1
  49. package/src/services/plugin-requires.ts +215 -0
  50. package/src/services/plugin-version-check.ts +9 -4
  51. package/src/services/profile-materializer.ts +61 -0
  52. package/src/services/profile-sync.ts +150 -0
  53. package/src/services/resolver.ts +293 -0
  54. package/src/services/symlink-manager.ts +146 -0
  55. package/src/services/toolchain.ts +97 -0
  56. package/src/services/version-check.ts +10 -11
  57. package/src/types/index.ts +138 -31
  58. package/src/ui/App.tsx +0 -4
  59. package/src/ui/screens/CliToolsScreen.tsx +11 -3
  60. package/src/ui/screens/index.ts +0 -1
  61. package/src/ui/state/reducer.ts +0 -101
  62. package/src/ui/state/types.ts +0 -35
  63. package/src/utils/command-utils.ts +23 -0
  64. package/src/utils/file-locking.ts +144 -0
  65. package/src/data/alias-flags.js +0 -205
  66. package/src/data/cli-tools.js +0 -123
  67. package/src/data/gitignore-defaults.js +0 -24
  68. package/src/data/gitignore-reasons.js +0 -97
  69. package/src/data/gitignore-templates.js +0 -21
  70. package/src/data/marketplaces.js +0 -138
  71. package/src/data/mcp-servers.js +0 -509
  72. package/src/data/predefined-profiles.js +0 -248
  73. package/src/data/settings-catalog.js +0 -625
  74. package/src/data/skill-repos.js +0 -160
  75. package/src/data/statuslines.js +0 -159
  76. package/src/data/statuslines.ts +0 -188
  77. package/src/index.js +0 -4
  78. package/src/index.ts +0 -5
  79. package/src/main.js +0 -170
  80. package/src/prerunner/index.js +0 -252
  81. package/src/services/alias-settings.js +0 -51
  82. package/src/services/alias-shell-writer.js +0 -1018
  83. package/src/services/alias-store.js +0 -129
  84. package/src/services/claude-cli.js +0 -189
  85. package/src/services/claude-runner.js +0 -28
  86. package/src/services/claude-settings.js +0 -1223
  87. package/src/services/gitignore-detector.js +0 -155
  88. package/src/services/gitignore-fixer.js +0 -231
  89. package/src/services/gitignore-prerun.js +0 -46
  90. package/src/services/gitignore-resolver.js +0 -143
  91. package/src/services/gitignore-service.js +0 -117
  92. package/src/services/local-marketplace.js +0 -339
  93. package/src/services/marketplace-fetcher.js +0 -96
  94. package/src/services/marketplace-sync.js +0 -94
  95. package/src/services/mcp-registry.js +0 -87
  96. package/src/services/plugin-manager.js +0 -473
  97. package/src/services/plugin-mcp-config.js +0 -177
  98. package/src/services/plugin-setup.js +0 -499
  99. package/src/services/plugin-version-check.js +0 -262
  100. package/src/services/profiles.js +0 -161
  101. package/src/services/settings-manager.js +0 -243
  102. package/src/services/skills-manager.js +0 -393
  103. package/src/services/skillsmp-client.js +0 -87
  104. package/src/services/update-cache.js +0 -52
  105. package/src/services/version-check.js +0 -99
  106. package/src/types/gitignore.js +0 -6
  107. package/src/types/index.js +0 -1
  108. package/src/ui/App.js +0 -355
  109. package/src/ui/adapters/pluginsAdapter.js +0 -139
  110. package/src/ui/adapters/settingsAdapter.js +0 -111
  111. package/src/ui/adapters/skillsAdapter.js +0 -154
  112. package/src/ui/components/CategoryHeader.js +0 -9
  113. package/src/ui/components/EmptyFilterState.js +0 -4
  114. package/src/ui/components/FlagDetailEditor.js +0 -0
  115. package/src/ui/components/ScopeIndicator.js +0 -30
  116. package/src/ui/components/ScrollableList.js +0 -36
  117. package/src/ui/components/SearchInput.js +0 -19
  118. package/src/ui/components/StyledText.js +0 -39
  119. package/src/ui/components/TabBar.js +0 -19
  120. package/src/ui/components/layout/FooterHints.js +0 -29
  121. package/src/ui/components/layout/Panel.js +0 -6
  122. package/src/ui/components/layout/ProgressBar.js +0 -14
  123. package/src/ui/components/layout/ScopeTabs.js +0 -6
  124. package/src/ui/components/layout/ScreenLayout.js +0 -16
  125. package/src/ui/components/layout/index.js +0 -5
  126. package/src/ui/components/modals/ConfirmModal.js +0 -14
  127. package/src/ui/components/modals/InputModal.js +0 -5
  128. package/src/ui/components/modals/LoadingModal.js +0 -17
  129. package/src/ui/components/modals/MessageModal.js +0 -16
  130. package/src/ui/components/modals/ModalContainer.js +0 -137
  131. package/src/ui/components/modals/SelectModal.js +0 -18
  132. package/src/ui/components/modals/VersionMismatchModal.js +0 -36
  133. package/src/ui/components/modals/index.js +0 -6
  134. package/src/ui/components/primitives/ActionHints.js +0 -13
  135. package/src/ui/components/primitives/DetailSection.js +0 -7
  136. package/src/ui/components/primitives/KeyValueLine.js +0 -8
  137. package/src/ui/components/primitives/ListCategoryRow.js +0 -8
  138. package/src/ui/components/primitives/MetaText.js +0 -8
  139. package/src/ui/components/primitives/ScopeDetail.js +0 -32
  140. package/src/ui/components/primitives/ScopeSquares.js +0 -11
  141. package/src/ui/components/primitives/SelectableRow.js +0 -5
  142. package/src/ui/components/primitives/index.js +0 -8
  143. package/src/ui/hooks/index.js +0 -4
  144. package/src/ui/hooks/useAsyncData.js +0 -77
  145. package/src/ui/hooks/useGitignoreModal.js +0 -74
  146. package/src/ui/hooks/useKeyboard.js +0 -13
  147. package/src/ui/hooks/useKeyboardHandler.js +0 -39
  148. package/src/ui/hooks/useMismatchModal.js +0 -77
  149. package/src/ui/registry.js +0 -1
  150. package/src/ui/renderers/cliToolRenderers.js +0 -54
  151. package/src/ui/renderers/gitignoreRenderers.js +0 -46
  152. package/src/ui/renderers/mcpRenderers.js +0 -26
  153. package/src/ui/renderers/pluginRenderers.js +0 -124
  154. package/src/ui/renderers/profileRenderers.js +0 -177
  155. package/src/ui/renderers/settingsRenderers.js +0 -73
  156. package/src/ui/renderers/skillRenderers.js +0 -138
  157. package/src/ui/screens/AliasScreen.js +0 -1111
  158. package/src/ui/screens/CliToolsScreen.js +0 -338
  159. package/src/ui/screens/EnvVarsScreen.js +0 -152
  160. package/src/ui/screens/GitignoreScreen.js +0 -328
  161. package/src/ui/screens/McpRegistryScreen.js +0 -238
  162. package/src/ui/screens/McpScreen.js +0 -176
  163. package/src/ui/screens/ModelSelectorScreen.js +0 -296
  164. package/src/ui/screens/ModelSelectorScreen.tsx +0 -444
  165. package/src/ui/screens/PluginsScreen.js +0 -769
  166. package/src/ui/screens/ProfilesScreen.js +0 -298
  167. package/src/ui/screens/SkillsScreen.js +0 -549
  168. package/src/ui/screens/StatusLineScreen.js +0 -206
  169. package/src/ui/screens/StatusLineScreen.tsx +0 -416
  170. package/src/ui/screens/index.js +0 -10
  171. package/src/ui/state/AnimationContext.js +0 -34
  172. package/src/ui/state/AppContext.js +0 -162
  173. package/src/ui/state/DimensionsContext.js +0 -71
  174. package/src/ui/state/reducer.js +0 -547
  175. package/src/ui/state/types.js +0 -1
  176. package/src/ui/theme.js +0 -47
  177. package/src/utils/clipboard.js +0 -56
  178. package/src/utils/command-utils.js +0 -19
  179. package/src/utils/fuzzy-search.js +0 -101
  180. package/src/utils/string-utils.js +0 -62
@@ -1,499 +0,0 @@
1
- /**
2
- * Plugin system dependency installer
3
- *
4
- * Auto-detects available package managers and installs plugin dependencies
5
- * declared in plugin.json's "setup" section.
6
- *
7
- * Supported managers:
8
- * - pip: Python packages (auto-detects uv > pip3 > pip)
9
- * - brew: Homebrew formulae (macOS/Linux)
10
- * - npm: Global npm packages
11
- * - cargo: Rust crates
12
- * - go: Go modules (via go install)
13
- *
14
- * Special keys:
15
- * - required: System binaries that must exist in PATH (not auto-installed)
16
- *
17
- * Example plugin.json:
18
- * {
19
- * "setup": {
20
- * "pip": ["browser-use", "mcp"],
21
- * "brew": ["memextech/tap/ht-mcp"],
22
- * "go": ["github.com/MadAppGang/tmux-mcp@latest"],
23
- * "required": ["tmux"]
24
- * }
25
- * }
26
- */
27
- import { execFile } from "node:child_process";
28
- import { promisify } from "node:util";
29
- import fs from "fs-extra";
30
- import path from "node:path";
31
- import os from "node:os";
32
- import { which } from "../utils/command-utils.js";
33
- const execFileAsync = promisify(execFile);
34
- /**
35
- * Run a command and return success/failure
36
- */
37
- async function run(cmd, args, timeoutMs = 120000) {
38
- try {
39
- const { stdout, stderr } = await execFileAsync(cmd, args, {
40
- timeout: timeoutMs,
41
- });
42
- return { ok: true, stdout: stdout.trim(), stderr: stderr.trim() };
43
- }
44
- catch (error) {
45
- const e = error;
46
- return {
47
- ok: false,
48
- stdout: e.stdout?.trim() || "",
49
- stderr: e.stderr?.trim() || e.message || "unknown error",
50
- };
51
- }
52
- }
53
- /**
54
- * Detect the best available Python package installer
55
- * Prefers: uv > pip3 > pip
56
- */
57
- async function detectPipCommand() {
58
- // Prefer uv (fast, modern)
59
- if (await which("uv")) {
60
- return {
61
- cmd: "uv",
62
- args: ["pip", "install", "--system", "--break-system-packages"],
63
- };
64
- }
65
- // Fall back to pip3
66
- if (await which("pip3")) {
67
- return {
68
- cmd: "pip3",
69
- args: ["install", "--break-system-packages"],
70
- };
71
- }
72
- // Fall back to pip
73
- if (await which("pip")) {
74
- return {
75
- cmd: "pip",
76
- args: ["install", "--break-system-packages"],
77
- };
78
- }
79
- return null;
80
- }
81
- /**
82
- * Check if a Python package is already installed
83
- */
84
- async function isPythonPackageInstalled(pkg) {
85
- // Convert package name to importable module name (e.g., browser-use → browser_use)
86
- const moduleName = pkg.replace(/-/g, "_");
87
- const result = await run("python3", ["-c", `import ${moduleName}`]);
88
- return result.ok;
89
- }
90
- /**
91
- * Check if a binary is available in PATH
92
- */
93
- async function isBinaryAvailable(name) {
94
- return (await which(name)) !== null;
95
- }
96
- /**
97
- * Extract binary name from a brew formula or npm package
98
- * e.g., "memextech/tap/ht-mcp" → "ht-mcp"
99
- */
100
- function extractBinaryName(pkg) {
101
- return pkg.split("/").pop() || pkg;
102
- }
103
- /**
104
- * Install pip packages
105
- */
106
- async function installPipPackages(packages, result) {
107
- const pip = await detectPipCommand();
108
- if (!pip) {
109
- for (const pkg of packages) {
110
- result.failed.push({ pkg: `pip:${pkg}`, error: "No pip/uv found" });
111
- }
112
- return;
113
- }
114
- // Filter out already installed
115
- const toInstall = [];
116
- for (const pkg of packages) {
117
- if (await isPythonPackageInstalled(pkg)) {
118
- result.skipped.push(`pip:${pkg}`);
119
- }
120
- else {
121
- toInstall.push(pkg);
122
- }
123
- }
124
- if (toInstall.length === 0)
125
- return;
126
- // Install all at once
127
- const { ok, stderr } = await run(pip.cmd, [...pip.args, ...toInstall]);
128
- if (ok) {
129
- for (const pkg of toInstall) {
130
- result.installed.push(`pip:${pkg}`);
131
- }
132
- }
133
- else {
134
- for (const pkg of toInstall) {
135
- result.failed.push({ pkg: `pip:${pkg}`, error: stderr });
136
- }
137
- }
138
- }
139
- /**
140
- * Install brew packages
141
- */
142
- async function installBrewPackages(packages, result) {
143
- const brewPath = await which("brew");
144
- if (!brewPath) {
145
- for (const pkg of packages) {
146
- result.failed.push({
147
- pkg: `brew:${pkg}`,
148
- error: "Homebrew not installed",
149
- });
150
- }
151
- return;
152
- }
153
- for (const pkg of packages) {
154
- const binaryName = extractBinaryName(pkg);
155
- if (await isBinaryAvailable(binaryName)) {
156
- result.skipped.push(`brew:${pkg}`);
157
- continue;
158
- }
159
- const { ok, stderr } = await run(brewPath, ["install", pkg]);
160
- if (ok) {
161
- result.installed.push(`brew:${pkg}`);
162
- }
163
- else {
164
- result.failed.push({ pkg: `brew:${pkg}`, error: stderr });
165
- }
166
- }
167
- }
168
- /**
169
- * Detect the best available Node.js package installer for globals
170
- * Prefers: bun > npm
171
- */
172
- async function detectNpmCommand() {
173
- const bunPath = await which("bun");
174
- if (bunPath) {
175
- return { cmd: bunPath, args: ["install", "-g"], label: "bun" };
176
- }
177
- const npmPath = await which("npm");
178
- if (npmPath) {
179
- return { cmd: npmPath, args: ["install", "-g"], label: "npm" };
180
- }
181
- return null;
182
- }
183
- /**
184
- * Install global Node.js packages (prefers bun > npm)
185
- */
186
- async function installNpmPackages(packages, result) {
187
- const installer = await detectNpmCommand();
188
- if (!installer) {
189
- for (const pkg of packages) {
190
- result.failed.push({ pkg: `npm:${pkg}`, error: "No bun or npm found" });
191
- }
192
- return;
193
- }
194
- for (const pkg of packages) {
195
- if (await isBinaryAvailable(pkg)) {
196
- result.skipped.push(`npm:${pkg}`);
197
- continue;
198
- }
199
- const { ok, stderr } = await run(installer.cmd, [...installer.args, pkg]);
200
- if (ok) {
201
- result.installed.push(`${installer.label}:${pkg}`);
202
- }
203
- else {
204
- result.failed.push({ pkg: `${installer.label}:${pkg}`, error: stderr });
205
- }
206
- }
207
- }
208
- /**
209
- * Install cargo packages
210
- */
211
- async function installCargoPackages(packages, result) {
212
- const cargoPath = await which("cargo");
213
- if (!cargoPath) {
214
- for (const pkg of packages) {
215
- result.failed.push({ pkg: `cargo:${pkg}`, error: "cargo not found" });
216
- }
217
- return;
218
- }
219
- for (const pkg of packages) {
220
- if (await isBinaryAvailable(pkg)) {
221
- result.skipped.push(`cargo:${pkg}`);
222
- continue;
223
- }
224
- const { ok, stderr } = await run(cargoPath, ["install", pkg], 300000);
225
- if (ok) {
226
- result.installed.push(`cargo:${pkg}`);
227
- }
228
- else {
229
- result.failed.push({ pkg: `cargo:${pkg}`, error: stderr });
230
- }
231
- }
232
- }
233
- /**
234
- * Extract binary name from a Go module path.
235
- * The binary name is the last path segment before @version.
236
- * e.g., "github.com/MadAppGang/tmux-mcp@latest" → "tmux-mcp"
237
- * "github.com/user/tool" → "tool"
238
- */
239
- export function extractGoBinaryName(pkg) {
240
- // Strip @version suffix first
241
- const withoutVersion = pkg.replace(/@[^/]*$/, "");
242
- return withoutVersion.split("/").pop() || pkg;
243
- }
244
- /**
245
- * Extract the pinned version from a Go module path, or null when unpinned.
246
- * e.g., "github.com/MadAppGang/tmux-mcp@v1.6.2" → "v1.6.2"
247
- * "github.com/MadAppGang/tmux-mcp@latest" → null (a moving target)
248
- * "github.com/user/tool" → null
249
- */
250
- export function extractGoVersion(pkg) {
251
- const at = pkg.lastIndexOf("@");
252
- if (at === -1)
253
- return null;
254
- const version = pkg.slice(at + 1);
255
- if (version === "" || version === "latest")
256
- return null;
257
- return version;
258
- }
259
- /** Normalize a semver-ish string for comparison: trim and drop a leading "v". */
260
- function normalizeVersion(v) {
261
- return v.trim().replace(/^v/, "");
262
- }
263
- /**
264
- * Read an installed binary's version via `<binary> --version`, normalized, or
265
- * null if it produced no usable version string. Used to tell whether a pinned Go
266
- * dependency is already at the right version.
267
- */
268
- async function getInstalledBinaryVersion(binaryName) {
269
- const { ok, stdout } = await run(binaryName, ["--version"], 10000);
270
- if (!ok || !stdout)
271
- return null;
272
- const match = stdout.split("\n")[0].match(/v?\d+\.\d+\.\d+[^\s]*/);
273
- return match ? normalizeVersion(match[0]) : null;
274
- }
275
- /**
276
- * Decide whether a Go dependency needs (re)installing. This is what makes a
277
- * plugin *update* actually update its binary: without a version check an
278
- * already-present binary is skipped forever, so a bumped plugin never pulls the
279
- * new binary.
280
- *
281
- * - binary absent → yes
282
- * - pinned version, installed reports the same one → no
283
- * - pinned version, installed reports a different one → yes
284
- * - pinned version, installed version can't be read → yes (can't confirm the
285
- * pin, so reinstall to be safe)
286
- * - unpinned (@latest) and present → no (a moving target
287
- * can't be verified without a network round-trip)
288
- */
289
- export async function goDepNeedsInstall(pkg) {
290
- const binaryName = extractGoBinaryName(pkg);
291
- if (!(await isBinaryAvailable(binaryName)))
292
- return true;
293
- const pinned = extractGoVersion(pkg);
294
- if (!pinned)
295
- return false;
296
- const installed = await getInstalledBinaryVersion(binaryName);
297
- if (installed === null)
298
- return true;
299
- return normalizeVersion(pinned) !== installed;
300
- }
301
- /**
302
- * Install Go packages via `go install`
303
- */
304
- async function installGoPackages(packages, result) {
305
- const goPath = await which("go");
306
- if (!goPath) {
307
- for (const pkg of packages) {
308
- result.failed.push({ pkg: `go:${pkg}`, error: "go not found" });
309
- }
310
- return;
311
- }
312
- for (const pkg of packages) {
313
- if (!(await goDepNeedsInstall(pkg))) {
314
- result.skipped.push(`go:${pkg}`);
315
- continue;
316
- }
317
- const { ok, stderr } = await run(goPath, ["install", pkg], 300000);
318
- if (ok) {
319
- result.installed.push(`go:${pkg}`);
320
- }
321
- else {
322
- result.failed.push({ pkg: `go:${pkg}`, error: stderr });
323
- }
324
- }
325
- }
326
- /**
327
- * Check required system binaries and report missing ones.
328
- * These are NOT installed by claudeup — the user must install them manually.
329
- */
330
- async function checkRequiredBinaries(binaries, result) {
331
- for (const name of binaries) {
332
- if (await isBinaryAvailable(name)) {
333
- result.skipped.push(`required:${name}`);
334
- }
335
- else {
336
- result.failed.push({
337
- pkg: `required:${name}`,
338
- error: `Required binary '${name}' not found in PATH. Please install it manually.`,
339
- });
340
- }
341
- }
342
- }
343
- /**
344
- * Read the setup config from a plugin's cached manifest
345
- */
346
- export async function getPluginSetupConfig(marketplace, pluginName) {
347
- const cacheDir = path.join(os.homedir(), ".claude", "plugins", "cache", marketplace);
348
- // Find the plugin directory (versioned)
349
- if (!(await fs.pathExists(cacheDir)))
350
- return null;
351
- const entries = await fs.readdir(cacheDir);
352
- // Look for the plugin name directory
353
- const pluginDir = entries.find((e) => e === pluginName);
354
- if (!pluginDir)
355
- return null;
356
- const pluginPath = path.join(cacheDir, pluginDir);
357
- const stat = await fs.stat(pluginPath);
358
- if (!stat.isDirectory())
359
- return null;
360
- // Find the version directory
361
- const versions = await fs.readdir(pluginPath);
362
- if (versions.length === 0)
363
- return null;
364
- // Use the latest version directory
365
- const latestVersion = versions.sort().pop();
366
- const manifestPath = path.join(pluginPath, latestVersion, "plugin.json");
367
- if (!(await fs.pathExists(manifestPath)))
368
- return null;
369
- try {
370
- const manifest = await fs.readJson(manifestPath);
371
- return manifest.setup || null;
372
- }
373
- catch {
374
- return null;
375
- }
376
- }
377
- /**
378
- * Read setup config from a local plugin directory (marketplace source)
379
- */
380
- export async function getPluginSetupFromSource(marketplace, pluginName) {
381
- const marketplaceDir = path.join(os.homedir(), ".claude", "plugins", "marketplaces", marketplace);
382
- if (!(await fs.pathExists(marketplaceDir)))
383
- return null;
384
- // Try common plugin locations
385
- for (const subdir of ["plugins", ""]) {
386
- const pluginJson = subdir
387
- ? path.join(marketplaceDir, subdir, pluginName, "plugin.json")
388
- : path.join(marketplaceDir, pluginName, "plugin.json");
389
- if (await fs.pathExists(pluginJson)) {
390
- try {
391
- const manifest = await fs.readJson(pluginJson);
392
- return manifest.setup || null;
393
- }
394
- catch {
395
- continue;
396
- }
397
- }
398
- }
399
- return null;
400
- }
401
- /**
402
- * Install all dependencies declared in a plugin's setup config.
403
- * Auto-detects available package managers.
404
- */
405
- export async function installPluginDeps(setup) {
406
- const result = {
407
- installed: [],
408
- skipped: [],
409
- failed: [],
410
- };
411
- if (setup.pip?.length) {
412
- await installPipPackages(setup.pip, result);
413
- }
414
- if (setup.brew?.length) {
415
- await installBrewPackages(setup.brew, result);
416
- }
417
- if (setup.npm?.length) {
418
- await installNpmPackages(setup.npm, result);
419
- }
420
- if (setup.cargo?.length) {
421
- await installCargoPackages(setup.cargo, result);
422
- }
423
- if (setup.go?.length) {
424
- await installGoPackages(setup.go, result);
425
- }
426
- if (setup.required?.length) {
427
- await checkRequiredBinaries(setup.required, result);
428
- }
429
- return result;
430
- }
431
- /**
432
- * Check which dependencies from a setup config are missing.
433
- * Returns a filtered SetupConfig with only missing deps.
434
- */
435
- export async function checkMissingDeps(setup) {
436
- const missing = {};
437
- if (setup.pip?.length) {
438
- const missingPip = [];
439
- for (const pkg of setup.pip) {
440
- if (!(await isPythonPackageInstalled(pkg))) {
441
- missingPip.push(pkg);
442
- }
443
- }
444
- if (missingPip.length > 0)
445
- missing.pip = missingPip;
446
- }
447
- if (setup.brew?.length) {
448
- const missingBrew = [];
449
- for (const pkg of setup.brew) {
450
- const bin = extractBinaryName(pkg);
451
- if (!(await isBinaryAvailable(bin))) {
452
- missingBrew.push(pkg);
453
- }
454
- }
455
- if (missingBrew.length > 0)
456
- missing.brew = missingBrew;
457
- }
458
- if (setup.npm?.length) {
459
- const missingNpm = [];
460
- for (const pkg of setup.npm) {
461
- if (!(await isBinaryAvailable(pkg))) {
462
- missingNpm.push(pkg);
463
- }
464
- }
465
- if (missingNpm.length > 0)
466
- missing.npm = missingNpm;
467
- }
468
- if (setup.cargo?.length) {
469
- const missingCargo = [];
470
- for (const pkg of setup.cargo) {
471
- if (!(await isBinaryAvailable(pkg))) {
472
- missingCargo.push(pkg);
473
- }
474
- }
475
- if (missingCargo.length > 0)
476
- missing.cargo = missingCargo;
477
- }
478
- if (setup.go?.length) {
479
- const missingGo = [];
480
- for (const pkg of setup.go) {
481
- if (await goDepNeedsInstall(pkg)) {
482
- missingGo.push(pkg);
483
- }
484
- }
485
- if (missingGo.length > 0)
486
- missing.go = missingGo;
487
- }
488
- if (setup.required?.length) {
489
- const missingRequired = [];
490
- for (const name of setup.required) {
491
- if (!(await isBinaryAvailable(name))) {
492
- missingRequired.push(name);
493
- }
494
- }
495
- if (missingRequired.length > 0)
496
- missing.required = missingRequired;
497
- }
498
- return missing;
499
- }