@tokenade/cli 0.8.12 → 0.8.13
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/tokenade.js +27 -8
- package/install.js +27 -46
- package/package.json +11 -2
- package/platform.js +97 -0
package/bin/tokenade.js
CHANGED
|
@@ -3,16 +3,35 @@
|
|
|
3
3
|
const { spawnSync } = require("node:child_process");
|
|
4
4
|
const path = require("node:path");
|
|
5
5
|
const fs = require("node:fs");
|
|
6
|
+
const { platformPackageName, binaryName } = require("../platform.js");
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
// Resolve the binary, first hit wins (see platform.js for the two delivery
|
|
9
|
+
// paths): (1) the matching per-platform optionalDependency from the registry,
|
|
10
|
+
// (2) ./vendor, where install.js's download fallback drops it.
|
|
11
|
+
function resolveBinary() {
|
|
12
|
+
const name = binaryName();
|
|
13
|
+
// 1. Platform sub-package. A skipped optional dep throws MODULE_NOT_FOUND;
|
|
14
|
+
// resolve its package.json so we don't depend on a "main" entry.
|
|
15
|
+
try {
|
|
16
|
+
const pkgJson = require.resolve(`${platformPackageName()}/package.json`);
|
|
17
|
+
const bin = path.join(path.dirname(pkgJson), name);
|
|
18
|
+
if (fs.existsSync(bin)) return bin;
|
|
19
|
+
} catch {
|
|
20
|
+
// optional dep absent — fall through to the vendored copy.
|
|
21
|
+
}
|
|
22
|
+
// 2. Downloaded fallback.
|
|
23
|
+
const vendored = path.join(__dirname, "..", "vendor", name);
|
|
24
|
+
if (fs.existsSync(vendored)) return vendored;
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const bin = resolveBinary();
|
|
29
|
+
if (!bin) {
|
|
14
30
|
console.error(
|
|
15
|
-
"tokenade: binary not found. Re-run `npm install -g @tokenade/cli`
|
|
31
|
+
"tokenade: binary not found. Re-run `npm install -g @tokenade/cli` " +
|
|
32
|
+
"(the platform binary ships as an optional dependency; a postinstall " +
|
|
33
|
+
"download is the fallback). If you installed with --no-optional or " +
|
|
34
|
+
"--ignore-scripts, reinstall without them.",
|
|
16
35
|
);
|
|
17
36
|
process.exit(1);
|
|
18
37
|
}
|
package/install.js
CHANGED
|
@@ -27,6 +27,11 @@ const http = require("node:http");
|
|
|
27
27
|
const https = require("node:https");
|
|
28
28
|
const crypto = require("node:crypto");
|
|
29
29
|
const { URL } = require("node:url");
|
|
30
|
+
const {
|
|
31
|
+
rustTarget,
|
|
32
|
+
platformPackageName,
|
|
33
|
+
binaryName,
|
|
34
|
+
} = require("./platform.js");
|
|
30
35
|
|
|
31
36
|
const DOWNLOADS_BASE =
|
|
32
37
|
process.env.TOKENADE_DOWNLOADS_BASE || "https://downloads.tokenade.net";
|
|
@@ -38,52 +43,6 @@ const VENDOR = path.join(__dirname, "vendor");
|
|
|
38
43
|
const REQUEST_TIMEOUT_MS = Number(process.env.TOKENADE_HTTP_TIMEOUT_MS) || 30000;
|
|
39
44
|
const MAX_ATTEMPTS = 4;
|
|
40
45
|
|
|
41
|
-
// Is this Linux userland musl (Alpine, Void-musl, …) rather than glibc?
|
|
42
|
-
// A glibc node reports its runtime glibc version in the process report header;
|
|
43
|
-
// a musl node does not. We fall back to looking for the musl loader on disk.
|
|
44
|
-
// When genuinely unsure we return false (glibc) — that preserves the historical
|
|
45
|
-
// x86_64-gnu mapping for the overwhelming glibc majority, so a detection miss is
|
|
46
|
-
// never worse than the status quo.
|
|
47
|
-
function isMuslLinux() {
|
|
48
|
-
if (process.platform !== "linux") return false;
|
|
49
|
-
try {
|
|
50
|
-
const header = process.report && process.report.getReport().header;
|
|
51
|
-
if (header && typeof header.glibcVersionRuntime === "string") return false;
|
|
52
|
-
if (header && "glibcVersionRuntime" in header) {
|
|
53
|
-
// key present but not a string ⇒ no glibc runtime ⇒ musl
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
} catch {
|
|
57
|
-
// fall through to the on-disk probe
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
return fs
|
|
61
|
-
.readdirSync("/lib")
|
|
62
|
-
.some((f) => f.startsWith("ld-musl-"));
|
|
63
|
-
} catch {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function rustTarget() {
|
|
69
|
-
const p = process.platform;
|
|
70
|
-
const a = process.arch;
|
|
71
|
-
if (p === "darwin")
|
|
72
|
-
return a === "arm64" ? "aarch64-apple-darwin" : "x86_64-apple-darwin";
|
|
73
|
-
if (p === "linux") {
|
|
74
|
-
if (a === "arm64") return "aarch64-unknown-linux-musl";
|
|
75
|
-
// x86_64: a fully-static musl build runs on glibc too, but musl's DNS
|
|
76
|
-
// resolver ignores /etc/nsswitch.conf — so we only hand musl to machines
|
|
77
|
-
// whose libc is already musl (Alpine et al.), and keep the proven glibc
|
|
78
|
-
// build as the default everywhere else.
|
|
79
|
-
return isMuslLinux()
|
|
80
|
-
? "x86_64-unknown-linux-musl"
|
|
81
|
-
: "x86_64-unknown-linux-gnu";
|
|
82
|
-
}
|
|
83
|
-
if (p === "win32") return "x86_64-pc-windows-gnu";
|
|
84
|
-
throw new Error(`unsupported platform: ${p}/${a}`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
46
|
// Resolve the proxy that applies to `targetUrl`, honouring the standard
|
|
88
47
|
// HTTPS_PROXY / HTTP_PROXY / ALL_PROXY and NO_PROXY env vars (both cases).
|
|
89
48
|
// Returns null when no proxy applies — including when NO_PROXY matches.
|
|
@@ -260,6 +219,28 @@ async function downloadTo(url, dest) {
|
|
|
260
219
|
|
|
261
220
|
async function main() {
|
|
262
221
|
const target = rustTarget();
|
|
222
|
+
|
|
223
|
+
// Fast path: the binary is already here, so the download is pure waste.
|
|
224
|
+
// It arrives one of two ways — the matching per-platform optionalDependency
|
|
225
|
+
// npm just installed from the registry, or a ./vendor copy from a previous
|
|
226
|
+
// run. Either way postinstall is a clean no-op. Crucially this returns
|
|
227
|
+
// BEFORE any network call, so an unreachable downloads.tokenade.net (blocked
|
|
228
|
+
// corporate proxy, offline sandbox) can never fail an install whose binary
|
|
229
|
+
// the registry already delivered.
|
|
230
|
+
try {
|
|
231
|
+
require.resolve(`${platformPackageName(target)}/package.json`);
|
|
232
|
+
console.log(
|
|
233
|
+
`✓ tokenade provided by ${platformPackageName(target)} (${target}) — no download needed.`,
|
|
234
|
+
);
|
|
235
|
+
return;
|
|
236
|
+
} catch {
|
|
237
|
+
// optional dep absent (skipped or no registry build) — try ./vendor.
|
|
238
|
+
}
|
|
239
|
+
if (fs.existsSync(path.join(VENDOR, binaryName()))) {
|
|
240
|
+
console.log(`✓ tokenade already vendored (${target}) — no download needed.`);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
263
244
|
const manifest = await fetchJson(MANIFEST_URL);
|
|
264
245
|
const entry = manifest.targets && manifest.targets[target];
|
|
265
246
|
if (!entry || !entry.url || !entry.sha256) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenade/cli",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "Tokenade
|
|
3
|
+
"version": "0.8.13",
|
|
4
|
+
"description": "Tokenade — cut your AI coding agent's token bill. Installs the Tokenade CLI (a local, paid token-reduction tool; activate via your browser).",
|
|
5
5
|
"homepage": "https://tokenade.net",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"bin": {
|
|
@@ -14,8 +14,17 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"tar": "^7.5.16"
|
|
16
16
|
},
|
|
17
|
+
"optionalDependencies": {
|
|
18
|
+
"@tokenade/cli-linux-x64": "0.8.13",
|
|
19
|
+
"@tokenade/cli-linux-x64-musl": "0.8.13",
|
|
20
|
+
"@tokenade/cli-linux-arm64-musl": "0.8.13",
|
|
21
|
+
"@tokenade/cli-darwin-arm64": "0.8.13",
|
|
22
|
+
"@tokenade/cli-darwin-x64": "0.8.13",
|
|
23
|
+
"@tokenade/cli-win32-x64": "0.8.13"
|
|
24
|
+
},
|
|
17
25
|
"files": [
|
|
18
26
|
"bin/",
|
|
27
|
+
"platform.js",
|
|
19
28
|
"install.js",
|
|
20
29
|
"uninstall.js",
|
|
21
30
|
"README.md"
|
package/platform.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* platform.js — the ONE place that maps this machine to a Rust target and to
|
|
4
|
+
* the matching per-platform npm package. Both bin/tokenade.js (runtime resolve)
|
|
5
|
+
* and install.js (download fallback) require it, so the platform detection —
|
|
6
|
+
* the musl probe especially — never drifts between the two.
|
|
7
|
+
*
|
|
8
|
+
* The per-platform packages (@tokenade/cli-<os>-<cpu>[-musl]) carry the actual
|
|
9
|
+
* binary and are listed as optionalDependencies of @tokenade/cli. npm installs
|
|
10
|
+
* ONLY the one whose os/cpu/libc match the host, straight from the registry —
|
|
11
|
+
* the esbuild/swc/biome pattern. install.js's download is a fallback for when
|
|
12
|
+
* those optional deps were skipped (--no-optional / --ignore-scripts) or the
|
|
13
|
+
* registry didn't carry a matching build.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
"use strict";
|
|
17
|
+
|
|
18
|
+
const fs = require("node:fs");
|
|
19
|
+
|
|
20
|
+
// Is this Linux userland musl (Alpine, Void-musl, …) rather than glibc?
|
|
21
|
+
// A glibc node reports its runtime glibc version in the process report header;
|
|
22
|
+
// a musl node does not. We fall back to looking for the musl loader on disk.
|
|
23
|
+
// When genuinely unsure we return false (glibc) — that preserves the historical
|
|
24
|
+
// x86_64-gnu mapping for the overwhelming glibc majority, so a detection miss is
|
|
25
|
+
// never worse than the status quo.
|
|
26
|
+
function isMuslLinux() {
|
|
27
|
+
if (process.platform !== "linux") return false;
|
|
28
|
+
try {
|
|
29
|
+
const header = process.report && process.report.getReport().header;
|
|
30
|
+
if (header && typeof header.glibcVersionRuntime === "string") return false;
|
|
31
|
+
if (header && "glibcVersionRuntime" in header) {
|
|
32
|
+
// key present but not a string ⇒ no glibc runtime ⇒ musl
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
// fall through to the on-disk probe
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
return fs
|
|
40
|
+
.readdirSync("/lib")
|
|
41
|
+
.some((f) => f.startsWith("ld-musl-"));
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function rustTarget() {
|
|
48
|
+
const p = process.platform;
|
|
49
|
+
const a = process.arch;
|
|
50
|
+
if (p === "darwin")
|
|
51
|
+
return a === "arm64" ? "aarch64-apple-darwin" : "x86_64-apple-darwin";
|
|
52
|
+
if (p === "linux") {
|
|
53
|
+
if (a === "arm64") return "aarch64-unknown-linux-musl";
|
|
54
|
+
// x86_64: a fully-static musl build runs on glibc too, but musl's DNS
|
|
55
|
+
// resolver ignores /etc/nsswitch.conf — so we only hand musl to machines
|
|
56
|
+
// whose libc is already musl (Alpine et al.), and keep the proven glibc
|
|
57
|
+
// build as the default everywhere else.
|
|
58
|
+
return isMuslLinux()
|
|
59
|
+
? "x86_64-unknown-linux-musl"
|
|
60
|
+
: "x86_64-unknown-linux-gnu";
|
|
61
|
+
}
|
|
62
|
+
if (p === "win32") return "x86_64-pc-windows-gnu";
|
|
63
|
+
throw new Error(`unsupported platform: ${p}/${a}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Rust target dir (in dist/) → the npm sub-package that carries its binary.
|
|
67
|
+
// Kept in lockstep with the six packages the release script publishes and the
|
|
68
|
+
// optionalDependencies in package.json — one source of truth for the mapping.
|
|
69
|
+
const PACKAGE_BY_TARGET = {
|
|
70
|
+
"x86_64-unknown-linux-gnu": "@tokenade/cli-linux-x64",
|
|
71
|
+
"x86_64-unknown-linux-musl": "@tokenade/cli-linux-x64-musl",
|
|
72
|
+
"aarch64-unknown-linux-musl": "@tokenade/cli-linux-arm64-musl",
|
|
73
|
+
"aarch64-apple-darwin": "@tokenade/cli-darwin-arm64",
|
|
74
|
+
"x86_64-apple-darwin": "@tokenade/cli-darwin-x64",
|
|
75
|
+
"x86_64-pc-windows-gnu": "@tokenade/cli-win32-x64",
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// The per-platform sub-package name for the current host, or the given target.
|
|
79
|
+
function platformPackageName(target) {
|
|
80
|
+
const t = target || rustTarget();
|
|
81
|
+
const pkg = PACKAGE_BY_TARGET[t];
|
|
82
|
+
if (!pkg) throw new Error(`no npm package mapped for target: ${t}`);
|
|
83
|
+
return pkg;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// The binary filename inside a sub-package (and in ./vendor).
|
|
87
|
+
function binaryName() {
|
|
88
|
+
return process.platform === "win32" ? "tokenade.exe" : "tokenade";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
isMuslLinux,
|
|
93
|
+
rustTarget,
|
|
94
|
+
platformPackageName,
|
|
95
|
+
binaryName,
|
|
96
|
+
PACKAGE_BY_TARGET,
|
|
97
|
+
};
|