openzoo 0.33.0 → 0.33.2
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/hosts.js +19 -14
- package/lib/launch.js +25 -14
- package/lib/proxy.js +10 -0
- package/lib/setup.js +8 -2
- package/package.json +1 -1
package/lib/hosts.js
CHANGED
|
@@ -35,19 +35,24 @@ const HOSTS = WIN
|
|
|
35
35
|
const BACKUP = `${HOSTS}.openzoo-backup`;
|
|
36
36
|
const MARK = '# openzoo: force the editor onto the local proxy';
|
|
37
37
|
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
/**
|
|
39
|
+
* The DEFAULT block — the model-list re-sync host only. Blocking this forces a
|
|
40
|
+
* subscribed account's editor onto our model list + endpoint without severing
|
|
41
|
+
* chat, so plain `openzoo cursor` works for subbed users.
|
|
42
|
+
*
|
|
43
|
+
* The chat-inference hosts (agent.api5.*) are NOT here: blocking them without a
|
|
44
|
+
* working StreamUnifiedChat translator severs chat entirely ("Reconnecting...",
|
|
45
|
+
* observed on a working ultra account). They live in AGENT_HOSTS, blocked only
|
|
46
|
+
* under --takeover where the impersonation backend actually answers chat.
|
|
47
|
+
*/
|
|
48
|
+
export const BACKEND_HOSTS = ['api2.cursor.sh'];
|
|
49
|
+
|
|
50
|
+
export const AGENT_HOSTS = [
|
|
46
51
|
'agent.api5.cursor.sh', 'agentn.api5.cursor.sh',
|
|
47
52
|
'agent-gcpp-uswest.api5.cursor.sh', 'agentn-gcpp-uswest.api5.cursor.sh',
|
|
48
53
|
'agent-gcpp-eucentral.api5.cursor.sh', 'agentn-gcpp-eucentral.api5.cursor.sh',
|
|
49
54
|
'agent-gcpp-apsoutheast.api5.cursor.sh', 'agentn-gcpp-apsoutheast.api5.cursor.sh',
|
|
50
|
-
]
|
|
55
|
+
];
|
|
51
56
|
|
|
52
57
|
export function isBlocked() {
|
|
53
58
|
try {
|
|
@@ -77,17 +82,17 @@ function flushDnsCmd() {
|
|
|
77
82
|
+ ' || service nscd restart 2>/dev/null; true';
|
|
78
83
|
}
|
|
79
84
|
|
|
80
|
-
export function blockBackend() {
|
|
85
|
+
export function blockBackend(hosts = BACKEND_HOSTS) {
|
|
81
86
|
// Add only the hosts NOT already present, so a prior api2-only block still gets
|
|
82
87
|
// the agent/chat hosts appended (early-returning on isBlocked left them out).
|
|
83
88
|
let current = '';
|
|
84
89
|
try { current = fs.readFileSync(HOSTS, 'utf8'); } catch { /* new */ }
|
|
85
|
-
const missing =
|
|
90
|
+
const missing = hosts.filter((h) => !new RegExp(`^\\s*127\\.0\\.0\\.1\\s+${h.replace(/\./g, '\\.')}\\b`, 'm').test(current));
|
|
86
91
|
if (!missing.length) return { already: true };
|
|
87
92
|
const entries = missing.map((h) => `127.0.0.1 ${h}`).join('\\n');
|
|
88
93
|
console.log('');
|
|
89
94
|
console.log('blocking the editor\'s backend so it cannot re-sync over your model list.');
|
|
90
|
-
console.log(` hosts : ${
|
|
95
|
+
console.log(` hosts : ${hosts.join(', ')} -> 127.0.0.1`);
|
|
91
96
|
console.log(` backup : ${BACKUP}`);
|
|
92
97
|
console.log(' NOTE : that host also carries the editor\'s auth/usage — it may report');
|
|
93
98
|
console.log(' being signed out. Undo any time with: npx openzoo unblock');
|
|
@@ -96,7 +101,7 @@ export function blockBackend() {
|
|
|
96
101
|
if (WIN) {
|
|
97
102
|
console.log(' windows: run this in an ADMINISTRATOR PowerShell, then relaunch:');
|
|
98
103
|
console.log(` Copy-Item "${HOSTS}" "${BACKUP}" -ErrorAction SilentlyContinue`);
|
|
99
|
-
for (const h of
|
|
104
|
+
for (const h of hosts) console.log(` Add-Content "${HOSTS}" "127.0.0.1 ${h}"`);
|
|
100
105
|
console.log(' ipconfig /flushdns');
|
|
101
106
|
return { ok: false, manual: true, blocked: false };
|
|
102
107
|
}
|
|
@@ -112,7 +117,7 @@ export function unblockBackend() {
|
|
|
112
117
|
if (!isBlocked()) return { already: true };
|
|
113
118
|
// Remove only OUR lines, never restore wholesale — the user may have edited
|
|
114
119
|
// /etc/hosts for unrelated reasons since the backup was taken.
|
|
115
|
-
const pattern = BACKEND_HOSTS.map((h) => h.replace(
|
|
120
|
+
const pattern = [...BACKEND_HOSTS, ...AGENT_HOSTS].map((h) => h.replace(/\./g, '\\.')).join('|');
|
|
116
121
|
if (WIN) {
|
|
117
122
|
console.log('windows: run in an ADMINISTRATOR PowerShell:');
|
|
118
123
|
console.log(` Copy-Item "${BACKUP}" "${HOSTS}" -Force; ipconfig /flushdns`);
|
package/lib/launch.js
CHANGED
|
@@ -71,17 +71,31 @@ 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
|
+
// A CLEAR banner BEFORE Claude Code takes the screen, so it is obvious this
|
|
88
|
+
// session routes through the zoo (the terminal title then shows live spend).
|
|
89
|
+
let wallet = '';
|
|
90
|
+
try { const { PayClient } = await import('./pay.js'); wallet = new PayClient().address; } catch { /* optional */ }
|
|
91
|
+
console.error('');
|
|
92
|
+
console.error(' \x1b[38;5;208m●\x1b[0m openzoo — this Claude Code session routes through the zoo');
|
|
93
|
+
console.error(` endpoint : ${base}`);
|
|
94
|
+
console.error(' auth : gateway token (ANTHROPIC_API_KEY unset — no api.anthropic.com billing)');
|
|
95
|
+
if (wallet) console.error(` wallet : ${wallet}`);
|
|
96
|
+
console.error(' spend : shown live in the terminal title bar; receipts in ~/.openzoo/proxy.log');
|
|
97
|
+
console.error(' every turn pays x402.');
|
|
98
|
+
console.error('');
|
|
85
99
|
const child = spawn(cli, rest, { stdio: 'inherit', env });
|
|
86
100
|
child.on('exit', (c) => process.exit(c ?? 0));
|
|
87
101
|
child.on('error', (e) => { console.error(`openzoo: could not launch claude: ${e.message}`); process.exit(1); });
|
|
@@ -130,13 +144,10 @@ export async function launchHarness(cmd, args) {
|
|
|
130
144
|
process.exit(1);
|
|
131
145
|
}
|
|
132
146
|
|
|
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
|
-
};
|
|
147
|
+
const env = { ...process.env };
|
|
148
|
+
delete env.ANTHROPIC_API_KEY; // conflicts with the gateway auth-token path
|
|
149
|
+
env.ANTHROPIC_BASE_URL = base;
|
|
150
|
+
env.ANTHROPIC_AUTH_TOKEN = process.env.ANTHROPIC_AUTH_TOKEN || 'sk-openzoo';
|
|
140
151
|
console.error(`openzoo: launching \`${cmd}\` on the zoo (ANTHROPIC_BASE_URL=${base}) — every turn pays x402`);
|
|
141
152
|
const child = spawn(cmd, args, { stdio: 'inherit', env });
|
|
142
153
|
child.on('exit', (code) => process.exit(code ?? 0));
|
package/lib/proxy.js
CHANGED
|
@@ -280,6 +280,7 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
280
280
|
// lines / payment receipts there corrupts that program's output (observed: the
|
|
281
281
|
// Solana receipt leaking into the Claude Code CLI). When silent, route this
|
|
282
282
|
// channel to a log file instead; only print to the console when we own it.
|
|
283
|
+
let paidCalls = 0;
|
|
283
284
|
let sayFile = null;
|
|
284
285
|
if (silent) {
|
|
285
286
|
try {
|
|
@@ -611,6 +612,15 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
611
612
|
if (requireToken) say(`${line} · session $${sessionSpent.toFixed(6)}`);
|
|
612
613
|
else if (viaTunnel) say(`${line} · public-url session $${tunnelSpent.toFixed(6)}`);
|
|
613
614
|
else say(line);
|
|
615
|
+
// ALWAYS-ON SPEND, TUI-SAFE. When a harness owns the terminal (silent),
|
|
616
|
+
// the receipt lines go to a file (they corrupt a TUI). But the running
|
|
617
|
+
// total should still be visible — so write it to the terminal TITLE via
|
|
618
|
+
// an OSC escape, which updates the window/tab title without touching the
|
|
619
|
+
// TUI's content. `openzoo ● $0.0042 · 12 calls` in the title bar, live.
|
|
620
|
+
if (receipt.ok && typeof receipt.billedUsd === 'number') { paidCalls += 1; }
|
|
621
|
+
if (sayFile) {
|
|
622
|
+
try { process.stderr.write(`]0;openzoo ● $${sessionSpent.toFixed(4)} · ${paidCalls} call${paidCalls === 1 ? '' : 's'}`); } catch { /* no tty */ }
|
|
623
|
+
}
|
|
614
624
|
scheduleRefresh(4000); // settlement lands on-chain in a few seconds
|
|
615
625
|
}
|
|
616
626
|
// Chat completions come back as one JSON object (settle-before-serve).
|
package/lib/setup.js
CHANGED
|
@@ -443,8 +443,14 @@ export async function setupEditor(which, target) {
|
|
|
443
443
|
//
|
|
444
444
|
// --no-block opts out for anyone who would rather keep the vendor catalog.
|
|
445
445
|
if (target0 === 'cursor' && !process.argv.includes('--no-block')) {
|
|
446
|
-
const { blockBackend, isBlocked } = await import('./hosts.js');
|
|
447
|
-
|
|
446
|
+
const { blockBackend, isBlocked, BACKEND_HOSTS, AGENT_HOSTS } = await import('./hosts.js');
|
|
447
|
+
// Default: block ONLY the model-list re-sync host (safe for subbed users).
|
|
448
|
+
// Under --takeover we also block the chat-inference hosts, because only then
|
|
449
|
+
// does the impersonation backend answer their chat — blocking them otherwise
|
|
450
|
+
// just severs Cursor ("Reconnecting...").
|
|
451
|
+
const hostsToBlock = process.argv.includes('--takeover')
|
|
452
|
+
? [...BACKEND_HOSTS, ...AGENT_HOSTS] : BACKEND_HOSTS;
|
|
453
|
+
const r = blockBackend(hostsToBlock);
|
|
448
454
|
if (r.already) console.log('backend: already blocked (required for routing; npx openzoo unblock to undo)');
|
|
449
455
|
else if (isBlocked()) console.log('backend: blocked -> 127.0.0.1 (this is what forces the editor onto the zoo)');
|
|
450
456
|
else console.log('backend: NOT blocked — the editor will keep using its own backend and\n nothing will reach the zoo. Re-run and enter your password.');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.2",
|
|
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",
|