claudeup 4.19.0 → 4.23.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.
- package/bin/claudeup.js +43 -19
- package/package.json +14 -19
- package/scripts/build-binaries.ts +77 -0
- package/scripts/check-optional-deps.ts +49 -0
- package/scripts/test-isolated.ts +43 -0
- package/src/__tests__/cli-router.test.ts +65 -0
- package/src/__tests__/conventions-integration.test.ts +745 -0
- package/src/__tests__/conventions-manager.test.ts +1172 -0
- package/src/__tests__/doctor-bins.test.ts +35 -0
- package/src/__tests__/doctor.test.ts +450 -0
- package/src/__tests__/dual-write-prevention.test.ts +14 -4
- package/src/__tests__/file-locking.test.ts +208 -0
- package/src/__tests__/gitignore-prerun.test.ts +1 -0
- package/src/__tests__/install-command.test.ts +81 -0
- package/src/__tests__/install-plan.test.ts +98 -0
- package/src/__tests__/manifest.test.ts +136 -0
- package/src/__tests__/marketplace-badge.test.ts +91 -0
- package/src/__tests__/marketplace-refresh.test.ts +267 -0
- package/src/__tests__/plugin-requires.test.ts +133 -0
- package/src/__tests__/plugin-setup.test.ts +7 -0
- package/src/__tests__/profile-command.test.ts +116 -0
- package/src/__tests__/profile-materializer.test.ts +82 -0
- package/src/__tests__/profile-sync.test.ts +136 -0
- package/src/__tests__/registry-version-resolution.test.ts +74 -0
- package/src/__tests__/resolve-executable.test.ts +42 -0
- package/src/__tests__/resolver.test.ts +218 -0
- package/src/__tests__/symlink-manager.test.ts +99 -0
- package/src/__tests__/toolchain.test.ts +56 -0
- package/src/cli/claude.ts +21 -0
- package/src/cli/doctor.ts +132 -0
- package/src/cli/install.ts +360 -0
- package/src/cli/profile.ts +166 -0
- package/src/cli/router.ts +107 -0
- package/src/cli/update.ts +85 -0
- package/src/data/cli-tools.ts +7 -7
- package/src/data/gitignore-defaults.ts +4 -0
- package/src/data/marketplaces.ts +12 -0
- package/src/data/predefined-profiles.ts +3 -4
- package/src/main.tsx +11 -154
- package/src/prerunner/index.ts +62 -27
- package/src/services/claude-cli.ts +19 -22
- package/src/services/claude-settings.ts +99 -26
- package/src/services/conventions-manager.ts +865 -0
- package/src/services/doctor-bins.ts +49 -0
- package/src/services/doctor.ts +509 -0
- package/src/services/install-plan.ts +107 -0
- package/src/services/manifest.ts +166 -0
- package/src/services/marketplace-refresh.ts +173 -0
- package/src/services/plugin-manager.ts +7 -1
- package/src/services/plugin-requires.ts +215 -0
- package/src/services/plugin-version-check.ts +9 -4
- package/src/services/profile-materializer.ts +61 -0
- package/src/services/profile-sync.ts +150 -0
- package/src/services/resolver.ts +293 -0
- package/src/services/symlink-manager.ts +146 -0
- package/src/services/toolchain.ts +97 -0
- package/src/services/version-check.ts +10 -11
- package/src/types/index.ts +138 -31
- package/src/ui/App.tsx +0 -4
- package/src/ui/adapters/pluginsAdapter.ts +13 -0
- package/src/ui/screens/CliToolsScreen.tsx +11 -3
- package/src/ui/screens/index.ts +0 -1
- package/src/ui/state/reducer.ts +0 -101
- package/src/ui/state/types.ts +0 -35
- package/src/utils/command-utils.ts +23 -0
- package/src/utils/file-locking.ts +144 -0
- package/src/data/alias-flags.js +0 -205
- package/src/data/cli-tools.js +0 -123
- package/src/data/gitignore-defaults.js +0 -24
- package/src/data/gitignore-reasons.js +0 -97
- package/src/data/gitignore-templates.js +0 -21
- package/src/data/marketplaces.js +0 -138
- package/src/data/mcp-servers.js +0 -509
- package/src/data/predefined-profiles.js +0 -248
- package/src/data/settings-catalog.js +0 -625
- package/src/data/skill-repos.js +0 -160
- package/src/data/statuslines.js +0 -159
- package/src/data/statuslines.ts +0 -188
- package/src/index.js +0 -4
- package/src/index.ts +0 -5
- package/src/main.js +0 -170
- package/src/prerunner/index.js +0 -252
- package/src/services/alias-settings.js +0 -51
- package/src/services/alias-shell-writer.js +0 -1018
- package/src/services/alias-store.js +0 -129
- package/src/services/claude-cli.js +0 -189
- package/src/services/claude-runner.js +0 -28
- package/src/services/claude-settings.js +0 -1223
- package/src/services/gitignore-detector.js +0 -155
- package/src/services/gitignore-fixer.js +0 -231
- package/src/services/gitignore-prerun.js +0 -46
- package/src/services/gitignore-resolver.js +0 -143
- package/src/services/gitignore-service.js +0 -117
- package/src/services/local-marketplace.js +0 -339
- package/src/services/marketplace-fetcher.js +0 -96
- package/src/services/marketplace-sync.js +0 -94
- package/src/services/mcp-registry.js +0 -87
- package/src/services/plugin-manager.js +0 -473
- package/src/services/plugin-mcp-config.js +0 -177
- package/src/services/plugin-setup.js +0 -499
- package/src/services/plugin-version-check.js +0 -262
- package/src/services/profiles.js +0 -161
- package/src/services/settings-manager.js +0 -243
- package/src/services/skills-manager.js +0 -393
- package/src/services/skillsmp-client.js +0 -87
- package/src/services/update-cache.js +0 -52
- package/src/services/version-check.js +0 -99
- package/src/types/gitignore.js +0 -6
- package/src/types/index.js +0 -1
- package/src/ui/App.js +0 -355
- package/src/ui/adapters/pluginsAdapter.js +0 -139
- package/src/ui/adapters/settingsAdapter.js +0 -111
- package/src/ui/adapters/skillsAdapter.js +0 -154
- package/src/ui/components/CategoryHeader.js +0 -9
- package/src/ui/components/EmptyFilterState.js +0 -4
- package/src/ui/components/FlagDetailEditor.js +0 -0
- package/src/ui/components/ScopeIndicator.js +0 -30
- package/src/ui/components/ScrollableList.js +0 -36
- package/src/ui/components/SearchInput.js +0 -19
- package/src/ui/components/StyledText.js +0 -39
- package/src/ui/components/TabBar.js +0 -19
- package/src/ui/components/layout/FooterHints.js +0 -29
- package/src/ui/components/layout/Panel.js +0 -6
- package/src/ui/components/layout/ProgressBar.js +0 -14
- package/src/ui/components/layout/ScopeTabs.js +0 -6
- package/src/ui/components/layout/ScreenLayout.js +0 -16
- package/src/ui/components/layout/index.js +0 -5
- package/src/ui/components/modals/ConfirmModal.js +0 -14
- package/src/ui/components/modals/InputModal.js +0 -5
- package/src/ui/components/modals/LoadingModal.js +0 -17
- package/src/ui/components/modals/MessageModal.js +0 -16
- package/src/ui/components/modals/ModalContainer.js +0 -137
- package/src/ui/components/modals/SelectModal.js +0 -18
- package/src/ui/components/modals/VersionMismatchModal.js +0 -36
- package/src/ui/components/modals/index.js +0 -6
- package/src/ui/components/primitives/ActionHints.js +0 -13
- package/src/ui/components/primitives/DetailSection.js +0 -7
- package/src/ui/components/primitives/KeyValueLine.js +0 -8
- package/src/ui/components/primitives/ListCategoryRow.js +0 -8
- package/src/ui/components/primitives/MetaText.js +0 -8
- package/src/ui/components/primitives/ScopeDetail.js +0 -32
- package/src/ui/components/primitives/ScopeSquares.js +0 -11
- package/src/ui/components/primitives/SelectableRow.js +0 -5
- package/src/ui/components/primitives/index.js +0 -8
- package/src/ui/hooks/index.js +0 -4
- package/src/ui/hooks/useAsyncData.js +0 -77
- package/src/ui/hooks/useGitignoreModal.js +0 -74
- package/src/ui/hooks/useKeyboard.js +0 -13
- package/src/ui/hooks/useKeyboardHandler.js +0 -39
- package/src/ui/hooks/useMismatchModal.js +0 -77
- package/src/ui/registry.js +0 -1
- package/src/ui/renderers/cliToolRenderers.js +0 -54
- package/src/ui/renderers/gitignoreRenderers.js +0 -46
- package/src/ui/renderers/mcpRenderers.js +0 -26
- package/src/ui/renderers/pluginRenderers.js +0 -124
- package/src/ui/renderers/profileRenderers.js +0 -177
- package/src/ui/renderers/settingsRenderers.js +0 -73
- package/src/ui/renderers/skillRenderers.js +0 -138
- package/src/ui/screens/AliasScreen.js +0 -1111
- package/src/ui/screens/CliToolsScreen.js +0 -338
- package/src/ui/screens/EnvVarsScreen.js +0 -152
- package/src/ui/screens/GitignoreScreen.js +0 -328
- package/src/ui/screens/McpRegistryScreen.js +0 -238
- package/src/ui/screens/McpScreen.js +0 -176
- package/src/ui/screens/ModelSelectorScreen.js +0 -296
- package/src/ui/screens/ModelSelectorScreen.tsx +0 -444
- package/src/ui/screens/PluginsScreen.js +0 -769
- package/src/ui/screens/ProfilesScreen.js +0 -298
- package/src/ui/screens/SkillsScreen.js +0 -549
- package/src/ui/screens/StatusLineScreen.js +0 -206
- package/src/ui/screens/StatusLineScreen.tsx +0 -416
- package/src/ui/screens/index.js +0 -10
- package/src/ui/state/AnimationContext.js +0 -34
- package/src/ui/state/AppContext.js +0 -162
- package/src/ui/state/DimensionsContext.js +0 -71
- package/src/ui/state/reducer.js +0 -547
- package/src/ui/state/types.js +0 -1
- package/src/ui/theme.js +0 -47
- package/src/utils/clipboard.js +0 -56
- package/src/utils/command-utils.js +0 -19
- package/src/utils/fuzzy-search.js +0 -101
- 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
|
-
}
|