openzoo 0.33.1 → 0.33.3
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 +58 -15
- package/lib/proxy.js +2 -0
- package/package.json +1 -1
package/lib/launch.js
CHANGED
|
@@ -71,19 +71,65 @@ export async function launchClaude(argv) {
|
|
|
71
71
|
const wantDesktop = argv.includes('--desktop');
|
|
72
72
|
const terminal = !wantDesktop;
|
|
73
73
|
const rest = argv.filter((a) => !['--terminal', '-t', '--desktop'].includes(a));
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
};
|
|
74
|
+
// GATEWAY AUTH, NOT API-KEY. Setting ANTHROPIC_API_KEY makes Claude Code bill
|
|
75
|
+
// api.anthropic.com and it TAKES PRECEDENCE over ANTHROPIC_BASE_URL — observed:
|
|
76
|
+
// "Both ... set · auth may not work" + "API Usage Billing", never hitting the
|
|
77
|
+
// zoo. A custom gateway uses BASE_URL + AUTH_TOKEN only; API_KEY must be UNSET
|
|
78
|
+
// (including any inherited one) or it wins.
|
|
79
|
+
const env = { ...process.env };
|
|
80
|
+
delete env.ANTHROPIC_API_KEY;
|
|
81
|
+
env.ANTHROPIC_BASE_URL = base;
|
|
82
|
+
env.ANTHROPIC_AUTH_TOKEN = process.env.ANTHROPIC_AUTH_TOKEN || 'sk-openzoo';
|
|
80
83
|
|
|
81
84
|
if (terminal) {
|
|
82
85
|
const cli = resolveClaudeCli();
|
|
83
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); }
|
|
84
|
-
|
|
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
|
+
fs.writeFileSync(scriptPath,
|
|
97
|
+
'#!/bin/sh\n'
|
|
98
|
+
+ `curl -s --max-time 1 ${base}/info 2>/dev/null | `
|
|
99
|
+
+ 'awk \'{ if (match($0,/"spendUsd":[0-9.eE-]+/)) { s=substr($0,RSTART+11,RLENGTH-11) } '
|
|
100
|
+
+ 'if (match($0,/"paidCalls":[0-9]+/)) { c=substr($0,RSTART+12,RLENGTH-12) } } '
|
|
101
|
+
+ 'END { printf "\\033[38;5;208m●\\033[0m openzoo $%.4f %s call%s · x402", s+0, c+0, (c==1?"":"s") }\'\n');
|
|
102
|
+
fs.chmodSync(scriptPath, 0o755);
|
|
103
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
104
|
+
let settings = {};
|
|
105
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')) || {}; } catch { settings = {}; }
|
|
106
|
+
const prev = settings.statusLine;
|
|
107
|
+
settings.statusLine = { type: 'command', command: `sh ${scriptPath}` };
|
|
108
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
109
|
+
fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
|
|
110
|
+
restoreStatus = () => {
|
|
111
|
+
try {
|
|
112
|
+
const cur = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
113
|
+
if (prev === undefined) delete cur.statusLine; else cur.statusLine = prev;
|
|
114
|
+
fs.writeFileSync(settingsPath, `${JSON.stringify(cur, null, 2)}\n`);
|
|
115
|
+
} catch { /* leave as-is */ }
|
|
116
|
+
};
|
|
117
|
+
} catch { /* HUD is best-effort */ }
|
|
118
|
+
|
|
119
|
+
// A CLEAR banner BEFORE Claude Code takes the screen, so it is obvious this
|
|
120
|
+
// session routes through the zoo (the terminal title then shows live spend).
|
|
121
|
+
let wallet = '';
|
|
122
|
+
try { const { PayClient } = await import('./pay.js'); wallet = new PayClient().address; } catch { /* optional */ }
|
|
123
|
+
console.error('');
|
|
124
|
+
console.error(' \x1b[38;5;208m●\x1b[0m openzoo — this Claude Code session routes through the zoo');
|
|
125
|
+
console.error(` endpoint : ${base}`);
|
|
126
|
+
console.error(' auth : gateway token (ANTHROPIC_API_KEY unset — no api.anthropic.com billing)');
|
|
127
|
+
if (wallet) console.error(` wallet : ${wallet}`);
|
|
128
|
+
console.error(' spend : live in the status line below (bottom of screen); receipts in ~/.openzoo/proxy.log');
|
|
129
|
+
console.error(' every turn pays x402.');
|
|
130
|
+
console.error('');
|
|
85
131
|
const child = spawn(cli, rest, { stdio: 'inherit', env });
|
|
86
|
-
child.on('exit', (c) => process.exit(c ?? 0));
|
|
132
|
+
child.on('exit', (c) => { try { restoreStatus?.(); } catch { /* ignore */ } process.exit(c ?? 0); });
|
|
87
133
|
child.on('error', (e) => { console.error(`openzoo: could not launch claude: ${e.message}`); process.exit(1); });
|
|
88
134
|
return;
|
|
89
135
|
}
|
|
@@ -130,13 +176,10 @@ export async function launchHarness(cmd, args) {
|
|
|
130
176
|
process.exit(1);
|
|
131
177
|
}
|
|
132
178
|
|
|
133
|
-
const env = {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// Claude Code sends model ids like "claude-opus-5"; the zoo serves those,
|
|
138
|
-
// and unknown ids are matched to the nearest served model regardless.
|
|
139
|
-
};
|
|
179
|
+
const env = { ...process.env };
|
|
180
|
+
delete env.ANTHROPIC_API_KEY; // conflicts with the gateway auth-token path
|
|
181
|
+
env.ANTHROPIC_BASE_URL = base;
|
|
182
|
+
env.ANTHROPIC_AUTH_TOKEN = process.env.ANTHROPIC_AUTH_TOKEN || 'sk-openzoo';
|
|
140
183
|
console.error(`openzoo: launching \`${cmd}\` on the zoo (ANTHROPIC_BASE_URL=${base}) — every turn pays x402`);
|
|
141
184
|
const child = spawn(cmd, args, { stdio: 'inherit', env });
|
|
142
185
|
child.on('exit', (code) => process.exit(code ?? 0));
|
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.
|
|
3
|
+
"version": "0.33.3",
|
|
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",
|