@tokenzero/cli 0.2.9 → 0.2.11
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/bin/tokenzero.mjs +57 -4
- package/package.json +1 -1
package/bin/tokenzero.mjs
CHANGED
|
@@ -25,6 +25,9 @@ var PRICES = {
|
|
|
25
25
|
"gpt-5.6-luna": P("openai", "fast", 0.2, 1.2, 0.02, 0.25, { longCtxAt: 272e3, longCtx: { input: 0.4, output: 1.8, cachedInput: 0.04, cacheWrite: 0.5 }, minCacheTokens: 1024 }),
|
|
26
26
|
// Daybreak cyber model: access needs OpenAI approval; 400K window
|
|
27
27
|
"gpt-5.6-cyber": P("openai", "frontier", 12.5, 75, 1.25, 15.625, { longCtxAt: 272e3, longCtx: { input: 25, output: 112.5, cachedInput: 2.5, cacheWrite: 31.25 }, minCacheTokens: 1024 }),
|
|
28
|
+
// 2026-11-01 (developers.openai.com/api/docs/pricing): Astra for Law at Astra rates (promo), Daybreak gpt-5.5-cyber.
|
|
29
|
+
"gpt-6-astra-law": P("openai", "frontier", 10, 50, 1, void 0, { minCacheTokens: 1024 }),
|
|
30
|
+
"gpt-5.5-cyber": P("openai", "frontier", 12.5, 75),
|
|
28
31
|
"gpt-5.3-codex": P("openai", "mid", 1.75, 14, 0.175, void 0, { minCacheTokens: 1024 }),
|
|
29
32
|
"gpt-5.5": P("openai", "frontier", 5, 30, 0.5, void 0, { fastFactor: 2.5, longCtxAt: 272e3, longCtx: { input: 10, output: 45, cachedInput: 1 }, minCacheTokens: 1024 }),
|
|
30
33
|
"gpt-5.5-pro": P("openai", "frontier", 30, 180),
|
|
@@ -44,6 +47,13 @@ var PRICES = {
|
|
|
44
47
|
"o3-mini": P("openai", "fast", 1.1, 4.4, 0.55, void 0, { minCacheTokens: 1024 }),
|
|
45
48
|
"o4-mini": P("openai", "fast", 1.1, 4.4, 0.275, void 0, { minCacheTokens: 1024 }),
|
|
46
49
|
"o1": P("openai", "frontier", 15, 60, 7.5),
|
|
50
|
+
"o1-pro": P("openai", "frontier", 150, 600),
|
|
51
|
+
// Legacy models still on the pricing page: audited repos use them, so receipts and findings price them correctly
|
|
52
|
+
// instead of falling back to the nearest prefix row (gpt-4o-2024-05-13 is $5/$15, not gpt-4o's $2.50/$10).
|
|
53
|
+
"gpt-4o-2024-05-13": P("openai", "mid", 5, 15),
|
|
54
|
+
"gpt-4-turbo": P("openai", "frontier", 10, 30),
|
|
55
|
+
"gpt-4-0613": P("openai", "frontier", 30, 60),
|
|
56
|
+
"gpt-3.5-turbo": P("openai", "fast", 0.5, 1.5),
|
|
47
57
|
"gpt-4.1": P("openai", "mid", 2, 8, 0.5, void 0, { fastFactor: 1.75, minCacheTokens: 1024 }),
|
|
48
58
|
"gpt-4.1-mini": P("openai", "fast", 0.4, 1.6, 0.1, void 0, { minCacheTokens: 1024 }),
|
|
49
59
|
"gpt-4.1-nano": P("openai", "fast", 0.1, 0.4, 0.025, void 0, { minCacheTokens: 1024 }),
|
|
@@ -87,7 +97,7 @@ var PRICES = {
|
|
|
87
97
|
"deepseek-v4-pro": P("deepseek", "mid", 1.32, 3.96, 0.044),
|
|
88
98
|
"deepseek-flash": P("deepseek", "fast", 0.3, 1.2, 6e-3)
|
|
89
99
|
};
|
|
90
|
-
var MODEL_ALIASES = { "deepseek-v4-flash": "deepseek-flash", "deepseek-v4-flash-vision-exp": "deepseek-flash" };
|
|
100
|
+
var MODEL_ALIASES = { "deepseek-v4-flash": "deepseek-flash", "deepseek-v4-flash-vision-exp": "deepseek-flash", "deepseek-v4.1-flash": "deepseek-flash", "deepseek-v4.1-flash-1017": "deepseek-flash", "deepseek-v4-pro-0813": "deepseek-v4-pro", "gpt-4-turbo-2024-04-09": "gpt-4-turbo", "gpt-4": "gpt-4-0613", "gpt-3.5-turbo-0125": "gpt-3.5-turbo" };
|
|
91
101
|
var CASCADE_TARGET = {
|
|
92
102
|
"gpt-6-astra": "gpt-6.1-sol",
|
|
93
103
|
"gpt-6.1-sol": "gpt-6.1-terra",
|
|
@@ -125,6 +135,7 @@ var CASCADE_TARGET = {
|
|
|
125
135
|
"gemini-2.5-flash": "gemini-2.5-flash-lite"
|
|
126
136
|
};
|
|
127
137
|
var KEYS_BY_LENGTH = Object.keys(PRICES).sort((a, b) => b.length - a.length);
|
|
138
|
+
var DISTINCT_VARIANT = /^(instruct|audio|realtime|search|transcribe|tts|image|embedding|vision|computer-use|deep-research)\b/;
|
|
128
139
|
function normalizeModel(model) {
|
|
129
140
|
if (typeof model !== "string" || !model) return null;
|
|
130
141
|
let m = model.toLowerCase().trim();
|
|
@@ -132,15 +143,23 @@ function normalizeModel(model) {
|
|
|
132
143
|
m = m.replace(/^(us|eu|apac|global)\./, "").replace(/^(anthropic|amazon|meta|mistral|openai)\./, "");
|
|
133
144
|
m = m.replace(/-v\d+(?::\d+)?$/, "");
|
|
134
145
|
if (MODEL_ALIASES[m]) m = MODEL_ALIASES[m];
|
|
146
|
+
if (PRICES[m]) return m;
|
|
135
147
|
m = m.replace(/@\d{8}$/, "");
|
|
136
148
|
m = m.replace(/-(\d{8}|\d{4}-\d{2}-\d{2})$/, "");
|
|
137
149
|
m = m.replace(/-(latest|preview|customtools|thinking)$/, "");
|
|
138
150
|
if (PRICES[m]) return m;
|
|
139
|
-
for (const k of KEYS_BY_LENGTH) if (m.startsWith(k + "-")
|
|
151
|
+
for (const k of KEYS_BY_LENGTH) if (m === k || m.startsWith(k + "-") && !DISTINCT_VARIANT.test(m.slice(k.length + 1))) return k;
|
|
140
152
|
return null;
|
|
141
153
|
}
|
|
154
|
+
var isFreeModel = (model) => typeof model === "string" && (/:free$/i.test(model.trim()) || /^openrouter\/free$/i.test(model.trim()));
|
|
155
|
+
var dynamicPrice = null;
|
|
156
|
+
var FREE_PRICE = { provider: "openrouter", tier: "fast", input: 0, output: 0, cachedInput: 0 };
|
|
142
157
|
function priceFor(model) {
|
|
158
|
+
if (isFreeModel(model)) return { key: String(model).trim().toLowerCase(), ...FREE_PRICE };
|
|
143
159
|
const key = normalizeModel(model);
|
|
160
|
+
if (key && PRICES[key]) return { key, ...PRICES[key] };
|
|
161
|
+
const dyn = typeof model === "string" && dynamicPrice ? dynamicPrice(model) : null;
|
|
162
|
+
if (dyn) return { key: String(model).trim().toLowerCase(), ...dyn };
|
|
144
163
|
return key ? { key, ...PRICES[key] } : null;
|
|
145
164
|
}
|
|
146
165
|
function costOf(model, u, factor = 1) {
|
|
@@ -340,7 +359,7 @@ function makeLocator(masked) {
|
|
|
340
359
|
};
|
|
341
360
|
return { text, starts, lineOf };
|
|
342
361
|
}
|
|
343
|
-
var ASSIGN = (name) => new RegExp(
|
|
362
|
+
var ASSIGN = (name) => new RegExp(`^[ \\t]*(?:export[ \\t]+)?(?:(?:const|let|var|final|static|private|public|readonly)[ \\t]+)*(?:self\\.|this\\.)?${name}[ \\t]*(?::[^=\\n]{0,200})?=(?![=>])`);
|
|
344
363
|
var KEYWORDS = /* @__PURE__ */ new Set(["self", "this", "await", "async", "return", "model", "messages", "role", "content", "system", "user", "true", "false", "True", "False", "None", "null", "undefined", "new", "max_tokens", "temperature", "client", "resp", "response"]);
|
|
345
364
|
function assignmentScope(loc, masked, line) {
|
|
346
365
|
const eq = masked[line].search(/=(?![=>])/);
|
|
@@ -409,6 +428,10 @@ var MODEL_NAME_TEST = /\b(?:model|model_?id|model_?name|modelId|modelName|deploy
|
|
|
409
428
|
var VOLATILE_CALL = /\b(?:datetime\.(?:now|utcnow)\(|date\.today\(|time\.time\(|time\.strftime\(|Date\.now\(|new Date\(\s*\)|\.toISOString\(|\.isoformat\(|\.toLocale(?:Date|Time)?String\(|uuid\.?uuid4\(|uuid4\(|randomUUID\(|nanoid\(|Math\.random\(|secrets\.token_hex\()/;
|
|
410
429
|
var INTERPOLATES = /\bf["']|\$\{|\.format\(|%\s*\(|["']\s*\+|\+\s*["']|`/;
|
|
411
430
|
var SYSTEM_CTX = /\bsystem(?:_?prompt|Prompt|_?message|Message|_?instructions?)?\b\s*[:=(]|["']role["']\s*:\s*["']system["']|\brole\s*:\s*["']system["']|\binstructions\s*[:=]/i;
|
|
431
|
+
var HARDCODED_KEY = /["'`]((?:sk-ant-api\d\d-|sk-ant-|sk-proj-|sk-or-v1-|sk-svcacct-)[A-Za-z0-9_-]{20,200}|sk-[A-Za-z0-9]{40,200}|AIza[0-9A-Za-z_-]{35}|xai-[A-Za-z0-9]{40,200}|gsk_[A-Za-z0-9]{40,200}|csk-[A-Za-z0-9]{30,200}|tz_live_[A-Za-z0-9_-]{24,200})["'`]/;
|
|
432
|
+
var KEY_PLACEHOLDER = /x{6,}|X{6,}|0{8,}|1234567|abcdef|your|example|placeholder|dummy|fake|test|redacted|\.\.\./i;
|
|
433
|
+
var keyProvider = (k) => k.startsWith("sk-ant-") ? "Anthropic" : k.startsWith("sk-or-") ? "OpenRouter" : k.startsWith("AIza") ? "Google" : k.startsWith("xai-") ? "xAI" : k.startsWith("gsk_") ? "Groq" : k.startsWith("csk-") ? "Cerebras" : k.startsWith("tz_live_") ? "TokenZero" : "OpenAI";
|
|
434
|
+
var PREFIX_DYNAMIC = /\bf(?:"""|'''|"|')\s{0,4}\{[A-Za-z_][\w.\[\]'"]{0,60}\}|`\s{0,4}\$\{[^}\n]{1,80}\}/;
|
|
412
435
|
var RANDOM_ID = /(?:\b(?:tool_call_id|toolCallId|call_id|callId|tool_use_id|toolUseId)\b|["']id["'])\s*[:=]\s*[^,\n]*?(?:uuid\.?uuid4\(|uuid4\(|randomUUID\(|nanoid\(|Math\.random\(|secrets\.token_hex\(|shortuuid)/;
|
|
413
436
|
var SET_TOOLS = /\btools?\s*=\s*\[[^\]\n]*\bfor\s+\w+\s+in\s+(?:set\(|\w*_?set\b|\w+\.keys\(\)\s*\|)/;
|
|
414
437
|
var OPENAI_COMPAT = /\bfrom\s+openai\s+import|\bimport\s+openai\b|from\s+["']openai["']|require\(\s*["']openai["']\s*\)|\bOpenAI\(|openrouter\.ai|chat\.completions\.create|litellm\.completion/;
|
|
@@ -420,6 +443,26 @@ var CACHE_USAGE_FIELD = /cache_read_input_tokens|cache_creation_input_tokens|cac
|
|
|
420
443
|
var COST_MATH = /\b(?:input_tokens|prompt_tokens|promptTokenCount|inputTokens|promptTokens|prompt_token_count)\b[^;\n]*(?:\*\s*[\w.[\]'"]*(?:price|rate|cost|per_?m|perM|usd)|\/\s*1[_,]?000[_,]?000|\/\s*1e6)|(?:price|rate|cost|per_?m|perM|usd)\w*\s*\*\s*[\w.[\]'"]*\b(?:input_tokens|prompt_tokens|promptTokenCount|inputTokens|promptTokens)\b/i;
|
|
421
444
|
var ZEROED_CACHE_USAGE = /\b(?:cache_creation_input_tokens|cache_read_input_tokens|cacheCreationInputTokens|cacheReadInputTokens|cached_tokens|cachedTokens)["']?\s*[:=]\s*0\b(?!\s*(?:if|\?|\|\||or\b|&&))/;
|
|
422
445
|
var isComment = (raw) => /^\s*(?:#|\/\/|\*|\/\*)/.test(raw);
|
|
446
|
+
function secretLint(path3, lines) {
|
|
447
|
+
const out2 = [];
|
|
448
|
+
for (let i = 0; i < lines.length && out2.length < 5; i++) {
|
|
449
|
+
const raw = lines[i];
|
|
450
|
+
if (raw.length > 2e4) continue;
|
|
451
|
+
const km = HARDCODED_KEY.exec(raw);
|
|
452
|
+
if (!km || KEY_PLACEHOLDER.test(km[1])) continue;
|
|
453
|
+
const who = keyProvider(km[1]);
|
|
454
|
+
out2.push({
|
|
455
|
+
file: path3,
|
|
456
|
+
severity: "high",
|
|
457
|
+
line: i + 1,
|
|
458
|
+
title: `Hard-coded ${who} API key in source`,
|
|
459
|
+
detail: `A string that looks like a live ${who} key (${km[1].slice(0, 7)}\u2026) is committed in this file. Anyone who can read the repository or its history can bill your account; a key committed to a public repo and never revoked is how the October 2026 LiteLLM supply-chain compromise began.`,
|
|
460
|
+
fix: "Revoke the key at the provider now (removing it from the file does not remove it from git history), then load it from the environment or a secret manager. TokenZero never needs your key in code: SDKs read OPENAI_API_KEY / ANTHROPIC_API_KEY as usual.",
|
|
461
|
+
est_savings_pct: 0
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
return out2;
|
|
465
|
+
}
|
|
423
466
|
function cacheLint(path3, lines, masked) {
|
|
424
467
|
const out2 = [];
|
|
425
468
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -474,6 +517,15 @@ function cacheLint(path3, lines, masked) {
|
|
|
474
517
|
fix: "Keep the system prompt static. Put the current time or request id in the last user message (or round it to the day if it must stay in the system prompt).",
|
|
475
518
|
est_savings_pct: 15
|
|
476
519
|
});
|
|
520
|
+
const pm = PREFIX_DYNAMIC.exec(raw);
|
|
521
|
+
if (pm && SYSTEM_CTX.test(raw.slice(0, pm.index + 1)) && raw.length - pm.index >= 160) add("prefix-order", {
|
|
522
|
+
severity: "medium",
|
|
523
|
+
line: i + 1,
|
|
524
|
+
title: "Per-request value at the start of the system prompt (everything after it misses the cache)",
|
|
525
|
+
detail: "The system prompt opens with an interpolated value and continues with a long fixed block. Provider caches match byte-for-byte from the first token, so when the opening varies per request the fixed block behind it is re-billed at the full input rate every time. Moving per-job details below the shared prompt took a published AI code-review pipeline from $2.43 to $0.77 per run.",
|
|
526
|
+
fix: "Put the fixed instructions first and the per-request details (names, ids, dates, file lists) at the end, ideally in the last user message.",
|
|
527
|
+
est_savings_pct: 20
|
|
528
|
+
});
|
|
477
529
|
if (RANDOM_ID.test(raw) || SET_TOOLS.test(raw)) add("serialization", {
|
|
478
530
|
severity: "low",
|
|
479
531
|
line: i + 1,
|
|
@@ -680,6 +732,7 @@ function analyzeFiles(files, repo = null, manifests2 = []) {
|
|
|
680
732
|
findings.push(...local);
|
|
681
733
|
siteFindings.push(local);
|
|
682
734
|
});
|
|
735
|
+
findings.push(...secretLint(f.path, lines));
|
|
683
736
|
if (callLines.length || hasCacheControl || USAGE_FIELD.test(src)) {
|
|
684
737
|
for (const fd of cacheLint(f.path, lines, masked)) findings.push({ ...fd, severity: kind === "library" || illustrative ? DOWN[fd.severity] : fd.severity });
|
|
685
738
|
}
|
|
@@ -834,7 +887,7 @@ function canonical(obj) {
|
|
|
834
887
|
}
|
|
835
888
|
|
|
836
889
|
// cli/src/main.ts
|
|
837
|
-
var VERSION = "0.2.
|
|
890
|
+
var VERSION = "0.2.11";
|
|
838
891
|
var BAKED_URL = true ? "https://tokenzero.sh" : "__TOKENZERO_DEFAULT_URL__";
|
|
839
892
|
function parseArgs(argv) {
|
|
840
893
|
const a = { _: [], flags: {} };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenzero/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "TokenZero CLI: connect a project to the TokenZero BYOK gateway, audit LLM token waste locally, check usage and verify signed savings receipts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|