openzoo 0.17.0 → 0.18.1
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/cursorcfg.js +12 -3
- package/lib/mcp.js +58 -0
- package/lib/proxy.js +36 -0
- package/lib/setup.js +7 -2
- package/package.json +1 -1
package/lib/cursorcfg.js
CHANGED
|
@@ -39,9 +39,18 @@ const sqlite = (db, sql) => execFileSync('sqlite3', [db, sql], { encoding: 'utf8
|
|
|
39
39
|
/** True when the editor process is running — writing under it gets clobbered. */
|
|
40
40
|
export function editorRunning(which) {
|
|
41
41
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
// MATCH THE APP BINARY, NOT THE WORD. `pgrep -f Cursor` matched 19
|
|
43
|
+
// processes on a machine with Cursor CLOSED: this very command
|
|
44
|
+
// (`node openzoo.js cursor` contains the word), leftover crashpad helpers,
|
|
45
|
+
// and macOS's own CursorUIViewService — so it always warned "already
|
|
46
|
+
// running" and told the user to quit an editor that was not open.
|
|
47
|
+
const needle = which === 'vscode'
|
|
48
|
+
? 'Visual Studio Code.app/Contents/MacOS/'
|
|
49
|
+
: 'Cursor.app/Contents/MacOS/Cursor';
|
|
50
|
+
const out = execFileSync('pgrep', ['-f', needle], { encoding: 'utf8' });
|
|
51
|
+
const pids = out.split('\n').map((s) => s.trim()).filter(Boolean)
|
|
52
|
+
.filter((p) => Number(p) !== process.pid && Number(p) !== process.ppid);
|
|
53
|
+
return pids.length > 0;
|
|
45
54
|
} catch { return false; }
|
|
46
55
|
}
|
|
47
56
|
|
package/lib/mcp.js
CHANGED
|
@@ -167,6 +167,64 @@ export function buildMcpServer() {
|
|
|
167
167
|
}
|
|
168
168
|
});
|
|
169
169
|
|
|
170
|
+
// WHY A DEDICATED TOOL: MCP server `instructions` are advisory and several
|
|
171
|
+
// clients (Cursor among them) never surface them to the model — so an agent
|
|
172
|
+
// with this server attached still answers "I have no visibility into the
|
|
173
|
+
// routing layer", which is the FIRST question users ask, every time. Tool
|
|
174
|
+
// DESCRIPTIONS are always in context, so the facts live here too, and the
|
|
175
|
+
// tool returns them live.
|
|
176
|
+
server.registerTool('zoo_status', {
|
|
177
|
+
description:
|
|
178
|
+
'Report what THIS openzoo proxy is and how it is routed, by querying it live (GET /v1/info + wallet + '
|
|
179
|
+
+ 'rails). Call it when asked about the openzoo setup: its endpoint, tunnel URL, upstream, wallet, '
|
|
180
|
+
+ 'balances, or what its context/binding actually does. '
|
|
181
|
+
+ 'SCOPE — it describes the PROXY, not you: it says nothing about which provider serves your own '
|
|
182
|
+
+ 'inference or what your own attention window is, and you should not infer either from it. If your '
|
|
183
|
+
+ 'requests are not going through this proxy, say so. '
|
|
184
|
+
+ 'It is useful for correcting two common overclaims, because the returned values are checkable: '
|
|
185
|
+
+ 'openzoo does not enlarge any model\'s attention window (it binds a corpus server-side and '
|
|
186
|
+
+ 'retrieves top-k chunks, which is lossy), and calls made THROUGH it are paid per-request via x402 '
|
|
187
|
+
+ 'from a local burner wallet rather than with an API key.',
|
|
188
|
+
inputSchema: {},
|
|
189
|
+
}, async () => {
|
|
190
|
+
const rails = await liveRails().catch(() => null);
|
|
191
|
+
let balances = null;
|
|
192
|
+
try {
|
|
193
|
+
const bals = await Promise.all(
|
|
194
|
+
FUNDING_ASSETS.map((a) => tokenBalance(client.connection, client.keypair.publicKey, a.mint)),
|
|
195
|
+
);
|
|
196
|
+
balances = Object.fromEntries(FUNDING_ASSETS.map((a, i) => [a.symbol, bals[i].ui ?? 0]));
|
|
197
|
+
} catch { /* advisory */ }
|
|
198
|
+
// ASK THE PROXY, DON'T ASSUME. The public tunnel URL is what a cloud agent
|
|
199
|
+
// actually reaches us on, and only the running proxy knows it — so read it
|
|
200
|
+
// from /v1/info instead of reporting a localhost URL that a remote caller
|
|
201
|
+
// cannot use.
|
|
202
|
+
let live = null;
|
|
203
|
+
try {
|
|
204
|
+
const r = await fetch(`http://localhost:${config.port}/v1/info`, { signal: AbortSignal.timeout(2500) });
|
|
205
|
+
if (r.ok) live = await r.json();
|
|
206
|
+
} catch { /* proxy not up — local facts below still hold */ }
|
|
207
|
+
return text({
|
|
208
|
+
publicTunnel: live?.publicTunnel ?? null,
|
|
209
|
+
useThisIfRemote: live?.publicTunnel ?? '(no tunnel — proxy not running, or localhost-only mode)',
|
|
210
|
+
proxy: `http://localhost:${config.port}/v1`,
|
|
211
|
+
mcp: `http://localhost:${config.port}/mcp`,
|
|
212
|
+
upstream: config.apiBase,
|
|
213
|
+
defaultModel: DEFAULT_MODEL,
|
|
214
|
+
payment: 'x402 per request from a local burner wallet — no API key, no account',
|
|
215
|
+
railsLiveNow: rails?.live ?? null,
|
|
216
|
+
wallet: { solana: client.address, evm: client.evmAddress },
|
|
217
|
+
balances,
|
|
218
|
+
contextTruth: {
|
|
219
|
+
yourAttentionWindow: 'unchanged — openzoo does not enlarge it',
|
|
220
|
+
boundCeiling: '~128M tokens client-usable via bind + retrieval',
|
|
221
|
+
singleRequestLimit: '~8MB; larger corpora are bound in parts',
|
|
222
|
+
retrieval: 'lossy — top-k chunks are retrieved, not the whole corpus',
|
|
223
|
+
},
|
|
224
|
+
note: 'A cloud-run harness cannot reach localhost; it needs the public tunnel URL the proxy prints at startup.',
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
170
228
|
server.registerTool('zoo_models', {
|
|
171
229
|
description: 'List the models the zoo serves, with per-token pricing (free endpoint, no payment).',
|
|
172
230
|
inputSchema: {},
|
package/lib/proxy.js
CHANGED
|
@@ -325,6 +325,42 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
325
325
|
return;
|
|
326
326
|
}
|
|
327
327
|
}
|
|
328
|
+
// ROUTING TRUTH, SERVED FROM WHATEVER URL YOU REACHED US ON. A cloud agent
|
|
329
|
+
// only ever touches the tunnel, so asking a local MCP process "what is my
|
|
330
|
+
// routing" is the wrong question — the answer has to come from the tunnel
|
|
331
|
+
// itself, and name the tunnel. Free and unauthenticated: discovery must
|
|
332
|
+
// never be the thing that is gated.
|
|
333
|
+
{
|
|
334
|
+
const p0 = (req.url || '').split('?')[0];
|
|
335
|
+
if (req.method === 'GET' && (p0 === '/v1/info' || p0 === '/info')) {
|
|
336
|
+
const self = viaTunnel && tunnelGate?.publicUrl
|
|
337
|
+
? `${tunnelGate.publicUrl}/v1`
|
|
338
|
+
: `http://localhost:${config.port}/v1`;
|
|
339
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
340
|
+
res.end(JSON.stringify({
|
|
341
|
+
youAreTalkingTo: 'openzoo proxy',
|
|
342
|
+
yourEndpoint: self,
|
|
343
|
+
reachedVia: viaTunnel ? 'public tunnel' : 'localhost',
|
|
344
|
+
publicTunnel: tunnelGate?.publicUrl ? `${tunnelGate.publicUrl}/v1` : null,
|
|
345
|
+
mcp: `${self.replace(/\/v1$/, '')}/mcp`,
|
|
346
|
+
upstream: config.apiBase,
|
|
347
|
+
payment: 'x402 per request from the operator\'s local burner wallet — no API key, no account',
|
|
348
|
+
auth: viaTunnel
|
|
349
|
+
? 'this public URL requires the oz_… bearer for paid endpoints; /v1/models and /v1/hrr/bind are free'
|
|
350
|
+
: 'localhost is keyless',
|
|
351
|
+
context: {
|
|
352
|
+
yourAttentionWindow: 'unchanged — openzoo does not enlarge it',
|
|
353
|
+
boundCeiling: '~128M tokens client-usable via bind + retrieval',
|
|
354
|
+
singleRequestLimit: '~8MB per request; larger corpora bind in parts',
|
|
355
|
+
retrieval: 'lossy top-k retrieval, NOT lossless compression',
|
|
356
|
+
},
|
|
357
|
+
tools: ['zoo_bind', 'zoo_ask', 'zoo_status', 'zoo_models', 'zoo_wallet', 'zoo_contexts'],
|
|
358
|
+
docs: 'https://openzoo.fun',
|
|
359
|
+
}, null, 2));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
328
364
|
if (viaTunnel) {
|
|
329
365
|
const got = (req.headers.authorization || '').replace(/^Bearer\s+/i, '').trim();
|
|
330
366
|
const authed = got === tunnelGate.token;
|
package/lib/setup.js
CHANGED
|
@@ -126,12 +126,17 @@ export async function setupEditor(which, target) {
|
|
|
126
126
|
const started = await startProxy({ silent: true, autoTunnel: true });
|
|
127
127
|
publicUrl = started?.publicUrl ?? null;
|
|
128
128
|
tunnelKey = started?.tunnelToken ?? null;
|
|
129
|
-
//
|
|
130
|
-
|
|
129
|
+
// Wait for cloudflared to publish the URL. It downloads on first run and
|
|
130
|
+
// routinely takes 30-45s; a 10s wait meant the tunnel line simply never
|
|
131
|
+
// printed and the user never learned the public URL existed.
|
|
132
|
+
process.stdout.write('waiting for tunnel');
|
|
133
|
+
for (let i = 0; i < 120 && !publicUrl; i++) {
|
|
131
134
|
await new Promise((r) => setTimeout(r, 500));
|
|
132
135
|
publicUrl = started?.publicUrl ?? null;
|
|
133
136
|
tunnelKey = started?.tunnelToken ?? null;
|
|
137
|
+
if (i % 4 === 3) process.stdout.write('.');
|
|
134
138
|
}
|
|
139
|
+
process.stdout.write(publicUrl ? ' ok\n' : ' (not up yet — it will print in this terminal when ready)\n');
|
|
135
140
|
} else {
|
|
136
141
|
console.log(`proxy already running on ${base}`);
|
|
137
142
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
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",
|