@tokenade/cli 0.9.3 → 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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/install.js +93 -5
  3. 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.</sub>
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
- require.resolve(`${platformPackageName(target)}/package.json`);
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
- console.log(`✓ tokenade already vendored (${target}) — no download needed.`);
241
- return;
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",
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.3",
19
- "@tokenade/cli-linux-x64-musl": "0.9.3",
20
- "@tokenade/cli-linux-arm64-musl": "0.9.3",
21
- "@tokenade/cli-darwin-arm64": "0.9.3",
22
- "@tokenade/cli-darwin-x64": "0.9.3",
23
- "@tokenade/cli-win32-x64": "0.9.3"
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/",