slash-tokens 1.5.3 → 1.5.5

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/dist/cli.js CHANGED
@@ -90,6 +90,13 @@ var AI_PATTERNS = [
90
90
  { name: "Cohere", regex: /from\s+['"]cohere|CohereClient|cohere\.chat/g },
91
91
  { name: "Mistral", regex: /from\s+['"]@mistralai|MistralClient/g }
92
92
  ];
93
+ var SDK_REPRESENTATIVE_MODEL = {
94
+ Anthropic: "claude-sonnet",
95
+ OpenAI: "gpt-5.4",
96
+ Gemini: "gemini-3.1-pro",
97
+ Grok: "grok-4.20"
98
+ };
99
+ var UNKNOWN_SDK_REPRESENTATIVE_MODEL = "claude-sonnet";
93
100
  var SKIP_DIRS = new Set([
94
101
  "node_modules",
95
102
  ".git",
@@ -159,13 +166,15 @@ function findCallSites(filePath, content) {
159
166
  const endLine = Math.min(lines.length, lineNum + 20);
160
167
  const context = lines.slice(startLine, endLine).join(`
161
168
  `);
162
- const tokensPerCall = slash(context);
169
+ const estimatedModel = SDK_REPRESENTATIVE_MODEL[pattern.name] ?? UNKNOWN_SDK_REPRESENTATIVE_MODEL;
170
+ const tokensPerCall = slash(context, estimatedModel);
163
171
  if (!sites.some((s) => s.line === lineNum && s.sdk === pattern.name)) {
164
172
  sites.push({
165
173
  file: filePath,
166
174
  line: lineNum,
167
175
  sdk: pattern.name,
168
- tokensPerCall
176
+ tokensPerCall,
177
+ estimatedModel
169
178
  });
170
179
  }
171
180
  }
@@ -193,6 +202,24 @@ function scan(dir) {
193
202
  };
194
203
  }
195
204
 
205
+ // src/models.ts
206
+ var MODELS = {
207
+ "claude-opus": { input: 5, output: 25, context: 1e6 },
208
+ "claude-opus-4.7": { input: 5, output: 25, context: 1e6 },
209
+ "claude-sonnet": { input: 2, output: 10, context: 1e6 },
210
+ "claude-haiku": { input: 1, output: 5, context: 200000 },
211
+ "grok-4.20": { input: 1.25, output: 2.5, context: 1e6 },
212
+ "grok-4-1-fast": { input: 1.25, output: 2.5, context: 1e6 },
213
+ "gemini-3.1-pro": { input: 2, output: 12, context: 1e6 },
214
+ "gemini-2.5-flash": { input: 0.3, output: 2.5, context: 1e6 },
215
+ "gpt-5.4": { input: 2.5, output: 15, context: 1e6 },
216
+ "gpt-5.4-mini": { input: 0.75, output: 4.5, context: 128000 },
217
+ "gpt-5.4-nano": { input: 0.2, output: 1.25, context: 128000 }
218
+ };
219
+ function getModel(name) {
220
+ return MODELS[name] || MODELS[name.toLowerCase()];
221
+ }
222
+
196
223
  // src/report.ts
197
224
  var R = "\x1B[0m";
198
225
  var B = "\x1B[1m";
@@ -231,9 +258,21 @@ function printReport(sites, filesScanned, timeMs, cwd) {
231
258
  const dailyInput = sites.reduce((sum, s) => sum + s.tokensPerCall * callsPerDay, 0);
232
259
  const dailyOutput = dailyInput * 2;
233
260
  const dailyTotal = dailyInput + dailyOutput;
234
- const monthlyTotal = dailyTotal * 30;
235
- const monthlyCostInput = dailyInput * 30 / 1e6 * 3;
236
- const monthlyCostOutput = dailyOutput * 30 / 1e6 * 15;
261
+ let monthlyCostInput = 0;
262
+ let monthlyCostOutput = 0;
263
+ let unknownProviderSites = 0;
264
+ for (const site of sites) {
265
+ const info = getModel(site.estimatedModel);
266
+ if (!info)
267
+ continue;
268
+ if (site.estimatedModel === UNKNOWN_SDK_REPRESENTATIVE_MODEL && !["Anthropic"].includes(site.sdk)) {
269
+ unknownProviderSites++;
270
+ }
271
+ const siteDailyInput = site.tokensPerCall * callsPerDay;
272
+ const siteDailyOutput = siteDailyInput * 2;
273
+ monthlyCostInput += siteDailyInput * 30 / 1e6 * info.input;
274
+ monthlyCostOutput += siteDailyOutput * 30 / 1e6 * info.output;
275
+ }
237
276
  const monthlyCost = monthlyCostInput + monthlyCostOutput;
238
277
  console.log(`${ORANGE} DAILY TOKEN VOLUME ${GRAY}(${callsPerDay} calls/site/day)${R}`);
239
278
  console.log(`${DIM} ${"─".repeat(60)}${R}`);
@@ -241,11 +280,14 @@ function printReport(sites, filesScanned, timeMs, cwd) {
241
280
  console.log(`${WHITE} Output tokens: ${B}${formatNum(dailyOutput)}${R} ${GRAY}(estimated)${R}`);
242
281
  console.log(`${WHITE} Total: ${ORANGE}${B}${formatNum(dailyTotal)} tokens/day${R}`);
243
282
  console.log("");
244
- console.log(`${ORANGE} MONTHLY COST ${GRAY}($3/$15 per MTok)${R}`);
283
+ console.log(`${ORANGE} MONTHLY COST ${GRAY}(real per-provider pricing)${R}`);
245
284
  console.log(`${DIM} ${"─".repeat(60)}${R}`);
246
285
  console.log(`${WHITE} Input: ${B}$${monthlyCostInput.toFixed(2)}/mo${R}`);
247
286
  console.log(`${WHITE} Output: ${B}$${monthlyCostOutput.toFixed(2)}/mo${R}`);
248
287
  console.log(`${WHITE} Total: ${ORANGE}${B}$${monthlyCost.toFixed(2)}/mo${R}`);
288
+ if (unknownProviderSites > 0) {
289
+ console.log(`${GRAY} (${unknownProviderSites} call site${unknownProviderSites === 1 ? "" : "s"} use an assumed price — the detected SDK doesn't reveal the exact provider)${R}`);
290
+ }
249
291
  console.log("");
250
292
  const recoveryPct = 10;
251
293
  const monthlyRecovered = monthlyCost * recoveryPct / 100;
package/dist/models.js CHANGED
@@ -1,4 +1,10 @@
1
1
  // Pricing as of April 2026 — USD per million tokens
2
+ // Claude Sonnet 5 pricing corrected 2026-08-23: was hardcoded at $3.00/
3
+ // $15.00 (the previously-scheduled Sept 1, 2026 increase), but Anthropic's
4
+ // own pricing page confirms that increase will NOT occur — $2.00/$10.00
5
+ // (the introductory rate) is now the permanent standard price. Found via
6
+ // a full-codebase review that independently re-verified every provider's
7
+ // live pricing, not just the model IDs already fixed that day.
2
8
  // xAI pricing re-derived 2026-08-23: grok-4.20 and grok-4-1-fast (the literal
3
9
  // API IDs, not just the generic keys below) were both fully retired — not
4
10
  // just old snapshots, they 404 on the live API. Current lineup has no cheap
@@ -12,7 +18,7 @@ export const MODELS = {
12
18
  // Anthropic
13
19
  'claude-opus': { input: 5.00, output: 25.00, context: 1000000 },
14
20
  'claude-opus-4.7': { input: 5.00, output: 25.00, context: 1000000 },
15
- 'claude-sonnet': { input: 3.00, output: 15.00, context: 1000000 },
21
+ 'claude-sonnet': { input: 2.00, output: 10.00, context: 1000000 },
16
22
  'claude-haiku': { input: 1.00, output: 5.00, context: 200000 },
17
23
  // xAI
18
24
  'grok-4.20': { input: 1.25, output: 2.50, context: 1000000 },
@@ -3,5 +3,7 @@ export interface Pattern {
3
3
  regex: RegExp;
4
4
  }
5
5
  export declare const AI_PATTERNS: Pattern[];
6
+ export declare const SDK_REPRESENTATIVE_MODEL: Record<string, string>;
7
+ export declare const UNKNOWN_SDK_REPRESENTATIVE_MODEL = "claude-sonnet";
6
8
  export declare const SKIP_DIRS: Set<string>;
7
9
  export declare const SCAN_EXTENSIONS: Set<string>;
package/dist/patterns.js CHANGED
@@ -20,6 +20,31 @@ export const AI_PATTERNS = [
20
20
  // Mistral
21
21
  { name: 'Mistral', regex: /from\s+['"]@mistralai|MistralClient/g },
22
22
  ];
23
+ // Representative model per detected SDK — the scanner only ever sees an
24
+ // SDK name (a regex match on import/call syntax), never the actual model
25
+ // string passed at runtime, so there's no way to know the exact model a
26
+ // found call site uses. Fixed 2026-08-23: this used to mean every SDK got
27
+ // the raw, uncalibrated token estimate AND one flat hardcoded $3/$15
28
+ // "GPT-4o class" price regardless of provider — the product's own
29
+ // top-of-funnel "here's what you'd save" numbers were both uncalibrated
30
+ // and mispriced for every provider except whichever one happened to match
31
+ // that flat rate by coincidence. Mapping each identified SDK to one real,
32
+ // representative model lets the scanner apply that model's real
33
+ // calibration factor and real price — still an approximation (we don't
34
+ // know the exact model), but a real per-provider one instead of a single
35
+ // guess applied to everyone.
36
+ export const SDK_REPRESENTATIVE_MODEL = {
37
+ 'Anthropic': 'claude-sonnet',
38
+ 'OpenAI': 'gpt-5.4',
39
+ 'Gemini': 'gemini-3.1-pro',
40
+ 'Grok': 'grok-4.20',
41
+ };
42
+ // Fallback for SDKs that don't map to one specific provider (Vercel AI,
43
+ // LangChain, and Bedrock can all wrap any underlying provider; raw
44
+ // fetch-to-AI-endpoint and Cohere/Mistral have no pricing data in MODELS
45
+ // at all). claude-sonnet is used as a documented, honest middle-of-the-
46
+ // road placeholder — not a claim about which model is actually running.
47
+ export const UNKNOWN_SDK_REPRESENTATIVE_MODEL = 'claude-sonnet';
23
48
  export const SKIP_DIRS = new Set([
24
49
  'node_modules', '.git', 'dist', 'build', '.next', '.nuxt', '.svelte-kit',
25
50
  'coverage', '.turbo', '.cache', '__pycache__', '.venv', 'venv',
package/dist/report.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { getModel } from './models.js';
2
+ import { UNKNOWN_SDK_REPRESENTATIVE_MODEL } from './patterns.js';
1
3
  // ANSI — zero deps
2
4
  const R = '\x1b[0m';
3
5
  const B = '\x1b[1m';
@@ -38,10 +40,26 @@ export function printReport(sites, filesScanned, timeMs, cwd) {
38
40
  const dailyInput = sites.reduce((sum, s) => sum + s.tokensPerCall * callsPerDay, 0);
39
41
  const dailyOutput = dailyInput * 2; // Estimate output at 2x input
40
42
  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;
43
+ // Cost per site using its own representative model's REAL current price
44
+ // (see patterns.ts SDK_REPRESENTATIVE_MODEL) — fixed 2026-08-23: this
45
+ // used to be one flat hardcoded $3/$15 "GPT-4o class" rate applied to
46
+ // every site regardless of detected provider, understating cost by up
47
+ // to ~67% for premium providers and overstating it for cheap ones.
48
+ let monthlyCostInput = 0;
49
+ let monthlyCostOutput = 0;
50
+ let unknownProviderSites = 0;
51
+ for (const site of sites) {
52
+ const info = getModel(site.estimatedModel);
53
+ if (!info)
54
+ continue; // shouldn't happen — estimatedModel always maps to a real MODELS entry
55
+ if (site.estimatedModel === UNKNOWN_SDK_REPRESENTATIVE_MODEL && !['Anthropic'].includes(site.sdk)) {
56
+ unknownProviderSites++;
57
+ }
58
+ const siteDailyInput = site.tokensPerCall * callsPerDay;
59
+ const siteDailyOutput = siteDailyInput * 2;
60
+ monthlyCostInput += (siteDailyInput * 30 / 1000000) * info.input;
61
+ monthlyCostOutput += (siteDailyOutput * 30 / 1000000) * info.output;
62
+ }
45
63
  const monthlyCost = monthlyCostInput + monthlyCostOutput;
46
64
  console.log(`${ORANGE} DAILY TOKEN VOLUME ${GRAY}(${callsPerDay} calls/site/day)${R}`);
47
65
  console.log(`${DIM} ${'─'.repeat(60)}${R}`);
@@ -49,11 +67,14 @@ export function printReport(sites, filesScanned, timeMs, cwd) {
49
67
  console.log(`${WHITE} Output tokens: ${B}${formatNum(dailyOutput)}${R} ${GRAY}(estimated)${R}`);
50
68
  console.log(`${WHITE} Total: ${ORANGE}${B}${formatNum(dailyTotal)} tokens/day${R}`);
51
69
  console.log('');
52
- console.log(`${ORANGE} MONTHLY COST ${GRAY}($3/$15 per MTok)${R}`);
70
+ console.log(`${ORANGE} MONTHLY COST ${GRAY}(real per-provider pricing)${R}`);
53
71
  console.log(`${DIM} ${'─'.repeat(60)}${R}`);
54
72
  console.log(`${WHITE} Input: ${B}$${monthlyCostInput.toFixed(2)}/mo${R}`);
55
73
  console.log(`${WHITE} Output: ${B}$${monthlyCostOutput.toFixed(2)}/mo${R}`);
56
74
  console.log(`${WHITE} Total: ${ORANGE}${B}$${monthlyCost.toFixed(2)}/mo${R}`);
75
+ if (unknownProviderSites > 0) {
76
+ console.log(`${GRAY} (${unknownProviderSites} call site${unknownProviderSites === 1 ? '' : 's'} use an assumed price — the detected SDK doesn't reveal the exact provider)${R}`);
77
+ }
57
78
  console.log('');
58
79
  // Slash — sunk cost recovery (conservative 10% gate efficiency)
59
80
  const recoveryPct = 10;
package/dist/scanner.d.ts CHANGED
@@ -3,6 +3,10 @@ export interface CallSite {
3
3
  line: number;
4
4
  sdk: string;
5
5
  tokensPerCall: number;
6
+ /** The representative model used to calibrate/price this site — see
7
+ * patterns.ts SDK_REPRESENTATIVE_MODEL. Not the actual runtime model
8
+ * (the scanner can't know that from source alone). */
9
+ estimatedModel: string;
6
10
  }
7
11
  export declare function scan(dir: string): {
8
12
  sites: CallSite[];
package/dist/scanner.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readdirSync, readFileSync, statSync } from 'fs';
2
2
  import { join, extname } from 'path';
3
3
  import { slash } from './slash.js';
4
- import { AI_PATTERNS, SKIP_DIRS, SCAN_EXTENSIONS } from './patterns.js';
4
+ import { AI_PATTERNS, SKIP_DIRS, SCAN_EXTENSIONS, SDK_REPRESENTATIVE_MODEL, UNKNOWN_SDK_REPRESENTATIVE_MODEL, } from './patterns.js';
5
5
  function walkDir(dir, files) {
6
6
  let entries;
7
7
  try {
@@ -44,7 +44,8 @@ function findCallSites(filePath, content) {
44
44
  const startLine = Math.max(0, lineNum - 5);
45
45
  const endLine = Math.min(lines.length, lineNum + 20);
46
46
  const context = lines.slice(startLine, endLine).join('\n');
47
- const tokensPerCall = slash(context);
47
+ const estimatedModel = SDK_REPRESENTATIVE_MODEL[pattern.name] ?? UNKNOWN_SDK_REPRESENTATIVE_MODEL;
48
+ const tokensPerCall = slash(context, estimatedModel);
48
49
  // Avoid duplicates at same location
49
50
  if (!sites.some(s => s.line === lineNum && s.sdk === pattern.name)) {
50
51
  sites.push({
@@ -52,6 +53,7 @@ function findCallSites(filePath, content) {
52
53
  line: lineNum,
53
54
  sdk: pattern.name,
54
55
  tokensPerCall,
56
+ estimatedModel,
55
57
  });
56
58
  }
57
59
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slash-tokens",
3
- "version": "1.5.3",
3
+ "version": "1.5.5",
4
4
  "description": "Token Optimization for Context Engineers. 4.8 KB WASM. Sub-millisecond. Zero dependencies.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",