@abacus-ai/cli 2.5.5-canary.8 → 2.6.0-canary.10
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/package.json +1 -1
- package/scripts/npm-wrapper/install.cjs +126 -41
- package/scripts/npm-wrapper/shim.cjs +85 -22
package/package.json
CHANGED
|
@@ -20,21 +20,103 @@ function detectInstallPm() {
|
|
|
20
20
|
return "npm";
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// glibc builds report a runtime glibc version; musl builds do not.
|
|
24
|
+
function isMuslLinux() {
|
|
25
|
+
if (process.platform !== "linux") return false;
|
|
26
|
+
try {
|
|
27
|
+
return !process.report.getReport().header.glibcVersionRuntime;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Native CPU arch — corrected for emulation so we match what install.sh/install.ps1
|
|
34
|
+
// resolve. process.arch reports x64 for a Rosetta-translated process on Apple Silicon
|
|
35
|
+
// and for an x64-emulated process on Windows ARM64; both should get the arm64 build.
|
|
36
|
+
function detectArch() {
|
|
37
|
+
let arch = process.arch;
|
|
38
|
+
if (arch === "x64" && process.platform === "darwin") {
|
|
39
|
+
try {
|
|
40
|
+
if (
|
|
41
|
+
execFileSync("sysctl", ["-n", "sysctl.proc_translated"], { encoding: "utf8" }).trim() ===
|
|
42
|
+
"1"
|
|
43
|
+
)
|
|
44
|
+
arch = "arm64";
|
|
45
|
+
} catch {}
|
|
46
|
+
}
|
|
47
|
+
if (arch === "x64" && process.platform === "win32") {
|
|
48
|
+
try {
|
|
49
|
+
const out = execFileSync(
|
|
50
|
+
"reg",
|
|
51
|
+
[
|
|
52
|
+
"query",
|
|
53
|
+
"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment",
|
|
54
|
+
"/v",
|
|
55
|
+
"PROCESSOR_ARCHITECTURE",
|
|
56
|
+
],
|
|
57
|
+
{ encoding: "utf8" },
|
|
58
|
+
);
|
|
59
|
+
if (/\bARM64\b/i.test(out)) arch = "arm64";
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
return arch;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// x64 CPUs without AVX2 need the -baseline build.
|
|
66
|
+
function isBaselineX64(arch) {
|
|
67
|
+
if (arch !== "x64") return false;
|
|
68
|
+
try {
|
|
69
|
+
if (process.platform === "linux") {
|
|
70
|
+
return !fs.readFileSync("/proc/cpuinfo", "utf8").includes("avx2");
|
|
71
|
+
}
|
|
72
|
+
if (process.platform === "darwin") {
|
|
73
|
+
return !/avx2/i.test(
|
|
74
|
+
execFileSync("sysctl", ["-n", "machdep.cpu.leaf7_features"], { encoding: "utf8" }),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
if (process.platform === "win32") {
|
|
78
|
+
// PF_AVX2_INSTRUCTIONS_AVAILABLE = 40
|
|
79
|
+
const out = execFileSync(
|
|
80
|
+
"powershell.exe",
|
|
81
|
+
[
|
|
82
|
+
"-NoProfile",
|
|
83
|
+
"-NonInteractive",
|
|
84
|
+
"-Command",
|
|
85
|
+
"[int]((Add-Type -PassThru -Name AbacusCpu -Namespace Win32 -MemberDefinition '[DllImport(\"kernel32.dll\")] public static extern bool IsProcessorFeaturePresent(int f);')::IsProcessorFeaturePresent(40))",
|
|
86
|
+
],
|
|
87
|
+
{ encoding: "utf8" },
|
|
88
|
+
);
|
|
89
|
+
return out.trim() === "0";
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
return false; // detection failed → assume modern CPU
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// -musl wins over -baseline: a glibc binary won't load on musl, and there's no
|
|
98
|
+
// combined artifact (a non-AVX2 musl host is an unbuilt combo).
|
|
99
|
+
function variantSuffix(arch) {
|
|
100
|
+
if (isMuslLinux()) return "-musl";
|
|
101
|
+
if (isBaselineX64(arch)) return "-baseline";
|
|
102
|
+
return "";
|
|
103
|
+
}
|
|
104
|
+
|
|
23
105
|
function detectPlatform() {
|
|
24
|
-
const arch =
|
|
106
|
+
const arch = detectArch();
|
|
25
107
|
if (arch !== "x64" && arch !== "arm64") {
|
|
26
108
|
throw new Error(`Unsupported architecture: ${arch}`);
|
|
27
109
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
case "linux":
|
|
32
|
-
return { target: `linux-${arch}`, ext: "tar.gz", binaryName: "abacusai" };
|
|
33
|
-
case "win32":
|
|
34
|
-
return { target: `win32-${arch}`, ext: "zip", binaryName: "abacusai.exe" };
|
|
35
|
-
default:
|
|
36
|
-
throw new Error(`Unsupported operating system: ${process.platform}`);
|
|
110
|
+
const platform = process.platform;
|
|
111
|
+
if (platform !== "darwin" && platform !== "linux" && platform !== "win32") {
|
|
112
|
+
throw new Error(`Unsupported operating system: ${platform}`);
|
|
37
113
|
}
|
|
114
|
+
const win = platform === "win32";
|
|
115
|
+
return {
|
|
116
|
+
target: `${platform}-${arch}${variantSuffix(arch)}`,
|
|
117
|
+
ext: win ? "zip" : "tar.gz",
|
|
118
|
+
binaryName: win ? "abacusai.exe" : "abacusai",
|
|
119
|
+
};
|
|
38
120
|
}
|
|
39
121
|
|
|
40
122
|
function download(url, dest) {
|
|
@@ -86,48 +168,52 @@ function extract(archivePath, destDir, ext) {
|
|
|
86
168
|
}
|
|
87
169
|
}
|
|
88
170
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
}
|
|
171
|
+
// Per-version cache under the user's home, shared across npm installs and every
|
|
172
|
+
// npx/bunx run — so the same version is downloaded once, and older versions stay
|
|
173
|
+
// available to roll back to if a release fails to launch.
|
|
174
|
+
const VERSIONS_DIR = path.join(os.homedir(), ".abacusai", "versions");
|
|
175
|
+
const MAX_CACHED_VERSIONS = 3;
|
|
96
176
|
|
|
97
|
-
|
|
177
|
+
// Keep the newest few version dirs (by mtime); the cache is ~80 MB each.
|
|
178
|
+
function pruneOldVersions(keep) {
|
|
179
|
+
let dirs;
|
|
98
180
|
try {
|
|
99
|
-
fs.
|
|
181
|
+
dirs = fs.readdirSync(VERSIONS_DIR);
|
|
182
|
+
} catch {
|
|
100
183
|
return;
|
|
101
|
-
} catch (err) {
|
|
102
|
-
if (process.platform !== "win32") throw err;
|
|
103
184
|
}
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
185
|
+
const scored = [];
|
|
186
|
+
for (const v of dirs) {
|
|
187
|
+
try {
|
|
188
|
+
scored.push({ v, mtime: fs.statSync(path.join(VERSIONS_DIR, v)).mtimeMs });
|
|
189
|
+
} catch {}
|
|
190
|
+
}
|
|
191
|
+
scored.sort((a, b) => b.mtime - a.mtime);
|
|
192
|
+
for (const { v } of scored.slice(MAX_CACHED_VERSIONS)) {
|
|
193
|
+
if (v === keep) continue;
|
|
194
|
+
try {
|
|
195
|
+
fs.rmSync(path.join(VERSIONS_DIR, v), { recursive: true, force: true });
|
|
196
|
+
} catch {}
|
|
197
|
+
}
|
|
112
198
|
}
|
|
113
199
|
|
|
114
200
|
async function main() {
|
|
115
201
|
const { target, ext, binaryName } = detectPlatform();
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const
|
|
202
|
+
const version = process.env.ABACUSAI_TARGET_VERSION || VERSION;
|
|
203
|
+
const versionDir = path.join(VERSIONS_DIR, version);
|
|
204
|
+
const targetPath = path.join(versionDir, binaryName);
|
|
119
205
|
|
|
120
|
-
if (
|
|
206
|
+
if (fs.existsSync(targetPath)) return;
|
|
121
207
|
|
|
122
|
-
fs.mkdirSync(
|
|
208
|
+
fs.mkdirSync(versionDir, { recursive: true });
|
|
123
209
|
|
|
124
210
|
const archiveName = `abacusai-${target}.${ext}`;
|
|
125
|
-
const url = `${BASE_URL}/${
|
|
211
|
+
const url = `${BASE_URL}/${version}/${archiveName}`;
|
|
126
212
|
|
|
127
213
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "abacusai-install-"));
|
|
128
214
|
const archivePath = path.join(tmpDir, archiveName);
|
|
129
215
|
|
|
130
|
-
process.stdout.write(`abacusai: downloading v${
|
|
216
|
+
process.stdout.write(`abacusai: downloading v${version} for ${target}...\n`);
|
|
131
217
|
|
|
132
218
|
try {
|
|
133
219
|
await download(url, archivePath);
|
|
@@ -137,13 +223,12 @@ async function main() {
|
|
|
137
223
|
const stagedPath = `${targetPath}.new`;
|
|
138
224
|
fs.copyFileSync(sourceBinary, stagedPath);
|
|
139
225
|
if (process.platform !== "win32") fs.chmodSync(stagedPath, 0o755);
|
|
226
|
+
fs.renameSync(stagedPath, targetPath); // atomic within the version dir
|
|
140
227
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
fs.writeFileSync(stampPath, VERSION);
|
|
144
|
-
fs.writeFileSync(path.join(binDir, ".install-source"), detectInstallPm());
|
|
228
|
+
fs.writeFileSync(path.join(versionDir, ".install-source"), detectInstallPm());
|
|
229
|
+
pruneOldVersions(version);
|
|
145
230
|
|
|
146
|
-
process.stdout.write(`abacusai: installed v${
|
|
231
|
+
process.stdout.write(`abacusai: installed v${version} to ${targetPath}\n`);
|
|
147
232
|
} finally {
|
|
148
233
|
try {
|
|
149
234
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
@@ -2,44 +2,107 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
const fs = require("node:fs");
|
|
5
|
+
const os = require("node:os");
|
|
5
6
|
const path = require("node:path");
|
|
6
7
|
const { spawnSync } = require("node:child_process");
|
|
7
8
|
|
|
8
9
|
const PKG_ROOT = path.resolve(__dirname, "..", "..");
|
|
9
|
-
const
|
|
10
|
+
const VERSION = require(path.join(PKG_ROOT, "package.json")).version;
|
|
10
11
|
const BINARY_NAME = process.platform === "win32" ? "abacusai.exe" : "abacusai";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
|
|
13
|
+
// Binaries are cached per-version under the user's home, NOT inside the package
|
|
14
|
+
// dir — so ephemeral runners (npx/bunx) that recreate the package each run still
|
|
15
|
+
// reuse the download, and so older versions remain available to roll back to.
|
|
16
|
+
const VERSIONS_DIR = path.join(os.homedir(), ".abacusai", "versions");
|
|
17
|
+
const binaryFor = (version) => path.join(VERSIONS_DIR, version, BINARY_NAME);
|
|
18
|
+
|
|
19
|
+
// A binary that dies with one of these never actually ran — wrong arch, missing
|
|
20
|
+
// libc, truncated download. Distinct from a normal non-zero exit.
|
|
21
|
+
const FATAL_SIGNALS = new Set(["SIGILL", "SIGSEGV", "SIGABRT", "SIGTRAP", "SIGBUS"]);
|
|
22
|
+
const LAUNCH_FAILURE_MS = 3000;
|
|
21
23
|
|
|
22
24
|
function readInstallSource() {
|
|
23
25
|
try {
|
|
24
|
-
return
|
|
26
|
+
return (
|
|
27
|
+
fs.readFileSync(path.join(VERSIONS_DIR, VERSION, ".install-source"), "utf8").trim() || "npm"
|
|
28
|
+
);
|
|
25
29
|
} catch {
|
|
26
30
|
return "npm";
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
function ensureBinary(version) {
|
|
35
|
+
const target = binaryFor(version);
|
|
36
|
+
if (fs.existsSync(target)) return target;
|
|
37
|
+
spawnSync(process.execPath, [path.join(__dirname, "install.cjs")], {
|
|
38
|
+
stdio: "inherit",
|
|
39
|
+
env: { ...process.env, ABACUSAI_TARGET_VERSION: version },
|
|
40
|
+
});
|
|
41
|
+
return fs.existsSync(target) ? target : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Newest cached binary other than `exclude` (by mtime), to roll back to.
|
|
45
|
+
function newestCachedFallback(exclude) {
|
|
46
|
+
let versions;
|
|
47
|
+
try {
|
|
48
|
+
versions = fs.readdirSync(VERSIONS_DIR);
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
let best = null;
|
|
53
|
+
for (const v of versions) {
|
|
54
|
+
if (v === exclude) continue;
|
|
55
|
+
const bin = binaryFor(v);
|
|
56
|
+
let mtime;
|
|
57
|
+
try {
|
|
58
|
+
mtime = fs.statSync(bin).mtimeMs;
|
|
59
|
+
} catch {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (!best || mtime > best.mtime) best = { version: v, bin, mtime };
|
|
63
|
+
}
|
|
64
|
+
return best;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function run(binPath) {
|
|
68
|
+
const start = Date.now();
|
|
69
|
+
const res = spawnSync(binPath, process.argv.slice(2), {
|
|
70
|
+
stdio: "inherit",
|
|
71
|
+
env: { ...process.env, ABACUSAI_INSTALL_SOURCE: readInstallSource() },
|
|
72
|
+
});
|
|
73
|
+
const launchFailed =
|
|
74
|
+
!!res.error ||
|
|
75
|
+
(!!res.signal && FATAL_SIGNALS.has(res.signal) && Date.now() - start < LAUNCH_FAILURE_MS);
|
|
76
|
+
return { res, launchFailed };
|
|
77
|
+
}
|
|
34
78
|
|
|
35
|
-
|
|
36
|
-
|
|
79
|
+
const binPath = ensureBinary(VERSION);
|
|
80
|
+
if (!binPath) {
|
|
81
|
+
process.stderr.write(`abacusai: failed to install native binary for v${VERSION}\n`);
|
|
37
82
|
process.exit(1);
|
|
38
83
|
}
|
|
39
84
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
85
|
+
let { res, launchFailed } = run(binPath);
|
|
86
|
+
|
|
87
|
+
// A corrupt/incompatible release must not brick the CLI: if the binary couldn't
|
|
88
|
+
// launch, fall back to the newest other cached version so the user keeps working
|
|
89
|
+
// (and can update past the bad release).
|
|
90
|
+
if (launchFailed) {
|
|
91
|
+
const fallback = newestCachedFallback(VERSION);
|
|
92
|
+
if (fallback) {
|
|
93
|
+
process.stderr.write(
|
|
94
|
+
`abacusai: v${VERSION} failed to launch — falling back to cached v${fallback.version}.\n`,
|
|
95
|
+
);
|
|
96
|
+
({ res } = run(fallback.bin));
|
|
97
|
+
}
|
|
43
98
|
}
|
|
44
99
|
|
|
45
|
-
|
|
100
|
+
if (res.error) {
|
|
101
|
+
process.stderr.write(`abacusai: failed to launch binary: ${res.error.message}\n`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
if (res.signal) {
|
|
105
|
+
process.kill(process.pid, res.signal);
|
|
106
|
+
} else {
|
|
107
|
+
process.exit(res.status ?? 0);
|
|
108
|
+
}
|