openzoo 0.34.4 → 0.34.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.
@@ -19,11 +19,13 @@ const port = Number(process.argv[2] || 443);
19
19
  const modelsPath = process.argv[3];
20
20
  const logPath = process.argv[4];
21
21
 
22
- const log = (s) => {
23
- const line = `${s}\n`;
24
- try { if (logPath) fs.appendFileSync(logPath, line); } catch { /* ignore */ }
25
- process.stdout.write(line);
26
- };
22
+ // WRITE EACH LINE ONCE. The launcher now redirects this process's stdout+stderr
23
+ // with `>>` to the SAME logPath (so a startup crash leaves evidence), so doing
24
+ // both an explicit appendFileSync AND a stdout write duplicated every single
25
+ // line in the file. stdout alone is the right channel: it reaches the log via
26
+ // the redirect, and it still shows up when the backend is run by hand on a
27
+ // terminal for debugging.
28
+ const log = (s) => { process.stdout.write(`${s}\n`); };
27
29
 
28
30
  let models = [];
29
31
  try { models = JSON.parse(fs.readFileSync(modelsPath, 'utf8')); }
@@ -35,21 +35,39 @@ import { encodeAvailableModels, encodeForMethod } from './cursorapi.js';
35
35
 
36
36
  const TLS_DIR = path.join(os.homedir(), '.openzoo', 'cursor-tls');
37
37
  const CURSOR_HOSTS = ['api2.cursor.sh', 'api3.cursor.sh', 'api4.cursor.sh', 'repo42.cursor.sh'];
38
+ // THE CERT MUST COVER ANTHROPIC TOO. This server impersonates api.anthropic.com
39
+ // for `claude --desktop`, but the SAN only ever listed the cursor hosts, so the
40
+ // client got a cert valid for a name it never asked for and killed the
41
+ // handshake before ALPN — logged as `HANDSHAKE FAILED ECONNRESET alpn=?`, with
42
+ // no request ever reaching us. Same cert, one more pair of names.
43
+ const ANTHROPIC_TLS_HOSTS = ['api.anthropic.com', 'api-staging.anthropic.com'];
44
+ const TLS_HOSTS = [...CURSOR_HOSTS, ...ANTHROPIC_TLS_HOSTS];
38
45
 
39
- /** Generate (once) a self-signed cert covering the cursor backends. openssl is
40
- * on every mac/linux; this is the only external tool and it is not a trust op. */
46
+ /** Generate (once) a self-signed cert covering every host we impersonate. openssl
47
+ * is on every mac/linux; this is the only external tool and it is not a trust op. */
41
48
  export function ensureCert(log = () => {}) {
42
49
  const cert = path.join(TLS_DIR, 'cert.pem');
43
50
  const key = path.join(TLS_DIR, 'key.pem');
44
- try { fs.accessSync(cert); fs.accessSync(key); return { cert, key }; } catch { /* make it */ }
51
+ // STALE-CERT INVALIDATION. A plain existence check meant anyone who had ever
52
+ // run an older build kept their cursor-only cert forever and never got the
53
+ // anthropic names — the fix would ship and appear to do nothing. Record the
54
+ // SAN set the cert was minted for and re-mint whenever that set changes.
55
+ const stamp = path.join(TLS_DIR, 'san.txt');
56
+ const want = TLS_HOSTS.join(',');
57
+ try {
58
+ fs.accessSync(cert); fs.accessSync(key);
59
+ if (fs.readFileSync(stamp, 'utf8').trim() === want) return { cert, key };
60
+ log('cursor-tls: cert predates the current host list — re-minting');
61
+ } catch { /* make it */ }
45
62
  fs.mkdirSync(TLS_DIR, { recursive: true });
46
- const san = `subjectAltName=${CURSOR_HOSTS.map((h) => `DNS:${h}`).join(',')}`;
63
+ const san = `subjectAltName=${TLS_HOSTS.map((h) => `DNS:${h}`).join(',')}`;
47
64
  execFileSync('openssl', [
48
65
  'req', '-x509', '-newkey', 'rsa:2048', '-nodes',
49
66
  '-keyout', key, '-out', cert, '-days', '3650',
50
67
  '-subj', '/CN=api2.cursor.sh', '-addext', san,
51
68
  ], { stdio: 'ignore' });
52
- log(`cursor-tls: self-signed cert minted at ${TLS_DIR} (no CA, no trust prompt)`);
69
+ fs.writeFileSync(stamp, want);
70
+ log(`cursor-tls: self-signed cert minted at ${TLS_DIR} covering ${TLS_HOSTS.length} hosts (no CA, no trust prompt)`);
53
71
  return { cert, key };
54
72
  }
55
73
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.34.4",
3
+ "version": "0.34.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",