@tokenzero/cli 0.1.4 → 0.1.6

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.
Files changed (2) hide show
  1. package/bin/tokenzero.mjs +183 -23
  2. package/package.json +1 -1
package/bin/tokenzero.mjs CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  // cli/src/main.ts
5
5
  import fs2 from "node:fs";
6
+ import os from "node:os";
6
7
  import path2 from "node:path";
7
8
  import crypto2 from "node:crypto";
8
9
  import readline from "node:readline/promises";
@@ -69,7 +70,18 @@ var PRICES = {
69
70
  "gemini-3-flash": P("gemini", "fast", 0.5, 3, 0.05),
70
71
  "gemini-2.5-pro": P("gemini", "frontier", 1.25, 10, 0.125, void 0, { longCtxAt: 2e5, longCtx: { input: 2.5, output: 15, cachedInput: 0.25 } }),
71
72
  "gemini-2.5-flash": P("gemini", "fast", 0.3, 2.5, 0.03),
72
- "gemini-2.5-flash-lite": P("gemini", "fast", 0.1, 0.4, 0.01)
73
+ "gemini-2.5-flash-lite": P("gemini", "fast", 0.1, 0.4, 0.01),
74
+ // ---- xAI Grok ----
75
+ "grok-4": P("xai", "frontier", 3, 15, 0.75),
76
+ "grok-4-fast": P("xai", "fast", 0.2, 0.8, 0.05),
77
+ "grok-4-mini": P("xai", "fast", 0.2, 0.8, 0.05),
78
+ "grok-3": P("xai", "mid", 3, 15, 0.75),
79
+ "grok-3-mini": P("xai", "fast", 0.3, 0.5, 0.075),
80
+ // ---- DeepSeek ----
81
+ "deepseek-v3": P("deepseek", "fast", 0.27, 1.1, 0.07),
82
+ "deepseek-chat": P("deepseek", "fast", 0.27, 1.1, 0.07),
83
+ "deepseek-r1": P("deepseek", "mid", 0.55, 2.19, 0.14),
84
+ "deepseek-reasoner": P("deepseek", "mid", 0.55, 2.19, 0.14)
73
85
  };
