@tokenzero/cli 0.2.8 → 0.2.10
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 +162 -3
- package/package.json +1 -1
package/bin/tokenzero.mjs
CHANGED
|
@@ -129,7 +129,7 @@ function normalizeModel(model) {
|
|
|
129
129
|
if (typeof model !== "string" || !model) return null;
|
|
130
130
|
let m = model.toLowerCase().trim();
|
|
131
131
|
m = m.replace(/^(openai|anthropic|google|models|vertex_ai|x-ai|xai|deepseek|bedrock|openrouter)\//, "");
|
|
132
|
-
m = m.replace(/^(us|eu|apac|global)\./, "").replace(/^(anthropic|amazon|meta|mistral)\./, "");
|
|
132
|
+
m = m.replace(/^(us|eu|apac|global)\./, "").replace(/^(anthropic|amazon|meta|mistral|openai)\./, "");
|
|
133
133
|
m = m.replace(/-v\d+(?::\d+)?$/, "");
|
|
134
134
|
if (MODEL_ALIASES[m]) m = MODEL_ALIASES[m];
|
|
135
135
|
m = m.replace(/@\d{8}$/, "");
|
|
@@ -139,8 +139,15 @@ function normalizeModel(model) {
|
|
|
139
139
|
for (const k of KEYS_BY_LENGTH) if (m.startsWith(k + "-") || m === k) return k;
|
|
140
140
|
return null;
|
|
141
141
|
}
|
|
142
|
+
var isFreeModel = (model) => typeof model === "string" && (/:free$/i.test(model.trim()) || /^openrouter\/free$/i.test(model.trim()));
|
|
143
|
+
var dynamicPrice = null;
|
|
144
|
+
var FREE_PRICE = { provider: "openrouter", tier: "fast", input: 0, output: 0, cachedInput: 0 };
|
|
142
145
|
function priceFor(model) {
|
|
146
|
+
if (isFreeModel(model)) return { key: String(model).trim().toLowerCase(), ...FREE_PRICE };
|
|
143
147
|
const key = normalizeModel(model);
|
|
148
|
+
if (key && PRICES[key]) return { key, ...PRICES[key] };
|
|
149
|
+
const dyn = typeof model === "string" && dynamicPrice ? dynamicPrice(model) : null;
|
|
150
|
+
if (dyn) return { key: String(model).trim().toLowerCase(), ...dyn };
|
|
144
151
|
return key ? { key, ...PRICES[key] } : null;
|
|
145
152
|
}
|
|
146
153
|
function costOf(model, u, factor = 1) {
|
|
@@ -340,7 +347,7 @@ function makeLocator(masked) {
|
|
|
340
347
|
};
|
|
341
348
|
return { text, starts, lineOf };
|
|
342
349
|
}
|
|
343
|
-
var ASSIGN = (name) => new RegExp(
|
|
350
|
+
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
351
|
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
352
|
function assignmentScope(loc, masked, line) {
|
|
346
353
|
const eq = masked[line].search(/=(?![=>])/);
|
|
@@ -403,6 +410,154 @@ function resolveScopes(loc, masked, from, depth = 2) {
|
|
|
403
410
|
return out2;
|
|
404
411
|
}
|
|
405
412
|
var AUDIT_AT = new Date(process.env.TOKENZERO_AUDIT_AT || Date.now());
|
|
413
|
+
var CACHE_DIRECTIVE = /cache_control|cacheControl|cachePoint|cache_point|prompt_cache_key|promptCacheKey|cachedContent|cached_content|supports?_?(?:prompt_?)?caching|supports?(?:Prompt)?Caching|enable_?(?:prompt_?)?cach(?:e|ing)|(?:prompt_?)?cache_?enabled|(?:prompt)?[cC]acheEnabled/;
|
|
414
|
+
var MODEL_GATE = /\b(?:if|elif|when|case)\b|&&|\|\||\?/;
|
|
415
|
+
var MODEL_NAME_TEST = /\b(?:model|model_?id|model_?name|modelId|modelName|deployment)\w*\s*(?:\.(?:startswith|startsWith|endswith|endsWith|includes|lower\(\)\.startswith|toLowerCase\(\)\.(?:startsWith|includes))\s*\(\s*\(?\s*["'`](?:claude|anthropic|gpt|gemini|o\d|deepseek|grok|qwen|minimax)|(?:===?|==)\s*["'`](?:claude|anthropic|gpt|gemini|deepseek|grok)[\w.-]*["'`])|["'`](?:claude|anthropic|gemini|gpt)[\w.-]*["'`]\s+in\s+(?:\w+\.)*(?:model|model_?id|model_?name)\b|\/\^?(?:claude|anthropic|gpt|gemini)[^/\n]*\/[a-z]*\.test\(\s*(?:\w+\.)*(?:model|modelId|modelName)/i;
|
|
416
|
+
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\()/;
|
|
417
|
+
var INTERPOLATES = /\bf["']|\$\{|\.format\(|%\s*\(|["']\s*\+|\+\s*["']|`/;
|
|
418
|
+
var SYSTEM_CTX = /\bsystem(?:_?prompt|Prompt|_?message|Message|_?instructions?)?\b\s*[:=(]|["']role["']\s*:\s*["']system["']|\brole\s*:\s*["']system["']|\binstructions\s*[:=]/i;
|
|
419
|
+
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})["'`]/;
|
|
420
|
+
var KEY_PLACEHOLDER = /x{6,}|X{6,}|0{8,}|1234567|abcdef|your|example|placeholder|dummy|fake|test|redacted|\.\.\./i;
|
|
421
|
+
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";
|
|
422
|
+
var PREFIX_DYNAMIC = /\bf(?:"""|'''|"|')\s{0,4}\{[A-Za-z_][\w.\[\]'"]{0,60}\}|`\s{0,4}\$\{[^}\n]{1,80}\}/;
|
|
423
|
+
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)/;
|
|
424
|
+
var SET_TOOLS = /\btools?\s*=\s*\[[^\]\n]*\bfor\s+\w+\s+in\s+(?:set\(|\w*_?set\b|\w+\.keys\(\)\s*\|)/;
|
|
425
|
+
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/;
|
|
426
|
+
var ANTHROPIC_SDK = /\bfrom\s+anthropic\s+import|\bimport\s+anthropic\b|@anthropic-ai\/sdk|\bAnthropic\(|messages\.create\(/;
|
|
427
|
+
var NON_CLAUDE_MODEL = /["'`](?:openai\/|google\/|deepseek\/|x-ai\/|meta-llama\/|mistralai\/|qwen\/)?(?:gpt-[\w.-]+|o[1-9][\w.-]*|gemini-[\w.-]+|deepseek-[\w.-]+|grok-[\w.-]+|llama[\w.-]*|mistral[\w.-]*|qwen[\w.-]*)["'`]/i;
|
|
428
|
+
var USER_ROLE = /["']?role["']?\s*:\s*["']user["']/;
|
|
429
|
+
var USAGE_FIELD = /\b(?:input_tokens|prompt_tokens|promptTokenCount|inputTokens|promptTokens|prompt_token_count)\b/;
|
|
430
|
+
var CACHE_USAGE_FIELD = /cache_read_input_tokens|cache_creation_input_tokens|cacheReadInputTokens|cacheCreationInputTokens|cached_tokens|cachedTokens|cachedContentTokenCount|cached_content_token_count|prompt_tokens_details|promptTokensDetails|cache_read|cacheRead|cache_write|cacheWrite|cacheReadTokens|cacheWriteTokens/;
|
|
431
|
+
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;
|
|
432
|
+
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|&&))/;
|
|
433
|
+
var isComment = (raw) => /^\s*(?:#|\/\/|\*|\/\*)/.test(raw);
|
|
434
|
+
function secretLint(path3, lines) {
|
|
435
|
+
const out2 = [];
|
|
436
|
+
for (let i = 0; i < lines.length && out2.length < 5; i++) {
|
|
437
|
+
const raw = lines[i];
|
|
438
|
+
if (raw.length > 2e4) continue;
|
|
439
|
+
const km = HARDCODED_KEY.exec(raw);
|
|
440
|
+
if (!km || KEY_PLACEHOLDER.test(km[1])) continue;
|
|
441
|
+
const who = keyProvider(km[1]);
|
|
442
|
+
out2.push({
|
|
443
|
+
file: path3,
|
|
444
|
+
severity: "high",
|
|
445
|
+
line: i + 1,
|
|
446
|
+
title: `Hard-coded ${who} API key in source`,
|
|
447
|
+
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.`,
|
|
448
|
+
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.",
|
|
449
|
+
est_savings_pct: 0
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
return out2;
|
|
453
|
+
}
|
|
454
|
+
function cacheLint(path3, lines, masked) {
|
|
455
|
+
const out2 = [];
|
|
456
|
+
const seen = /* @__PURE__ */ new Set();
|
|
457
|
+
const add = (k, fd) => {
|
|
458
|
+
if (!seen.has(k)) {
|
|
459
|
+
seen.add(k);
|
|
460
|
+
out2.push({ file: path3, ...fd });
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
const near = (i, re, before, after) => lines.slice(Math.max(0, i - before), Math.min(lines.length, i + after + 1)).some((l) => !isComment(l) && re.test(l));
|
|
464
|
+
const src = lines.join("\n");
|
|
465
|
+
const hasDirective = CACHE_DIRECTIVE.test(src);
|
|
466
|
+
const nonClaudeViaCompat = OPENAI_COMPAT.test(src) && !ANTHROPIC_SDK.test(src) && NON_CLAUDE_MODEL.test(src);
|
|
467
|
+
const ownerOf = (i) => {
|
|
468
|
+
for (let k = i; k >= Math.max(0, i - 6); k--) {
|
|
469
|
+
const l = lines[k], u = l.search(USER_ROLE), p = l.search(/["']role["']\s*:\s*["'](?:system|developer)["']|\brole\s*:\s*["'](?:system|developer)["']|\bsystem\s*[=:]|\btools?\s*[=:]|["'](?:system|tools)["']\s*:/);
|
|
470
|
+
if (u >= 0 || p >= 0) return u >= 0 && (p < 0 || (k === i ? u < l.search(/cache_control|cacheControl/) && u > p : u > p)) ? "user" : "prefix";
|
|
471
|
+
}
|
|
472
|
+
return null;
|
|
473
|
+
};
|
|
474
|
+
const markers = lines.map((l, i) => !isComment(l) && /cache_control|cacheControl|cachePoint/.test(l) ? i : -1).filter((i) => i >= 0);
|
|
475
|
+
const systemMarked = markers.some((i) => ownerOf(i) === "prefix");
|
|
476
|
+
const USER_MARK = /["']?role["']?\s*:\s*["']user["']|\bcontents\s*[:=]|\bmessages\s*[:=]|\binput\s*[:=]/;
|
|
477
|
+
const inSystemValue = (i) => {
|
|
478
|
+
const raw = lines[i], v = raw.search(VOLATILE_CALL), sp = raw.search(SYSTEM_CTX), up = raw.search(USER_MARK);
|
|
479
|
+
if (sp >= 0 && sp < v) return !(up > sp && up < v);
|
|
480
|
+
if (up >= 0 && up < v) return false;
|
|
481
|
+
for (let k = i - 1; k >= Math.max(0, i - 4); k--) {
|
|
482
|
+
if (isComment(lines[k])) continue;
|
|
483
|
+
const ks = lines[k].search(SYSTEM_CTX), ku = lines[k].search(USER_MARK);
|
|
484
|
+
if (ks >= 0) return !(ku > ks);
|
|
485
|
+
if (ku >= 0) return false;
|
|
486
|
+
}
|
|
487
|
+
return false;
|
|
488
|
+
};
|
|
489
|
+
lines.forEach((raw, i) => {
|
|
490
|
+
if (isComment(raw)) return;
|
|
491
|
+
const code2 = masked[i] ?? "";
|
|
492
|
+
if (hasDirective && MODEL_GATE.test(code2 + raw) && MODEL_NAME_TEST.test(raw) && near(i, CACHE_DIRECTIVE, 3, 10)) add("gating", {
|
|
493
|
+
severity: "medium",
|
|
494
|
+
line: i + 1,
|
|
495
|
+
title: "Prompt caching gated on a hardcoded model-name check",
|
|
496
|
+
detail: 'Caching is turned on only when the model id matches a fixed name or prefix. When a provider renames a model, adds a "-latest" alias or you switch to a new cacheable model, the check stops matching and every call quietly pays the full input rate. This is the most common prompt-caching defect in agent code (31% of 120 studied defects, arXiv:2610.34817).',
|
|
497
|
+
fix: 'Decide caching from the provider (or a capability table that defaults to "on"), not from the model string; or route through TokenZero, which applies the right cache markers for every priced model.',
|
|
498
|
+
est_savings_pct: 10
|
|
499
|
+
});
|
|
500
|
+
if (VOLATILE_CALL.test(raw) && INTERPOLATES.test(raw) && inSystemValue(i) && !RANDOM_ID.test(raw)) add("volatile", {
|
|
501
|
+
severity: "medium",
|
|
502
|
+
line: i + 1,
|
|
503
|
+
title: "Volatile value (time, date or random id) interpolated into the system prompt",
|
|
504
|
+
detail: "A value that changes on every request is written into the system prompt, which sits at the start of the cached prefix. Provider caches match byte-for-byte from the start, so no request ever reuses the previous one and the cache discount is lost on the whole prompt (11% of studied defects, arXiv:2610.34817).",
|
|
505
|
+
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).",
|
|
506
|
+
est_savings_pct: 15
|
|
507
|
+
});
|
|
508
|
+
const pm = PREFIX_DYNAMIC.exec(raw);
|
|
509
|
+
if (pm && SYSTEM_CTX.test(raw.slice(0, pm.index + 1)) && raw.length - pm.index >= 160) add("prefix-order", {
|
|
510
|
+
severity: "medium",
|
|
511
|
+
line: i + 1,
|
|
512
|
+
title: "Per-request value at the start of the system prompt (everything after it misses the cache)",
|
|
513
|
+
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.",
|
|
514
|
+
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.",
|
|
515
|
+
est_savings_pct: 20
|
|
516
|
+
});
|
|
517
|
+
if (RANDOM_ID.test(raw) || SET_TOOLS.test(raw)) add("serialization", {
|
|
518
|
+
severity: "low",
|
|
519
|
+
line: i + 1,
|
|
520
|
+
title: RANDOM_ID.test(raw) ? "Random ids generated for messages or tool calls in the prompt" : "Tool list built from an unordered set",
|
|
521
|
+
detail: RANDOM_ID.test(raw) ? "An id minted with a random generator is written into the conversation. If it is regenerated when the history is rebuilt, the same turn serializes to different bytes and the cached prefix stops matching (nondeterministic serialization, 8% of studied defects, arXiv:2610.34817)." : "Iterating a set gives no stable order, so the same tools can serialize in a different order across processes and break the cached prefix (nondeterministic serialization, 8% of studied defects, arXiv:2610.34817).",
|
|
522
|
+
fix: RANDOM_ID.test(raw) ? "Mint ids once, store them with the message, and reuse the stored value; never regenerate them per request." : "Sort the tools (for example by name) before sending them.",
|
|
523
|
+
est_savings_pct: 5
|
|
524
|
+
});
|
|
525
|
+
if (nonClaudeViaCompat && /cache_control|cacheControl/.test(raw)) add("marker", {
|
|
526
|
+
severity: "medium",
|
|
527
|
+
line: i + 1,
|
|
528
|
+
title: "Anthropic cache_control marker sent through an OpenAI-compatible client to non-Claude models",
|
|
529
|
+
detail: "This file uses an OpenAI-compatible client and names non-Claude models, but attaches Anthropic-style `cache_control` markers. Providers that do not accept the field reject the request or ignore it, so calls fail or run uncached (incompatible marker, 8% of studied defects, arXiv:2610.34817).",
|
|
530
|
+
fix: "Attach `cache_control` only for Claude models; OpenAI, Gemini and DeepSeek cache stable prefixes automatically (use `prompt_cache_key` on OpenAI). TokenZero adds the right marker per provider.",
|
|
531
|
+
est_savings_pct: 0
|
|
532
|
+
});
|
|
533
|
+
if (!systemMarked && /cache_control|cacheControl/.test(raw) && ownerOf(i) === "user" && lines.slice(Math.max(0, i - 4), i + 2).some((l) => INTERPOLATES.test(l) || /\b(?:user_input|query|question|request|input)\b/.test(l))) add("misplaced", {
|
|
534
|
+
severity: "low",
|
|
535
|
+
line: i + 1,
|
|
536
|
+
title: "Cache breakpoint on per-request user content, none on the system prompt or tools",
|
|
537
|
+
detail: "The only `cache_control` marker is on a user message whose text changes per request, while the static system prompt and tools are unmarked. Each call writes a new cache entry (billed at the cache-write surcharge) that the next call cannot read (misplaced marker, 3% of studied defects, arXiv:2610.34817). In multi-turn chats a breakpoint on the latest turn is fine as long as the system prompt or tools also carry one.",
|
|
538
|
+
fix: "Add a breakpoint after the last tool or the system prompt; keep the per-turn one only for long conversations.",
|
|
539
|
+
est_savings_pct: 5
|
|
540
|
+
});
|
|
541
|
+
if (ZEROED_CACHE_USAGE.test(code2)) add("usage", {
|
|
542
|
+
severity: "medium",
|
|
543
|
+
line: i + 1,
|
|
544
|
+
title: "Cached-token usage hardcoded to zero",
|
|
545
|
+
detail: "Cache-read or cache-write token counts are set to 0 instead of read from the response, so cost reports and budgets misstate spend (usage miscount, 19% of studied defects, arXiv:2610.34817).",
|
|
546
|
+
fix: "Read `cache_read_input_tokens` / `cache_creation_input_tokens` (Anthropic), `prompt_tokens_details.cached_tokens` (OpenAI) or `cachedContentTokenCount` (Gemini) from the usage object.",
|
|
547
|
+
est_savings_pct: 0
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
const costLine = lines.findIndex((l) => !isComment(l) && COST_MATH.test(l));
|
|
551
|
+
if (costLine >= 0 && !CACHE_USAGE_FIELD.test(src)) add("usage", {
|
|
552
|
+
severity: "medium",
|
|
553
|
+
line: costLine + 1,
|
|
554
|
+
title: "Cost math from token usage ignores cached tokens",
|
|
555
|
+
detail: "Spend is computed from input/prompt token counts with no reference to cached tokens anywhere in this file. Anthropic reports cache reads and writes separately from `input_tokens` (so this undercounts writes and misses the read discount); OpenAI includes cached tokens in `prompt_tokens` (so this overcounts). Either way reported cost is wrong (usage miscount, 19% of studied defects, arXiv:2610.34817).",
|
|
556
|
+
fix: "Price cache reads, cache writes and uncached input separately from the provider usage fields, or read the per-request cost from TokenZero receipts.",
|
|
557
|
+
est_savings_pct: 0
|
|
558
|
+
});
|
|
559
|
+
return out2;
|
|
560
|
+
}
|
|
406
561
|
function analyzeFiles(files, repo = null, manifests2 = []) {
|
|
407
562
|
const findings = [];
|
|
408
563
|
const callSites = [];
|
|
@@ -565,6 +720,10 @@ function analyzeFiles(files, repo = null, manifests2 = []) {
|
|
|
565
720
|
findings.push(...local);
|
|
566
721
|
siteFindings.push(local);
|
|
567
722
|
});
|
|
723
|
+
findings.push(...secretLint(f.path, lines));
|
|
724
|
+
if (callLines.length || hasCacheControl || USAGE_FIELD.test(src)) {
|
|
725
|
+
for (const fd of cacheLint(f.path, lines, masked)) findings.push({ ...fd, severity: kind === "library" || illustrative ? DOWN[fd.severity] : fd.severity });
|
|
726
|
+
}
|
|
568
727
|
}
|
|
569
728
|
const libTokens = promptLibrary.reduce((a, p) => a + p.tokens, 0);
|
|
570
729
|
if (callSites.length && !repoCaches && libTokens >= 1024 && !findings.some((f) => /static prompt tokens/.test(f.title))) {
|
|
@@ -716,7 +875,7 @@ function canonical(obj) {
|
|
|
716
875
|
}
|
|
717
876
|
|
|
718
877
|
// cli/src/main.ts
|
|
719
|
-
var VERSION = "0.2.
|
|
878
|
+
var VERSION = "0.2.10";
|
|
720
879
|
var BAKED_URL = true ? "https://tokenzero.sh" : "__TOKENZERO_DEFAULT_URL__";
|
|
721
880
|
function parseArgs(argv) {
|
|
722
881
|
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.10",
|
|
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",
|