openzoo 0.50.95 → 0.50.97

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.
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Cafe spend chip — the ⓘ $N <details> that folds formatSpendFooter.
2
+ * Cafe/Grok Bot spend HUD. Live totals come from /oz-spend and /api/ozSpend;
3
+ * leftover ::oz-spend:: footers in old transcripts still fold into a chip.
3
4
  *
4
5
  * Cafe injects this via grokbotweb-shim concatenation. Grok Bot.app cannot
5
6
  * be patched (asar integrity), so `openzoo bot` launches Chromium with a
@@ -14,18 +15,82 @@ import { spendChipLabel } from './spendProof.js';
14
15
 
15
16
  export const GROKBOT_CDP_PORT = Number(process.env.OZ_GROKBOT_CDP_PORT || 9444);
16
17
 
17
- /** Session totals for a floating pill when the open canvas has no footer. */
18
- export function sessionSpendLabel(home = os.homedir()) {
18
+ export function spendHudPath(home = os.homedir()) {
19
+ return path.join(home, '.openzoo', 'spend-hud.json');
20
+ }
21
+
22
+ export function writeSpendHud(state, home = os.homedir()) {
23
+ try {
24
+ const spent = Number(state?.spent) || 0;
25
+ const would = Number(state?.would) || 0;
26
+ const saved = Number(state?.saved) || 0;
27
+ const pct = Number(state?.pct) || 0;
28
+ const payload = {
29
+ spent,
30
+ would,
31
+ saved,
32
+ pct,
33
+ label: String(state?.label || ''),
34
+ body: String(state?.body || ''),
35
+ billedUsd: state?.billedUsd ?? null,
36
+ updatedAt: Date.now(),
37
+ };
38
+ fs.mkdirSync(path.dirname(spendHudPath(home)), { recursive: true, mode: 0o700 });
39
+ fs.writeFileSync(spendHudPath(home), JSON.stringify(payload), { mode: 0o600 });
40
+ return true;
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+
46
+ export function readSpendHud(home = os.homedir()) {
47
+ try {
48
+ const j = JSON.parse(fs.readFileSync(spendHudPath(home), 'utf8'));
49
+ return j && typeof j === 'object' ? j : null;
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ function sessionTotals(home) {
19
56
  try {
20
57
  const s = JSON.parse(fs.readFileSync(path.join(home, '.openzoo', 'session.json'), 'utf8'));
21
58
  const spent = Number(s.spentUsd || s.spendUsd || 0);
22
59
  const would = Number(s.directUsd || 0);
23
60
  const saved = Number(s.savedUsd != null ? s.savedUsd : Math.max(0, would - spent));
24
- if (!(spent > 0.00005)) return '';
25
- return spendChipLabel({ spent, would, saved, pct: would > 0 ? (100 * saved) / would : 0 });
61
+ const pct = would > 0 ? (100 * saved) / would : 0;
62
+ const label = spent > 0.00005 ? spendChipLabel({ spent, would, saved, pct }) : '';
63
+ return { spent, would, saved, pct, label };
26
64
  } catch {
27
- return '';
65
+ return { spent: 0, would: 0, saved: 0, pct: 0, label: '' };
66
+ }
67
+ }
68
+
69
+ function sessionBody(sess) {
70
+ if (!(sess.spent > 0.00005)) return '';
71
+ return `spent $${sess.spent.toFixed(4)} · OpenRouter would $${sess.would.toFixed(4)} · saved $${sess.saved.toFixed(4)} (${Math.round(sess.pct)}%)`;
72
+ }
73
+
74
+ /** Session totals + last-call dump for the HUD pill. */
75
+ export function sessionSpendState(home = os.homedir()) {
76
+ const sess = sessionTotals(home);
77
+ const hud = readSpendHud(home);
78
+ if (hud && (hud.label || hud.body)) {
79
+ return {
80
+ spent: Number(hud.spent) || sess.spent,
81
+ would: Number(hud.would) || sess.would,
82
+ saved: Number(hud.saved) || sess.saved,
83
+ pct: Number(hud.pct) || sess.pct,
84
+ label: String(hud.label || sess.label || ''),
85
+ body: String(hud.body || sessionBody(sess)),
86
+ billedUsd: hud.billedUsd ?? null,
87
+ };
28
88
  }
89
+ return { ...sess, body: sessionBody(sess), billedUsd: null };
90
+ }
91
+
92
+ export function sessionSpendLabel(home = os.homedir()) {
93
+ return sessionSpendState(home).label;
29
94
  }
30
95
 
31
96
  export function grokBotChromiumArgs(port = GROKBOT_CDP_PORT) {
@@ -159,9 +224,13 @@ function ozEnsureSpendCss() {
159
224
  }
160
225
  s.textContent = [
161
226
  '.oz-spend{margin:.55rem 0 0;font-size:12px;color:inherit;opacity:.82;max-width:36em}',
162
- '#oz-spend-float>summary{cursor:grab}',
227
+ '#oz-spend-hud{opacity:1;max-width:min(28em,calc(100vw - 24px));pointer-events:auto}',
228
+ '#oz-spend-hud>summary{cursor:grab;background:#e85d1c;color:#fff;font-weight:600;font-size:13px;',
229
+ 'border:0;box-shadow:0 4px 18px rgba(0,0,0,.5)}',
230
+ '#oz-spend-hud>.oz-spend-body{background:rgba(20,20,22,.94);color:#f4f4f5;padding:8px 10px;',
231
+ 'border-radius:10px;margin-top:6px}',
163
232
  '.oz-spend>summary{cursor:help;list-style:none;display:inline-flex;align-items:center;gap:.35rem;',
164
- 'padding:3px 9px;border-radius:999px;border:1px solid rgba(255,255,255,.18);white-space:nowrap;',
233
+ 'padding:6px 12px;border-radius:999px;border:1px solid rgba(255,255,255,.18);white-space:nowrap;',
165
234
  'max-width:100%;overflow:hidden;text-overflow:ellipsis}',
166
235
  '.oz-spend>summary::-webkit-details-marker{display:none}',
167
236
  '.oz-spend-body{white-space:pre-wrap;margin:.55rem 0 0;font-size:11px;line-height:1.45;opacity:.88;overflow-wrap:anywhere}',
@@ -213,11 +282,19 @@ export function stripSpendFromText(s) {
213
282
  return t;
214
283
  }
215
284
 
285
+ function ozInComposer(el) {
286
+ if (!el) return false;
287
+ if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT' || el.isContentEditable) return true;
288
+ return !!(el.closest && el.closest('textarea, input, [contenteditable="true"]'));
289
+ }
290
+
216
291
  function ozBlankSpendIn(root) {
292
+ if (ozInComposer(root)) return;
217
293
  const w = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);
218
294
  while (w.nextNode()) {
219
295
  const tn = w.currentNode;
220
296
  if (tn.parentElement && tn.parentElement.closest && tn.parentElement.closest('.oz-spend')) continue;
297
+ if (ozInComposer(tn.parentElement)) continue;
221
298
  const next = stripSpendFromText(tn.nodeValue || '');
222
299
  if (next !== tn.nodeValue) tn.nodeValue = next;
223
300
  }
@@ -228,6 +305,7 @@ function ozHideSpendLeftovers() {
228
305
  for (let i = 0; i < nodes.length; i += 1) {
229
306
  const el = nodes[i];
230
307
  if (el.closest && el.closest('.oz-spend')) continue;
308
+ if (ozInComposer(el)) continue;
231
309
  if (el.querySelector && el.querySelector('.oz-spend')) continue;
232
310
  const vis = ozVisibleSpendText(el);
233
311
  if (!spendOnlyText(vis)) continue;
@@ -290,10 +368,11 @@ function ozCollapseSpend() {
290
368
  for (let i = 0; i < nodes.length; i += 1) {
291
369
  const el = nodes[i];
292
370
  if (el.closest && el.closest('.oz-spend')) continue;
371
+ if (ozInComposer(el)) continue;
293
372
  const t = ozVisibleSpendText(el);
294
373
  if (!/::oz-spend::|this call \$|spent \$/i.test(t)) continue;
295
374
  const host = ozSpendHost(el);
296
- if (!host || seen.has(host)) continue;
375
+ if (!host || seen.has(host) || ozInComposer(host)) continue;
297
376
  seen.add(host);
298
377
  hosts.push(host);
299
378
  }
@@ -302,10 +381,11 @@ function ozCollapseSpend() {
302
381
  const vis = ozVisibleSpendText(host);
303
382
  const split = splitSpendText(vis);
304
383
  if (!split || !split.body || split.body.length < 12) continue;
384
+ if (split.summary) window.__OZ_SPEND_LAST__ = split.summary;
305
385
  ozEnsureSpendCss();
306
386
  if (spendOnlyText(vis)) {
307
387
  const prev = ozPreviousMessageCard(host);
308
- if (prev && prev !== host) {
388
+ if (prev && prev !== host && !ozInComposer(prev)) {
309
389
  ozAttachSpendChip(prev, split);
310
390
  host.setAttribute('data-oz-spend-hide', '1');
311
391
  continue;
@@ -318,19 +398,44 @@ function ozCollapseSpend() {
318
398
  ozEnsureFloatSpend();
319
399
  }
320
400
 
321
- function ozEnsureFloatSpend() {
322
- const label = String(window.__OZ_SESSION_SPEND__ || '').trim();
323
- const msgPills = document.querySelectorAll('.oz-spend:not(#oz-spend-float)').length;
324
- let el = document.getElementById('oz-spend-float');
325
- if (!label || msgPills) {
326
- if (el) el.remove();
327
- return;
401
+ /** Live HUD label: latest folded chip, then a read-only canvas scan, then inject snapshot. */
402
+ function ozFloatLabel() {
403
+ const pills = document.querySelectorAll('.oz-spend:not(#oz-spend-hud):not(#oz-spend-float) summary');
404
+ if (pills.length) {
405
+ const lab = String(pills[pills.length - 1].textContent || '').replace(/^ⓘ\s*/, '').trim();
406
+ if (lab) {
407
+ window.__OZ_SPEND_LAST__ = lab;
408
+ return lab;
409
+ }
328
410
  }
411
+ try {
412
+ const nodes = document.querySelectorAll('[class*="sand-message"]');
413
+ for (let i = nodes.length - 1; i >= 0; i -= 1) {
414
+ if (ozInComposer(nodes[i])) continue;
415
+ const vis = ozVisibleSpendText(nodes[i]);
416
+ if (!/::oz-spend::|this call \$|spent \$/i.test(vis)) continue;
417
+ const split = splitSpendText(vis);
418
+ if (split && split.summary) {
419
+ window.__OZ_SPEND_LAST__ = split.summary;
420
+ return split.summary;
421
+ }
422
+ }
423
+ } catch (e) {}
424
+ if (window.__OZ_SPEND_LAST__) return String(window.__OZ_SPEND_LAST__);
425
+ return String(window.__OZ_SESSION_SPEND__ || '').trim();
426
+ }
427
+
428
+ function ozEnsureFloatSpend() {
429
+ const leftover = document.getElementById('oz-spend-float');
430
+ if (leftover) leftover.remove();
431
+ const label = ozFloatLabel() || String(window.__OZ_SESSION_SPEND__ || '').trim() || 'openzoo';
432
+ let el = document.getElementById('oz-spend-hud');
329
433
  ozEnsureSpendCss();
330
434
  if (!el) {
331
435
  el = document.createElement('details');
332
- el.id = 'oz-spend-float';
436
+ el.id = 'oz-spend-hud';
333
437
  el.className = 'oz-spend';
438
+ el.open = true;
334
439
  const sum = document.createElement('summary');
335
440
  const body = document.createElement('div');
336
441
  body.className = 'oz-spend-body';
@@ -343,6 +448,9 @@ function ozEnsureFloatSpend() {
343
448
  ozDragFloat(el);
344
449
  const sum = el.querySelector('summary');
345
450
  if (sum) sum.textContent = 'ⓘ ' + label;
451
+ const bodyEl = el.querySelector('.oz-spend-body');
452
+ const dump = String(window.__OZ_SPEND_BODY__ || '').trim();
453
+ if (bodyEl && dump) bodyEl.textContent = dump;
346
454
  }
347
455
 
348
456
  function ozSavedFloatPos() {
@@ -357,9 +465,15 @@ function ozPlaceFloat(el) {
357
465
  if (!el) return;
358
466
  el.style.position = 'fixed';
359
467
  el.style.zIndex = '2147483646';
360
- el.style.opacity = '0.95';
468
+ el.style.opacity = '1';
469
+ el.style.pointerEvents = 'auto';
470
+ const vw = Math.max(320, Number(window.innerWidth) || 800);
471
+ const vh = Math.max(240, Number(window.innerHeight) || 600);
361
472
  const saved = ozSavedFloatPos();
362
- if (saved) {
473
+ const onScreen = saved
474
+ && saved.left >= 0 && saved.top >= 0
475
+ && saved.left < vw - 24 && saved.top < vh - 24;
476
+ if (onScreen) {
363
477
  el.style.left = Math.max(8, saved.left) + 'px';
364
478
  el.style.top = Math.max(8, saved.top) + 'px';
365
479
  el.style.right = 'auto';
@@ -368,8 +482,8 @@ function ozPlaceFloat(el) {
368
482
  }
369
483
  el.style.left = 'auto';
370
484
  el.style.right = '16px';
371
- el.style.top = '48px';
372
- el.style.bottom = 'auto';
485
+ el.style.top = 'auto';
486
+ el.style.bottom = '88px';
373
487
  }
374
488
 
375
489
  function ozDragFloat(el) {
@@ -414,31 +528,71 @@ function ozDragFloat(el) {
414
528
  }, true);
415
529
  }
416
530
 
531
+ function ozLabelFromInfo(j) {
532
+ if (!j || typeof j !== 'object') return '';
533
+ if (typeof j.label === 'string' && j.label.trim()) return j.label.trim();
534
+ const spent = Number(j.spentUsd ?? j.spendUsd ?? j.spent ?? 0);
535
+ const would = Number(j.directUsd ?? j.would ?? 0);
536
+ const saved = Number(j.savedUsd != null ? j.savedUsd : (j.saved != null ? j.saved : Math.max(0, would - spent)));
537
+ if (!(spent > 0.00005)) return '';
538
+ return labelFromSpendBody(
539
+ 'spent $' + spent.toFixed(4) + ' OpenRouter would $' + would.toFixed(4) + ' saved $' + saved.toFixed(4),
540
+ '',
541
+ );
542
+ }
543
+
544
+ function ozPollSpend() {
545
+ const urls = ['/oz-spend', '/api/ozSpend', 'https://127.0.0.1:8443/api/ozSpend', 'http://127.0.0.1:8402/v1/info'];
546
+ (async () => {
547
+ for (let i = 0; i < urls.length; i += 1) {
548
+ try {
549
+ const r = await fetch(urls[i], { signal: AbortSignal.timeout(1500) });
550
+ if (!r || !r.ok) continue;
551
+ const j = await r.json();
552
+ const lab = ozLabelFromInfo(j);
553
+ if (!lab) continue;
554
+ window.__OZ_SESSION_SPEND__ = lab;
555
+ if (typeof j.body === 'string' && j.body.trim()) window.__OZ_SPEND_BODY__ = j.body.trim();
556
+ ozEnsureFloatSpend();
557
+ return;
558
+ } catch (e) {}
559
+ }
560
+ })();
561
+ }
562
+
417
563
  function ozWatchSpend() {
418
564
  let t = 0;
565
+ const inComposer = () => ozInComposer(document.activeElement);
419
566
  const run = () => {
420
- const a = document.activeElement;
421
- if (a && (a.tagName === 'TEXTAREA' || a.tagName === 'INPUT' || a.isContentEditable)) return;
422
- try { ozCollapseSpend(); } catch (e) {}
567
+ try {
568
+ if (inComposer()) {
569
+ ozEnsureSpendCss();
570
+ ozEnsureFloatSpend();
571
+ } else {
572
+ ozCollapseSpend();
573
+ }
574
+ } catch (e) {}
423
575
  };
424
- const debounced = () => { clearTimeout(t); t = setTimeout(run, 600); };
576
+ const debounced = () => { clearTimeout(t); t = setTimeout(run, 400); };
425
577
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', run);
426
578
  else run();
427
579
  try {
428
580
  const mo = new MutationObserver(debounced);
429
- const start = () => { if (document.body) mo.observe(document.body, { childList: true, subtree: false }); };
581
+ const start = () => { if (document.body) mo.observe(document.body, { childList: true, subtree: true }); };
430
582
  if (document.body) start();
431
583
  else document.addEventListener('DOMContentLoaded', start);
432
584
  } catch (e) {}
433
585
  try { window.addEventListener('resize', debounced); } catch (e) {}
586
+ try { setInterval(() => { run(); ozPollSpend(); }, 2000); } catch (e) {}
587
+ try { ozPollSpend(); } catch (e) {}
434
588
  }
435
589
 
436
590
  export function spendChipSource() {
437
591
  return [
438
592
  '(function ozSpendChip(){',
439
593
  "'use strict';",
440
- 'if (window.__OZ_SPEND_CHIP__ === 12) return;',
441
- 'window.__OZ_SPEND_CHIP__ = 12;',
594
+ 'if (window.__OZ_SPEND_CHIP__ === 16) return;',
595
+ 'window.__OZ_SPEND_CHIP__ = 16;',
442
596
  chipUsd.toString(),
443
597
  labelFromSpendBody.toString(),
444
598
  spendLinesOnly.toString(),
@@ -448,15 +602,19 @@ export function spendChipSource() {
448
602
  ozSpendHost.toString(),
449
603
  ozVisibleSpendText.toString(),
450
604
  stripSpendFromText.toString(),
605
+ ozInComposer.toString(),
451
606
  ozBlankSpendIn.toString(),
452
607
  ozHideSpendLeftovers.toString(),
453
608
  ozPreviousMessageCard.toString(),
454
609
  ozAttachSpendChip.toString(),
455
610
  ozCollapseSpend.toString(),
611
+ ozFloatLabel.toString(),
456
612
  ozEnsureFloatSpend.toString(),
457
613
  ozSavedFloatPos.toString(),
458
614
  ozPlaceFloat.toString(),
459
615
  ozDragFloat.toString(),
616
+ ozLabelFromInfo.toString(),
617
+ ozPollSpend.toString(),
460
618
  ozWatchSpend.toString(),
461
619
  'ozWatchSpend();',
462
620
  '})();',