openzoo 0.50.96 → 0.50.98

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.
@@ -45,5 +45,5 @@ process.on('unhandledRejection', (e) => {
45
45
  log(`cursor-backend: UNHANDLED REJECTION ${e && e.message ? e.message : e}`);
46
46
  });
47
47
 
48
- startCursorBackend({ port, models, log });
48
+ await startCursorBackend({ port, models, log });
49
49
  log(`cursor-backend: standalone up on :${port} (${models.length} models)`);
package/bin/openzoo.js CHANGED
@@ -96,7 +96,16 @@ usage:
96
96
  After a reboot the login item comes back without
97
97
  that env — bot bounces it so send works. Already-
98
98
  hijacked sessions are left alone.
99
+ Missing Grok Bot is fetched from the grokbot-v*
100
+ multiarch GitHub release (vendor feed fallback).
99
101
  --quit force bounce · --no-quit never bounce
102
+ --no-fetch do not download Grok Bot if missing
103
+ --daemon / -d fork the sidecar to background
104
+ (pid ~/.openzoo/bot.pid, log bot.log)
105
+ then launch Grok Bot. Needed from the
106
+ OpenZoo Bot AppImage so this process
107
+ is not the GUI.
108
+ --stop kill the --daemon sidecar
100
109
  --verbose print every request the app makes
101
110
  (default is quiet: milestones + problems)
102
111
  --web serve the renderer in a browser instead of
@@ -330,7 +339,7 @@ async function main() {
330
339
  }
331
340
  case 'ask': {
332
341
  const question = process.argv[3];
333
- if (!question) throw new Error('usage: openzoo ask "<question>" [--context <id>] [--model <id>] [--system <text>]');
342
+ if (!question) throw new Error('usage: openzoo ask "<question>" [--context <id>] [--model <id>] [--system <text>] [--web [--web-results N]]');
334
343
  const ci = process.argv.indexOf('--context');
335
344
  const mi = process.argv.indexOf('--model');
336
345
  // A BARE QUESTION IS A DIFFERENT PRODUCT FROM A BRIEFED ONE.
@@ -344,7 +353,20 @@ async function main() {
344
353
  // DHH, Hyprland and theming. Same gateway, same product, one had context.
345
354
  // A caller that knows where it is running can now say so.
346
355
  const si = process.argv.indexOf('--system');
347
- const system = si !== -1 ? process.argv[si + 1] : '';
356
+ let system = si !== -1 ? process.argv[si + 1] : '';
357
+ // --web: a keyless DuckDuckGo search, top results injected into THIS
358
+ // call's system prompt. The x402 rail strips OpenRouter's `plugins`
359
+ // field, so search-then-inject has to happen here, on the caller's
360
+ // side. EGRESS: the question text goes to duckduckgo.com. Also on with
361
+ // OPENZOO_ASK_WEB=1; --web-results N caps the count (default 5).
362
+ const wantWeb = process.argv.includes('--web') || process.env.OPENZOO_ASK_WEB === '1';
363
+ if (wantWeb) {
364
+ const wi = process.argv.indexOf('--web-results');
365
+ const n = wi !== -1 ? Number(process.argv[wi + 1]) || 5 : 5;
366
+ const { webSearch, formatWebResults } = await import('../lib/websearch.js');
367
+ const hits = await webSearch(question, n).catch((e) => { console.error(`web search failed: ${e.message}`); return []; });
368
+ if (hits.length) system = (system ? system + '\n\n' : '') + formatWebResults(question, hits);
369
+ }
348
370
  const { PayClient } = await import('../lib/pay.js');
349
371
  const { config } = await import('../lib/config.js');
350
372
  const client = new PayClient();
@@ -389,6 +411,17 @@ async function main() {
389
411
  case '-h':
390
412
  console.log(HELP);
391
413
  break;
414
+ case 'version':
415
+ case '--version':
416
+ case '-v':
417
+ case '-V': {
418
+ // Every installer, mise shim and shell script that probes a CLI asks
419
+ // this first; answering "unknown command" to it failed real installs.
420
+ const { readFileSync } = await import('fs');
421
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
422
+ console.log(pkg.version);
423
+ break;
424
+ }
392
425
  default:
393
426
  console.error(`unknown command: ${cmd}\n`);
394
427
  console.log(HELP);
package/lib/cursorapi.js CHANGED
@@ -305,3 +305,68 @@ export function connectFrame(payload) {
305
305
  head.writeUInt32BE(payload.length, 1);
306
306
  return Buffer.concat([head, payload]);
307
307
  }
308
+
309
+ /**
310
+ * Fake Cursor login. Grok Bot polls GET /auth/poll until the JSON has
311
+ * accessToken + refreshToken (asar SandBackendLoginManager.waitForResult).
312
+ * empty-ok `{}` is a 200 without those keys → login fails → "Log in with Cursor".
313
+ * accessToken is JWT-decoded for sub/email/exp (createLoggedInStatus).
314
+ */
315
+ export function fakeCursorJwt({
316
+ sub = 'openzoo-user',
317
+ email = 'user@openzoo.local',
318
+ name = 'openzoo',
319
+ now = Math.floor(Date.now() / 1000),
320
+ } = {}) {
321
+ const b64url = (obj) => Buffer.from(JSON.stringify(obj)).toString('base64url');
322
+ return `${b64url({ alg: 'none', typ: 'JWT' })}.${b64url({
323
+ sub, email, name, exp: now + 365 * 86400, iat: now,
324
+ })}.oz`;
325
+ }
326
+
327
+ export function fakeCursorTokens() {
328
+ const accessToken = fakeCursorJwt();
329
+ const refreshToken = fakeCursorJwt();
330
+ return { accessToken, refreshToken, authId: 'openzoo-user' };
331
+ }
332
+
333
+ const LOGIN_DEEP_HTML = `<!doctype html>
334
+ <html lang="en"><head>
335
+ <meta charset="utf-8">
336
+ <meta http-equiv="refresh" content="0;url=about:blank">
337
+ <title></title>
338
+ </head>
339
+ <body data-component="oz-fake-login">
340
+ <script>
341
+ try { window.open('', '_self'); } catch (e) {}
342
+ try { window.close(); } catch (e) {}
343
+ try { location.replace('about:blank'); } catch (e) {}
344
+ </script>
345
+ </body></html>
346
+ `;
347
+
348
+ /** Path without query. Returns a reply descriptor or null. */
349
+ export function fakeCursorAuthReply(urlPath, { passthrough = false } = {}) {
350
+ if (passthrough) return null;
351
+ const path0 = String(urlPath || '').split('?')[0];
352
+ const tok = fakeCursorTokens();
353
+ if (path0 === '/auth/poll') {
354
+ return { kind: 'poll', contentType: 'application/json', body: JSON.stringify(tok) };
355
+ }
356
+ if (path0 === '/oauth/token') {
357
+ return {
358
+ kind: 'oauth',
359
+ contentType: 'application/json',
360
+ body: JSON.stringify({
361
+ access_token: tok.accessToken,
362
+ refresh_token: tok.refreshToken,
363
+ token_type: 'Bearer',
364
+ expires_in: 365 * 86400,
365
+ }),
366
+ };
367
+ }
368
+ if (path0 === '/loginDeepControl') {
369
+ return { kind: 'login-page', contentType: 'text/html; charset=utf-8', body: LOGIN_DEEP_HTML };
370
+ }
371
+ return null;
372
+ }