openzoo 0.20.1 → 0.20.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/bin/openzoo.js +4 -2
- package/lib/hosts.js +110 -0
- package/lib/setup.js +15 -1
- package/package.json +1 -1
package/bin/openzoo.js
CHANGED
|
@@ -6,8 +6,10 @@ const HELP = `openzoo — local x402-paying proxy + MCP server for openzoo.fun
|
|
|
6
6
|
usage:
|
|
7
7
|
npx openzoo start the proxy: http://localhost:8402/v1 (keyless) PLUS a
|
|
8
8
|
public HTTPS url for cloud IDEs (key required, printed at start)
|
|
9
|
-
npx openzoo cursor [
|
|
10
|
-
|
|
9
|
+
npx openzoo cursor [dir] start proxy+tunnel, write MCP config + every model
|
|
10
|
+
into the picker, and LAUNCH Cursor on [dir]
|
|
11
|
+
(defaults to the current directory; ~ works;
|
|
12
|
+
a missing dir is created)
|
|
11
13
|
--profile use an isolated editor profile the
|
|
12
14
|
vendor account cannot re-sync over
|
|
13
15
|
npx openzoo vscode [path] same, for VS Code
|
package/lib/hosts.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Force the editor off its own backend by blackholing it in /etc/hosts.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS IS NEEDED: Cursor renders its model picker from IN-MEMORY state that
|
|
5
|
+
* it re-syncs from `api2.cursor.sh` on window focus. Writing the database is not
|
|
6
|
+
* enough — measured, the DB held 413 openzoo models and the correct selection
|
|
7
|
+
* while the UI showed none, because the sync repainted memory a moment later.
|
|
8
|
+
* The same host is `bcProxyUrl`, the route Cursor's own inference takes, so
|
|
9
|
+
* while it is reachable the editor will always prefer it over a custom endpoint.
|
|
10
|
+
*
|
|
11
|
+
* Pointing it at 127.0.0.1 makes that path fail, leaving the configured OpenAI
|
|
12
|
+
* base URL as the only way out.
|
|
13
|
+
*
|
|
14
|
+
* COLLATERAL, STATED PLAINLY: that host also carries auth and usage reporting.
|
|
15
|
+
* The editor may report being signed out or degraded. This is a system-wide
|
|
16
|
+
* change requiring sudo — so it is OPT-IN (`--block-backend`), always backs up
|
|
17
|
+
* to /etc/hosts.openzoo-backup, and `--unblock` restores it.
|
|
18
|
+
*/
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Hosts-file location and the privilege/flush mechanics differ per platform:
|
|
24
|
+
* macOS /etc/hosts sudo + dscacheutil/mDNSResponder
|
|
25
|
+
* Linux /etc/hosts sudo + systemd-resolve or nscd (best effort)
|
|
26
|
+
* Windows %SystemRoot%\\System32\\drivers\\etc\\hosts, needs an ELEVATED shell
|
|
27
|
+
* Everything below branches on that rather than assuming macOS.
|
|
28
|
+
*/
|
|
29
|
+
const WIN = process.platform === 'win32';
|
|
30
|
+
const HOSTS = WIN
|
|
31
|
+
? `${process.env.SystemRoot || 'C:\\Windows'}\\System32\\drivers\\etc\\hosts`
|
|
32
|
+
: '/etc/hosts';
|
|
33
|
+
const BACKUP = `${HOSTS}.openzoo-backup`;
|
|
34
|
+
const MARK = '# openzoo: force the editor onto the local proxy';
|
|
35
|
+
|
|
36
|
+
/** Hosts the editor uses for model sync + its own inference proxy. */
|
|
37
|
+
export const BACKEND_HOSTS = ['api2.cursor.sh'];
|
|
38
|
+
|
|
39
|
+
export function isBlocked() {
|
|
40
|
+
try {
|
|
41
|
+
const txt = fs.readFileSync(HOSTS, 'utf8');
|
|
42
|
+
return BACKEND_HOSTS.every((h) => new RegExp(`^\\s*127\\.0\\.0\\.1\\s+${h.replace('.', '\\.')}`, 'm').test(txt));
|
|
43
|
+
} catch { return false; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Run a privileged command. On unix sudo prompts in the user's own terminal;
|
|
48
|
+
* on Windows we cannot elevate from here, so we return false and the caller
|
|
49
|
+
* prints the line to run in an Administrator shell.
|
|
50
|
+
*/
|
|
51
|
+
function privileged(unixLine) {
|
|
52
|
+
if (WIN) return false;
|
|
53
|
+
const r = spawnSync('sudo', ['sh', '-c', unixLine], { stdio: 'inherit' });
|
|
54
|
+
return r.status === 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Flush the OS resolver cache, best effort, per platform. */
|
|
58
|
+
function flushDnsCmd() {
|
|
59
|
+
if (process.platform === 'darwin') {
|
|
60
|
+
return 'dscacheutil -flushcache 2>/dev/null; killall -HUP mDNSResponder 2>/dev/null; true';
|
|
61
|
+
}
|
|
62
|
+
// Linux: whichever of these exists; none is fatal.
|
|
63
|
+
return 'resolvectl flush-caches 2>/dev/null || systemd-resolve --flush-caches 2>/dev/null'
|
|
64
|
+
+ ' || service nscd restart 2>/dev/null; true';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function blockBackend() {
|
|
68
|
+
if (isBlocked()) return { already: true };
|
|
69
|
+
const entries = BACKEND_HOSTS.map((h) => `127.0.0.1 ${h}`).join('\\n');
|
|
70
|
+
console.log('');
|
|
71
|
+
console.log('blocking the editor\'s backend so it cannot re-sync over your model list.');
|
|
72
|
+
console.log(` hosts : ${BACKEND_HOSTS.join(', ')} -> 127.0.0.1`);
|
|
73
|
+
console.log(` backup : ${BACKUP}`);
|
|
74
|
+
console.log(' NOTE : that host also carries the editor\'s auth/usage — it may report');
|
|
75
|
+
console.log(' being signed out. Undo any time with: npx openzoo unblock');
|
|
76
|
+
console.log(' sudo will ask for your password now.');
|
|
77
|
+
if (WIN) {
|
|
78
|
+
console.log(' windows: run this in an ADMINISTRATOR PowerShell, then relaunch:');
|
|
79
|
+
console.log(` Copy-Item "${HOSTS}" "${BACKUP}" -ErrorAction SilentlyContinue`);
|
|
80
|
+
for (const h of BACKEND_HOSTS) console.log(` Add-Content "${HOSTS}" "127.0.0.1 ${h}"`);
|
|
81
|
+
console.log(' ipconfig /flushdns');
|
|
82
|
+
return { ok: false, manual: true, blocked: false };
|
|
83
|
+
}
|
|
84
|
+
const ok = privileged(
|
|
85
|
+
`cp -n ${HOSTS} ${BACKUP} 2>/dev/null; `
|
|
86
|
+
+ `printf '\\n${MARK}\\n${entries}\\n' >> ${HOSTS}; `
|
|
87
|
+
+ flushDnsCmd(),
|
|
88
|
+
);
|
|
89
|
+
return { ok, blocked: isBlocked() };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function unblockBackend() {
|
|
93
|
+
if (!isBlocked()) return { already: true };
|
|
94
|
+
// Remove only OUR lines, never restore wholesale — the user may have edited
|
|
95
|
+
// /etc/hosts for unrelated reasons since the backup was taken.
|
|
96
|
+
const pattern = BACKEND_HOSTS.map((h) => h.replace('.', '\\.')).join('|');
|
|
97
|
+
if (WIN) {
|
|
98
|
+
console.log('windows: run in an ADMINISTRATOR PowerShell:');
|
|
99
|
+
console.log(` Copy-Item "${BACKUP}" "${HOSTS}" -Force; ipconfig /flushdns`);
|
|
100
|
+
return { ok: false, manual: true };
|
|
101
|
+
}
|
|
102
|
+
// BSD sed (macOS) needs -i ''; GNU sed (linux) must NOT have it. Use a temp
|
|
103
|
+
// file instead so one command is correct on both.
|
|
104
|
+
const re = `/^[[:space:]]*127\\.0\\.0\\.1[[:space:]]+(${pattern})[[:space:]]*$/d; /openzoo: force the editor/d`;
|
|
105
|
+
const ok = privileged(
|
|
106
|
+
`sed -E '${re}' ${HOSTS} > ${HOSTS}.oztmp && cat ${HOSTS}.oztmp > ${HOSTS} && rm -f ${HOSTS}.oztmp; `
|
|
107
|
+
+ flushDnsCmd(),
|
|
108
|
+
);
|
|
109
|
+
return { ok, blocked: isBlocked() };
|
|
110
|
+
}
|
package/lib/setup.js
CHANGED
|
@@ -251,7 +251,21 @@ export async function setupEditor(which, target) {
|
|
|
251
251
|
// Open the directory you ran this from, or the one you named. The earlier
|
|
252
252
|
// "helpfully" substitute a ~/openzoo folder when run from $HOME was worse
|
|
253
253
|
// than the problem it dodged: it silently opened the wrong project.
|
|
254
|
-
|
|
254
|
+
// WORKSPACE: `openzoo cursor ~/somedir` opens that directory. Resolve it
|
|
255
|
+
// ourselves — a quoted `~` never reaches the shell's expansion, and a path
|
|
256
|
+
// that does not exist yet should be created rather than silently opening the
|
|
257
|
+
// wrong folder (the editor treats a missing path as a file and opens blank).
|
|
258
|
+
let cwd = process.cwd();
|
|
259
|
+
if (target && !target.startsWith('-')) {
|
|
260
|
+
cwd = target.startsWith('~')
|
|
261
|
+
? path.join(os.homedir(), target.slice(1))
|
|
262
|
+
: path.resolve(target);
|
|
263
|
+
if (!fs.existsSync(cwd)) {
|
|
264
|
+
fs.mkdirSync(cwd, { recursive: true });
|
|
265
|
+
console.log(`workspace: created ${cwd}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
console.log(`workspace: ${cwd}`);
|
|
255
269
|
|
|
256
270
|
const picked = pickEditor(which);
|
|
257
271
|
if (!picked) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.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",
|