openzoo 0.18.4 → 0.18.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/bin/openzoo.js +2 -0
- package/lib/cursorcfg.js +48 -0
- package/lib/setup.js +33 -18
- package/package.json +1 -1
package/bin/openzoo.js
CHANGED
|
@@ -8,6 +8,8 @@ usage:
|
|
|
8
8
|
public HTTPS url for cloud IDEs (key required, printed at start)
|
|
9
9
|
npx openzoo cursor [path] start proxy+tunnel, write MCP config, and LAUNCH
|
|
10
10
|
Cursor already pointed at the zoo (env inherited)
|
|
11
|
+
--profile use an isolated editor profile the
|
|
12
|
+
vendor account cannot re-sync over
|
|
11
13
|
npx openzoo vscode [path] same, for VS Code
|
|
12
14
|
npx openzoo editor [path] whichever is installed (Cursor wins if both)
|
|
13
15
|
npx openzoo launch <cmd> [args] launch a TERMINAL Messages API client
|
package/lib/cursorcfg.js
CHANGED
|
@@ -149,3 +149,51 @@ export function writeEditorProviderConfig(which, { baseUrl, models }) {
|
|
|
149
149
|
const modelsStick = models.every((m) => verified.models.includes(m));
|
|
150
150
|
return { db, before, baseUrl, added, verified, modelsStick };
|
|
151
151
|
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* PIN the settings with a SQLite trigger so the editor cannot revert them.
|
|
155
|
+
*
|
|
156
|
+
* Cursor treats the model list and selection as SERVER-authoritative: it syncs
|
|
157
|
+
* them from the account on every launch and overwrites local state. Measured
|
|
158
|
+
* three times — written and verified while closed, then availableAPIKeyModels
|
|
159
|
+
* [] and composer.defaultModel "default" again after relaunch.
|
|
160
|
+
*
|
|
161
|
+
* Making the whole state.vscdb read-only would break the editor (it stores
|
|
162
|
+
* window layout, history, everything there). A trigger is surgical: Cursor
|
|
163
|
+
* keeps full write access, and only OUR fields are re-applied, on any write
|
|
164
|
+
* that clobbers them. Removable with unpinEditorProviderConfig.
|
|
165
|
+
*/
|
|
166
|
+
export function pinEditorProviderConfig(which, { baseUrl, models }) {
|
|
167
|
+
const db = storagePath(which);
|
|
168
|
+
if (!fs.existsSync(db)) return null;
|
|
169
|
+
const esc = (v) => String(v).replace(/'/g, "''");
|
|
170
|
+
const modelJson = esc(JSON.stringify(models.map((m) => ({ name: m, defaultOn: true, supportsAgent: true }))));
|
|
171
|
+
const primary = esc(models[0]);
|
|
172
|
+
const base = esc(baseUrl);
|
|
173
|
+
const sql = `
|
|
174
|
+
DROP TRIGGER IF EXISTS openzoo_pin;
|
|
175
|
+
CREATE TRIGGER openzoo_pin AFTER UPDATE ON ItemTable
|
|
176
|
+
WHEN NEW.key = '${KEY}'
|
|
177
|
+
AND (json_extract(NEW.value,'$.openAIBaseUrl') IS NOT '${base}'
|
|
178
|
+
OR json_extract(NEW.value,'$.featureModelConfigs.composer.defaultModel') IS NOT '${primary}')
|
|
179
|
+
BEGIN
|
|
180
|
+
UPDATE ItemTable SET value = json_set(
|
|
181
|
+
NEW.value,
|
|
182
|
+
'$.openAIBaseUrl', '${base}',
|
|
183
|
+
'$.useOpenAIKey', json('true'),
|
|
184
|
+
'$.availableAPIKeyModels', json('${modelJson}'),
|
|
185
|
+
'$.featureModelConfigs.composer.defaultModel', '${primary}',
|
|
186
|
+
'$.featureModelConfigs.cmdK.defaultModel', '${primary}'
|
|
187
|
+
) WHERE key = NEW.key;
|
|
188
|
+
END;`;
|
|
189
|
+
try { sqlite(db, sql); } catch (e) { return { error: e.message }; }
|
|
190
|
+
return { pinned: true, db };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Remove the pin — the editor goes back to managing its own settings. */
|
|
194
|
+
export function unpinEditorProviderConfig(which) {
|
|
195
|
+
const db = storagePath(which);
|
|
196
|
+
if (!fs.existsSync(db)) return null;
|
|
197
|
+
try { sqlite(db, 'DROP TRIGGER IF EXISTS openzoo_pin;'); return { unpinned: true }; }
|
|
198
|
+
catch (e) { return { error: e.message }; }
|
|
199
|
+
}
|
package/lib/setup.js
CHANGED
|
@@ -20,10 +20,23 @@ import os from 'node:os';
|
|
|
20
20
|
import path from 'node:path';
|
|
21
21
|
import { spawn } from 'node:child_process';
|
|
22
22
|
import { config } from './config.js';
|
|
23
|
-
import { writeEditorProviderConfig, editorRunning, quitEditor } from './cursorcfg.js';
|
|
23
|
+
import { writeEditorProviderConfig, editorRunning, quitEditor, pinEditorProviderConfig } from './cursorcfg.js';
|
|
24
24
|
|
|
25
25
|
const DEFAULT_MODELS = ['anthropic/claude-opus-5', 'deepseek/deepseek-v4-pro-0813'];
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* An ISOLATED editor profile we own.
|
|
29
|
+
*
|
|
30
|
+
* The editor's normal profile syncs provider settings from the vendor account
|
|
31
|
+
* and overwrites local state on launch — measured repeatedly. `--user-data-dir`
|
|
32
|
+
* gives it a profile directory we control, so our settings are the only ones
|
|
33
|
+
* there and nothing upstream reclaims them. Opt in with OPENZOO_PROFILE=1 (or
|
|
34
|
+
* --profile) because a fresh profile means signing in again and re-adding
|
|
35
|
+
* extensions; that is a real cost, not a detail to spring on someone.
|
|
36
|
+
*/
|
|
37
|
+
const PROFILE_DIR = process.env.OPENZOO_PROFILE_DIR
|
|
38
|
+
|| path.join(os.homedir(), '.openzoo', 'editor-profile');
|
|
39
|
+
|
|
27
40
|
const MCP_FILES = {
|
|
28
41
|
cursor: path.join(os.homedir(), '.cursor', 'mcp.json'),
|
|
29
42
|
vscode: path.join(os.homedir(), '.vscode', 'mcp.json'),
|
|
@@ -183,21 +196,15 @@ export async function setupEditor(which, target) {
|
|
|
183
196
|
} else if (wrote) {
|
|
184
197
|
console.log(`settings: openAIBaseUrl -> ${wrote.verified?.openAIBaseUrl || base}`);
|
|
185
198
|
console.log(' useOpenAIKey -> true');
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
console.log(
|
|
194
|
-
|
|
195
|
-
console.log(' Settings -> Models -> + Add model');
|
|
196
|
-
console.log(` ${models[0]}`);
|
|
197
|
-
console.log(' then select it in the chat model dropdown.');
|
|
198
|
-
console.log(' Built-in models and "Auto" route to Cursor\'s backend, never here.');
|
|
199
|
-
console.log(' Verify it worked: send one message and watch this terminal for a');
|
|
200
|
-
console.log(' "paid $0.0… · rail solana · tx …" line. No line = it did not route.');
|
|
199
|
+
// PIN so the editor's account-sync cannot revert it on launch.
|
|
200
|
+
const pin = pinEditorProviderConfig(target0, { baseUrl: base, models });
|
|
201
|
+
console.log(` models -> ${models.join(', ')}`);
|
|
202
|
+
console.log(` selected -> ${models[0]}`);
|
|
203
|
+
console.log(pin?.pinned
|
|
204
|
+
? ' pinned -> yes (the editor re-syncs these from its account; a DB trigger re-applies them)'
|
|
205
|
+
: ` pinned -> NO (${pin?.error || 'unavailable'}) — the editor may revert these on launch`);
|
|
206
|
+
console.log(' verify: send one message, watch for "paid $0.0… · rail solana · tx …" here.');
|
|
207
|
+
}
|
|
201
208
|
|
|
202
209
|
// 3. LAUNCH with that env. Editor resolved platform-agnostically; Cursor
|
|
203
210
|
// wins when both are installed.
|
|
@@ -208,8 +215,16 @@ export async function setupEditor(which, target) {
|
|
|
208
215
|
console.error(`(config is written either way: ${mcpFile})`);
|
|
209
216
|
return;
|
|
210
217
|
}
|
|
211
|
-
|
|
212
|
-
const
|
|
218
|
+
const useProfile = process.env.OPENZOO_PROFILE === '1' || process.argv.includes('--profile');
|
|
219
|
+
const args = [cwd];
|
|
220
|
+
if (useProfile) {
|
|
221
|
+
fs.mkdirSync(PROFILE_DIR, { recursive: true });
|
|
222
|
+
args.unshift(`--user-data-dir=${PROFILE_DIR}`, `--extensions-dir=${path.join(PROFILE_DIR, 'extensions')}`);
|
|
223
|
+
console.log(`profile: ${PROFILE_DIR} (isolated — vendor account sync cannot reclaim these settings)`);
|
|
224
|
+
console.log(' first run in this profile asks you to sign in; extensions are separate.');
|
|
225
|
+
}
|
|
226
|
+
console.log(`launching ${picked.which}${useProfile ? ' (isolated profile)' : ''}...`);
|
|
227
|
+
const child = spawn(picked.cmd, args, { stdio: 'ignore', env, detached: true });
|
|
213
228
|
child.on('error', (e) => {
|
|
214
229
|
console.error(`could not launch ${picked.which}: ${e.message}`);
|
|
215
230
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.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",
|