openzoo 0.29.8 → 0.30.0
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/openzoo.js +5 -0
- package/lib/launch.js +74 -0
- package/lib/setup.js +8 -0
- package/package.json +1 -1
package/bin/openzoo.js
CHANGED
|
@@ -19,6 +19,8 @@ usage:
|
|
|
19
19
|
only "Auto". Undo: npx openzoo unblock
|
|
20
20
|
npx openzoo vscode [path] same, for VS Code
|
|
21
21
|
npx openzoo editor [path] whichever is installed (Cursor wins if both)
|
|
22
|
+
npx openzoo claude [dir] [--terminal] launch Claude on the zoo — desktop app
|
|
23
|
+
by default, --terminal for the Claude Code CLI
|
|
22
24
|
npx openzoo launch <cmd> [args] launch a TERMINAL Messages API client
|
|
23
25
|
(claude, aider...) already pointed at the zoo
|
|
24
26
|
npx openzoo mcp stdio MCP server (tools: zoo_ask, zoo_bind, zoo_models, zoo_wallet, zoo_contexts)
|
|
@@ -69,6 +71,9 @@ async function main() {
|
|
|
69
71
|
case 'mcp':
|
|
70
72
|
await (await import('../lib/mcp.js')).startMcp();
|
|
71
73
|
break;
|
|
74
|
+
case 'claude':
|
|
75
|
+
await (await import('../lib/launch.js')).launchClaude(process.argv.slice(3));
|
|
76
|
+
break;
|
|
72
77
|
case 'launch': {
|
|
73
78
|
const rest = process.argv.slice(3);
|
|
74
79
|
const [harness, hargs] = [rest[0], rest.slice(1)];
|
package/lib/launch.js
CHANGED
|
@@ -10,8 +10,82 @@
|
|
|
10
10
|
* (`npx openzoo` in another terminal); we check first and say so if not.
|
|
11
11
|
*/
|
|
12
12
|
import { spawn } from 'node:child_process';
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
import path from 'node:path';
|
|
13
16
|
import { config } from './config.js';
|
|
14
17
|
|
|
18
|
+
/** Resolve the Claude DESKTOP app binary, platform-agnostically. Spawn the
|
|
19
|
+
* binary directly (not `open -a`) so the env — ANTHROPIC_BASE_URL — survives;
|
|
20
|
+
* macOS `open` hands off to launchd and drops it. */
|
|
21
|
+
function resolveClaudeDesktop() {
|
|
22
|
+
const bundles = [
|
|
23
|
+
'/Applications/Claude.app/Contents/MacOS/Claude',
|
|
24
|
+
path.join(os.homedir(), 'Applications', 'Claude.app', 'Contents', 'MacOS', 'Claude'),
|
|
25
|
+
];
|
|
26
|
+
for (const b of bundles) { try { fs.accessSync(b, fs.constants.X_OK); return b; } catch { /* next */ } }
|
|
27
|
+
// Linux/Windows or PATH install.
|
|
28
|
+
for (const dir of (process.env.PATH || '').split(path.delimiter)) {
|
|
29
|
+
for (const n of ['claude-desktop', 'Claude']) {
|
|
30
|
+
const f = path.join(dir, n + (process.platform === 'win32' ? '.exe' : ''));
|
|
31
|
+
try { fs.accessSync(f, fs.constants.X_OK); return f; } catch { /* next */ }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Resolve the Claude Code TERMINAL CLI. */
|
|
38
|
+
function resolveClaudeCli() {
|
|
39
|
+
for (const dir of (process.env.PATH || '').split(path.delimiter)) {
|
|
40
|
+
const f = path.join(dir, 'claude' + (process.platform === 'win32' ? '.cmd' : ''));
|
|
41
|
+
try { fs.accessSync(f, fs.constants.X_OK); return f; } catch { /* next */ }
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* `npx openzoo claude [dir] [--terminal]` — launch Claude on the zoo.
|
|
48
|
+
* DEFAULT is the desktop app; `--terminal` (or `-t`) runs the Claude Code CLI.
|
|
49
|
+
* Both get ANTHROPIC_BASE_URL so inference pays x402.
|
|
50
|
+
*/
|
|
51
|
+
export async function launchClaude(argv) {
|
|
52
|
+
const base = `http://localhost:${config.port}/v1`;
|
|
53
|
+
try {
|
|
54
|
+
const r = await fetch(`${base}/models`, { signal: AbortSignal.timeout(4000) });
|
|
55
|
+
if (!r.ok) throw new Error(String(r.status));
|
|
56
|
+
} catch {
|
|
57
|
+
console.error(`openzoo: no proxy reachable at ${base}\nstart it first: npx openzoo`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
const terminal = argv.includes('--terminal') || argv.includes('-t');
|
|
61
|
+
const rest = argv.filter((a) => a !== '--terminal' && a !== '-t');
|
|
62
|
+
const env = {
|
|
63
|
+
...process.env,
|
|
64
|
+
ANTHROPIC_BASE_URL: base,
|
|
65
|
+
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || 'sk-openzoo',
|
|
66
|
+
ANTHROPIC_AUTH_TOKEN: process.env.ANTHROPIC_AUTH_TOKEN || 'sk-openzoo',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
if (terminal) {
|
|
70
|
+
const cli = resolveClaudeCli();
|
|
71
|
+
if (!cli) { console.error('openzoo: `claude` CLI not found on PATH — install Claude Code, or drop --terminal for the desktop app'); process.exit(1); }
|
|
72
|
+
console.error(`openzoo: Claude Code (terminal) on the zoo — every turn pays x402`);
|
|
73
|
+
const child = spawn(cli, rest, { stdio: 'inherit', env });
|
|
74
|
+
child.on('exit', (c) => process.exit(c ?? 0));
|
|
75
|
+
child.on('error', (e) => { console.error(`openzoo: could not launch claude: ${e.message}`); process.exit(1); });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const app = resolveClaudeDesktop();
|
|
80
|
+
if (!app) { console.error('openzoo: Claude desktop app not found — use `npx openzoo claude --terminal` for the CLI'); process.exit(1); }
|
|
81
|
+
console.error(`openzoo: Claude desktop on the zoo (ANTHROPIC_BASE_URL=${base})`);
|
|
82
|
+
console.error(' note: the desktop app routes through the zoo only if it honours ANTHROPIC_BASE_URL;');
|
|
83
|
+
console.error(' --terminal (Claude Code CLI) is the guaranteed-x402 path.');
|
|
84
|
+
const child = spawn(app, rest, { stdio: 'ignore', env, detached: true });
|
|
85
|
+
child.on('error', (e) => console.error(`openzoo: could not launch Claude desktop: ${e.message}`));
|
|
86
|
+
child.unref();
|
|
87
|
+
}
|
|
88
|
+
|
|
15
89
|
export async function launchHarness(cmd, args) {
|
|
16
90
|
const base = `http://localhost:${config.port}/v1`;
|
|
17
91
|
// Fail early with a clear message rather than letting the harness spew
|
package/lib/setup.js
CHANGED
|
@@ -328,6 +328,14 @@ export async function setupEditor(which, target) {
|
|
|
328
328
|
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || 'sk-openzoo',
|
|
329
329
|
ANTHROPIC_AUTH_TOKEN: process.env.ANTHROPIC_AUTH_TOKEN || 'sk-openzoo',
|
|
330
330
|
};
|
|
331
|
+
// TRUST OUR SELF-SIGNED IMPERSONATION CERT IN THE EDITOR'S NODE STACK TOO.
|
|
332
|
+
// --ignore-certificate-errors covers only the Chromium RENDERER (browser fetch:
|
|
333
|
+
// stripe, updates — those succeeded). The editor's gRPC/Connect transport runs
|
|
334
|
+
// in the Electron MAIN process on Node's own TLS, which ignores that flag and
|
|
335
|
+
// rejected our cert — the ECONNRESET-before-ALPN wall in the backend log, and
|
|
336
|
+
// exactly the model/chat calls we need. NODE_TLS_REJECT_UNAUTHORIZED=0 is the
|
|
337
|
+
// Node-side switch. Only set under takeover, where we own the endpoint.
|
|
338
|
+
if (which === 'cursor' && !process.argv.includes('--no-takeover')) env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
331
339
|
|
|
332
340
|
console.log('');
|
|
333
341
|
console.log(`mcp: ${mcpFile} (openzoo: zoo_bind, zoo_ask, zoo_models, zoo_wallet, zoo_contexts)`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
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",
|