@mcptoolshop/backpropagate 1.0.4 → 1.0.5
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/backpropagate.js +189 -11
- package/package.json +1 -1
package/bin/backpropagate.js
CHANGED
|
@@ -1,20 +1,198 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const { spawnSync } = require("child_process");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const os = require("os");
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Linux: bootstrap a private venv and run backpropagate from PyPI.
|
|
11
|
+
//
|
|
12
|
+
// PyTorch's libtorch_cpu.so is ~1.5 GB on x86_64 Linux. A PyInstaller
|
|
13
|
+
// --onefile binary lands at 4.16 GB — 2x GitHub's release asset limit.
|
|
14
|
+
// macOS/Windows don't hit this because Accelerate is system-provided and
|
|
15
|
+
// Windows torch is smaller.
|
|
16
|
+
//
|
|
17
|
+
// Instead of shipping a broken giant binary, we bootstrap a managed venv
|
|
18
|
+
// on first run and exec the pip-installed CLI from there.
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
6
21
|
if (process.platform === "linux") {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
const TOOL = "backpropagate";
|
|
23
|
+
const VERSION = "1.0.4";
|
|
24
|
+
|
|
25
|
+
// XDG-compliant install root
|
|
26
|
+
const dataHome = process.env.XDG_DATA_HOME
|
|
27
|
+
|| path.join(os.homedir(), ".local", "share");
|
|
28
|
+
const installRoot = process.env.BACKPROPAGATE_BOOTSTRAP_ROOT
|
|
29
|
+
|| path.join(dataHome, TOOL);
|
|
30
|
+
const venvDir = path.join(installRoot, "venv");
|
|
31
|
+
const metaPath = path.join(installRoot, "install.json");
|
|
32
|
+
const venvBin = path.join(venvDir, "bin", TOOL);
|
|
33
|
+
const venvPython = path.join(venvDir, "bin", "python3");
|
|
34
|
+
|
|
35
|
+
// -- helpers --------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
function findPython() {
|
|
38
|
+
// Prefer python3, fall back to python (if it's 3.x)
|
|
39
|
+
for (const cmd of ["python3", "python"]) {
|
|
40
|
+
const r = spawnSync(cmd, ["--version"], {
|
|
41
|
+
encoding: "utf8",
|
|
42
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
43
|
+
});
|
|
44
|
+
if (r.status === 0) {
|
|
45
|
+
const ver = (r.stdout || r.stderr || "").trim();
|
|
46
|
+
const match = ver.match(/Python\s+(\d+)\.(\d+)/);
|
|
47
|
+
if (match && parseInt(match[1], 10) >= 3 && parseInt(match[2], 10) >= 10) {
|
|
48
|
+
return cmd;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readMeta() {
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(fs.readFileSync(metaPath, "utf8"));
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function writeMeta(obj) {
|
|
64
|
+
fs.mkdirSync(installRoot, { recursive: true });
|
|
65
|
+
fs.writeFileSync(metaPath, JSON.stringify(obj, null, 2) + "\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function fail(message, hint) {
|
|
69
|
+
process.stderr.write(`\n${TOOL}: ${message}\n`);
|
|
70
|
+
if (hint) process.stderr.write(`\n${hint}\n`);
|
|
71
|
+
process.stderr.write("\n");
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// -- bootstrap ------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
function bootstrap() {
|
|
78
|
+
const forceReinstall = process.env.BACKPROPAGATE_FORCE_REINSTALL === "1";
|
|
79
|
+
const meta = readMeta();
|
|
80
|
+
|
|
81
|
+
const versionMatch = meta && meta.version === VERSION;
|
|
82
|
+
const binaryPresent = fs.existsSync(venvBin);
|
|
83
|
+
|
|
84
|
+
if (versionMatch && binaryPresent && !forceReinstall) {
|
|
85
|
+
return; // fast path — venv exists, version matches, no forced reinstall
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Decide what to tell the user
|
|
89
|
+
if (forceReinstall) {
|
|
90
|
+
process.stderr.write(`Forced reinstall requested (BACKPROPAGATE_FORCE_REINSTALL=1).\n`);
|
|
91
|
+
} else if (meta && !versionMatch) {
|
|
92
|
+
process.stderr.write(
|
|
93
|
+
`Updating ${TOOL}: ${meta.version} -> ${VERSION}\n`
|
|
94
|
+
);
|
|
95
|
+
} else if (meta && !binaryPresent) {
|
|
96
|
+
process.stderr.write(
|
|
97
|
+
`Repairing ${TOOL}: binary missing, reinstalling ${VERSION}...\n`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Find system Python
|
|
102
|
+
const python = findPython();
|
|
103
|
+
if (!python) {
|
|
104
|
+
fail(
|
|
105
|
+
"Python 3.10+ is required but not found.",
|
|
106
|
+
"Install Python and try again:\n" +
|
|
107
|
+
" Ubuntu/Debian: sudo apt install python3 python3-venv\n" +
|
|
108
|
+
" Fedora/RHEL: sudo dnf install python3\n" +
|
|
109
|
+
" Arch: sudo pacman -S python\n" +
|
|
110
|
+
" Or use pyenv: https://github.com/pyenv/pyenv"
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Recreate venv on force-reinstall (nuke corrupted state)
|
|
115
|
+
if (forceReinstall && fs.existsSync(venvDir)) {
|
|
116
|
+
process.stderr.write("Removing existing venv...\n");
|
|
117
|
+
fs.rmSync(venvDir, { recursive: true, force: true });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Create venv if missing
|
|
121
|
+
if (!fs.existsSync(venvPython)) {
|
|
122
|
+
if (!meta) {
|
|
123
|
+
process.stderr.write(
|
|
124
|
+
`First run on Linux: setting up local Python environment for ${TOOL}...\n`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
fs.mkdirSync(installRoot, { recursive: true });
|
|
128
|
+
const venvResult = spawnSync(python, ["-m", "venv", venvDir], {
|
|
129
|
+
stdio: "inherit",
|
|
130
|
+
});
|
|
131
|
+
if (venvResult.status !== 0) {
|
|
132
|
+
fail(
|
|
133
|
+
"Failed to create Python virtual environment.",
|
|
134
|
+
"The venv module may be missing. Try:\n" +
|
|
135
|
+
" Ubuntu/Debian: sudo apt install python3-venv\n" +
|
|
136
|
+
" Fedora/RHEL: sudo dnf install python3-libs"
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Install or upgrade backpropagate
|
|
142
|
+
// --force-reinstall needed for repair (binary missing but pip metadata intact)
|
|
143
|
+
// and force-reinstall (nuked venv rebuilt). Without it, pip sees the same
|
|
144
|
+
// version in metadata and skips recreating the entrypoint script.
|
|
145
|
+
const needsForce = !binaryPresent || forceReinstall;
|
|
146
|
+
const pipArgs = ["-m", "pip", "install", "--quiet", "--upgrade"];
|
|
147
|
+
if (needsForce) pipArgs.push("--force-reinstall");
|
|
148
|
+
pipArgs.push(`${TOOL}==${VERSION}`);
|
|
149
|
+
|
|
150
|
+
process.stderr.write(`Installing ${TOOL} ${VERSION}${needsForce ? " (force)" : ""}...\n`);
|
|
151
|
+
const pipResult = spawnSync(venvPython, pipArgs, { stdio: "inherit" });
|
|
152
|
+
if (pipResult.status !== 0) {
|
|
153
|
+
fail(
|
|
154
|
+
`pip install failed (exit ${pipResult.status}).`,
|
|
155
|
+
"Check your network connection and try again.\n" +
|
|
156
|
+
"You can also install manually:\n" +
|
|
157
|
+
` pip install ${TOOL}==${VERSION}`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Verify the binary actually exists after install
|
|
162
|
+
if (!fs.existsSync(venvBin)) {
|
|
163
|
+
fail(
|
|
164
|
+
`Installation completed but ${TOOL} binary not found at expected path.`,
|
|
165
|
+
`Expected: ${venvBin}\n` +
|
|
166
|
+
"Try installing manually:\n" +
|
|
167
|
+
` pipx install ${TOOL}`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Record metadata
|
|
172
|
+
writeMeta({
|
|
173
|
+
version: VERSION,
|
|
174
|
+
installedAt: new Date().toISOString(),
|
|
175
|
+
python,
|
|
176
|
+
venvDir,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
process.stderr.write("Ready.\n");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// -- exec -----------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
bootstrap();
|
|
185
|
+
const result = spawnSync(venvBin, process.argv.slice(2), { stdio: "inherit" });
|
|
186
|
+
if (result.error) {
|
|
187
|
+
fail(`Failed to execute ${TOOL}: ${result.error.message}`);
|
|
188
|
+
}
|
|
189
|
+
process.exit(result.status ?? 1);
|
|
15
190
|
}
|
|
16
191
|
|
|
17
|
-
//
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// macOS + Windows: download and launch prebuilt binary via npm-launcher
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
|
|
18
196
|
process.env.MCPTOOLSHOP_LAUNCH_CONFIG = JSON.stringify({
|
|
19
197
|
toolName: "backpropagate",
|
|
20
198
|
owner: "mcp-tool-shop-org",
|