slash-tokens 0.0.1 → 0.2.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 CHANGED
@@ -1,5 +1,128 @@
1
- # slash-tokens
1
+ # /slash-tokens
2
2
 
3
3
  Token budget estimation in 4.8 KB WASM. Sub-millisecond. Zero dependencies.
4
4
 
5
- Coming soon.
5
+ I don't count tokens. I slash them.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install slash-tokens
11
+ ```
12
+
13
+ ## Use
14
+
15
+ ```js
16
+ import { slash } from 'slash-tokens'
17
+
18
+ const tokens = slash('Your content here')
19
+
20
+ if (tokens > 128_000) trim(content) // Context window gate
21
+ const cost = tokens * PRICE_PER_TOKEN // Cost estimation
22
+ ```
23
+
24
+ One function. Returns a number. That's the entire API.
25
+
26
+ ## Scan your project
27
+
28
+ ```bash
29
+ bunx slash-tokens
30
+ ```
31
+
32
+ Finds every AI API call in your codebase and estimates your token burn:
33
+
34
+ ```
35
+ ⚡ /slash
36
+ Token burn analysis
37
+
38
+ Scanned 47 files in 3ms
39
+
40
+ CALL SITES
41
+ ────────────────────────────────────────────────────────
42
+ src/api/chat.ts:24 Anthropic ~2,400 tok/call
43
+ src/api/summary.ts:11 OpenAI ~8,200 tok/call
44
+ src/lib/agent.ts:88 Vercel AI ~1,100 tok/call
45
+
46
+ DAILY BURN (100 calls/site/day)
47
+ ────────────────────────────────────────────────────────
48
+ Input tokens: 1,520,000
49
+ Output tokens: 3,040,000 (estimated)
50
+ Total: 4,560,000 tokens/day
51
+
52
+ MONTHLY COST ($3/$15 per MTok)
53
+ ────────────────────────────────────────────────────────
54
+ Total: $1,504.80/mo
55
+
56
+ ⚡ SLASH SAVINGS (10% gate efficiency)
57
+ ────────────────────────────────────────────────────────
58
+ Annual savings: $1,805.76/yr
59
+ ```
60
+
61
+ Detects: OpenAI, Anthropic, Vercel AI, LangChain, Gemini, Bedrock, Grok, Cohere, Mistral.
62
+
63
+ ## How it works
64
+
65
+ Single-pass byte classification over your content. No vocabulary tables. No BPE decode. Classifies content type (prose, code, JSON, YAML, mixed), applies a calibrated chars-per-token ratio, adds a safety margin.
66
+
67
+ Budget gate — better to say "doesn't fit" than overflow. The API returns exact counts after the call. Pre-call, you only need go/no-go.
68
+
69
+ ## Numbers
70
+
71
+ | | js-tiktoken | gpt-tokenizer | slash-tokens |
72
+ |---|---|---|---|
73
+ | Size | ~1 MB | 300 KB+ | **4.8 KB** |
74
+ | Cold start | Heavy | Moderate | **Sub-ms** |
75
+ | Edge-ready | Painful | Difficult | **Native** |
76
+ | Models | GPT only | GPT only | **Any model** |
77
+ | Dependencies | npm tree | npm tree | **Zero** |
78
+ | Allocations | Many | Many | **Zero** |
79
+
80
+ ## Runtime support
81
+
82
+ Works everywhere WASM runs:
83
+
84
+ - Node.js
85
+ - Bun
86
+ - Deno
87
+ - Cloudflare Workers
88
+ - Vercel Edge
89
+ - Browser
90
+
91
+ ## API
92
+
93
+ ### `slash(content: string): number`
94
+
95
+ Estimate token count for a string. Returns `u32`.
96
+
97
+ ```js
98
+ slash('') // 0
99
+ slash('hello') // 1
100
+ slash(longDocument) // 4,283
101
+ ```
102
+
103
+ ### `slashBytes(bytes: Uint8Array): number`
104
+
105
+ Estimate token count from raw bytes. Skips `TextEncoder` overhead.
106
+
107
+ ## What this is not
108
+
109
+ This is not a tokenizer. It doesn't decode BPE. It doesn't reproduce exact token counts. No tokenizer can — Claude, Grok, and Gemini vocabularies are not public.
110
+
111
+ This is a **budget gate**. It answers "does this fit?" with 96-98% accuracy in sub-millisecond time. The 2-4% margin is intentional — overestimate, never underestimate.
112
+
113
+ ## Engine
114
+
115
+ 4,865 bytes of Zig-compiled WASM (`wasm32-freestanding`). No allocator. No floats. No heap. Base64-embedded — no separate `.wasm` file to load.
116
+
117
+ Built from [xai-faf-zig](https://github.com/Wolfe-Jam/xai-faf-zig). 172 tests including 65 adversarial (CJK, emoji, binary, base64, threshold boundaries).
118
+
119
+ ## Links
120
+
121
+ - [slashtokens.com](https://slashtokens.com) — product site
122
+ - [mcpaas.live/slash](https://mcpaas.live/slash) — live one-pager
123
+ - [npm](https://www.npmjs.com/package/slash-tokens)
124
+ - [xai-faf-zig](https://github.com/Wolfe-Jam/xai-faf-zig) — WASM engine source
125
+
126
+ ## License
127
+
128
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,323 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/scanner.ts
4
+ import { readdirSync, readFileSync, statSync } from "fs";
5
+ import { join, extname } from "path";
6
+
7
+ // src/wasm.ts
8
+ var WASM_BASE64 = "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AA4AAABLAxAAEwAAALABEAAQAAAAGgIQABYAAAAwBBAADQAAAJ0DEAAOAAAAsQMQAA0AAAB6AxAADgAAAMsCEAAQAAAAbAMQAA0AAAA+BBAACwAAAFYEEAAKAAAAvwMQAAwAAAAjAxAADAAAAP0DEAAVAAAAsQIQABEAAAAxAhAAEgAAAMEBEAARAAAAiQMQABMAAADoAhAAEgAAAPABEAARAAAAIQQQAA4AAABLAxAAEwAAALABEAAQAAAAGgIQABYAAAAwBBAADQAAAJ0DEAAOAAAAsQMQAA0AAAB6AxAADgAAAMsCEAAQAAAAbAMQAA0AAAA+BBAACwAAAFYEEAAKAAAACgMQABMAAACbAhAAFQAAAG4CEAAQAAAAAgIQABcAAAB/AhAAGwAAANwCEAALAAAA8QMQAAsAAABfAxAADAAAABMEEAANAAAA0wEQABwAAABWAhAAFwAAANsDEAAVAAAAc3RhY2sudWlfbGlicmFyeQBodW1hbl9jb250ZXh0LndoeQBtb25vcmVwby52ZXJzaW9uaW5nX3N0cmF0ZWd5AGh1bWFuX2NvbnRleHQuaG93AG1vbm9yZXBvLnBhY2thZ2VzX2NvdW50AHN0YWNrLnN0YXRlX21hbmFnZW1lbnQAaHVtYW5fY29udGV4dC53aGF0AGRldmVsb3BtZW50IHRlYW1zAG1vbm9yZXBvLnNoYXJlZF9jb25maWdzAHN0YWNrLndvcmtzcGFjZXMAbW9ub3JlcG8uYnVpbGRfb3JjaGVzdHJhdG9yAHN0YWNrLnBhY2thZ2VfbWFuYWdlcgBodW1hbl9jb250ZXh0LndobwB1bmtub3duAHN0YWNrLmNvbm5lY3Rpb24Ac3RhY2suYWRtaW4AaHVtYW5fY29udGV4dC53aGVuAGNsb3VkIHBsYXRmb3JtAHN0YWNrLm1vbm9yZXBvX3Rvb2wAbnVsbABwcm9qZWN0LmdvYWwAZGVzY3JpYmUgeW91ciBwcm9qZWN0IGdvYWwAc3RhY2suY3NzX2ZyYW1ld29yawBzdGFjay5zZWFyY2gAc3RhY2suaG9zdGluZwBzdGFjay5kYXRhYmFzZQBodW1hbl9jb250ZXh0LndoZXJlAHN0YWNrLmFwaV90eXBlAG5vbmUAc3RhY2sucnVudGltZQBwcm9qZWN0Lm5hbWUAbm90IGFwcGxpY2FibGUAbW9ub3JlcG8ucmVtb3RlX2NhY2hlAHN0YWNrLmNhY2hlAHByb2plY3QubWFpbl9sYW5ndWFnZQBzdGFjay5zdG9yYWdlAHN0YWNrLmZyb250ZW5kAHN0YWNrLmJhY2tlbmQAc3RhY2suYnVpbGQAc2xvdGlnbm9yZWQAc3RhY2suY2ljZABuL2EAOgAuACAJDQoiJwAgCSInAA0ACgAgCQAwAxAAGgAAAEQCEAARAAAA+wIQAA4AAAAeAxAABAAAAKwDEAAEAAAAwwIQAAcAAABhBBAAAwAAAMwDEAAOAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAA=";
9
+ var WASM_INPUT_OFFSET = 4096;
10
+ var encoder = new TextEncoder;
11
+ var _instance = null;
12
+ function decodeBase64(b64) {
13
+ if (typeof Buffer !== "undefined") {
14
+ return new Uint8Array(Buffer.from(b64, "base64"));
15
+ }
16
+ const binary = atob(b64);
17
+ const bytes = new Uint8Array(binary.length);
18
+ for (let i = 0;i < binary.length; i++) {
19
+ bytes[i] = binary.charCodeAt(i);
20
+ }
21
+ return bytes;
22
+ }
23
+ function getInstance() {
24
+ if (_instance)
25
+ return _instance;
26
+ const bytes = decodeBase64(WASM_BASE64);
27
+ const module = new WebAssembly.Module(bytes);
28
+ _instance = new WebAssembly.Instance(module);
29
+ return _instance;
30
+ }
31
+ function writeToMemory(content) {
32
+ const instance = getInstance();
33
+ const bytes = encoder.encode(content);
34
+ const memory = instance.exports.memory;
35
+ const buffer = new Uint8Array(memory.buffer);
36
+ const maxLen = Math.min(bytes.length, buffer.length - WASM_INPUT_OFFSET - 1);
37
+ buffer.set(bytes.subarray(0, maxLen), WASM_INPUT_OFFSET);
38
+ return maxLen;
39
+ }
40
+
41
+ // src/slash.ts
42
+ var WASM_INPUT_OFFSET2 = 4096;
43
+ function slash(content) {
44
+ if (!content)
45
+ return 0;
46
+ const len = writeToMemory(content);
47
+ const instance = getInstance();
48
+ return instance.exports.estimate_tokens(WASM_INPUT_OFFSET2, len);
49
+ }
50
+
51
+ // src/patterns.ts
52
+ var AI_PATTERNS = [
53
+ { name: "OpenAI", regex: /openai\.chat\.completions\.create|new OpenAI\(|from\s+['"]openai['"]/g },
54
+ { name: "Anthropic", regex: /anthropic\.messages\.create|new Anthropic\(|from\s+['"]@anthropic-ai/g },
55
+ { name: "Vercel AI", regex: /from\s+['"]ai['"]|generateText|streamText|generateObject/g },
56
+ { name: "LangChain", regex: /from\s+['"]langchain|ChatOpenAI|ChatAnthropic|\.invoke\(/g },
57
+ { name: "Gemini", regex: /GoogleGenerativeAI|generateContent|from\s+['"]@google\/generative/g },
58
+ { name: "Bedrock", regex: /BedrockRuntimeClient|InvokeModelCommand/g },
59
+ { name: "Grok", regex: /x\.ai\/api|xai\.chat|from\s+['"]grok/g },
60
+ { name: "fetch (AI)", regex: /fetch\(.*api\.openai\.com|fetch\(.*api\.anthropic\.com|fetch\(.*generativelanguage\.googleapis/g },
61
+ { name: "Cohere", regex: /from\s+['"]cohere|CohereClient|cohere\.chat/g },
62
+ { name: "Mistral", regex: /from\s+['"]@mistralai|MistralClient/g }
63
+ ];
64
+ var SKIP_DIRS = new Set([
65
+ "node_modules",
66
+ ".git",
67
+ "dist",
68
+ "build",
69
+ ".next",
70
+ ".nuxt",
71
+ ".svelte-kit",
72
+ "coverage",
73
+ ".turbo",
74
+ ".cache",
75
+ "__pycache__",
76
+ ".venv",
77
+ "venv",
78
+ "target",
79
+ "zig-out",
80
+ "zig-cache"
81
+ ]);
82
+ var SCAN_EXTENSIONS = new Set([
83
+ ".ts",
84
+ ".tsx",
85
+ ".js",
86
+ ".jsx",
87
+ ".mjs",
88
+ ".mts",
89
+ ".py",
90
+ ".rs"
91
+ ]);
92
+
93
+ // src/scanner.ts
94
+ function walkDir(dir, files) {
95
+ let entries;
96
+ try {
97
+ entries = readdirSync(dir);
98
+ } catch {
99
+ return;
100
+ }
101
+ for (const entry of entries) {
102
+ if (SKIP_DIRS.has(entry))
103
+ continue;
104
+ const full = join(dir, entry);
105
+ let stat;
106
+ try {
107
+ stat = statSync(full);
108
+ } catch {
109
+ continue;
110
+ }
111
+ if (stat.isDirectory()) {
112
+ walkDir(full, files);
113
+ } else if (SCAN_EXTENSIONS.has(extname(entry))) {
114
+ files.push(full);
115
+ }
116
+ }
117
+ }
118
+ function findCallSites(filePath, content) {
119
+ const sites = [];
120
+ const lines = content.split(`
121
+ `);
122
+ for (const pattern of AI_PATTERNS) {
123
+ pattern.regex.lastIndex = 0;
124
+ let match;
125
+ while ((match = pattern.regex.exec(content)) !== null) {
126
+ const beforeMatch = content.substring(0, match.index);
127
+ const lineNum = beforeMatch.split(`
128
+ `).length;
129
+ const startLine = Math.max(0, lineNum - 5);
130
+ const endLine = Math.min(lines.length, lineNum + 20);
131
+ const context = lines.slice(startLine, endLine).join(`
132
+ `);
133
+ const tokensPerCall = slash(context);
134
+ if (!sites.some((s) => s.line === lineNum && s.sdk === pattern.name)) {
135
+ sites.push({
136
+ file: filePath,
137
+ line: lineNum,
138
+ sdk: pattern.name,
139
+ tokensPerCall
140
+ });
141
+ }
142
+ }
143
+ }
144
+ return sites;
145
+ }
146
+ function scan(dir) {
147
+ const start = performance.now();
148
+ const files = [];
149
+ walkDir(dir, files);
150
+ const sites = [];
151
+ for (const file of files) {
152
+ let content;
153
+ try {
154
+ content = readFileSync(file, "utf-8");
155
+ } catch {
156
+ continue;
157
+ }
158
+ sites.push(...findCallSites(file, content));
159
+ }
160
+ return {
161
+ sites,
162
+ filesScanned: files.length,
163
+ timeMs: Math.round(performance.now() - start)
164
+ };
165
+ }
166
+
167
+ // src/report.ts
168
+ var R = "\x1B[0m";
169
+ var B = "\x1B[1m";
170
+ var DIM = "\x1B[2m";
171
+ var ORANGE = "\x1B[38;2;255;68;0m";
172
+ var GOLD = "\x1B[38;2;230;161;65m";
173
+ var WHITE = "\x1B[97m";
174
+ var GRAY = "\x1B[90m";
175
+ function pad(s, n) {
176
+ return s.length >= n ? s : s + " ".repeat(n - s.length);
177
+ }
178
+ function formatNum(n) {
179
+ return n.toLocaleString("en-US");
180
+ }
181
+ function printReport(sites, filesScanned, timeMs, cwd) {
182
+ console.log("");
183
+ console.log(`${ORANGE}${B} ⚡ /slash${R}`);
184
+ console.log(`${GRAY} Token burn analysis${R}`);
185
+ console.log("");
186
+ console.log(`${DIM} Scanned ${filesScanned} files in ${timeMs}ms${R}`);
187
+ console.log("");
188
+ if (sites.length === 0) {
189
+ console.log(`${WHITE} No AI API call sites detected.${R}`);
190
+ console.log(`${GRAY} Supported: OpenAI, Anthropic, Vercel AI, LangChain, Gemini, Bedrock, Grok${R}`);
191
+ console.log("");
192
+ return;
193
+ }
194
+ console.log(`${ORANGE} CALL SITES${R}`);
195
+ console.log(`${DIM} ${"─".repeat(60)}${R}`);
196
+ for (const site of sites) {
197
+ const loc = site.file.replace(cwd + "/", "") + ":" + site.line;
198
+ console.log(`${WHITE} ${pad(loc, 40)} ${GOLD}${pad(site.sdk, 12)} ${ORANGE}~${formatNum(site.tokensPerCall)} tok/call${R}`);
199
+ }
200
+ console.log("");
201
+ const callsPerDay = 100;
202
+ const dailyInput = sites.reduce((sum, s) => sum + s.tokensPerCall * callsPerDay, 0);
203
+ const dailyOutput = dailyInput * 2;
204
+ const dailyTotal = dailyInput + dailyOutput;
205
+ const monthlyTotal = dailyTotal * 30;
206
+ const monthlyCostInput = dailyInput * 30 / 1e6 * 3;
207
+ const monthlyCostOutput = dailyOutput * 30 / 1e6 * 15;
208
+ const monthlyCost = monthlyCostInput + monthlyCostOutput;
209
+ console.log(`${ORANGE} DAILY BURN ${GRAY}(${callsPerDay} calls/site/day)${R}`);
210
+ console.log(`${DIM} ${"─".repeat(60)}${R}`);
211
+ console.log(`${WHITE} Input tokens: ${B}${formatNum(dailyInput)}${R}`);
212
+ console.log(`${WHITE} Output tokens: ${B}${formatNum(dailyOutput)}${R} ${GRAY}(estimated)${R}`);
213
+ console.log(`${WHITE} Total: ${ORANGE}${B}${formatNum(dailyTotal)} tokens/day${R}`);
214
+ console.log("");
215
+ console.log(`${ORANGE} MONTHLY COST ${GRAY}($3/$15 per MTok)${R}`);
216
+ console.log(`${DIM} ${"─".repeat(60)}${R}`);
217
+ console.log(`${WHITE} Input: ${B}$${monthlyCostInput.toFixed(2)}/mo${R}`);
218
+ console.log(`${WHITE} Output: ${B}$${monthlyCostOutput.toFixed(2)}/mo${R}`);
219
+ console.log(`${WHITE} Total: ${ORANGE}${B}$${monthlyCost.toFixed(2)}/mo${R}`);
220
+ console.log("");
221
+ const savingsPct = 10;
222
+ const monthlySaved = monthlyCost * savingsPct / 100;
223
+ console.log(`${GOLD} ⚡ SLASH SAVINGS ${GRAY}(${savingsPct}% gate efficiency)${R}`);
224
+ console.log(`${DIM} ${"─".repeat(60)}${R}`);
225
+ console.log(`${WHITE} Monthly savings: ${GOLD}${B}$${monthlySaved.toFixed(2)}/mo${R}`);
226
+ console.log(`${WHITE} Annual savings: ${GOLD}${B}$${(monthlySaved * 12).toFixed(2)}/yr${R}`);
227
+ console.log("");
228
+ console.log(`${GRAY} 4.8 KB WASM · sub-ms · zero deps${R}`);
229
+ console.log(`${GRAY} → ${ORANGE}slashtokens.com${R}`);
230
+ console.log("");
231
+ }
232
+
233
+ // src/config.ts
234
+ var _key = null;
235
+ var _endpoint = "https://mcpaas.live/api/slash/transact";
236
+ function init(opts) {
237
+ _key = opts.key;
238
+ if (opts.endpoint)
239
+ _endpoint = opts.endpoint;
240
+ }
241
+ function resolveKey(perCallKey) {
242
+ const key = perCallKey || _key || process.env.SLASH_KEY;
243
+ if (!key) {
244
+ throw new Error(`No Slash API key. Set SLASH_KEY env var, call init({ key }), or pass key to report().
245
+ ` + "Register: POST https://mcpaas.live/api/slash/register");
246
+ }
247
+ return key;
248
+ }
249
+ function getEndpoint() {
250
+ return _endpoint;
251
+ }
252
+
253
+ // src/transact.ts
254
+ async function report(opts) {
255
+ const key = resolveKey(opts.key);
256
+ const endpoint = getEndpoint();
257
+ const res = await fetch(endpoint, {
258
+ method: "POST",
259
+ headers: {
260
+ "Content-Type": "application/json",
261
+ Authorization: `Bearer ${key}`
262
+ },
263
+ body: JSON.stringify({
264
+ tokens_estimated: opts.tokens_estimated,
265
+ tokens_saved: opts.tokens_saved,
266
+ model: opts.model || "unknown",
267
+ action: opts.action,
268
+ cost_saved_usd: opts.cost_saved_usd
269
+ })
270
+ });
271
+ if (res.status === 401)
272
+ throw new Error("Invalid Slash API key");
273
+ if (res.status === 402)
274
+ throw new Error("Insufficient balance. Purchase credits at slashtokens.com");
275
+ if (res.status === 429)
276
+ throw new Error("Rate limited. Try again shortly.");
277
+ if (!res.ok)
278
+ throw new Error(`Slash API error: ${res.status}`);
279
+ return res.json();
280
+ }
281
+
282
+ // src/cli.ts
283
+ var args = process.argv.slice(2);
284
+ var keyArg = args.find((a) => a.startsWith("--key="));
285
+ var key = keyArg?.split("=")[1] || process.env.SLASH_KEY;
286
+ if (key)
287
+ init({ key });
288
+ var cwd = process.cwd();
289
+ var { sites, filesScanned, timeMs } = scan(cwd);
290
+ printReport(sites, filesScanned, timeMs, cwd);
291
+ var R2 = "\x1B[0m";
292
+ var B2 = "\x1B[1m";
293
+ var GREEN = "\x1B[38;2;74;222;128m";
294
+ var GOLD2 = "\x1B[38;2;230;161;65m";
295
+ var GRAY2 = "\x1B[90m";
296
+ var ORANGE2 = "\x1B[38;2;255;68;0m";
297
+ if (key && sites.length > 0) {
298
+ const callsPerDay = 100;
299
+ const dailyInput = sites.reduce((sum, s) => sum + s.tokensPerCall * callsPerDay, 0);
300
+ const dailyOutput = dailyInput * 2;
301
+ const monthlyCostInput = dailyInput * 30 / 1e6 * 3;
302
+ const monthlyCostOutput = dailyOutput * 30 / 1e6 * 15;
303
+ const monthlyCost = monthlyCostInput + monthlyCostOutput;
304
+ const monthlySaved = monthlyCost * 0.1;
305
+ report({
306
+ tokens_estimated: dailyInput * 30,
307
+ tokens_saved: Math.round(dailyInput * 30 * 0.1),
308
+ model: "scan",
309
+ action: "reduced",
310
+ cost_saved_usd: monthlySaved
311
+ }).then((result) => {
312
+ console.log(`${GREEN}${B2} ✓ Reported to MCPaaS${R2} ${GRAY2}(${result.transaction_id})${R2}`);
313
+ console.log(`${GRAY2} Fee: $${result.fee_usd.toFixed(4)} · Balance: $${result.balance_remaining_usd.toFixed(2)}${R2}`);
314
+ console.log("");
315
+ }).catch((err) => {
316
+ console.log(`${GOLD2} ⚠ Report failed: ${err.message}${R2}`);
317
+ console.log("");
318
+ });
319
+ } else if (!key && sites.length > 0) {
320
+ console.log(`${GRAY2} → Report savings: ${ORANGE2}bunx slash-tokens --key=YOUR_KEY${R2}`);
321
+ console.log(`${GRAY2} → Get a key: POST https://mcpaas.live/api/slash/register${R2}`);
322
+ console.log("");
323
+ }
@@ -0,0 +1,6 @@
1
+ export declare function init(opts: {
2
+ key: string;
3
+ endpoint?: string;
4
+ }): void;
5
+ export declare function resolveKey(perCallKey?: string): string;
6
+ export declare function getEndpoint(): string;
package/dist/config.js ADDED
@@ -0,0 +1,18 @@
1
+ let _key = null;
2
+ let _endpoint = 'https://mcpaas.live/api/slash/transact';
3
+ export function init(opts) {
4
+ _key = opts.key;
5
+ if (opts.endpoint)
6
+ _endpoint = opts.endpoint;
7
+ }
8
+ export function resolveKey(perCallKey) {
9
+ const key = perCallKey || _key || process.env.SLASH_KEY;
10
+ if (!key) {
11
+ throw new Error('No Slash API key. Set SLASH_KEY env var, call init({ key }), or pass key to report().\n' +
12
+ 'Register: POST https://mcpaas.live/api/slash/register');
13
+ }
14
+ return key;
15
+ }
16
+ export function getEndpoint() {
17
+ return _endpoint;
18
+ }
@@ -0,0 +1,5 @@
1
+ export { slash, slashBytes } from './slash.js';
2
+ export { report } from './transact.js';
3
+ export type { ReportOptions, ReportResult } from './transact.js';
4
+ export { init } from './config.js';
5
+ export { resolveKey } from './config.js';
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // Core estimation
2
+ export { slash, slashBytes } from './slash.js';
3
+ // Transaction reporting
4
+ export { report } from './transact.js';
5
+ // Configuration
6
+ export { init } from './config.js';
7
+ export { resolveKey } from './config.js';
@@ -0,0 +1,7 @@
1
+ export interface Pattern {
2
+ name: string;
3
+ regex: RegExp;
4
+ }
5
+ export declare const AI_PATTERNS: Pattern[];
6
+ export declare const SKIP_DIRS: Set<string>;
7
+ export declare const SCAN_EXTENSIONS: Set<string>;
@@ -0,0 +1,30 @@
1
+ export const AI_PATTERNS = [
2
+ // OpenAI
3
+ { name: 'OpenAI', regex: /openai\.chat\.completions\.create|new OpenAI\(|from\s+['"]openai['"]/g },
4
+ // Anthropic
5
+ { name: 'Anthropic', regex: /anthropic\.messages\.create|new Anthropic\(|from\s+['"]@anthropic-ai/g },
6
+ // Vercel AI SDK
7
+ { name: 'Vercel AI', regex: /from\s+['"]ai['"]|generateText|streamText|generateObject/g },
8
+ // LangChain
9
+ { name: 'LangChain', regex: /from\s+['"]langchain|ChatOpenAI|ChatAnthropic|\.invoke\(/g },
10
+ // Google Gemini
11
+ { name: 'Gemini', regex: /GoogleGenerativeAI|generateContent|from\s+['"]@google\/generative/g },
12
+ // AWS Bedrock
13
+ { name: 'Bedrock', regex: /BedrockRuntimeClient|InvokeModelCommand/g },
14
+ // xAI Grok
15
+ { name: 'Grok', regex: /x\.ai\/api|xai\.chat|from\s+['"]grok/g },
16
+ // Raw fetch to AI endpoints
17
+ { name: 'fetch (AI)', regex: /fetch\(.*api\.openai\.com|fetch\(.*api\.anthropic\.com|fetch\(.*generativelanguage\.googleapis/g },
18
+ // Cohere
19
+ { name: 'Cohere', regex: /from\s+['"]cohere|CohereClient|cohere\.chat/g },
20
+ // Mistral
21
+ { name: 'Mistral', regex: /from\s+['"]@mistralai|MistralClient/g },
22
+ ];
23
+ export const SKIP_DIRS = new Set([
24
+ 'node_modules', '.git', 'dist', 'build', '.next', '.nuxt', '.svelte-kit',
25
+ 'coverage', '.turbo', '.cache', '__pycache__', '.venv', 'venv',
26
+ 'target', 'zig-out', 'zig-cache',
27
+ ]);
28
+ export const SCAN_EXTENSIONS = new Set([
29
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.mts', '.py', '.rs',
30
+ ]);
@@ -0,0 +1,2 @@
1
+ import { CallSite } from './scanner.js';
2
+ export declare function printReport(sites: CallSite[], filesScanned: number, timeMs: number, cwd: string): void;
package/dist/report.js ADDED
@@ -0,0 +1,69 @@
1
+ // ANSI — zero deps
2
+ const R = '\x1b[0m';
3
+ const B = '\x1b[1m';
4
+ const DIM = '\x1b[2m';
5
+ const ORANGE = '\x1b[38;2;255;68;0m';
6
+ const GOLD = '\x1b[38;2;230;161;65m';
7
+ const WHITE = '\x1b[97m';
8
+ const GRAY = '\x1b[90m';
9
+ function pad(s, n) {
10
+ return s.length >= n ? s : s + ' '.repeat(n - s.length);
11
+ }
12
+ function formatNum(n) {
13
+ return n.toLocaleString('en-US');
14
+ }
15
+ export function printReport(sites, filesScanned, timeMs, cwd) {
16
+ console.log('');
17
+ console.log(`${ORANGE}${B} ⚡ /slash${R}`);
18
+ console.log(`${GRAY} Token burn analysis${R}`);
19
+ console.log('');
20
+ console.log(`${DIM} Scanned ${filesScanned} files in ${timeMs}ms${R}`);
21
+ console.log('');
22
+ if (sites.length === 0) {
23
+ console.log(`${WHITE} No AI API call sites detected.${R}`);
24
+ console.log(`${GRAY} Supported: OpenAI, Anthropic, Vercel AI, LangChain, Gemini, Bedrock, Grok${R}`);
25
+ console.log('');
26
+ return;
27
+ }
28
+ // Call sites
29
+ console.log(`${ORANGE} CALL SITES${R}`);
30
+ console.log(`${DIM} ${'─'.repeat(60)}${R}`);
31
+ for (const site of sites) {
32
+ const loc = site.file.replace(cwd + '/', '') + ':' + site.line;
33
+ console.log(`${WHITE} ${pad(loc, 40)} ${GOLD}${pad(site.sdk, 12)} ${ORANGE}~${formatNum(site.tokensPerCall)} tok/call${R}`);
34
+ }
35
+ console.log('');
36
+ // Daily estimates (100 calls/site/day)
37
+ const callsPerDay = 100;
38
+ const dailyInput = sites.reduce((sum, s) => sum + s.tokensPerCall * callsPerDay, 0);
39
+ const dailyOutput = dailyInput * 2; // Estimate output at 2x input
40
+ const dailyTotal = dailyInput + dailyOutput;
41
+ const monthlyTotal = dailyTotal * 30;
42
+ // Cost at $3/MTok input, $15/MTok output (GPT-4o class)
43
+ const monthlyCostInput = (dailyInput * 30 / 1000000) * 3;
44
+ const monthlyCostOutput = (dailyOutput * 30 / 1000000) * 15;
45
+ const monthlyCost = monthlyCostInput + monthlyCostOutput;
46
+ console.log(`${ORANGE} DAILY BURN ${GRAY}(${callsPerDay} calls/site/day)${R}`);
47
+ console.log(`${DIM} ${'─'.repeat(60)}${R}`);
48
+ console.log(`${WHITE} Input tokens: ${B}${formatNum(dailyInput)}${R}`);
49
+ console.log(`${WHITE} Output tokens: ${B}${formatNum(dailyOutput)}${R} ${GRAY}(estimated)${R}`);
50
+ console.log(`${WHITE} Total: ${ORANGE}${B}${formatNum(dailyTotal)} tokens/day${R}`);
51
+ console.log('');
52
+ console.log(`${ORANGE} MONTHLY COST ${GRAY}($3/$15 per MTok)${R}`);
53
+ console.log(`${DIM} ${'─'.repeat(60)}${R}`);
54
+ console.log(`${WHITE} Input: ${B}$${monthlyCostInput.toFixed(2)}/mo${R}`);
55
+ console.log(`${WHITE} Output: ${B}$${monthlyCostOutput.toFixed(2)}/mo${R}`);
56
+ console.log(`${WHITE} Total: ${ORANGE}${B}$${monthlyCost.toFixed(2)}/mo${R}`);
57
+ console.log('');
58
+ // Slash savings (conservative 10% gate savings)
59
+ const savingsPct = 10;
60
+ const monthlySaved = monthlyCost * savingsPct / 100;
61
+ console.log(`${GOLD} ⚡ SLASH SAVINGS ${GRAY}(${savingsPct}% gate efficiency)${R}`);
62
+ console.log(`${DIM} ${'─'.repeat(60)}${R}`);
63
+ console.log(`${WHITE} Monthly savings: ${GOLD}${B}$${monthlySaved.toFixed(2)}/mo${R}`);
64
+ console.log(`${WHITE} Annual savings: ${GOLD}${B}$${(monthlySaved * 12).toFixed(2)}/yr${R}`);
65
+ console.log('');
66
+ console.log(`${GRAY} 4.8 KB WASM · sub-ms · zero deps${R}`);
67
+ console.log(`${GRAY} → ${ORANGE}slashtokens.com${R}`);
68
+ console.log('');
69
+ }
@@ -0,0 +1,11 @@
1
+ export interface CallSite {
2
+ file: string;
3
+ line: number;
4
+ sdk: string;
5
+ tokensPerCall: number;
6
+ }
7
+ export declare function scan(dir: string): {
8
+ sites: CallSite[];
9
+ filesScanned: number;
10
+ timeMs: number;
11
+ };
@@ -0,0 +1,81 @@
1
+ import { readdirSync, readFileSync, statSync } from 'fs';
2
+ import { join, extname } from 'path';
3
+ import { slash } from './slash.js';
4
+ import { AI_PATTERNS, SKIP_DIRS, SCAN_EXTENSIONS } from './patterns.js';
5
+ function walkDir(dir, files) {
6
+ let entries;
7
+ try {
8
+ entries = readdirSync(dir);
9
+ }
10
+ catch {
11
+ return;
12
+ }
13
+ for (const entry of entries) {
14
+ if (SKIP_DIRS.has(entry))
15
+ continue;
16
+ const full = join(dir, entry);
17
+ let stat;
18
+ try {
19
+ stat = statSync(full);
20
+ }
21
+ catch {
22
+ continue;
23
+ }
24
+ if (stat.isDirectory()) {
25
+ walkDir(full, files);
26
+ }
27
+ else if (SCAN_EXTENSIONS.has(extname(entry))) {
28
+ files.push(full);
29
+ }
30
+ }
31
+ }
32
+ function findCallSites(filePath, content) {
33
+ const sites = [];
34
+ const lines = content.split('\n');
35
+ for (const pattern of AI_PATTERNS) {
36
+ // Reset regex
37
+ pattern.regex.lastIndex = 0;
38
+ let match;
39
+ while ((match = pattern.regex.exec(content)) !== null) {
40
+ // Find line number
41
+ const beforeMatch = content.substring(0, match.index);
42
+ const lineNum = beforeMatch.split('\n').length;
43
+ // Estimate tokens from surrounding context (grab the function/block)
44
+ const startLine = Math.max(0, lineNum - 5);
45
+ const endLine = Math.min(lines.length, lineNum + 20);
46
+ const context = lines.slice(startLine, endLine).join('\n');
47
+ const tokensPerCall = slash(context);
48
+ // Avoid duplicates at same location
49
+ if (!sites.some(s => s.line === lineNum && s.sdk === pattern.name)) {
50
+ sites.push({
51
+ file: filePath,
52
+ line: lineNum,
53
+ sdk: pattern.name,
54
+ tokensPerCall,
55
+ });
56
+ }
57
+ }
58
+ }
59
+ return sites;
60
+ }
61
+ export function scan(dir) {
62
+ const start = performance.now();
63
+ const files = [];
64
+ walkDir(dir, files);
65
+ const sites = [];
66
+ for (const file of files) {
67
+ let content;
68
+ try {
69
+ content = readFileSync(file, 'utf-8');
70
+ }
71
+ catch {
72
+ continue;
73
+ }
74
+ sites.push(...findCallSites(file, content));
75
+ }
76
+ return {
77
+ sites,
78
+ filesScanned: files.length,
79
+ timeMs: Math.round(performance.now() - start),
80
+ };
81
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Estimate token count for a string.
3
+ * Sub-millisecond. Zero allocations in WASM.
4
+ */
5
+ export declare function slash(content: string): number;
6
+ /**
7
+ * Estimate token count from raw bytes.
8
+ */
9
+ export declare function slashBytes(bytes: Uint8Array): number;
package/dist/slash.js ADDED
@@ -0,0 +1,26 @@
1
+ import { getInstance, writeToMemory } from './wasm.js';
2
+ const WASM_INPUT_OFFSET = 4096;
3
+ /**
4
+ * Estimate token count for a string.
5
+ * Sub-millisecond. Zero allocations in WASM.
6
+ */
7
+ export function slash(content) {
8
+ if (!content)
9
+ return 0;
10
+ const len = writeToMemory(content);
11
+ const instance = getInstance();
12
+ return instance.exports.estimate_tokens(WASM_INPUT_OFFSET, len);
13
+ }
14
+ /**
15
+ * Estimate token count from raw bytes.
16
+ */
17
+ export function slashBytes(bytes) {
18
+ if (!bytes.length)
19
+ return 0;
20
+ const instance = getInstance();
21
+ const memory = instance.exports.memory;
22
+ const buffer = new Uint8Array(memory.buffer);
23
+ const maxLen = Math.min(bytes.length, buffer.length - WASM_INPUT_OFFSET - 1);
24
+ buffer.set(bytes.subarray(0, maxLen), WASM_INPUT_OFFSET);
25
+ return instance.exports.estimate_tokens(WASM_INPUT_OFFSET, maxLen);
26
+ }
@@ -0,0 +1,15 @@
1
+ export interface ReportOptions {
2
+ key?: string;
3
+ tokens_estimated: number;
4
+ tokens_saved: number;
5
+ model?: string;
6
+ action: 'skipped' | 'reduced' | 'routed';
7
+ cost_saved_usd: number;
8
+ }
9
+ export interface ReportResult {
10
+ transaction_id: string;
11
+ fee_usd: number;
12
+ balance_remaining_usd: number;
13
+ timestamp: string;
14
+ }
15
+ export declare function report(opts: ReportOptions): Promise<ReportResult>;
@@ -0,0 +1,28 @@
1
+ import { resolveKey, getEndpoint } from './config.js';
2
+ export async function report(opts) {
3
+ const key = resolveKey(opts.key);
4
+ const endpoint = getEndpoint();
5
+ const res = await fetch(endpoint, {
6
+ method: 'POST',
7
+ headers: {
8
+ 'Content-Type': 'application/json',
9
+ 'Authorization': `Bearer ${key}`,
10
+ },
11
+ body: JSON.stringify({
12
+ tokens_estimated: opts.tokens_estimated,
13
+ tokens_saved: opts.tokens_saved,
14
+ model: opts.model || 'unknown',
15
+ action: opts.action,
16
+ cost_saved_usd: opts.cost_saved_usd,
17
+ }),
18
+ });
19
+ if (res.status === 401)
20
+ throw new Error('Invalid Slash API key');
21
+ if (res.status === 402)
22
+ throw new Error('Insufficient balance. Purchase credits at slashtokens.com');
23
+ if (res.status === 429)
24
+ throw new Error('Rate limited. Try again shortly.');
25
+ if (!res.ok)
26
+ throw new Error(`Slash API error: ${res.status}`);
27
+ return res.json();
28
+ }
package/dist/wasm.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function getInstance(): WebAssembly.Instance;
2
+ export declare function writeToMemory(content: string): number;
package/dist/wasm.js ADDED
@@ -0,0 +1,34 @@
1
+ // Auto-generated WASM binary (4,865 bytes / xai-faf-zig ghost engine)
2
+ // Contains estimate_tokens(ptr, len) -> u32
3
+ const WASM_BASE64 = "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AA4AAABLAxAAEwAAALABEAAQAAAAGgIQABYAAAAwBBAADQAAAJ0DEAAOAAAAsQMQAA0AAAB6AxAADgAAAMsCEAAQAAAAbAMQAA0AAAA+BBAACwAAAFYEEAAKAAAAvwMQAAwAAAAjAxAADAAAAP0DEAAVAAAAsQIQABEAAAAxAhAAEgAAAMEBEAARAAAAiQMQABMAAADoAhAAEgAAAPABEAARAAAAIQQQAA4AAABLAxAAEwAAALABEAAQAAAAGgIQABYAAAAwBBAADQAAAJ0DEAAOAAAAsQMQAA0AAAB6AxAADgAAAMsCEAAQAAAAbAMQAA0AAAA+BBAACwAAAFYEEAAKAAAACgMQABMAAACbAhAAFQAAAG4CEAAQAAAAAgIQABcAAAB/AhAAGwAAANwCEAALAAAA8QMQAAsAAABfAxAADAAAABMEEAANAAAA0wEQABwAAABWAhAAFwAAANsDEAAVAAAAc3RhY2sudWlfbGlicmFyeQBodW1hbl9jb250ZXh0LndoeQBtb25vcmVwby52ZXJzaW9uaW5nX3N0cmF0ZWd5AGh1bWFuX2NvbnRleHQuaG93AG1vbm9yZXBvLnBhY2thZ2VzX2NvdW50AHN0YWNrLnN0YXRlX21hbmFnZW1lbnQAaHVtYW5fY29udGV4dC53aGF0AGRldmVsb3BtZW50IHRlYW1zAG1vbm9yZXBvLnNoYXJlZF9jb25maWdzAHN0YWNrLndvcmtzcGFjZXMAbW9ub3JlcG8uYnVpbGRfb3JjaGVzdHJhdG9yAHN0YWNrLnBhY2thZ2VfbWFuYWdlcgBodW1hbl9jb250ZXh0LndobwB1bmtub3duAHN0YWNrLmNvbm5lY3Rpb24Ac3RhY2suYWRtaW4AaHVtYW5fY29udGV4dC53aGVuAGNsb3VkIHBsYXRmb3JtAHN0YWNrLm1vbm9yZXBvX3Rvb2wAbnVsbABwcm9qZWN0LmdvYWwAZGVzY3JpYmUgeW91ciBwcm9qZWN0IGdvYWwAc3RhY2suY3NzX2ZyYW1ld29yawBzdGFjay5zZWFyY2gAc3RhY2suaG9zdGluZwBzdGFjay5kYXRhYmFzZQBodW1hbl9jb250ZXh0LndoZXJlAHN0YWNrLmFwaV90eXBlAG5vbmUAc3RhY2sucnVudGltZQBwcm9qZWN0Lm5hbWUAbm90IGFwcGxpY2FibGUAbW9ub3JlcG8ucmVtb3RlX2NhY2hlAHN0YWNrLmNhY2hlAHByb2plY3QubWFpbl9sYW5ndWFnZQBzdGFjay5zdG9yYWdlAHN0YWNrLmZyb250ZW5kAHN0YWNrLmJhY2tlbmQAc3RhY2suYnVpbGQAc2xvdGlnbm9yZWQAc3RhY2suY2ljZABuL2EAOgAuACAJDQoiJwAgCSInAA0ACgAgCQAwAxAAGgAAAEQCEAARAAAA+wIQAA4AAAAeAxAABAAAAKwDEAAEAAAAwwIQAAcAAABhBBAAAwAAAMwDEAAOAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAA=";
4
+ const WASM_INPUT_OFFSET = 4096;
5
+ const encoder = new TextEncoder();
6
+ let _instance = null;
7
+ function decodeBase64(b64) {
8
+ if (typeof Buffer !== 'undefined') {
9
+ return new Uint8Array(Buffer.from(b64, 'base64'));
10
+ }
11
+ const binary = atob(b64);
12
+ const bytes = new Uint8Array(binary.length);
13
+ for (let i = 0; i < binary.length; i++) {
14
+ bytes[i] = binary.charCodeAt(i);
15
+ }
16
+ return bytes;
17
+ }
18
+ export function getInstance() {
19
+ if (_instance)
20
+ return _instance;
21
+ const bytes = decodeBase64(WASM_BASE64);
22
+ const module = new WebAssembly.Module(bytes);
23
+ _instance = new WebAssembly.Instance(module);
24
+ return _instance;
25
+ }
26
+ export function writeToMemory(content) {
27
+ const instance = getInstance();
28
+ const bytes = encoder.encode(content);
29
+ const memory = instance.exports.memory;
30
+ const buffer = new Uint8Array(memory.buffer);
31
+ const maxLen = Math.min(bytes.length, buffer.length - WASM_INPUT_OFFSET - 1);
32
+ buffer.set(bytes.subarray(0, maxLen), WASM_INPUT_OFFSET);
33
+ return maxLen;
34
+ }
package/package.json CHANGED
@@ -1,8 +1,36 @@
1
1
  {
2
2
  "name": "slash-tokens",
3
- "version": "0.0.1",
4
- "description": "Token budget estimation in 4.8 KB WASM. Sub-millisecond. Zero dependencies.",
3
+ "version": "0.2.0",
4
+ "description": "Token budget estimation in 4.8 KB WASM. Sub-millisecond. Zero dependencies. I don't count tokens. I slash them.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "type": "module",
9
+ "bin": {
10
+ "slash-tokens": "dist/cli.js"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.js",
15
+ "types": "./dist/index.d.ts"
16
+ }
17
+ },
18
+ "files": ["dist"],
19
+ "sideEffects": false,
20
+ "scripts": {
21
+ "build": "npx tsc && bun build src/cli.ts --outfile=dist/cli.js --target=node",
22
+ "test": "bun test"
23
+ },
5
24
  "author": "wolfejam",
6
25
  "license": "MIT",
7
- "keywords": ["tokens", "wasm", "edge", "budget", "estimation", "llm", "ai"]
26
+ "keywords": ["tokens", "wasm", "edge", "budget", "estimation", "llm", "ai", "openai", "anthropic", "claude", "grok", "gemini"],
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/Wolfe-Jam/slash-tokens"
30
+ },
31
+ "homepage": "https://slashtokens.com",
32
+ "devDependencies": {
33
+ "@types/node": "^25.5.0",
34
+ "typescript": "^6.0.2"
35
+ }
8
36
  }