openzoo 0.50.88 → 0.50.89

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 CHANGED
@@ -330,7 +330,7 @@ async function main() {
330
330
  }
331
331
  case 'ask': {
332
332
  const question = process.argv[3];
333
- if (!question) throw new Error('usage: openzoo ask "<question>" [--context <id>] [--model <id>] [--system <text>]');
333
+ if (!question) throw new Error('usage: openzoo ask "<question>" [--context <id>] [--model <id>] [--system <text>] [--web [--web-results N]]');
334
334
  const ci = process.argv.indexOf('--context');
335
335
  const mi = process.argv.indexOf('--model');
336
336
  // A BARE QUESTION IS A DIFFERENT PRODUCT FROM A BRIEFED ONE.
@@ -344,7 +344,20 @@ async function main() {
344
344
  // DHH, Hyprland and theming. Same gateway, same product, one had context.
345
345
  // A caller that knows where it is running can now say so.
346
346
  const si = process.argv.indexOf('--system');
347
- const system = si !== -1 ? process.argv[si + 1] : '';
347
+ let system = si !== -1 ? process.argv[si + 1] : '';
348
+ // --web: a keyless DuckDuckGo search, top results injected into THIS
349
+ // call's system prompt. The x402 rail strips OpenRouter's `plugins`
350
+ // field, so search-then-inject has to happen here, on the caller's
351
+ // side. EGRESS: the question text goes to duckduckgo.com. Also on with
352
+ // OPENZOO_ASK_WEB=1; --web-results N caps the count (default 5).
353
+ const wantWeb = process.argv.includes('--web') || process.env.OPENZOO_ASK_WEB === '1';
354
+ if (wantWeb) {
355
+ const wi = process.argv.indexOf('--web-results');
356
+ const n = wi !== -1 ? Number(process.argv[wi + 1]) || 5 : 5;
357
+ const { webSearch, formatWebResults } = await import('../lib/websearch.js');
358
+ const hits = await webSearch(question, n).catch((e) => { console.error(`web search failed: ${e.message}`); return []; });
359
+ if (hits.length) system = (system ? system + '\n\n' : '') + formatWebResults(question, hits);
360
+ }
348
361
  const { PayClient } = await import('../lib/pay.js');
349
362
  const { config } = await import('../lib/config.js');
350
363
  const client = new PayClient();
@@ -0,0 +1,31 @@
1
+ // Keyless web search for `openzoo ask --web`: DuckDuckGo's HTML endpoint,
2
+ // scraped for title / url / snippet. No API key, no account, one GET. The
3
+ // only thing that leaves is the question text, to duckduckgo.com.
4
+ const strip = (s) => String(s || '')
5
+ .replace(/<[^>]+>/g, '')
6
+ .replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&#x27;/g, "'").replace(/&lt;/g, '<').replace(/&gt;/g, '>')
7
+ .replace(/\s+/g, ' ').trim();
8
+
9
+ export async function webSearch(query, max = 5) {
10
+ const res = await fetch('https://html.duckduckgo.com/html/?q=' + encodeURIComponent(query), {
11
+ headers: { 'user-agent': 'Mozilla/5.0 openzoo-ask/1.0' },
12
+ signal: AbortSignal.timeout(12_000),
13
+ });
14
+ if (!res.ok) throw new Error(`duckduckgo HTTP ${res.status}`);
15
+ const html = await res.text();
16
+ const out = [];
17
+ const re = /<a[^>]*class="result__a"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
18
+ let m;
19
+ while ((m = re.exec(html)) && out.length < Math.max(1, Math.min(10, max))) {
20
+ let url = m[1];
21
+ const redirected = url.match(/uddg=([^&]+)/);
22
+ if (redirected) url = decodeURIComponent(redirected[1]);
23
+ out.push({ title: strip(m[2]), url, snippet: strip(m[3]).slice(0, 400) });
24
+ }
25
+ return out;
26
+ }
27
+
28
+ export function formatWebResults(query, hits) {
29
+ const lines = hits.map((h, i) => `${i + 1}. ${h.title} — ${h.url}\n ${h.snippet}`);
30
+ return `Web search results for "${query}" (DuckDuckGo, fetched just now; cite the url when you rely on one):\n${lines.join('\n')}`;
31
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.50.88",
3
+ "version": "0.50.89",
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",