@tokenade/cli 0.3.0
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 +32 -0
- package/bin/tokenade-mcp.js +24 -0
- package/bin/tokenade.js +24 -0
- package/install.js +126 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @tokenade/cli
|
|
2
|
+
|
|
3
|
+
**Tokenade** cuts your AI coding agent's token bill — semantic code search,
|
|
4
|
+
output filtering and compaction, applied automatically as a local MCP server +
|
|
5
|
+
inspectable agent hooks. Paid product (freemium) — see <https://tokenade.net>.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install -g @tokenade/cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The postinstall downloads the prebuilt binary for your platform from the signed
|
|
14
|
+
release manifest (`downloads.tokenade.net/manifest.json`) and verifies its
|
|
15
|
+
SHA-256. Nothing is run blindly.
|
|
16
|
+
|
|
17
|
+
## Activate (browser — no key to type)
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
tokenade login # opens tokenade.net to authorize this machine
|
|
21
|
+
tokenade install # registers the MCP server + hooks with your coding agent
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`tokenade login` opens your browser so you (logged into your dashboard) approve
|
|
25
|
+
this device. Each plan allows 3 machines; manage them at
|
|
26
|
+
<https://tokenade.net/dashboard>.
|
|
27
|
+
|
|
28
|
+
## What leaves your machine
|
|
29
|
+
|
|
30
|
+
Nothing, except anonymous token-savings counts and an update check. Tokenade
|
|
31
|
+
runs locally; its agent hooks live in your editor/agent config (inspectable,
|
|
32
|
+
reversible).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
const { spawnSync } = require("node:child_process");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
|
|
7
|
+
const bin = path.join(
|
|
8
|
+
__dirname,
|
|
9
|
+
"..",
|
|
10
|
+
"vendor",
|
|
11
|
+
process.platform === "win32" ? "tokenade-mcp.exe" : "tokenade-mcp",
|
|
12
|
+
);
|
|
13
|
+
if (!fs.existsSync(bin)) {
|
|
14
|
+
console.error(
|
|
15
|
+
"tokenade-mcp: binary not found. Re-run `npm install -g @tokenade/cli`.",
|
|
16
|
+
);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const r = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
|
|
20
|
+
if (r.error) {
|
|
21
|
+
console.error(`tokenade-mcp: ${r.error.message}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
process.exit(r.status === null ? 1 : r.status);
|
package/bin/tokenade.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
const { spawnSync } = require("node:child_process");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
|
|
7
|
+
const bin = path.join(
|
|
8
|
+
__dirname,
|
|
9
|
+
"..",
|
|
10
|
+
"vendor",
|
|
11
|
+
process.platform === "win32" ? "tokenade.exe" : "tokenade",
|
|
12
|
+
);
|
|
13
|
+
if (!fs.existsSync(bin)) {
|
|
14
|
+
console.error(
|
|
15
|
+
"tokenade: binary not found. Re-run `npm install -g @tokenade/cli` (the postinstall downloads it).",
|
|
16
|
+
);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const r = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
|
|
20
|
+
if (r.error) {
|
|
21
|
+
console.error(`tokenade: ${r.error.message}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
process.exit(r.status === null ? 1 : r.status);
|
package/install.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* postinstall — download the prebuilt Tokenade binary for this platform from
|
|
4
|
+
* the signed release manifest, verify its SHA-256, and drop `tokenade` +
|
|
5
|
+
* `tokenade-mcp` into ./vendor. The bin shims in ./bin exec them.
|
|
6
|
+
*
|
|
7
|
+
* Closed-source commercial binary, distributed via the npm registry (the trust
|
|
8
|
+
* anchor) — same pattern as esbuild/biome/swc. Nothing is executed blindly:
|
|
9
|
+
* the checksum comes from downloads.tokenade.net/manifest.json.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
"use strict";
|
|
13
|
+
|
|
14
|
+
const fs = require("node:fs");
|
|
15
|
+
const path = require("node:path");
|
|
16
|
+
const os = require("node:os");
|
|
17
|
+
const https = require("node:https");
|
|
18
|
+
const crypto = require("node:crypto");
|
|
19
|
+
const tar = require("tar");
|
|
20
|
+
|
|
21
|
+
const DOWNLOADS_BASE =
|
|
22
|
+
process.env.TOKENADE_DOWNLOADS_BASE || "https://downloads.tokenade.net";
|
|
23
|
+
const MANIFEST_URL = `${DOWNLOADS_BASE}/manifest.json`;
|
|
24
|
+
const VENDOR = path.join(__dirname, "vendor");
|
|
25
|
+
|
|
26
|
+
function rustTarget() {
|
|
27
|
+
const p = process.platform;
|
|
28
|
+
const a = process.arch;
|
|
29
|
+
if (p === "darwin")
|
|
30
|
+
return a === "arm64" ? "aarch64-apple-darwin" : "x86_64-apple-darwin";
|
|
31
|
+
if (p === "linux")
|
|
32
|
+
return a === "arm64"
|
|
33
|
+
? "aarch64-unknown-linux-musl"
|
|
34
|
+
: "x86_64-unknown-linux-gnu";
|
|
35
|
+
if (p === "win32") return "x86_64-pc-windows-gnu";
|
|
36
|
+
throw new Error(`unsupported platform: ${p}/${a}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function get(url, redirects = 0) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
if (redirects > 5) return reject(new Error("too many redirects"));
|
|
42
|
+
https
|
|
43
|
+
.get(url, (res) => {
|
|
44
|
+
if (
|
|
45
|
+
res.statusCode &&
|
|
46
|
+
res.statusCode >= 300 &&
|
|
47
|
+
res.statusCode < 400 &&
|
|
48
|
+
res.headers.location
|
|
49
|
+
) {
|
|
50
|
+
res.resume();
|
|
51
|
+
return resolve(get(res.headers.location, redirects + 1));
|
|
52
|
+
}
|
|
53
|
+
if (res.statusCode !== 200) {
|
|
54
|
+
res.resume();
|
|
55
|
+
return reject(new Error(`GET ${url} → HTTP ${res.statusCode}`));
|
|
56
|
+
}
|
|
57
|
+
resolve(res);
|
|
58
|
+
})
|
|
59
|
+
.on("error", reject);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function fetchJson(url) {
|
|
64
|
+
const res = await get(url);
|
|
65
|
+
const chunks = [];
|
|
66
|
+
for await (const c of res) chunks.push(c);
|
|
67
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function downloadTo(url, dest) {
|
|
71
|
+
const res = await get(url);
|
|
72
|
+
const hash = crypto.createHash("sha256");
|
|
73
|
+
await new Promise((resolve, reject) => {
|
|
74
|
+
const out = fs.createWriteStream(dest);
|
|
75
|
+
res.on("data", (d) => hash.update(d));
|
|
76
|
+
res.pipe(out);
|
|
77
|
+
out.on("finish", resolve);
|
|
78
|
+
out.on("error", reject);
|
|
79
|
+
res.on("error", reject);
|
|
80
|
+
});
|
|
81
|
+
return hash.digest("hex");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function main() {
|
|
85
|
+
const target = rustTarget();
|
|
86
|
+
const manifest = await fetchJson(MANIFEST_URL);
|
|
87
|
+
const entry = manifest.targets && manifest.targets[target];
|
|
88
|
+
if (!entry || !entry.url || !entry.sha256) {
|
|
89
|
+
throw new Error(`no published build for ${target}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const tmp = path.join(
|
|
93
|
+
os.tmpdir(),
|
|
94
|
+
`tokenade-${process.pid}-${path.basename(entry.url)}`,
|
|
95
|
+
);
|
|
96
|
+
const got = await downloadTo(entry.url, tmp);
|
|
97
|
+
if (got !== entry.sha256) {
|
|
98
|
+
fs.rmSync(tmp, { force: true });
|
|
99
|
+
throw new Error(
|
|
100
|
+
`checksum mismatch for ${target}\n expected ${entry.sha256}\n got ${got}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fs.rmSync(VENDOR, { recursive: true, force: true });
|
|
105
|
+
fs.mkdirSync(VENDOR, { recursive: true });
|
|
106
|
+
await tar.x({ file: tmp, cwd: VENDOR });
|
|
107
|
+
fs.rmSync(tmp, { force: true });
|
|
108
|
+
|
|
109
|
+
if (process.platform !== "win32") {
|
|
110
|
+
for (const name of ["tokenade", "tokenade-mcp"]) {
|
|
111
|
+
const f = path.join(VENDOR, name);
|
|
112
|
+
if (fs.existsSync(f)) fs.chmodSync(f, 0o755);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log(`✓ tokenade ${manifest.version} installed (${target})`);
|
|
117
|
+
console.log(" Next: run `tokenade login` to activate this machine.");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
main().catch((err) => {
|
|
121
|
+
console.error(`✗ tokenade install failed: ${err.message}`);
|
|
122
|
+
console.error(
|
|
123
|
+
" You can retry, or download manually from https://downloads.tokenade.net",
|
|
124
|
+
);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tokenade/cli",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Tokenade — cut your AI coding agent's token bill. Installs the Tokenade CLI + MCP server (a local, paid token-reduction tool; activate via your browser).",
|
|
5
|
+
"homepage": "https://tokenade.net",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"bin": {
|
|
8
|
+
"tokenade": "bin/tokenade.js",
|
|
9
|
+
"tokenade-mcp": "bin/tokenade-mcp.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"postinstall": "node install.js"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"tar": "^7.4.3"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin/",
|
|
19
|
+
"install.js",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"os": [
|
|
26
|
+
"darwin",
|
|
27
|
+
"linux",
|
|
28
|
+
"win32"
|
|
29
|
+
],
|
|
30
|
+
"cpu": [
|
|
31
|
+
"x64",
|
|
32
|
+
"arm64"
|
|
33
|
+
],
|
|
34
|
+
"keywords": [
|
|
35
|
+
"tokenade",
|
|
36
|
+
"ai",
|
|
37
|
+
"tokens",
|
|
38
|
+
"mcp",
|
|
39
|
+
"claude",
|
|
40
|
+
"coding-agent",
|
|
41
|
+
"cli"
|
|
42
|
+
],
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
}
|
|
46
|
+
}
|