openzoo 0.21.4 → 0.21.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.
- package/lib/setup.js +18 -7
- package/lib/tunnel.js +20 -1
- package/package.json +1 -1
package/lib/setup.js
CHANGED
|
@@ -203,7 +203,7 @@ export async function setupEditor(which, target) {
|
|
|
203
203
|
// does not, we say so plainly and still launch.
|
|
204
204
|
if (!publicUrl) {
|
|
205
205
|
process.stdout.write('waiting for the public tunnel');
|
|
206
|
-
for (let i = 0; i <
|
|
206
|
+
for (let i = 0; i < 180 && !started?.publicUrl; i++) { // up to 90s
|
|
207
207
|
await new Promise((r) => setTimeout(r, 500));
|
|
208
208
|
if (i % 4 === 3) process.stdout.write('.');
|
|
209
209
|
}
|
|
@@ -212,9 +212,7 @@ export async function setupEditor(which, target) {
|
|
|
212
212
|
tunnelKey = started?.tunnelToken ?? tunnelKey;
|
|
213
213
|
}
|
|
214
214
|
if (publicUrl) console.log(`tunnel: ${publicUrl}/v1 api_key ${tunnelKey}`);
|
|
215
|
-
else console.log('tunnel: did not come up in
|
|
216
|
-
+ ' the editor will say "Access to private networks is forbidden"\n'
|
|
217
|
-
+ ' until you re-run this command.');
|
|
215
|
+
else console.log('tunnel: did not come up in 90s.');
|
|
218
216
|
} else {
|
|
219
217
|
console.log(`proxy already running on ${base}`);
|
|
220
218
|
// A proxy someone else started owns the tunnel; ask it for the public URL.
|
|
@@ -227,8 +225,13 @@ export async function setupEditor(which, target) {
|
|
|
227
225
|
}
|
|
228
226
|
// What the EDITOR is configured with. Localhost only as a last resort, and
|
|
229
227
|
// said out loud, because it will fail with the private-networks error.
|
|
230
|
-
|
|
231
|
-
|
|
228
|
+
// NEVER WRITE LOCALHOST INTO THE EDITOR. Its server makes the request, so a
|
|
229
|
+
// private address is unreachable from there and every call returns "Access to
|
|
230
|
+
// private networks is forbidden". Writing it anyway does not degrade
|
|
231
|
+
// gracefully — it overwrites a config that may have been working with one
|
|
232
|
+
// that provably cannot. With no tunnel we leave the existing settings alone
|
|
233
|
+
// and say why.
|
|
234
|
+
const editorBase = publicUrl ? `${publicUrl}/v1` : null;
|
|
232
235
|
|
|
233
236
|
// 2. ENV INTO THE EDITOR. Both vendor shapes, so an OpenAI-compatible pane
|
|
234
237
|
// and an Anthropic-shaped one (Claude Code extension) both route here.
|
|
@@ -256,6 +259,12 @@ export async function setupEditor(which, target) {
|
|
|
256
259
|
// globalStorage sqlite — not an encrypted store, as previously assumed — so
|
|
257
260
|
// the "paste four things into Settings" ritual is unnecessary. Must happen
|
|
258
261
|
// while the editor is CLOSED or it rewrites them from memory on exit.
|
|
262
|
+
if (!editorBase) {
|
|
263
|
+
console.log('settings: NOT touched — a tunnel is required for the editor path and none came up.');
|
|
264
|
+
console.log(' (localhost cannot work here: the editor calls the endpoint from ITS server,');
|
|
265
|
+
console.log(' which answers "Access to private networks is forbidden".)');
|
|
266
|
+
console.log(' your previous settings are left as they were — re-run when the network settles.');
|
|
267
|
+
}
|
|
259
268
|
const picked0 = pickEditor(which);
|
|
260
269
|
const target0 = picked0?.which || which || 'cursor';
|
|
261
270
|
// Quit it FOR the user — a running editor reverts our write on exit.
|
|
@@ -266,7 +275,9 @@ export async function setupEditor(which, target) {
|
|
|
266
275
|
}
|
|
267
276
|
const models = await catalogModels(base);
|
|
268
277
|
let wrote = null;
|
|
269
|
-
|
|
278
|
+
if (editorBase) {
|
|
279
|
+
try { wrote = writeEditorProviderConfig(target0, { baseUrl: editorBase, models, apiKey: tunnelKey }); } catch (e) { wrote = { error: e.message }; }
|
|
280
|
+
}
|
|
270
281
|
if (wrote?.error) {
|
|
271
282
|
console.log(`settings: could not write automatically (${wrote.error}) — set them in Settings → Models`);
|
|
272
283
|
} else if (wrote) {
|
package/lib/tunnel.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* OPENZOO_MAX_USD_PER_CALL and/or OPENZOO_TUNNEL_MAX_USD to add them;
|
|
21
21
|
* - every tunnel-served request is logged with its running spend.
|
|
22
22
|
*/
|
|
23
|
-
import { spawn } from 'node:child_process';
|
|
23
|
+
import { spawn, execFileSync } from 'node:child_process';
|
|
24
24
|
import { createWriteStream } from 'node:fs';
|
|
25
25
|
import fs from 'node:fs';
|
|
26
26
|
import path from 'node:path';
|
|
@@ -91,8 +91,27 @@ export async function ensureCloudflared(log = console.log) {
|
|
|
91
91
|
return cached;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Kill cloudflared processes left over from previous runs against OUR port.
|
|
96
|
+
*
|
|
97
|
+
* WHY: the proxy exits with its terminal but cloudflared does not always go with
|
|
98
|
+
* it, so repeated `openzoo cursor` runs stack tunnels. Observed: two orphaned
|
|
99
|
+
* cloudflared processes, the editor configured with a third hostname, and every
|
|
100
|
+
* request answering 530/1033 — the editor was pointed at a tunnel whose origin
|
|
101
|
+
* had died while other live tunnels sat there serving nothing.
|
|
102
|
+
*/
|
|
103
|
+
function killStaleTunnels(port, log) {
|
|
104
|
+
try {
|
|
105
|
+
const out = execFileSync('pgrep', ['-f', `cloudflared tunnel --url http://localhost:${port}`], { encoding: 'utf8' });
|
|
106
|
+
const pids = out.split('\n').map((x) => x.trim()).filter(Boolean).filter((x) => Number(x) !== process.pid);
|
|
107
|
+
for (const pid of pids) { try { process.kill(Number(pid), 'SIGTERM'); } catch { /* already gone */ } }
|
|
108
|
+
if (pids.length) log(`tunnel: cleared ${pids.length} stale cloudflared process(es) from an earlier run`);
|
|
109
|
+
} catch { /* pgrep exits non-zero when nothing matches — that is the good case */ }
|
|
110
|
+
}
|
|
111
|
+
|
|
94
112
|
/** Start a quick tunnel to localhost:<port>; resolve with its public URL. */
|
|
95
113
|
export function startCloudflared(bin, port, log) {
|
|
114
|
+
killStaleTunnels(port, log);
|
|
96
115
|
return new Promise((resolve, reject) => {
|
|
97
116
|
// --edge-ip-version 4: cloudflared prefers IPv6+QUIC to reach the edge
|
|
98
117
|
// whenever the interface merely HAS an IPv6 address — it never checks the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.6",
|
|
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",
|