openzoo 0.18.1 → 0.18.2

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
@@ -54,6 +54,34 @@ export function editorRunning(which) {
54
54
  } catch { return false; }
55
55
  }
56
56
 
57
+ /**
58
+ * Quit the editor and wait for it to exit.
59
+ *
60
+ * WHY WE DO THIS FOR THE USER: the editor holds its settings in memory and
61
+ * flushes them on exit, so a write performed while it is running is silently
62
+ * reverted — measured: availableAPIKeyModels came back [] every launch, the
63
+ * model never appeared in the dropdown, and the user was told to "just pick
64
+ * it". Writing while it is CLOSED sticks. Telling a human to quit their editor
65
+ * and re-run a command is not automation, so we quit it, write, and relaunch.
66
+ */
67
+ export async function quitEditor(which) {
68
+ if (!editorRunning(which)) return { wasRunning: false };
69
+ const app = which === 'vscode' ? 'Visual Studio Code' : 'Cursor';
70
+ try {
71
+ if (process.platform === 'darwin') {
72
+ // Graceful: ask the app to quit so it flushes state normally.
73
+ execFileSync('osascript', ['-e', `tell application "${app}" to quit`], { stdio: 'ignore' });
74
+ } else {
75
+ execFileSync('pkill', ['-TERM', '-f', which === 'vscode' ? 'Code' : 'Cursor'], { stdio: 'ignore' });
76
+ }
77
+ } catch { /* fall through to the wait; it may already be closing */ }
78
+ for (let i = 0; i < 40; i++) { // up to 20s
79
+ if (!editorRunning(which)) return { wasRunning: true, quit: true };
80
+ await new Promise((r) => setTimeout(r, 500));
81
+ }
82
+ return { wasRunning: true, quit: false };
83
+ }
84
+
57
85
  /**
58
86
  * Point the editor at the zoo: base URL, key toggle, and the models the picker
59
87
  * should offer. Returns what changed, or null when the store is unavailable
@@ -88,5 +116,22 @@ export function writeEditorProviderConfig(which, { baseUrl, models }) {
88
116
  // Single-quote escaping for the SQL literal.
89
117
  const json = JSON.stringify(doc).replace(/'/g, "''");
90
118
  sqlite(db, `UPDATE ItemTable SET value='${json}' WHERE key='${KEY}';`);
91
- return { db, before, baseUrl, added };
119
+
120
+ // VERIFY, DO NOT ASSUME. `availableAPIKeyModels` is SERVER-SYNCED: Cursor
121
+ // rebuilds it from its own account state on launch and silently discards a
122
+ // local write, so this function used to report "models added" for a list
123
+ // that was empty again seconds later — and the user was told to pick a model
124
+ // that never appeared in the dropdown. Read it back and say which parts
125
+ // actually stuck.
126
+ let verified = { openAIBaseUrl: null, models: [] };
127
+ try {
128
+ const raw2 = sqlite(db, `SELECT value FROM ItemTable WHERE key='${KEY}';`).trim();
129
+ const doc2 = JSON.parse(raw2);
130
+ verified = {
131
+ openAIBaseUrl: doc2.openAIBaseUrl,
132
+ models: (doc2.availableAPIKeyModels || []).map((m) => (typeof m === 'string' ? m : m?.name)).filter(Boolean),
133
+ };
134
+ } catch { /* verification is advisory */ }
135
+ const modelsStick = models.every((m) => verified.models.includes(m));
136
+ return { db, before, baseUrl, added, verified, modelsStick };
92
137
  }
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 } from './cursorcfg.js';
23
+ import { writeEditorProviderConfig, editorRunning, quitEditor } from './cursorcfg.js';
24
24
 
25
25
  const DEFAULT_MODELS = ['anthropic/claude-opus-5', 'deepseek/deepseek-v4-pro-0813'];
26
26
 
@@ -169,8 +169,11 @@ export async function setupEditor(which, target) {
169
169
  // while the editor is CLOSED or it rewrites them from memory on exit.
170
170
  const picked0 = pickEditor(which);
171
171
  const target0 = picked0?.which || which || 'cursor';
172
+ // Quit it FOR the user — a running editor reverts our write on exit.
172
173
  if (editorRunning(target0)) {
173
- console.log(`NOTE: ${target0} is already running — quit it and re-run so settings stick.`);
174
+ console.log(`${target0} is running — quitting it so settings stick (it relaunches below)...`);
175
+ const q = await quitEditor(target0);
176
+ if (!q.quit) console.log(` could not close ${target0} automatically; settings may not persist`);
174
177
  }
175
178
  const models = [DEFAULT_MODELS[0], ...DEFAULT_MODELS.slice(1)];
176
179
  let wrote = null;
@@ -178,10 +181,22 @@ export async function setupEditor(which, target) {
178
181
  if (wrote?.error) {
179
182
  console.log(`settings: could not write automatically (${wrote.error}) — set them in Settings → Models`);
180
183
  } else if (wrote) {
181
- console.log(`settings: openAIBaseUrl -> ${base} (was ${wrote.before.openAIBaseUrl || 'unset'})`);
182
- console.log(` useOpenAIKey -> true`);
183
- console.log(` models added -> ${wrote.added.length ? wrote.added.join(', ') : '(already present)'}`);
184
- console.log(' pick one of those in the model dropdown; built-ins bypass the zoo.');
184
+ console.log(`settings: openAIBaseUrl -> ${wrote.verified?.openAIBaseUrl || base}`);
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
+ }
185
200
  }
186
201
 
187
202
  // 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.1",
3
+ "version": "0.18.2",
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",