slash-tokens 0.1.0 → 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 +9 -92
- package/dist/auto.d.ts +1 -0
- package/dist/auto.js +9 -0
- package/dist/cli.js +87 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +18 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +11 -57
- package/dist/intercept.d.ts +11 -0
- package/dist/intercept.js +120 -0
- package/dist/models.d.ts +8 -0
- package/dist/models.js +24 -0
- package/dist/patterns.js +30 -0
- package/dist/preflight.d.ts +16 -0
- package/dist/preflight.js +27 -0
- package/dist/report.d.ts +1 -1
- package/dist/report.js +69 -0
- package/dist/scanner.js +81 -0
- package/dist/slash.js +26 -0
- package/dist/transact.d.ts +15 -0
- package/dist/transact.js +28 -0
- package/dist/wasm.js +34 -0
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# /slash-tokens
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Pre-flight checks for API calls. 4.8 KB WASM. Sub-millisecond. Zero dependencies.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Fly First. Pay Economy.**
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -15,106 +15,23 @@ npm install slash-tokens
|
|
|
15
15
|
```js
|
|
16
16
|
import { slash } from 'slash-tokens'
|
|
17
17
|
|
|
18
|
-
const tokens = slash(
|
|
18
|
+
const tokens = slash(prompt)
|
|
19
19
|
|
|
20
|
-
if (tokens >
|
|
21
|
-
|
|
20
|
+
if (tokens > contextWindow) trim(prompt)
|
|
21
|
+
if (tokens < cheapThreshold) useHaiku()
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
One function. Returns a
|
|
24
|
+
One function. Returns a token estimate. Sub-millisecond. Any model.
|
|
25
25
|
|
|
26
|
-
##
|
|
26
|
+
## Evaluate a repo
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
bunx slash-tokens
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
## Docs
|
|
33
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).
|
|
34
|
+
Everything is at **[slashtokens.com](https://slashtokens.com)**
|
|
118
35
|
|
|
119
36
|
## License
|
|
120
37
|
|
package/dist/auto.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/auto.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { patchFetch, onIntercept } from './intercept.js';
|
|
2
|
+
// Auto mode: import this file and every LLM call is pre-flighted.
|
|
3
|
+
// Usage: import 'slash-tokens/auto'
|
|
4
|
+
patchFetch();
|
|
5
|
+
// Default handler: log to console
|
|
6
|
+
onIntercept((event) => {
|
|
7
|
+
const status = event.fits ? 'OK' : 'OVER LIMIT';
|
|
8
|
+
console.log(`[slash] ${event.provider} ${event.model} | ${event.tokens.toLocaleString()} tokens | $${event.cost.toFixed(4)} | ${status}`);
|
|
9
|
+
});
|
package/dist/cli.js
CHANGED
|
@@ -230,7 +230,94 @@ function printReport(sites, filesScanned, timeMs, cwd) {
|
|
|
230
230
|
console.log("");
|
|
231
231
|
}
|
|
232
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
|
+
|
|
233
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 });
|
|
234
288
|
var cwd = process.cwd();
|
|
235
289
|
var { sites, filesScanned, timeMs } = scan(cwd);
|
|
236
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
|
+
}
|
package/dist/config.d.ts
ADDED
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
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
export { slash, slashBytes } from './slash';
|
|
1
|
+
export { slash, slashBytes } from './slash.js';
|
|
2
|
+
export { preflight } from './preflight.js';
|
|
3
|
+
export type { PreflightResult, Alternative } from './preflight.js';
|
|
4
|
+
export { MODELS, listModels } from './models.js';
|
|
5
|
+
export type { ModelInfo } from './models.js';
|
|
6
|
+
export { report } from './transact.js';
|
|
7
|
+
export type { ReportOptions, ReportResult } from './transact.js';
|
|
8
|
+
export { init } from './config.js';
|
|
9
|
+
export { resolveKey } from './config.js';
|
package/dist/index.js
CHANGED
|
@@ -1,57 +1,11 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
for (let i = 0;i < binary.length; i++) {
|
|
13
|
-
bytes[i] = binary.charCodeAt(i);
|
|
14
|
-
}
|
|
15
|
-
return bytes;
|
|
16
|
-
}
|
|
17
|
-
function getInstance() {
|
|
18
|
-
if (_instance)
|
|
19
|
-
return _instance;
|
|
20
|
-
const bytes = decodeBase64(WASM_BASE64);
|
|
21
|
-
const module = new WebAssembly.Module(bytes);
|
|
22
|
-
_instance = new WebAssembly.Instance(module);
|
|
23
|
-
return _instance;
|
|
24
|
-
}
|
|
25
|
-
function writeToMemory(content) {
|
|
26
|
-
const instance = getInstance();
|
|
27
|
-
const bytes = encoder.encode(content);
|
|
28
|
-
const memory = instance.exports.memory;
|
|
29
|
-
const buffer = new Uint8Array(memory.buffer);
|
|
30
|
-
const maxLen = Math.min(bytes.length, buffer.length - WASM_INPUT_OFFSET - 1);
|
|
31
|
-
buffer.set(bytes.subarray(0, maxLen), WASM_INPUT_OFFSET);
|
|
32
|
-
return maxLen;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// src/slash.ts
|
|
36
|
-
var WASM_INPUT_OFFSET2 = 4096;
|
|
37
|
-
function slash(content) {
|
|
38
|
-
if (!content)
|
|
39
|
-
return 0;
|
|
40
|
-
const len = writeToMemory(content);
|
|
41
|
-
const instance = getInstance();
|
|
42
|
-
return instance.exports.estimate_tokens(WASM_INPUT_OFFSET2, len);
|
|
43
|
-
}
|
|
44
|
-
function slashBytes(bytes) {
|
|
45
|
-
if (!bytes.length)
|
|
46
|
-
return 0;
|
|
47
|
-
const instance = getInstance();
|
|
48
|
-
const memory = instance.exports.memory;
|
|
49
|
-
const buffer = new Uint8Array(memory.buffer);
|
|
50
|
-
const maxLen = Math.min(bytes.length, buffer.length - WASM_INPUT_OFFSET2 - 1);
|
|
51
|
-
buffer.set(bytes.subarray(0, maxLen), WASM_INPUT_OFFSET2);
|
|
52
|
-
return instance.exports.estimate_tokens(WASM_INPUT_OFFSET2, maxLen);
|
|
53
|
-
}
|
|
54
|
-
export {
|
|
55
|
-
slashBytes,
|
|
56
|
-
slash
|
|
57
|
-
};
|
|
1
|
+
// Core estimation
|
|
2
|
+
export { slash, slashBytes } from './slash.js';
|
|
3
|
+
// Pre-flight checks
|
|
4
|
+
export { preflight } from './preflight.js';
|
|
5
|
+
// Model intelligence
|
|
6
|
+
export { MODELS, listModels } from './models.js';
|
|
7
|
+
// Transaction reporting
|
|
8
|
+
export { report } from './transact.js';
|
|
9
|
+
// Configuration
|
|
10
|
+
export { init } from './config.js';
|
|
11
|
+
export { resolveKey } from './config.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface InterceptEvent {
|
|
2
|
+
endpoint: string;
|
|
3
|
+
provider: string;
|
|
4
|
+
model: string;
|
|
5
|
+
tokens: number;
|
|
6
|
+
cost: number;
|
|
7
|
+
fits: boolean;
|
|
8
|
+
timestamp: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function onIntercept(handler: (event: InterceptEvent) => void): void;
|
|
11
|
+
export declare function patchFetch(): void;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { slash } from './slash.js';
|
|
2
|
+
import { getModel } from './models.js';
|
|
3
|
+
// AI API endpoint detection — same patterns as scanner, runtime version
|
|
4
|
+
const AI_ENDPOINTS = [
|
|
5
|
+
{
|
|
6
|
+
pattern: /api\.anthropic\.com/,
|
|
7
|
+
provider: 'Anthropic',
|
|
8
|
+
modelExtractor: (body) => body?.model || 'claude-sonnet',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
pattern: /api\.openai\.com/,
|
|
12
|
+
provider: 'OpenAI',
|
|
13
|
+
modelExtractor: (body) => body?.model || 'gpt-4o',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
pattern: /generativelanguage\.googleapis\.com/,
|
|
17
|
+
provider: 'Google',
|
|
18
|
+
modelExtractor: (body) => 'gemini-2.0-flash',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
pattern: /api\.x\.ai/,
|
|
22
|
+
provider: 'xAI',
|
|
23
|
+
modelExtractor: (body) => body?.model || 'grok-3',
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
// Normalize model names to our pricing table keys
|
|
27
|
+
function normalizeModel(raw) {
|
|
28
|
+
const lower = raw.toLowerCase();
|
|
29
|
+
if (lower.includes('opus'))
|
|
30
|
+
return 'claude-opus';
|
|
31
|
+
if (lower.includes('sonnet'))
|
|
32
|
+
return 'claude-sonnet';
|
|
33
|
+
if (lower.includes('haiku'))
|
|
34
|
+
return 'claude-haiku';
|
|
35
|
+
if (lower.includes('grok') && lower.includes('mini'))
|
|
36
|
+
return 'grok-3-mini';
|
|
37
|
+
if (lower.includes('grok'))
|
|
38
|
+
return 'grok-3';
|
|
39
|
+
if (lower.includes('gemini') && lower.includes('pro'))
|
|
40
|
+
return 'gemini-2.0-pro';
|
|
41
|
+
if (lower.includes('gemini'))
|
|
42
|
+
return 'gemini-2.0-flash';
|
|
43
|
+
if (lower.includes('4o-mini') || lower.includes('4o_mini'))
|
|
44
|
+
return 'gpt-4o-mini';
|
|
45
|
+
if (lower.includes('4o'))
|
|
46
|
+
return 'gpt-4o';
|
|
47
|
+
if (lower.includes('4.1') && lower.includes('nano'))
|
|
48
|
+
return 'gpt-4.1-nano';
|
|
49
|
+
if (lower.includes('4.1') && lower.includes('mini'))
|
|
50
|
+
return 'gpt-4.1-mini';
|
|
51
|
+
if (lower.includes('4.1'))
|
|
52
|
+
return 'gpt-4.1';
|
|
53
|
+
return raw;
|
|
54
|
+
}
|
|
55
|
+
// Extract message content from request body
|
|
56
|
+
function extractContent(body) {
|
|
57
|
+
if (!body)
|
|
58
|
+
return '';
|
|
59
|
+
// Anthropic: { messages: [{ content: "..." }] }
|
|
60
|
+
// OpenAI: { messages: [{ content: "..." }] }
|
|
61
|
+
if (body.messages && Array.isArray(body.messages)) {
|
|
62
|
+
return body.messages
|
|
63
|
+
.map((m) => typeof m.content === 'string' ? m.content : JSON.stringify(m.content))
|
|
64
|
+
.join('\n');
|
|
65
|
+
}
|
|
66
|
+
// Anthropic: { prompt: "..." }
|
|
67
|
+
if (typeof body.prompt === 'string')
|
|
68
|
+
return body.prompt;
|
|
69
|
+
// Google: { contents: [{ parts: [{ text: "..." }] }] }
|
|
70
|
+
if (body.contents && Array.isArray(body.contents)) {
|
|
71
|
+
return body.contents
|
|
72
|
+
.flatMap((c) => c.parts || [])
|
|
73
|
+
.map((p) => p.text || '')
|
|
74
|
+
.join('\n');
|
|
75
|
+
}
|
|
76
|
+
return JSON.stringify(body);
|
|
77
|
+
}
|
|
78
|
+
let _onIntercept = null;
|
|
79
|
+
export function onIntercept(handler) {
|
|
80
|
+
_onIntercept = handler;
|
|
81
|
+
}
|
|
82
|
+
export function patchFetch() {
|
|
83
|
+
const originalFetch = globalThis.fetch;
|
|
84
|
+
globalThis.fetch = async function slashFetch(input, init) {
|
|
85
|
+
const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
|
|
86
|
+
// Check if this is an AI API call
|
|
87
|
+
const match = AI_ENDPOINTS.find(ep => ep.pattern.test(url));
|
|
88
|
+
if (match && init?.body) {
|
|
89
|
+
try {
|
|
90
|
+
const bodyStr = typeof init.body === 'string' ? init.body : undefined;
|
|
91
|
+
if (bodyStr) {
|
|
92
|
+
const body = JSON.parse(bodyStr);
|
|
93
|
+
const content = extractContent(body);
|
|
94
|
+
const rawModel = match.modelExtractor(body);
|
|
95
|
+
const model = normalizeModel(rawModel);
|
|
96
|
+
const tokens = slash(content);
|
|
97
|
+
const info = getModel(model);
|
|
98
|
+
const cost = info ? Math.round(((tokens / 1000000) * info.input) * 1000000) / 1000000 : 0;
|
|
99
|
+
const fits = info ? tokens <= info.context : true;
|
|
100
|
+
const event = {
|
|
101
|
+
endpoint: url,
|
|
102
|
+
provider: match.provider,
|
|
103
|
+
model,
|
|
104
|
+
tokens,
|
|
105
|
+
cost,
|
|
106
|
+
fits,
|
|
107
|
+
timestamp: new Date().toISOString(),
|
|
108
|
+
};
|
|
109
|
+
if (_onIntercept) {
|
|
110
|
+
_onIntercept(event);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Non-blocking — never break the actual API call
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return originalFetch(input, init);
|
|
119
|
+
};
|
|
120
|
+
}
|
package/dist/models.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface ModelInfo {
|
|
2
|
+
input: number;
|
|
3
|
+
output: number;
|
|
4
|
+
context: number;
|
|
5
|
+
}
|
|
6
|
+
export declare const MODELS: Record<string, ModelInfo>;
|
|
7
|
+
export declare function getModel(name: string): ModelInfo | undefined;
|
|
8
|
+
export declare function listModels(): string[];
|
package/dist/models.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const MODELS = {
|
|
2
|
+
// Anthropic
|
|
3
|
+
'claude-opus': { input: 15.00, output: 75.00, context: 200000 },
|
|
4
|
+
'claude-sonnet': { input: 3.00, output: 15.00, context: 200000 },
|
|
5
|
+
'claude-haiku': { input: 0.25, output: 1.25, context: 200000 },
|
|
6
|
+
// xAI
|
|
7
|
+
'grok-3': { input: 5.00, output: 15.00, context: 131072 },
|
|
8
|
+
'grok-3-mini': { input: 0.30, output: 0.50, context: 131072 },
|
|
9
|
+
// Google
|
|
10
|
+
'gemini-2.0-flash': { input: 0.10, output: 0.40, context: 1048576 },
|
|
11
|
+
'gemini-2.0-pro': { input: 1.25, output: 5.00, context: 1048576 },
|
|
12
|
+
// OpenAI
|
|
13
|
+
'gpt-4o': { input: 2.50, output: 10.00, context: 128000 },
|
|
14
|
+
'gpt-4o-mini': { input: 0.15, output: 0.60, context: 128000 },
|
|
15
|
+
'gpt-4.1': { input: 2.00, output: 8.00, context: 1047576 },
|
|
16
|
+
'gpt-4.1-mini': { input: 0.40, output: 1.60, context: 1047576 },
|
|
17
|
+
'gpt-4.1-nano': { input: 0.10, output: 0.40, context: 1047576 },
|
|
18
|
+
};
|
|
19
|
+
export function getModel(name) {
|
|
20
|
+
return MODELS[name] || MODELS[name.toLowerCase()];
|
|
21
|
+
}
|
|
22
|
+
export function listModels() {
|
|
23
|
+
return Object.keys(MODELS);
|
|
24
|
+
}
|
package/dist/patterns.js
ADDED
|
@@ -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,16 @@
|
|
|
1
|
+
export interface Alternative {
|
|
2
|
+
model: string;
|
|
3
|
+
cost: number;
|
|
4
|
+
savings: number;
|
|
5
|
+
savingsPercent: number;
|
|
6
|
+
}
|
|
7
|
+
export interface PreflightResult {
|
|
8
|
+
tokens: number;
|
|
9
|
+
cost: number;
|
|
10
|
+
fits: boolean;
|
|
11
|
+
model: string;
|
|
12
|
+
context: number;
|
|
13
|
+
utilization: number;
|
|
14
|
+
options: Alternative[];
|
|
15
|
+
}
|
|
16
|
+
export declare function preflight(content: string, model: string): PreflightResult;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { slash } from './slash.js';
|
|
2
|
+
import { getModel, MODELS } from './models.js';
|
|
3
|
+
export function preflight(content, model) {
|
|
4
|
+
const tokens = slash(content);
|
|
5
|
+
const info = getModel(model);
|
|
6
|
+
if (!info) {
|
|
7
|
+
throw new Error(`Unknown model: "${model}". Available: ${Object.keys(MODELS).join(', ')}`);
|
|
8
|
+
}
|
|
9
|
+
const cost = Math.round(((tokens / 1000000) * info.input) * 1000000) / 1000000;
|
|
10
|
+
const fits = tokens <= info.context;
|
|
11
|
+
const utilization = Math.round((tokens / info.context) * 10000) / 10000;
|
|
12
|
+
const options = Object.entries(MODELS)
|
|
13
|
+
.filter(([m]) => m !== model)
|
|
14
|
+
.filter(([_, v]) => v.context >= tokens)
|
|
15
|
+
.map(([m, v]) => {
|
|
16
|
+
const altCost = Math.round(((tokens / 1000000) * v.input) * 1000000) / 1000000;
|
|
17
|
+
return {
|
|
18
|
+
model: m,
|
|
19
|
+
cost: altCost,
|
|
20
|
+
savings: Math.round((cost - altCost) * 1000000) / 1000000,
|
|
21
|
+
savingsPercent: cost > 0 ? Math.round(((cost - altCost) / cost) * 10000) / 100 : 0,
|
|
22
|
+
};
|
|
23
|
+
})
|
|
24
|
+
.filter(o => o.savings > 0)
|
|
25
|
+
.sort((a, b) => a.cost - b.cost);
|
|
26
|
+
return { tokens, cost, fits, model, context: info.context, utilization, options };
|
|
27
|
+
}
|
package/dist/report.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { CallSite } from './scanner';
|
|
1
|
+
import { CallSite } from './scanner.js';
|
|
2
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
|
+
}
|
package/dist/scanner.js
ADDED
|
@@ -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
|
+
}
|
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>;
|
package/dist/transact.js
ADDED
|
@@ -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.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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slash-tokens",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Pre-flight checks for API calls. 4.8 KB WASM. Sub-millisecond. Zero dependencies. Fly First. Pay Economy.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -13,12 +13,16 @@
|
|
|
13
13
|
".": {
|
|
14
14
|
"import": "./dist/index.js",
|
|
15
15
|
"types": "./dist/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./auto": {
|
|
18
|
+
"import": "./dist/auto.js",
|
|
19
|
+
"types": "./dist/auto.d.ts"
|
|
16
20
|
}
|
|
17
21
|
},
|
|
18
22
|
"files": ["dist"],
|
|
19
23
|
"sideEffects": false,
|
|
20
24
|
"scripts": {
|
|
21
|
-
"build": "
|
|
25
|
+
"build": "npx tsc && bun build src/cli.ts --outfile=dist/cli.js --target=node",
|
|
22
26
|
"test": "bun test"
|
|
23
27
|
},
|
|
24
28
|
"author": "wolfejam",
|