openzoo 0.18.3 → 0.18.5

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 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,7 +20,7 @@ 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
 
@@ -183,20 +183,14 @@ export async function setupEditor(which, target) {
183
183
  } else if (wrote) {
184
184
  console.log(`settings: openAIBaseUrl -> ${wrote.verified?.openAIBaseUrl || base}`);
185
185
  console.log(' useOpenAIKey -> true');
186
- if (wrote.modelsStick) {
187
- console.log(` models -> ${models.join(', ')}`);
188
- console.log(' pick one in the model dropdown; built-ins bypass the zoo.');
189
- } else {
190
- // Cursor re-syncs this list from its account on launch, so a local write
191
- // does not survive. Say so instead of promising a dropdown entry that
192
- // will not be there.
193
- console.log(' models -> NOT writable (Cursor re-syncs this list on launch)');
194
- console.log('');
195
- console.log(' ONE-TIME manual step, in Cursor → Settings → Models:');
196
- console.log(` + Add model -> ${models[0]}`);
197
- console.log(' then toggle OFF the built-in models, or Cursor keeps using its own backend.');
198
- console.log(' (the base URL + key above are already set for you and DO persist)');
199
- }
186
+ // PIN so the editor's account-sync cannot revert it on launch.
187
+ const pin = pinEditorProviderConfig(target0, { baseUrl: base, models });
188
+ console.log(` models -> ${models.join(', ')}`);
189
+ console.log(` selected -> ${models[0]}`);
190
+ console.log(pin?.pinned
191
+ ? ' pinned -> yes (the editor re-syncs these from its account; a DB trigger re-applies them)'
192
+ : ` pinned -> NO (${pin?.error || 'unavailable'}) — the editor may revert these on launch`);
193
+ console.log(' verify: send one message, watch for "paid $0.0… · rail solana · tx …" here.');
200
194
  }
201
195
 
202
196
  // 3. LAUNCH with that env. Editor resolved platform-agnostically; Cursor
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.18.3",
3
+ "version": "0.18.5",
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",