openzoo 0.33.2 → 0.33.4

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/lib/launch.js CHANGED
@@ -84,6 +84,42 @@ export async function launchClaude(argv) {
84
84
  if (terminal) {
85
85
  const cli = resolveClaudeCli();
86
86
  if (!cli) { console.error('openzoo: `claude` CLI not found on PATH — install Claude Code, or drop --terminal for the desktop app'); process.exit(1); }
87
+ // ALWAYS-ON HUD via Claude Code's NATIVE status line (the title bar is owned
88
+ // by Claude Code and gets overwritten, so OSC there is useless). We write a
89
+ // tiny status script that reads the proxy's /v1/info, and merge a statusLine
90
+ // into ~/.claude/settings.json — PRESERVING any existing one (restored on
91
+ // exit). Shows live spend + call count at the bottom of every turn.
92
+ let restoreStatus = null;
93
+ try {
94
+ const scriptPath = path.join(os.homedir(), '.openzoo', 'statusline.sh');
95
+ fs.mkdirSync(path.dirname(scriptPath), { recursive: true });
96
+ // node JSON.parse, not awk — /v1/info is pretty-printed (space after the
97
+ // colon) and a fixed-offset awk regex silently read 0. This is robust.
98
+ fs.writeFileSync(scriptPath,
99
+ '#!/bin/sh\n'
100
+ + `curl -s --max-time 1 ${base}/info 2>/dev/null | `
101
+ + `${JSON.stringify(process.execPath)} -e `
102
+ + '\'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{'
103
+ + 'try{const j=JSON.parse(s);'
104
+ + 'process.stdout.write("\\x1b[38;5;208m\\u25cf\\x1b[0m openzoo $"+(Number(j.spendUsd)||0).toFixed(4)+" "+(j.paidCalls||0)+" call"+((j.paidCalls||0)===1?"":"s")+" \\u00b7 x402")}'
105
+ + 'catch{process.stdout.write("\\x1b[38;5;208m\\u25cf\\x1b[0m openzoo \\u00b7 x402")}})\'\n');
106
+ fs.chmodSync(scriptPath, 0o755);
107
+ const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
108
+ let settings = {};
109
+ try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')) || {}; } catch { settings = {}; }
110
+ const prev = settings.statusLine;
111
+ settings.statusLine = { type: 'command', command: `sh ${scriptPath}` };
112
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
113
+ fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
114
+ restoreStatus = () => {
115
+ try {
116
+ const cur = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
117
+ if (prev === undefined) delete cur.statusLine; else cur.statusLine = prev;
118
+ fs.writeFileSync(settingsPath, `${JSON.stringify(cur, null, 2)}\n`);
119
+ } catch { /* leave as-is */ }
120
+ };
121
+ } catch { /* HUD is best-effort */ }
122
+
87
123
  // A CLEAR banner BEFORE Claude Code takes the screen, so it is obvious this
88
124
  // session routes through the zoo (the terminal title then shows live spend).
89
125
  let wallet = '';
@@ -93,11 +129,11 @@ export async function launchClaude(argv) {
93
129
  console.error(` endpoint : ${base}`);
94
130
  console.error(' auth : gateway token (ANTHROPIC_API_KEY unset — no api.anthropic.com billing)');
95
131
  if (wallet) console.error(` wallet : ${wallet}`);
96
- console.error(' spend : shown live in the terminal title bar; receipts in ~/.openzoo/proxy.log');
132
+ console.error(' spend : live in the status line below (bottom of screen); receipts in ~/.openzoo/proxy.log');
97
133
  console.error(' every turn pays x402.');
98
134
  console.error('');
99
135
  const child = spawn(cli, rest, { stdio: 'inherit', env });
100
- child.on('exit', (c) => process.exit(c ?? 0));
136
+ child.on('exit', (c) => { try { restoreStatus?.(); } catch { /* ignore */ } process.exit(c ?? 0); });
101
137
  child.on('error', (e) => { console.error(`openzoo: could not launch claude: ${e.message}`); process.exit(1); });
102
138
  return;
103
139
  }
package/lib/proxy.js CHANGED
@@ -373,6 +373,8 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
373
373
  reachedVia: viaTunnel ? 'public tunnel' : 'localhost',
374
374
  publicTunnel: tunnelGate?.publicUrl ? `${tunnelGate.publicUrl}/v1` : null,
375
375
  servedRequests,
376
+ spendUsd: sessionSpent,
377
+ paidCalls,
376
378
  mcp: `${self.replace(/\/v1$/, '')}/mcp`,
377
379
  upstream: config.apiBase,
378
380
  payment: 'x402 per request from the operator\'s local burner wallet — no API key, no account',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.33.2",
3
+ "version": "0.33.4",
4
4
  "description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
5
5
  "license": "MIT",
6
6
  "type": "module",