74
86
  var CASCADE_TARGET = {
75
87
  "gpt-6-astra": "gpt-6.1-sol",
@@ -100,13 +112,19 @@ var CASCADE_TARGET = {
100
112
  "gemini-3.1-pro": "gemini-3.8-flash",
101
113
  "gemini-3.8-flash": "gemini-3.5-flash-lite",
102
114
  "gemini-2.5-pro": "gemini-2.5-flash",
103
- "gemini-2.5-flash": "gemini-2.5-flash-lite"
115
+ "gemini-2.5-flash": "gemini-2.5-flash-lite",
116
+ "grok-4": "grok-4-fast",
117
+ "grok-3": "grok-3-mini",
118
+ "deepseek-r1": "deepseek-v3",
119
+ "deepseek-reasoner": "deepseek-chat"
104
120
  };
105
121
  var KEYS_BY_LENGTH = Object.keys(PRICES).sort((a, b) => b.length - a.length);
106
122
  function normalizeModel(model) {
107
123
  if (typeof model !== "string" || !model) return null;
108
124
  let m = model.toLowerCase().trim();
109
- m = m.replace(/^(openai|anthropic|google|models|vertex_ai)\//, "");
125
+ m = m.replace(/^(openai|anthropic|google|gemini|x-ai|xai|deepseek|bedrock|models|vertex_ai)\//, "");
126
+ m = m.replace(/^(?:us\.|eu\.|apac\.)?anthropic\./, "");
127
+ m = m.replace(/-v\d+:\d+$/, "");
110
128
  m = m.replace(/@\d{8}$/, "");
111
129
  m = m.replace(/-(\d{8}|\d{4}-\d{2}-\d{2})$/, "");
112
130
  m = m.replace(/-(latest|preview|customtools|thinking)$/, "");
@@ -649,7 +667,7 @@ function canonical(obj) {
649
667
  }
650
668
 
651
669
  // cli/src/main.ts
652
- var VERSION = "0.1.4";
670
+ var VERSION = "0.1.6";
653
671
  var BAKED_URL = true ? "https://tokenzero.sh" : "__TOKENZERO_DEFAULT_URL__";
654
672
  function parseArgs(argv) {
655
673
  const a = { _: [], flags: {} };
@@ -856,41 +874,175 @@ ${bold("Your code")}`);
856
874
  if (sn) out(` ${s.padEnd(10)} ${sn}`);
857
875
  }
858
876
  }
877
+ if (a.flags["github-action"]) await setupGithubAction(root, a);
859
878
  if (a.flags.mcp) await setupMcp(root, base, a);
879
+ if (a.flags["agent-rules"]) await setupAgentRules(root, base, a);
880
+ if (key && wsId) saveCreds({ workspace_id: wsId, api_key: key, url: base });
860
881
  out(`
861
882
  ${bold("Next")}`);
862
- if (wsId) out(` dashboard ${base}/dashboard?workspace=${wsId}`);
883
+ if (wsId) out(` dashboard ${base}/dashboard?workspace=${wsId}${key ? "#key=" + redactKey(key, key) : ""}`);
863
884
  out(` usage ${selfCmd(base)} usage`);
864
885
  if (wsId) out(` badge turn on public stats in the dashboard, then: [![LLM spend verified by TokenZero](${base}/v1/badge/${wsId}.svg)](${base}/dashboard?workspace=${wsId})`);
886
+ if (!a.flags["github-action"]) out(` PR checks ${selfCmd(base)} init --github-action`);
865
887
  if (!a.flags.mcp) out(` coding agents: ${selfCmd(base)} init --mcp`);
866
888
  }
867
- var selfCmd = (base) => `npx -y ${base}/cli/tokenzero.tgz`;
889
+ var selfCmd = (_base) => "npx -y @tokenzero/cli";
868
890
  var redactKey = (s, key) => key ? s.replace(key, key.slice(0, 12) + "\u2026" + key.slice(-4)) : s;
891
+ var GH_ACTION_YAML = `name: TokenZero LLM Cost Check
892
+ on: [pull_request]
893
+ permissions:
894
+ contents: read
895
+ pull-requests: write
896
+ jobs:
897
+ cost-audit:
898
+ runs-on: ubuntu-latest
899
+ steps:
900
+ - uses: actions/checkout@v4
901
+ with:
902
+ fetch-depth: 0
903
+ - uses: tokenzero-ai/tokenzero/action@v0
904
+ with:
905
+ github-token: \${{ secrets.GITHUB_TOKEN }}
906
+ `;
907
+ async function setupGithubAction(root, a) {
908
+ out(`
909
+ ${bold("GitHub Action")} ${dim("(PR token-waste & model-arbitrage check)")}`);
910
+ const wfDir = path2.join(root, ".github", "workflows");
911
+ const file = path2.join(wfDir, "tokenzero.yml");
912
+ if (fs2.existsSync(file)) {
913
+ out(dim(` ${path2.relative(root, file)} already exists`));
914
+ return;
915
+ }
916
+ if (await confirm(` Add ${path2.relative(root, file)} to audit every PR?`, a)) {
917
+ fs2.mkdirSync(wfDir, { recursive: true });
918
+ fs2.writeFileSync(file, GH_ACTION_YAML);
919
+ out(green(` \u2713 wrote ${path2.relative(root, file)}`));
920
+ }
921
+ }
869
922
  async function setupMcp(root, base, a) {
870
923
  out(`
871
924
  ${bold("MCP")} ${dim("(audit_repo_token_waste, optimize_prompt_payload, calculate_model_arbitrage)")}`);
872
925
  out(` Claude Code: claude mcp add --transport http tokenzero ${base}/mcp`);
873
- const cursorDir = path2.join(root, ".cursor");
874
- const file = path2.join(cursorDir, "mcp.json");
875
- let cfg = {};
876
- if (fs2.existsSync(file)) {
877
- try {
878
- cfg = JSON.parse(fs2.readFileSync(file, "utf8"));
879
- } catch {
880
- out(yellow(` ! ${file} is not valid JSON; not touching it`));
881
- return;
926
+ out(` Local stdio: npx -y @tokenzero/mcp`);
927
+ for (const rel of [path2.join(".cursor", "mcp.json"), ".mcp.json"]) {
928
+ const file = path2.join(root, rel);
929
+ let cfg = {};
930
+ if (fs2.existsSync(file)) {
931
+ try {
932
+ cfg = JSON.parse(fs2.readFileSync(file, "utf8"));
933
+ } catch {
934
+ out(yellow(` ! ${file} is not valid JSON; not touching it`));
935
+ continue;
936
+ }
882
937
  }
938
+ if (cfg?.mcpServers?.tokenzero?.url === `${base}/mcp`) {
939
+ out(dim(` ${rel}: already configured`));
940
+ continue;
941
+ }
942
+ if (await confirm(` Add TokenZero to ${rel}?`, a)) {
943
+ cfg.mcpServers = { ...cfg.mcpServers ?? {}, tokenzero: { url: `${base}/mcp` } };
944
+ fs2.mkdirSync(path2.dirname(file), { recursive: true });
945
+ fs2.writeFileSync(file, JSON.stringify(cfg, null, 2) + "\n");
946
+ out(green(` \u2713 wrote ${rel}`));
947
+ }
948
+ }
949
+ }
950
+ var AGENT_RULES_MARKER = "<!-- tokenzero-agent-rules -->";
951
+ function credsPath() {
952
+ return path2.join(process.env.TOKENZERO_CONFIG_DIR || path2.join(os.homedir(), ".config", "tokenzero"), "credentials.json");
953
+ }
954
+ function saveCreds(c2) {
955
+ try {
956
+ const p = credsPath();
957
+ fs2.mkdirSync(path2.dirname(p), { recursive: true, mode: 448 });
958
+ fs2.writeFileSync(p, JSON.stringify({ ...c2, updated_at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n", { mode: 384 });
959
+ } catch {
960
+ }
961
+ }
962
+ function loadCreds() {
963
+ try {
964
+ return JSON.parse(fs2.readFileSync(credsPath(), "utf8"));
965
+ } catch {
966
+ return null;
883
967
  }
884
- if (cfg?.mcpServers?.tokenzero?.url === `${base}/mcp`) {
885
- out(dim(" Cursor: already configured"));
968
+ }
969
+ async function setupAgentRules(root, base, a) {
970
+ out(`
971
+ ${bold("Agent Rules")} ${dim("(AGENTS.md FinOps & MCP routing rules)")}`);
972
+ const file = path2.join(root, "AGENTS.md");
973
+ const existing = fs2.existsSync(file) ? fs2.readFileSync(file, "utf8") : "";
974
+ if (existing.includes(AGENT_RULES_MARKER)) {
975
+ out(dim(" AGENTS.md: already includes TokenZero FinOps rules"));
886
976
  return;
887
977
  }
888
- if (await confirm(` Add TokenZero to ${path2.relative(root, file)} for Cursor?`, a)) {
889
- cfg.mcpServers = { ...cfg.mcpServers ?? {}, tokenzero: { url: `${base}/mcp` } };
890
- fs2.mkdirSync(cursorDir, { recursive: true });
891
- fs2.writeFileSync(file, JSON.stringify(cfg, null, 2) + "\n");
892
- out(green(` \u2713 wrote ${path2.relative(root, file)}`));
978
+ const block = [
979
+ AGENT_RULES_MARKER,
980
+ "## TokenZero LLM FinOps & MCP Gateway",
981
+ `- **1-Line BYOK Gateway:** Point \`OPENAI_BASE_URL=${base}/v1\` and \`ANTHROPIC_BASE_URL=${base}\` (with \`X-TokenZero-Key\` or \`/w/<key>/v1\`). Provider keys pass straight through and are never stored.`,
982
+ `- **MCP Tools:** Run \`npx -y @tokenzero/mcp\` or connect HTTP MCP at \`${base}/mcp\` for \`audit_repo_token_waste\`, \`calculate_model_arbitrage\`, \`optimize_prompt_payload\`, and \`provision_workspace\`.`,
983
+ `- **Local Audit:** Run \`npx -y @tokenzero/cli audit\` before opening PRs that add or modify LLM calls.`
984
+ ].join("\n") + "\n";
985
+ if (await confirm(" Add TokenZero FinOps & MCP rules to AGENTS.md?", a)) {
986
+ const sep = existing && !existing.endsWith("\n") ? "\n\n" : existing ? "\n" : "";
987
+ fs2.writeFileSync(file, existing + sep + block);
988
+ out(green(" \u2713 wrote AGENTS.md"));
989
+ }
990
+ }
991
+ async function cmdSignup(a) {
992
+ const base = baseUrl(a);
993
+ if (!base) {
994
+ err(red("No gateway URL. Pass --url https://\u2026 or set TOKENZERO_URL."));
995
+ process.exitCode = 2;
996
+ return;
997
+ }
998
+ const name = a.flags.name || path2.basename(process.cwd()) || "cli-signup";
999
+ const r = await api(base, "/v1/workspaces", { method: "POST", body: JSON.stringify({ name }) });
1000
+ if (r.status !== 201 && r.status !== 200) {
1001
+ err(red(`signup failed (${r.status}): ${JSON.stringify(r.body).slice(0, 200)}`));
1002
+ process.exitCode = 1;
1003
+ return;
1004
+ }
1005
+ const wsId = r.body.workspace?.id ?? r.body.workspace_id ?? r.body.id;
1006
+ const key = r.body.key ?? r.body.api_key;
1007
+ saveCreds({ workspace_id: wsId, api_key: key, url: base });
1008
+ const payload = {
1009
+ workspace_id: wsId,
1010
+ api_key: key,
1011
+ dashboard_url: `${base}/dashboard?workspace=${wsId}`,
1012
+ one_click_dashboard_url: `${base}/dashboard?workspace=${wsId}#key=${key}`,
1013
+ openai_base_url: `${base}/w/${key}/v1`,
1014
+ anthropic_base_url: `${base}/w/${key}`,
1015
+ mcp_url: `${base}/mcp`,
1016
+ badge_svg_url: `${base}/v1/badge/${wsId}.svg`
1017
+ };
1018
+ if (a.flags.json) {
1019
+ out(JSON.stringify(payload, null, 2));
1020
+ return;
1021
+ }
1022
+ out(`${bold("TokenZero Workspace Provisioned (0-Click / No Form)")}`);
1023
+ out(` workspace_id ${cyan(wsId)}`);
1024
+ out(` api_key ${green(key)}`);
1025
+ out(` 1-click dash ${cyan(payload.one_click_dashboard_url)}`);
1026
+ out(` OPENAI_BASE_URL ${payload.openai_base_url}`);
1027
+ out(` ANTHROPIC_BASE_URL ${payload.anthropic_base_url}`);
1028
+ out(dim(` Saved credentials to ${credsPath()}`));
1029
+ }
1030
+ async function cmdDashboard(a) {
1031
+ const saved = loadCreds();
1032
+ const base = baseUrl(a) || saved?.url || "https://tokenzero.sh";
1033
+ const key = a.flags.key || process.env.TOKENZERO_KEY || saved?.api_key || "";
1034
+ const wsId = saved?.workspace_id || "";
1035
+ if (!key && !wsId) {
1036
+ err(red("No saved workspace credentials. Run `npx -y @tokenzero/cli signup` or `tokenzero init` first."));
1037
+ process.exitCode = 2;
1038
+ return;
1039
+ }
1040
+ const url = `${base}/dashboard${wsId ? "?workspace=" + wsId : ""}${key ? "#key=" + key : ""}`;
1041
+ if (a.flags.json) {
1042
+ out(JSON.stringify({ workspace_id: wsId || null, dashboard_url: url }, null, 2));
1043
+ return;
893
1044
  }
1045
+ out(url);
894
1046
  }
895
1047
  async function cmdAudit(a) {
896
1048
  const root = path2.resolve(a._[1] ?? ".");
@@ -1007,7 +1159,11 @@ async function cmdVerify(a) {
1007
1159
  function help() {
1008
1160
  out(`${bold("tokenzero")} v${VERSION} \u2014 cut your LLM bill with a BYOK gateway
1009
1161
 
1010
- ${bold("tokenzero init")} [dir] [--url URL] [--name NAME] [--yes] [--no-env] [--mcp]
1162
+ ${bold("tokenzero signup")} [--name NAME] [--json]
1163
+ Instant 1-command workspace & API key provisioning (no form, saves to ~/.config/tokenzero).
1164
+ ${bold("tokenzero dashboard")} [--json]
1165
+ Print your 1-click auto-unlock dashboard URL (#key= fragment, never sent in HTTP headers).
1166
+ ${bold("tokenzero init")} [dir] [--url URL] [--name NAME] [--yes] [--no-env] [--mcp] [--github-action] [--agent-rules]
1011
1167
  Audit locally, create a workspace, point OPENAI_BASE_URL / ANTHROPIC_BASE_URL at the gateway.
1012
1168
  ${bold("tokenzero audit")} [dir] [--json | --format markdown] [--diff file|-] [--fail-over PCT]
1013
1169
  Static token-waste audit. Runs locally; nothing is uploaded. --fail-over makes it a CI gate.
@@ -1026,6 +1182,10 @@ async function main() {
1026
1182
  const cmd = a._[0];
1027
1183
  if (a.flags.version || cmd === "version") return out(VERSION);
1028
1184
  switch (cmd) {
1185
+ case "signup":
1186
+ return cmdSignup(a);
1187
+ case "dashboard":
1188
+ return cmdDashboard(a);
1029
1189
  case "init":
1030
1190
  return cmdInit(a);
1031
1191
  case "audit":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenzero/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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",