openzoo 0.50.41 → 0.50.42

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.
@@ -21,11 +21,11 @@ export function grokBotChromiumArgs(port = GROKBOT_CDP_PORT) {
21
21
  /** Pure. Same split the renderer IIFE uses. */
22
22
  export function splitSpendText(text) {
23
23
  const s = String(text || '');
24
- let m = s.match(/::oz-spend::(\$[0-9.]+|[^\n]*)\s*([\s\S]*)$/);
25
- if (m && (m[2] || '').length > 8) {
24
+ let m = s.match(/::oz-spend::([^\n]+)\n?([\s\S]*)$/);
25
+ if (m && ((m[2] || '').length > 8 || /\$[0-9.]/.test(m[1] || ''))) {
26
26
  let summary = (m[1] || '').trim();
27
- const body = m[2].trim();
28
- if (!/^\$[0-9.]+$/.test(summary) || summary === '$0.0000') {
27
+ const body = (m[2] || '').trim();
28
+ if (!summary || summary === '$0.0000' || summary === 'spend') {
29
29
  const spent = body.match(/spent \$([0-9.]+)/i);
30
30
  const call = body.match(/this call \$([0-9.]+)/i);
31
31
  const n = Number((call && Number(call[1]) > 0.00005 ? call[1] : null) || (spent && spent[1]) || 0);
@@ -69,7 +69,8 @@ function ozEnsureSpendCss() {
69
69
  s.textContent = [
70
70
  '.oz-spend{margin:.55rem 0 0;font-size:12px;color:inherit;opacity:.82;max-width:36em}',
71
71
  '.oz-spend>summary{cursor:help;list-style:none;display:inline-flex;align-items:center;gap:.35rem;',
72
- 'padding:3px 9px;border-radius:999px;border:1px solid rgba(255,255,255,.18)}',
72
+ 'padding:3px 9px;border-radius:999px;border:1px solid rgba(255,255,255,.18);white-space:nowrap;',
73
+ 'max-width:100%;overflow:hidden;text-overflow:ellipsis}',
73
74
  '.oz-spend>summary::-webkit-details-marker{display:none}',
74
75
  '.oz-spend-body{white-space:pre-wrap;margin:.55rem 0 0;font-size:11px;line-height:1.45;opacity:.88;overflow-wrap:anywhere}',
75
76
  '[data-oz-spend-hide]{display:none!important}',
package/lib/spendProof.js CHANGED
@@ -250,9 +250,53 @@ export function mergeTurnProof(prev, data) {
250
250
  }
251
251
  }
252
252
 
253
+ function chipUsd(n) {
254
+ const x = Number(n) || 0;
255
+ if (Math.abs(x) >= 0.01) return `$${x.toFixed(2)}`;
256
+ return `$${x.toFixed(4)}`;
257
+ }
258
+
259
+ function chipMult(would, spent) {
260
+ if (!(spent > 0) || !(would > 0)) return null;
261
+ const m = would / spent;
262
+ if (!Number.isFinite(m) || m < 1) return null;
263
+ if (m >= 100) return `${Math.round(m)}×`;
264
+ if (m >= 10) return `${m.toFixed(1)}×`;
265
+ return `${m.toFixed(2)}×`;
266
+ }
267
+
268
+ /** Collapsed chip: spent · saved $ · saved % / multiplier. Visible before click. */
269
+ export function spendChipLabel({
270
+ billedUsd,
271
+ directUsd,
272
+ spent = 0,
273
+ would = 0,
274
+ saved = 0,
275
+ pct = 0,
276
+ } = {}) {
277
+ let spentN = Number(spent);
278
+ let wouldN = Number(would);
279
+ let savedN = Number(saved);
280
+ if (!(spentN > 0.00005) && Number(billedUsd) > 0.00005) {
281
+ spentN = Number(billedUsd);
282
+ wouldN = Number(directUsd) > 0 ? Number(directUsd) : wouldN;
283
+ savedN = Math.max(0, wouldN - spentN);
284
+ }
285
+ if (!(savedN > 0) && wouldN > spentN) savedN = wouldN - spentN;
286
+ let pctN = Number(pct);
287
+ if (wouldN > 0) pctN = 100 * Math.max(0, savedN) / wouldN;
288
+ const bits = [chipUsd(spentN), `saved ${chipUsd(savedN)}`];
289
+ const sav = [];
290
+ if (Number.isFinite(pctN)) sav.push(`${Math.round(pctN)}%`);
291
+ const mult = chipMult(wouldN, spentN);
292
+ if (mult) sav.push(mult);
293
+ if (sav.length) bits.push(sav.join('/'));
294
+ return bits.join(' · ');
295
+ }
296
+
253
297
  /**
254
298
  * Two leading newlines, then the existing spend lines, then optional
255
- * tx / memo / proves. Never throws.
299
+ * tx / memo / proves. Never throws. Collapsed chip reads the ::oz-spend:: line.
256
300
  */
257
301
  export function formatSpendFooter({
258
302
  billedUsd,
@@ -280,15 +324,18 @@ export function formatSpendFooter({
280
324
  const savedN = Number.isFinite(Number(saved)) ? Number(saved) : 0;
281
325
  const pctN = Number.isFinite(Number(pct)) ? Number(pct) : 0;
282
326
  const bal = balance != null && Number.isFinite(Number(balance)) ? Number(balance) : null;
283
- const balTxt = bal != null ? ` · balance $${bal.toFixed(2)}` : '';
327
+ const balTxt = bal != null && bal > 0.004 ? ` · balance $${bal.toFixed(2)}` : '';
284
328
  lines.push(`spent $${spentN.toFixed(4)}${balTxt} · OpenRouter would $${wouldN.toFixed(4)} · saved $${savedN.toFixed(4)} (${pctN.toFixed(0)}%)`);
285
329
 
286
330
  const sigs = collectTxs({ tx: tx ?? x.tx, txs: txs ?? x.txs });
287
331
  const r = rail || x.rail;
288
332
  const net = network || x.network;
289
- for (const sig of sigs) {
290
- const url = explorerUrl(sig, { rail: r, network: net });
291
- if (url) lines.push(`tx ${url}`);
333
+ const last = sigs[sigs.length - 1];
334
+ if (last) {
335
+ const url = explorerUrl(last, { rail: r, network: net });
336
+ if (url) {
337
+ lines.push(sigs.length > 1 ? `tx ${url} (+${sigs.length - 1} earlier)` : `tx ${url}`);
338
+ }
292
339
  }
293
340
  const mem = memo ?? x.memo;
294
341
  if (mem != null && String(mem).length) {
@@ -297,9 +344,9 @@ export function formatSpendFooter({
297
344
  if (d.proves) lines.push(`proves ${d.proves}`);
298
345
  }
299
346
  const body = lines.filter((l) => l !== '').join('\n');
300
- const billedN = Number(billedUsd);
301
- const tagN = Number.isFinite(billedN) && billedN > 0.00005 ? billedN : spentN;
302
- const tag = `$${tagN.toFixed(4)}`;
347
+ const tag = spendChipLabel({
348
+ billedUsd, directUsd, spent: spentN, would: wouldN, saved: savedN, pct: pctN,
349
+ });
303
350
  return `\n\n::oz-spend::${tag}\n${body}`;
304
351
  } catch {
305
352
  return '\n\n';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.50.41",
3
+ "version": "0.50.42",
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",