@tokenade/cli 0.9.2 → 0.9.4
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/README.md +2 -2
- package/install.js +93 -5
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -72,11 +72,11 @@ Every agent gets Tokenade's prompt- and CLI-level features — **code search**,
|
|
|
72
72
|
| **Cursor** | ✅ | — | ✅ | ✅ | ✅ | ✅ | — | ✅ | — |
|
|
73
73
|
| **Grok** | ✅ | — | ✅ | ✅ | ✅ | ✅ | — | ✅ | — |
|
|
74
74
|
| **Droid** | ✅ | — | — | ✅ | ✅ | ✅ | — | ✅ | — |
|
|
75
|
-
| **Aider** |
|
|
75
|
+
| **Aider** | ◐⁶ | — | — | ✅ | ✅ | ✅ | — | ✅ | — |
|
|
76
76
|
| **Windsurf**⁵ | — | — | ✅ | ✅ | ✅ | ◐ | — | ✅ | — |
|
|
77
77
|
| **Antigravity**⁵ | — | — | ✅ | ✅ | ✅ | ◐ | — | ✅ | — |
|
|
78
78
|
|
|
79
|
-
<sub>¹ Batching and lean-output savings apply everywhere; prompt-cache trimming is available on Claude Code today. ² Automatic secret redaction requires both command and file-read coverage. ³ Copilot **CLI** — the VS Code Copilot extension is not covered. ⁴ Inherits full coverage from the Claude Code it runs on. ⁵ MCP-based integration: Tokenade compacts MCP tool outputs; the agent's native command/read/web tools aren't reachable
|
|
79
|
+
<sub>¹ Batching and lean-output savings apply everywhere; prompt-cache trimming is available on Claude Code today. ² Automatic secret redaction requires both command and file-read coverage. ³ Copilot **CLI** — the VS Code Copilot extension is not covered. ⁴ Inherits full coverage from the Claude Code it runs on. ⁵ MCP-based integration: Tokenade compacts MCP tool outputs; the agent's native command/read/web tools aren't reachable. ⁶ **Aider** is wrap-only: command compaction is not automatic — run commands through `tokenade wrap …`, or enable the opt-in PATH shim with `tokenade install --shim`.</sub>
|
|
80
80
|
|
|
81
81
|
### Command-line vs. desktop editions
|
|
82
82
|
|
package/install.js
CHANGED
|
@@ -38,6 +38,71 @@ const DOWNLOADS_BASE =
|
|
|
38
38
|
const MANIFEST_URL = `${DOWNLOADS_BASE}/manifest.json`;
|
|
39
39
|
const VENDOR = path.join(__dirname, "vendor");
|
|
40
40
|
|
|
41
|
+
// Leading-byte format sniff. SHA-256 proves the bytes match what the manifest
|
|
42
|
+
// served — NOT that they are an executable for THIS OS. Catches a truncated
|
|
43
|
+
// download, an HTML error page, a gzip/tar that never extracted, or a
|
|
44
|
+
// wrong-OS binary before we even try to exec it.
|
|
45
|
+
function looksLikeExecutable(head, platform) {
|
|
46
|
+
if (head[0] === 0x23 && head[1] === 0x21) return false; // "#!" script/text
|
|
47
|
+
if (head[0] === 0x3c) return false; // "<" HTML/XML error page
|
|
48
|
+
if (head[0] === 0x1f && head[1] === 0x8b) return false; // gzip (unextracted)
|
|
49
|
+
if (platform === "win32") return head[0] === 0x4d && head[1] === 0x5a; // "MZ" PE
|
|
50
|
+
if (platform === "darwin") {
|
|
51
|
+
const m = head.readUInt32BE(0);
|
|
52
|
+
// Mach-O thin (feedface/feedfacf + byte-swapped) or fat/universal.
|
|
53
|
+
return (
|
|
54
|
+
m === 0xfeedface || m === 0xfeedfacf ||
|
|
55
|
+
m === 0xcefaedfe || m === 0xcffaedfe ||
|
|
56
|
+
m === 0xcafebabe || m === 0xcafebabf
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
// linux / other unix → ELF ("\x7fELF")
|
|
60
|
+
return head[0] === 0x7f && head[1] === 0x45 && head[2] === 0x4c && head[3] === 0x46;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Assert `binPath` is a runnable tokenade binary on THIS machine, or throw a
|
|
64
|
+
// clear, actionable error. Two gates: (1) the format sniff above, (2) a
|
|
65
|
+
// `--version` spawn probe — a wrong-arch Mach-O has valid magic but ENOEXECs
|
|
66
|
+
// when exec'd (no Rosetta), surfacing as a spawn `error`. Mirrors the Rust
|
|
67
|
+
// updater's pre-swap health probe: SHA/signature prove transfer, this proves
|
|
68
|
+
// it RUNS. Refusing loudly here beats leaving an ENOEXEC binary that bricks
|
|
69
|
+
// every later shimmed `node`/hook call (os error 35 fork-bomb, etc.).
|
|
70
|
+
function assertRunnable(binPath, label) {
|
|
71
|
+
if (!fs.existsSync(binPath)) {
|
|
72
|
+
throw new Error(`${label}: binary missing at ${binPath}`);
|
|
73
|
+
}
|
|
74
|
+
const fd = fs.openSync(binPath, "r");
|
|
75
|
+
const head = Buffer.alloc(8);
|
|
76
|
+
let read = 0;
|
|
77
|
+
try {
|
|
78
|
+
read = fs.readSync(fd, head, 0, 8, 0);
|
|
79
|
+
} finally {
|
|
80
|
+
fs.closeSync(fd);
|
|
81
|
+
}
|
|
82
|
+
if (read < 4) throw new Error(`${label}: binary truncated (${read} bytes)`);
|
|
83
|
+
if (!looksLikeExecutable(head, process.platform)) {
|
|
84
|
+
const hex = head.slice(0, 4).toString("hex");
|
|
85
|
+
throw new Error(
|
|
86
|
+
`${label}: not a ${process.platform} executable (leading bytes 0x${hex}) — corrupt or wrong-arch download`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const { spawnSync } = require("node:child_process");
|
|
90
|
+
const r = spawnSync(binPath, ["--version"], { stdio: "ignore", timeout: 15000 });
|
|
91
|
+
if (r.error) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`${label}: does not execute on ${process.platform}/${process.arch} (${r.error.code || r.error.message}) — wrong architecture or corrupt`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (r.signal) {
|
|
97
|
+
throw new Error(`${label}: killed by ${r.signal} on startup — unsigned or corrupt`);
|
|
98
|
+
}
|
|
99
|
+
// It exec'd (the thing we care about). A non-zero `--version` is unexpected
|
|
100
|
+
// but not proof of a broken binary, so warn rather than fail the install.
|
|
101
|
+
if (typeof r.status === "number" && r.status !== 0) {
|
|
102
|
+
console.error(` warn: ${label}: \`--version\` exited ${r.status}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
41
106
|
// Per-request wall clock. A download that stalls past this is treated as a
|
|
42
107
|
// network error and retried, rather than hanging the whole `npm install`.
|
|
43
108
|
const REQUEST_TIMEOUT_MS = Number(process.env.TOKENADE_HTTP_TIMEOUT_MS) || 30000;
|
|
@@ -227,18 +292,35 @@ async function main() {
|
|
|
227
292
|
// BEFORE any network call, so an unreachable downloads.tokenade.net (blocked
|
|
228
293
|
// corporate proxy, offline sandbox) can never fail an install whose binary
|
|
229
294
|
// the registry already delivered.
|
|
295
|
+
let platPkgDir = null;
|
|
230
296
|
try {
|
|
231
|
-
|
|
297
|
+
platPkgDir = path.dirname(
|
|
298
|
+
require.resolve(`${platformPackageName(target)}/package.json`),
|
|
299
|
+
);
|
|
300
|
+
} catch {
|
|
301
|
+
// optional dep absent (skipped or no registry build) — try ./vendor.
|
|
302
|
+
}
|
|
303
|
+
if (platPkgDir) {
|
|
304
|
+
// The launcher prefers the platform package over ./vendor, so a broken
|
|
305
|
+
// one can't be healed by a later download — validate it RUNS here and
|
|
306
|
+
// fail loudly if not. Throw propagates to main().catch (msg + exit 1).
|
|
307
|
+
assertRunnable(path.join(platPkgDir, binaryName()), platformPackageName(target));
|
|
232
308
|
console.log(
|
|
233
309
|
`✓ tokenade provided by ${platformPackageName(target)} (${target}) — no download needed.`,
|
|
234
310
|
);
|
|
235
311
|
return;
|
|
236
|
-
} catch {
|
|
237
|
-
// optional dep absent (skipped or no registry build) — try ./vendor.
|
|
238
312
|
}
|
|
239
313
|
if (fs.existsSync(path.join(VENDOR, binaryName()))) {
|
|
240
|
-
|
|
241
|
-
|
|
314
|
+
try {
|
|
315
|
+
assertRunnable(path.join(VENDOR, binaryName()), "vendored tokenade");
|
|
316
|
+
console.log(`✓ tokenade already vendored (${target}) — no download needed.`);
|
|
317
|
+
return;
|
|
318
|
+
} catch (e) {
|
|
319
|
+
// A previous run left a broken vendor binary (the 0.5.1 ENOEXEC field
|
|
320
|
+
// bug). Drop it and re-download rather than trusting it.
|
|
321
|
+
console.error(` vendored binary unusable (${e.message}); re-downloading.`);
|
|
322
|
+
fs.rmSync(VENDOR, { recursive: true, force: true });
|
|
323
|
+
}
|
|
242
324
|
}
|
|
243
325
|
|
|
244
326
|
const manifest = await fetchJson(MANIFEST_URL);
|
|
@@ -315,6 +397,12 @@ async function main() {
|
|
|
315
397
|
}
|
|
316
398
|
}
|
|
317
399
|
|
|
400
|
+
// Final gate: the freshly downloaded binary must RUN here. SHA-256 matched
|
|
401
|
+
// whatever the manifest served — a wrong-arch or truncated build passes that
|
|
402
|
+
// yet ENOEXECs at runtime, bricking every later shimmed `node`/hook call.
|
|
403
|
+
// Mirrors the Rust updater's pre-swap `--version` probe. Throw → loud fail.
|
|
404
|
+
assertRunnable(path.join(VENDOR, binaryName()), `tokenade ${manifest.version}`);
|
|
405
|
+
|
|
318
406
|
console.log(`✓ tokenade ${manifest.version} installed (${target})`);
|
|
319
407
|
console.log(" Next: run `tokenade install` — that's it, savings start immediately.");
|
|
320
408
|
console.log(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenade/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
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",
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
"tar": "^7.5.16"
|
|
16
16
|
},
|
|
17
17
|
"optionalDependencies": {
|
|
18
|
-
"@tokenade/cli-linux-x64": "0.9.
|
|
19
|
-
"@tokenade/cli-linux-x64-musl": "0.9.
|
|
20
|
-
"@tokenade/cli-linux-arm64-musl": "0.9.
|
|
21
|
-
"@tokenade/cli-darwin-arm64": "0.9.
|
|
22
|
-
"@tokenade/cli-darwin-x64": "0.9.
|
|
23
|
-
"@tokenade/cli-win32-x64": "0.9.
|
|
18
|
+
"@tokenade/cli-linux-x64": "0.9.4",
|
|
19
|
+
"@tokenade/cli-linux-x64-musl": "0.9.4",
|
|
20
|
+
"@tokenade/cli-linux-arm64-musl": "0.9.4",
|
|
21
|
+
"@tokenade/cli-darwin-arm64": "0.9.4",
|
|
22
|
+
"@tokenade/cli-darwin-x64": "0.9.4",
|
|
23
|
+
"@tokenade/cli-win32-x64": "0.9.4"
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"bin/",
|