@tokenzero/cli 0.1.7 → 0.1.8
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 +54 -3
- package/package.json +1 -1
package/bin/tokenzero.mjs
CHANGED
|
@@ -710,7 +710,7 @@ function canonical(obj) {
|
|
|
710
710
|
}
|
|
711
711
|
|
|
712
712
|
// cli/src/main.ts
|
|
713
|
-
var VERSION = "0.1.
|
|
713
|
+
var VERSION = "0.1.8";
|
|
714
714
|
var BAKED_URL = true ? "https://tokenzero.sh" : "__TOKENZERO_DEFAULT_URL__";
|
|
715
715
|
function parseArgs(argv) {
|
|
716
716
|
const a = { _: [], flags: {} };
|
|
@@ -730,7 +730,7 @@ function parseArgs(argv) {
|
|
|
730
730
|
}
|
|
731
731
|
return a;
|
|
732
732
|
}
|
|
733
|
-
var VALUE_FLAGS = /* @__PURE__ */ new Set(["url", "name", "key", "format", "diff", "fail-over", "env-file"]);
|
|
733
|
+
var VALUE_FLAGS = /* @__PURE__ */ new Set(["url", "name", "key", "format", "diff", "fail-over", "env-file", "limit"]);
|
|
734
734
|
var tty = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
735
735
|
var c = (code2) => (s) => tty ? `\x1B[${code2}m${s}\x1B[0m` : s;
|
|
736
736
|
var bold = c("1");
|
|
@@ -1183,8 +1183,9 @@ async function cmdRotate(a) {
|
|
|
1183
1183
|
async function cmdVerify(a) {
|
|
1184
1184
|
const base = baseUrl(a);
|
|
1185
1185
|
const id = a._[1];
|
|
1186
|
+
if (base && a.flags.chain) return cmdVerifyChain(a, base);
|
|
1186
1187
|
if (!base || !id) {
|
|
1187
|
-
err(red("usage: tokenzero verify <request_id> [--url \u2026]"));
|
|
1188
|
+
err(red("usage: tokenzero verify <request_id> [--url \u2026] | tokenzero verify --chain [--limit N]"));
|
|
1188
1189
|
process.exitCode = 2;
|
|
1189
1190
|
return;
|
|
1190
1191
|
}
|
|
@@ -1208,6 +1209,55 @@ async function cmdVerify(a) {
|
|
|
1208
1209
|
out(` baseline $${rc.baseline_usd} \xB7 actual $${rc.actual_usd} \xB7 saved $${rc.savings_usd} \xB7 engines ${rc.engines.join(",") || "none"} \xB7 pricing ${rc.pricing_version}`);
|
|
1209
1210
|
if (!ok) process.exitCode = 1;
|
|
1210
1211
|
}
|
|
1212
|
+
async function cmdVerifyChain(a, base) {
|
|
1213
|
+
const saved = loadCreds();
|
|
1214
|
+
const key = a.flags.key || process.env.TOKENZERO_KEY || saved?.api_key || "";
|
|
1215
|
+
if (!key) {
|
|
1216
|
+
err(red("--chain needs the workspace key (TOKENZERO_KEY, --key, or saved credentials)"));
|
|
1217
|
+
process.exitCode = 2;
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
const limit = Number(a.flags.limit) || 50;
|
|
1221
|
+
const [c2, k] = await Promise.all([api(base, `/v1/ledger/chain?limit=${limit}`, { headers: { "x-tokenzero-key": key } }), api(base, "/.well-known/tokenzero.json")]);
|
|
1222
|
+
if (c2.status !== 200) {
|
|
1223
|
+
err(red(`chain: ${c2.status} ${JSON.stringify(c2.body).slice(0, 200)}`));
|
|
1224
|
+
process.exitCode = 1;
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
const items = c2.body.items ?? [];
|
|
1228
|
+
const keys = /* @__PURE__ */ new Map();
|
|
1229
|
+
for (const jwk of k.body?.keys ?? []) keys.set(jwk.kid, crypto2.createPublicKey({ key: { kty: jwk.kty, crv: jwk.crv, x: jwk.x }, format: "jwk" }));
|
|
1230
|
+
const sha = (s) => crypto2.createHash("sha256").update(s).digest("hex");
|
|
1231
|
+
let ok = true, reason = "";
|
|
1232
|
+
for (let i = 0; i < items.length && ok; i++) {
|
|
1233
|
+
const { receipt, signature } = items[i];
|
|
1234
|
+
const pub = keys.get(receipt.key_id);
|
|
1235
|
+
if (!pub || !crypto2.verify(null, Buffer.from(canonical(receipt)), pub, Buffer.from(signature, "base64url"))) {
|
|
1236
|
+
ok = false;
|
|
1237
|
+
reason = `bad signature at seq ${receipt.seq}`;
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
const prev = items[i - 1];
|
|
1241
|
+
if (prev && receipt.seq !== prev.receipt.seq + 1) {
|
|
1242
|
+
ok = false;
|
|
1243
|
+
reason = `seq ${receipt.seq} does not follow ${prev.receipt.seq}`;
|
|
1244
|
+
} else if (prev && receipt.prev_receipt_hash !== sha(prev.signature)) {
|
|
1245
|
+
ok = false;
|
|
1246
|
+
reason = `prev_receipt_hash mismatch at seq ${receipt.seq}`;
|
|
1247
|
+
} else if (!prev && receipt.seq === 1 && receipt.prev_receipt_hash !== "0".repeat(64)) {
|
|
1248
|
+
ok = false;
|
|
1249
|
+
reason = "genesis receipt does not point at the zero hash";
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
if (a.flags.json) {
|
|
1253
|
+
out(JSON.stringify({ valid: ok, reason: reason || null, checked: items.length, head_seq: c2.body.head_seq, server_verification: c2.body.verification }, null, 2));
|
|
1254
|
+
if (!ok) process.exitCode = 1;
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
out(`${ok ? green("\u2713 chain intact") : red("\u2717 CHAIN BROKEN")}: ${items.length} receipt(s) checked locally${c2.body.head_seq != null ? `, head seq ${c2.body.head_seq}` : ""}${reason ? " \u2014 " + reason : ""}`);
|
|
1258
|
+
if (items.length) out(dim(` ${items[0].receipt.seq ?? "?"} \u2026 ${items.at(-1).receipt.seq ?? "?"} \xB7 savings in this run $${items.reduce((s, x) => s + (x.receipt.savings_usd ?? 0), 0).toFixed(4)} \xB7 key ${items[0].receipt.key_id}`));
|
|
1259
|
+
if (!ok) process.exitCode = 1;
|
|
1260
|
+
}
|
|
1211
1261
|
function help() {
|
|
1212
1262
|
out(`${bold("tokenzero")} v${VERSION} \u2014 cut your LLM bill with a BYOK gateway
|
|
1213
1263
|
|
|
@@ -1220,6 +1270,7 @@ function help() {
|
|
|
1220
1270
|
Static token-waste audit. Runs locally; nothing is uploaded. --fail-over makes it a CI gate.
|
|
1221
1271
|
${bold("tokenzero usage")} [--json] Routed spend vs the free tier, savings, fee preview.
|
|
1222
1272
|
${bold("tokenzero verify")} <request_id> Verify a savings receipt's signature offline.
|
|
1273
|
+
${bold("tokenzero verify --chain")} [--limit N] Verify the workspace's append-only receipt chain (signatures, seq, prev hashes).
|
|
1223
1274
|
${bold("tokenzero rotate-key")} [--yes] Issue a new workspace key; rewrites it in .env. Old key dies immediately.
|
|
1224
1275
|
|
|
1225
1276
|
Env: TOKENZERO_URL, TOKENZERO_KEY, NO_COLOR`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenzero/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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",
|