openzoo 0.49.16 → 0.49.17
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/README.md +8 -8
- package/bin/openzoo.js +0 -11
- package/lib/config.js +18 -28
- package/lib/demo.js +7 -10
- package/lib/evm.js +22 -19
- package/lib/grokui.mjs +13 -317
- package/lib/info.js +9 -10
- package/lib/mcp.js +1 -3
- package/lib/pay.js +74 -270
- package/lib/podagent.mjs +5 -16
- package/lib/proxy.js +4 -10
- package/lib/x402.js +145 -54
- package/package.json +2 -2
- package/lib/evmwrap.js +0 -163
- package/lib/subscription.js +0 -207
- package/lib/wrap.js +0 -332
- package/lib/xbot.js +0 -1576
- package/lib/xburner.js +0 -80
package/lib/grokui.mjs
CHANGED
|
@@ -16,12 +16,6 @@ import { routeChatBody } from './modelroute.js';
|
|
|
16
16
|
import { peekDirectiveStatus, formatRaceStatus, STALE_THINKING_MS, summarizeRaceFailures, RACE_EVERY_FAILED } from './livestatus.js';
|
|
17
17
|
import { creditBalance } from './info.js';
|
|
18
18
|
import { guardFindCwd } from './runguard.js';
|
|
19
|
-
import {
|
|
20
|
-
SUBSCRIPTIONS_PAGE,
|
|
21
|
-
saveSubscription, clearSubscription,
|
|
22
|
-
subscriptionPublicView, parseSubscriptionPaste,
|
|
23
|
-
billingTiers, billingCheckout, fetchBillingKey, ingestBillingKeyResponse,
|
|
24
|
-
} from './subscription.js';
|
|
25
19
|
import {
|
|
26
20
|
prepareChildDir, finishChildDir, lockWorktree, unlockWorktree,
|
|
27
21
|
parsePrRef, fetchSpecsForOrigin, agentSlug,
|
|
@@ -3837,24 +3831,8 @@ const APP_HTML = `<!doctype html>
|
|
|
3837
3831
|
</div>
|
|
3838
3832
|
<div id="walletOverlay" data-component="wallet-modal">
|
|
3839
3833
|
<div id="walletBox">
|
|
3840
|
-
<h3>Pay
|
|
3841
|
-
<div class="wsub">
|
|
3842
|
-
<div id="subLane" data-component="subscribe-lane">
|
|
3843
|
-
<div class="wlanetitle">Subscribe with a card</div>
|
|
3844
|
-
<div class="wtag">Subscription key · no x402</div>
|
|
3845
|
-
<div class="wsub">Same plans as the public page. Checkout opens in the system browser — never an in-app Stripe window. After Stripe, this app polls the site’s key endpoint with the checkout session, or you paste the key from the success page.</div>
|
|
3846
|
-
<div id="subStatus"></div>
|
|
3847
|
-
<div id="subTiers">loading plans…</div>
|
|
3848
|
-
<div class="wpaste">
|
|
3849
|
-
<div class="wlab">I already subscribed — paste key</div>
|
|
3850
|
-
<input id="subKeyInp" type="text" autocomplete="off" spellcheck="false"
|
|
3851
|
-
placeholder="key, or the /billing/done?session=… URL">
|
|
3852
|
-
<button type="button" id="subKeyBtn">Save key</button>
|
|
3853
|
-
<button type="button" id="subForgetBtn" hidden>Remove key</button>
|
|
3854
|
-
</div>
|
|
3855
|
-
<div class="wnote" id="subNote"></div>
|
|
3856
|
-
<div class="wquiet"><a id="subPageLink" href="${SUBSCRIPTIONS_PAGE}" target="_blank" rel="noopener">Full subscriptions page</a></div>
|
|
3857
|
-
</div>
|
|
3834
|
+
<h3>Pay per call</h3>
|
|
3835
|
+
<div class="wsub">Wallet/x402 is the only pay lane. There is no key to buy and none to paste: every call settles on chain from the burner below.</div>
|
|
3858
3836
|
<div class="wlane" id="x402Lane" data-component="x402-lane">
|
|
3859
3837
|
<div class="wlanetitle">Wallet / x402</div>
|
|
3860
3838
|
<div class="wsub">This is <b>your</b> local burner on this machine (or this box). Keys stay in ~/.openzoo/wallet.json. It is not openzoo’s wallet, not a shared zoo account, not the model’s. You fund these deposit addresses; the app pays x402 per call from this wallet. Public addresses only — the UI never shows the key.</div>
|
|
@@ -3910,7 +3888,6 @@ const APP_HTML = `<!doctype html>
|
|
|
3910
3888
|
</div>
|
|
3911
3889
|
<div id="hud">
|
|
3912
3890
|
<div class="htitle">YOUR WALLET · THIS SESSION</div>
|
|
3913
|
-
<div class="hrow" id="hSubRow" hidden><span>subscription</span><span id="hSub" class="hlime">—</span></div>
|
|
3914
3891
|
<div class="hrow"><span>prepaid credit</span><span id="hCredit" class="hlime">—</span></div>
|
|
3915
3892
|
<div class="hrow"><span>you've paid</span><span id="hYouSpent">—</span></div>
|
|
3916
3893
|
<div class="hrow"><span>our cost (cogs)</span><span id="hYouCogs">—</span></div>
|
|
@@ -4389,9 +4366,8 @@ const APP_HTML = `<!doctype html>
|
|
|
4389
4366
|
if (!w || (!w.solana && !w.evm && w.creditUsd == null)) {
|
|
4390
4367
|
const p = document.createElement('div');
|
|
4391
4368
|
p.className = 'wnote wempty';
|
|
4392
|
-
p.textContent = 'Could not reach the local openzoo proxy on :8402. It may still be starting — try again in a few seconds.
|
|
4369
|
+
p.textContent = 'Could not reach the local openzoo proxy on :8402. It may still be starting — try again in a few seconds.';
|
|
4393
4370
|
walletBody.appendChild(p);
|
|
4394
|
-
renderSubLane(w && w.subscription ? w.subscription : null);
|
|
4395
4371
|
return;
|
|
4396
4372
|
}
|
|
4397
4373
|
if (w.creditUsd != null && w.creditUsd !== '') {
|
|
@@ -4425,197 +4401,32 @@ const APP_HTML = `<!doctype html>
|
|
|
4425
4401
|
note.className = 'wnote';
|
|
4426
4402
|
// funded === false is the genuinely-empty case. Undefined means the proxy
|
|
4427
4403
|
// did not say, and guessing "empty" there would send someone to top up a
|
|
4428
|
-
// wallet that is fine.
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
: (w.funded === false
|
|
4434
|
-
? 'This wallet is EMPTY — wallet/x402 calls will fail with HTTP 402 until you fund the addresses above, or subscribe with a card at the top. ' + (w.funding || '')
|
|
4435
|
-
: (w.funding || ''));
|
|
4436
|
-
if (w.funded === false && !subOn) note.classList.add('wempty');
|
|
4404
|
+
// wallet that is fine.
|
|
4405
|
+
note.textContent = w.funded === false
|
|
4406
|
+
? 'This wallet is EMPTY — every call will fail with HTTP 402 until you fund the addresses above. ' + (w.funding || '')
|
|
4407
|
+
: (w.funding || '');
|
|
4408
|
+
if (w.funded === false) note.classList.add('wempty');
|
|
4437
4409
|
if (note.textContent.trim()) walletBody.appendChild(note);
|
|
4438
|
-
renderSubLane(w.subscription || null);
|
|
4439
|
-
}
|
|
4440
|
-
var subPollTimer = null;
|
|
4441
|
-
var subBuying = null;
|
|
4442
|
-
function setSubNote(text, empty) {
|
|
4443
|
-
const el = document.getElementById('subNote');
|
|
4444
|
-
if (!el) return;
|
|
4445
|
-
el.textContent = text || '';
|
|
4446
|
-
el.className = empty ? 'wnote wempty' : 'wnote';
|
|
4447
|
-
}
|
|
4448
|
-
function renderSubLane(sub) {
|
|
4449
|
-
const status = document.getElementById('subStatus');
|
|
4450
|
-
const forget = document.getElementById('subForgetBtn');
|
|
4451
|
-
if (status) {
|
|
4452
|
-
status.innerHTML = '';
|
|
4453
|
-
if (sub && sub.active) {
|
|
4454
|
-
const p = document.createElement('div');
|
|
4455
|
-
p.className = 'wsubon';
|
|
4456
|
-
p.textContent = sub.label || (sub.tierName || 'Subscription') + ' · no x402';
|
|
4457
|
-
status.appendChild(p);
|
|
4458
|
-
}
|
|
4459
|
-
}
|
|
4460
|
-
if (forget) forget.hidden = !(sub && sub.active);
|
|
4461
|
-
loadSubTiers();
|
|
4462
|
-
}
|
|
4463
|
-
async function loadSubTiers() {
|
|
4464
|
-
const box = document.getElementById('subTiers');
|
|
4465
|
-
if (!box) return;
|
|
4466
|
-
let body = null;
|
|
4467
|
-
try {
|
|
4468
|
-
const r = await fetch(API + '/billing/tiers');
|
|
4469
|
-
body = r.ok ? await r.json() : null;
|
|
4470
|
-
} catch (e) { body = null; }
|
|
4471
|
-
box.innerHTML = '';
|
|
4472
|
-
const tiers = body && body.ok && Array.isArray(body.tiers) ? body.tiers : [];
|
|
4473
|
-
if (!tiers.length) {
|
|
4474
|
-
const p = document.createElement('div');
|
|
4475
|
-
p.className = 'wnote wempty';
|
|
4476
|
-
p.textContent = 'Could not load live plans from zoo.openzoo.fun — try again, or use the full subscriptions page.';
|
|
4477
|
-
box.appendChild(p);
|
|
4478
|
-
return;
|
|
4479
|
-
}
|
|
4480
|
-
tiers.forEach(function (t) {
|
|
4481
|
-
const art = document.createElement('div');
|
|
4482
|
-
art.className = 'wtier' + (t.id === 'pro' ? ' hot' : '');
|
|
4483
|
-
art.setAttribute('data-tier', t.id);
|
|
4484
|
-
const tag = document.createElement('div');
|
|
4485
|
-
tag.className = 'wtag';
|
|
4486
|
-
tag.textContent = t.id === 'pro' ? 'Most teams want this' : '';
|
|
4487
|
-
const name = document.createElement('div');
|
|
4488
|
-
name.className = 'wtn';
|
|
4489
|
-
name.textContent = t.name || t.id;
|
|
4490
|
-
const price = document.createElement('div');
|
|
4491
|
-
price.className = 'wtp';
|
|
4492
|
-
price.textContent = '$' + ((Number(t.monthlyCents) || 0) / 100).toFixed(0) + '/mo';
|
|
4493
|
-
const share = document.createElement('div');
|
|
4494
|
-
share.className = 'wts';
|
|
4495
|
-
share.textContent = (t.savingsSharePct != null ? t.savingsSharePct : '?') + '% savings share';
|
|
4496
|
-
const blurb = document.createElement('div');
|
|
4497
|
-
blurb.className = 'wtb';
|
|
4498
|
-
blurb.textContent = t.blurb || '';
|
|
4499
|
-
const btn = document.createElement('button');
|
|
4500
|
-
btn.type = 'button';
|
|
4501
|
-
btn.textContent = subBuying === t.id ? 'Opening checkout…' : ('Get ' + (t.name || t.id));
|
|
4502
|
-
btn.disabled = subBuying != null;
|
|
4503
|
-
btn.addEventListener('click', function () { buyTier(t.id); });
|
|
4504
|
-
art.append(tag, name, price, share, blurb, btn);
|
|
4505
|
-
box.appendChild(art);
|
|
4506
|
-
});
|
|
4507
|
-
}
|
|
4508
|
-
function openSystemBrowser(url) {
|
|
4509
|
-
// Electron's setWindowOpenHandler routes target=_blank to shell.openExternal.
|
|
4510
|
-
// Never load Stripe inside this window.
|
|
4511
|
-
window.open(url, '_blank', 'noopener,noreferrer');
|
|
4512
|
-
}
|
|
4513
|
-
function stopSubPoll() {
|
|
4514
|
-
if (subPollTimer) { clearInterval(subPollTimer); subPollTimer = null; }
|
|
4515
|
-
}
|
|
4516
|
-
function startSubPoll(sessionId) {
|
|
4517
|
-
stopSubPoll();
|
|
4518
|
-
var tries = 0;
|
|
4519
|
-
async function tick() {
|
|
4520
|
-
tries += 1;
|
|
4521
|
-
try {
|
|
4522
|
-
const r = await fetch(API + '/billing/key?session=' + encodeURIComponent(sessionId));
|
|
4523
|
-
const j = r.ok ? await r.json() : null;
|
|
4524
|
-
if (j && j.saved) {
|
|
4525
|
-
stopSubPoll();
|
|
4526
|
-
setSubNote('Subscription key saved · no x402');
|
|
4527
|
-
await openWallet();
|
|
4528
|
-
return;
|
|
4529
|
-
}
|
|
4530
|
-
if (j && j.pending) setSubNote('Waiting for Stripe to confirm…');
|
|
4531
|
-
else if (j && j.error && j.error !== 'session required') setSubNote(j.error, true);
|
|
4532
|
-
} catch (e) { /* keep polling */ }
|
|
4533
|
-
if (tries >= 60) {
|
|
4534
|
-
stopSubPoll();
|
|
4535
|
-
setSubNote('Still waiting on Stripe — paste the key from the success page if you have it.');
|
|
4536
|
-
}
|
|
4537
|
-
}
|
|
4538
|
-
subPollTimer = setInterval(tick, 2000);
|
|
4539
|
-
tick();
|
|
4540
|
-
}
|
|
4541
|
-
async function buyTier(tier) {
|
|
4542
|
-
subBuying = tier;
|
|
4543
|
-
setSubNote('');
|
|
4544
|
-
loadSubTiers();
|
|
4545
|
-
try {
|
|
4546
|
-
const r = await fetch(API + '/billing/checkout', {
|
|
4547
|
-
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
4548
|
-
body: JSON.stringify({ tier: tier }),
|
|
4549
|
-
});
|
|
4550
|
-
const j = await r.json();
|
|
4551
|
-
if (!j || !j.ok || !j.url) throw new Error((j && j.error) || 'checkout failed');
|
|
4552
|
-
openSystemBrowser(j.url);
|
|
4553
|
-
setSubNote('Checkout opened in your system browser. This window will pick up the key when Stripe confirms.');
|
|
4554
|
-
if (j.sessionId) startSubPoll(j.sessionId);
|
|
4555
|
-
} catch (e) {
|
|
4556
|
-
setSubNote(e.message || String(e), true);
|
|
4557
|
-
}
|
|
4558
|
-
subBuying = null;
|
|
4559
|
-
loadSubTiers();
|
|
4560
|
-
}
|
|
4561
|
-
async function savePastedSub() {
|
|
4562
|
-
const inp = document.getElementById('subKeyInp');
|
|
4563
|
-
const paste = inp ? inp.value.trim() : '';
|
|
4564
|
-
if (!paste) { setSubNote('Paste a key or the billing/done URL.', true); return; }
|
|
4565
|
-
try {
|
|
4566
|
-
const r = await fetch(API + '/billing/key', {
|
|
4567
|
-
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
4568
|
-
body: JSON.stringify({ paste: paste }),
|
|
4569
|
-
});
|
|
4570
|
-
const j = await r.json();
|
|
4571
|
-
if (j && j.pending && j.session) {
|
|
4572
|
-
setSubNote('Waiting for Stripe to confirm…');
|
|
4573
|
-
startSubPoll(j.session);
|
|
4574
|
-
return;
|
|
4575
|
-
}
|
|
4576
|
-
if (!j || !j.saved) throw new Error((j && j.error) || 'could not save key');
|
|
4577
|
-
if (inp) inp.value = '';
|
|
4578
|
-
setSubNote('Subscription key saved · no x402');
|
|
4579
|
-
await openWallet();
|
|
4580
|
-
} catch (e) {
|
|
4581
|
-
setSubNote(e.message || String(e), true);
|
|
4582
|
-
}
|
|
4583
|
-
}
|
|
4584
|
-
async function forgetSub() {
|
|
4585
|
-
try {
|
|
4586
|
-
await fetch(API + '/billing/key', { method: 'DELETE' });
|
|
4587
|
-
} catch (e) { /* still refresh */ }
|
|
4588
|
-
setSubNote('Subscription key removed. Wallet/x402 is the pay method again.');
|
|
4589
|
-
await openWallet();
|
|
4590
4410
|
}
|
|
4591
4411
|
document.getElementById('walletBtn').addEventListener('click', () => {
|
|
4592
4412
|
echoSlash('/pay');
|
|
4593
4413
|
openWallet();
|
|
4594
4414
|
});
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
if (subForgetBtn) subForgetBtn.addEventListener('click', forgetSub);
|
|
4599
|
-
const subKeyInp = document.getElementById('subKeyInp');
|
|
4600
|
-
if (subKeyInp) subKeyInp.addEventListener('keydown', function (e) {
|
|
4601
|
-
if (e.key === 'Enter') { e.preventDefault(); savePastedSub(); }
|
|
4602
|
-
});
|
|
4603
|
-
// First launch: if the burner is empty AND there is no subscription, open
|
|
4604
|
-
// the pay modal once so they see card plans first — and burner addresses
|
|
4605
|
-
// below. localStorage so a funded session, a saved key, or a dismiss does
|
|
4606
|
-
// not keep popping it.
|
|
4415
|
+
// First launch: if the burner is empty, open the pay modal once so the
|
|
4416
|
+
// deposit addresses are the first thing seen. localStorage so a funded
|
|
4417
|
+
// session or a dismiss does not keep popping it.
|
|
4607
4418
|
(async function maybeOpenWalletOnce() {
|
|
4608
4419
|
if (localStorage.getItem('openzoo.wallet.seen')) return;
|
|
4609
4420
|
for (let i = 0; i < 8; i++) {
|
|
4610
4421
|
try {
|
|
4611
4422
|
const r = await fetch(API + '/wallet');
|
|
4612
4423
|
const w = r.ok ? await r.json() : null;
|
|
4613
|
-
if (!w || (!w.solana && !w.evm
|
|
4424
|
+
if (!w || (!w.solana && !w.evm)) {
|
|
4614
4425
|
await new Promise((res) => setTimeout(res, 400));
|
|
4615
4426
|
continue;
|
|
4616
4427
|
}
|
|
4617
4428
|
localStorage.setItem('openzoo.wallet.seen', '1');
|
|
4618
|
-
if (w.funded === false
|
|
4429
|
+
if (w.funded === false) await openWallet();
|
|
4619
4430
|
return;
|
|
4620
4431
|
} catch (e) {
|
|
4621
4432
|
await new Promise((res) => setTimeout(res, 400));
|
|
@@ -6077,16 +5888,6 @@ const APP_HTML = `<!doctype html>
|
|
|
6077
5888
|
const you = await (await fetch(API + '/hud-summary')).json();
|
|
6078
5889
|
const creditEl = document.getElementById('hCredit');
|
|
6079
5890
|
if (creditEl) creditEl.textContent = (you.creditUsd == null) ? '—' : usd(Number(you.creditUsd) || 0);
|
|
6080
|
-
const subRow = document.getElementById('hSubRow');
|
|
6081
|
-
const subEl = document.getElementById('hSub');
|
|
6082
|
-
if (subRow && subEl) {
|
|
6083
|
-
if (you.subscription && you.subscription.active) {
|
|
6084
|
-
subRow.hidden = false;
|
|
6085
|
-
subEl.textContent = you.subscription.label || you.subscription.tierName || 'Subscription key · no x402';
|
|
6086
|
-
} else {
|
|
6087
|
-
subRow.hidden = true;
|
|
6088
|
-
}
|
|
6089
|
-
}
|
|
6090
5891
|
const spent = Number(you.spentUsd) || 0;
|
|
6091
5892
|
const cogs = Number(you.cogsUsd) || 0;
|
|
6092
5893
|
const direct = Number(you.directUsd) || 0;
|
|
@@ -6185,116 +5986,12 @@ const server = http.createServer((req, res) => {
|
|
|
6185
5986
|
try {
|
|
6186
5987
|
w.creditUsd = await creditBalance();
|
|
6187
5988
|
} catch { /* leave credit off if the gateway is down */ }
|
|
6188
|
-
// Subscription is local (~/.openzoo/subscription.json). Merge it here so
|
|
6189
|
-
// an older :8402 that does not yet know about Stripe still shows the lane.
|
|
6190
|
-
w.subscription = subscriptionPublicView();
|
|
6191
5989
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
6192
5990
|
res.end(JSON.stringify(w));
|
|
6193
5991
|
})();
|
|
6194
5992
|
return;
|
|
6195
5993
|
}
|
|
6196
5994
|
|
|
6197
|
-
// Live Stripe plans — never a stale hardcoded $9/$29/$99. Same origin so
|
|
6198
|
-
// the renderer does not have to talk to zoo.openzoo.fun itself.
|
|
6199
|
-
if (req.method === 'GET' && req.url === '/billing/tiers') {
|
|
6200
|
-
(async () => {
|
|
6201
|
-
try {
|
|
6202
|
-
const body = await billingTiers();
|
|
6203
|
-
res.writeHead(200, { 'content-type': 'application/json' });
|
|
6204
|
-
res.end(JSON.stringify(body));
|
|
6205
|
-
} catch (e) {
|
|
6206
|
-
res.writeHead(502, { 'content-type': 'application/json' });
|
|
6207
|
-
res.end(JSON.stringify({ ok: false, error: e.message }));
|
|
6208
|
-
}
|
|
6209
|
-
})();
|
|
6210
|
-
return;
|
|
6211
|
-
}
|
|
6212
|
-
|
|
6213
|
-
if (req.method === 'POST' && req.url === '/billing/checkout') {
|
|
6214
|
-
const chunks = [];
|
|
6215
|
-
req.on('data', (d) => chunks.push(d));
|
|
6216
|
-
req.on('end', async () => {
|
|
6217
|
-
let tier = '';
|
|
6218
|
-
try { tier = JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}').tier || ''; }
|
|
6219
|
-
catch { /* ignore */ }
|
|
6220
|
-
try {
|
|
6221
|
-
const body = await billingCheckout(tier);
|
|
6222
|
-
res.writeHead(200, { 'content-type': 'application/json' });
|
|
6223
|
-
res.end(JSON.stringify(body));
|
|
6224
|
-
} catch (e) {
|
|
6225
|
-
res.writeHead(502, { 'content-type': 'application/json' });
|
|
6226
|
-
res.end(JSON.stringify({ ok: false, error: e.message }));
|
|
6227
|
-
}
|
|
6228
|
-
});
|
|
6229
|
-
return;
|
|
6230
|
-
}
|
|
6231
|
-
|
|
6232
|
-
// GET ?session= polls the same endpoint the public /billing/done page uses.
|
|
6233
|
-
// On a key, persist it locally and return a public view — never the secret
|
|
6234
|
-
// (this UI can sit on a public box URL).
|
|
6235
|
-
if (req.method === 'GET' && (req.url || '').startsWith('/billing/key')) {
|
|
6236
|
-
(async () => {
|
|
6237
|
-
const q = new URL(req.url, 'http://x').searchParams;
|
|
6238
|
-
const session = q.get('session') || q.get('session_id') || '';
|
|
6239
|
-
try {
|
|
6240
|
-
const body = await fetchBillingKey(session);
|
|
6241
|
-
res.writeHead(200, { 'content-type': 'application/json' });
|
|
6242
|
-
res.end(JSON.stringify(ingestBillingKeyResponse(body, {
|
|
6243
|
-
sessionId: session,
|
|
6244
|
-
tier: q.get('tier') || null,
|
|
6245
|
-
})));
|
|
6246
|
-
} catch (e) {
|
|
6247
|
-
res.writeHead(502, { 'content-type': 'application/json' });
|
|
6248
|
-
res.end(JSON.stringify({ ok: false, error: e.message }));
|
|
6249
|
-
}
|
|
6250
|
-
})();
|
|
6251
|
-
return;
|
|
6252
|
-
}
|
|
6253
|
-
|
|
6254
|
-
if (req.method === 'POST' && req.url === '/billing/key') {
|
|
6255
|
-
const chunks = [];
|
|
6256
|
-
req.on('data', (d) => chunks.push(d));
|
|
6257
|
-
req.on('end', async () => {
|
|
6258
|
-
let paste = '';
|
|
6259
|
-
let tier = '';
|
|
6260
|
-
try {
|
|
6261
|
-
const j = JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}');
|
|
6262
|
-
paste = j.paste || j.key || '';
|
|
6263
|
-
tier = j.tier || '';
|
|
6264
|
-
} catch { /* ignore */ }
|
|
6265
|
-
const parsed = parseSubscriptionPaste(paste);
|
|
6266
|
-
if (parsed.error) {
|
|
6267
|
-
res.writeHead(400, { 'content-type': 'application/json' });
|
|
6268
|
-
res.end(JSON.stringify({ ok: false, error: parsed.error }));
|
|
6269
|
-
return;
|
|
6270
|
-
}
|
|
6271
|
-
if (parsed.session) {
|
|
6272
|
-
try {
|
|
6273
|
-
const body = await fetchBillingKey(parsed.session);
|
|
6274
|
-
const out = ingestBillingKeyResponse(body, { sessionId: parsed.session, tier });
|
|
6275
|
-
if (out.pending) out.session = parsed.session;
|
|
6276
|
-
res.writeHead(200, { 'content-type': 'application/json' });
|
|
6277
|
-
res.end(JSON.stringify(out));
|
|
6278
|
-
} catch (e) {
|
|
6279
|
-
res.writeHead(502, { 'content-type': 'application/json' });
|
|
6280
|
-
res.end(JSON.stringify({ ok: false, error: e.message }));
|
|
6281
|
-
}
|
|
6282
|
-
return;
|
|
6283
|
-
}
|
|
6284
|
-
saveSubscription({ key: parsed.key, tier: tier || null });
|
|
6285
|
-
res.writeHead(200, { 'content-type': 'application/json' });
|
|
6286
|
-
res.end(JSON.stringify({ ok: true, saved: true, ...subscriptionPublicView() }));
|
|
6287
|
-
});
|
|
6288
|
-
return;
|
|
6289
|
-
}
|
|
6290
|
-
|
|
6291
|
-
if (req.method === 'DELETE' && req.url === '/billing/key') {
|
|
6292
|
-
clearSubscription();
|
|
6293
|
-
res.writeHead(200, { 'content-type': 'application/json' });
|
|
6294
|
-
res.end(JSON.stringify({ ok: true, saved: false, ...subscriptionPublicView() }));
|
|
6295
|
-
return;
|
|
6296
|
-
}
|
|
6297
|
-
|
|
6298
5995
|
// Restart grokui in place — and ACTUALLY PICK UP THE NEW BUILD.
|
|
6299
5996
|
//
|
|
6300
5997
|
// Exiting is the restart: on a production box, box-server's ensureOz() poll
|
|
@@ -6351,7 +6048,6 @@ const server = http.createServer((req, res) => {
|
|
|
6351
6048
|
try {
|
|
6352
6049
|
you.creditUsd = await creditBalance();
|
|
6353
6050
|
} catch { /* credit is advisory */ }
|
|
6354
|
-
you.subscription = subscriptionPublicView();
|
|
6355
6051
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
6356
6052
|
res.end(JSON.stringify(you));
|
|
6357
6053
|
})();
|
package/lib/info.js
CHANGED
|
@@ -39,8 +39,11 @@ function fmtUi(raw, decimals) {
|
|
|
39
39
|
* The quote already carries `extra.tokenUsd` for every asset it settles in —
|
|
40
40
|
* the very "priced at the 402" this file kept promising in a footnote while
|
|
41
41
|
* printing `$?`. So a wallet holding 845,486 TOKEN summed to "≈ $0.11 known".
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
*
|
|
43
|
+
* Symbols are the plain tickers the wallet holds. They used to arrive as
|
|
44
|
+
* wrapper twins that had to be mapped back to the underlying before a holding
|
|
45
|
+
* could find its price; the gateway quotes the native mint now, so the symbol
|
|
46
|
+
* on the row is already the symbol in the wallet.
|
|
44
47
|
*/
|
|
45
48
|
export async function quotedPrices() {
|
|
46
49
|
const out = {};
|
|
@@ -71,10 +74,7 @@ export async function quotedPrices() {
|
|
|
71
74
|
const usd = Number(row?.extra?.tokenUsd);
|
|
72
75
|
const sym = String(row?.extra?.symbol || '');
|
|
73
76
|
if (!sym || !Number.isFinite(usd)) continue;
|
|
74
|
-
// wTOKENx -> TOKEN, wUSDGx -> USDG, yUSDCx -> USDC
|
|
75
|
-
const plain = sym.replace(/^[wy]/, '').replace(/x$/, '').toUpperCase();
|
|
76
77
|
out[sym.toUpperCase()] = usd;
|
|
77
|
-
if (!(plain in out)) out[plain] = usd;
|
|
78
78
|
}
|
|
79
79
|
} catch { /* offline: fall back to printing what we know */ }
|
|
80
80
|
return out;
|
|
@@ -179,11 +179,10 @@ export async function affordableUsd() {
|
|
|
179
179
|
const perUsd = BigInt(row.maxAmountRequired || '0');
|
|
180
180
|
if (perUsd <= 0n) continue;
|
|
181
181
|
try {
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
// sent them off to fund an account that was already funded.
|
|
182
|
+
// The quote names the raw mint, so the wallet's balance of it IS what it
|
|
183
|
+
// can spend. Back when rows named a Token-2022 twin this had to count the
|
|
184
|
+
// convertible plain token too, and not doing so told a user holding
|
|
185
|
+
// 14,847 TOKEN their wallet "covers only $1.0230".
|
|
187
186
|
let usd;
|
|
188
187
|
if (client.spendableUsdForAccept) {
|
|
189
188
|
usd = await client.spendableUsdForAccept(row);
|
package/lib/mcp.js
CHANGED
|
@@ -8,7 +8,6 @@ import { tokenBalance } from './x402.js';
|
|
|
8
8
|
import { askWithContext, contextCacheDisabled, BIND_MIN_CHARS } from './hrr.js';
|
|
9
9
|
import { listContexts } from './contexts.js';
|
|
10
10
|
import { withNamespace } from './namespace.js';
|
|
11
|
-
import { subscriptionPublicView } from './subscription.js';
|
|
12
11
|
|
|
13
12
|
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
14
13
|
// The model zoo_ask uses when the caller does not name one. Opus 5 by default:
|
|
@@ -383,10 +382,9 @@ export function buildMcpServer() {
|
|
|
383
382
|
railsLiveNow: rails?.live ?? null,
|
|
384
383
|
fundWith: hint || `${FUNDING_ASSETS.map((a) => a.symbol).join(' or ')} on Solana`,
|
|
385
384
|
solanaMints: Object.fromEntries(FUNDING_ASSETS.map((a) => [a.symbol, a.mint])),
|
|
386
|
-
fundHint: 'send a few cents of a listed asset to the address for that rail — Solana assets to solanaAddress, Base assets to evmAddress.
|
|
385
|
+
fundHint: 'send a few cents of a listed asset to the address for that rail — Solana assets to solanaAddress, Base and Robinhood assets to evmAddress. These are the exact mints the 402 quotes: whatever you fund is what gets spent, with no conversion or wrapping step. Force a rail with OPENZOO_RAIL=solana|base|robinhood.',
|
|
387
386
|
balances,
|
|
388
387
|
receipts: client.receipts.map((r) => ({ at: r.at, line: r.line })),
|
|
389
|
-
subscription: subscriptionPublicView(),
|
|
390
388
|
});
|
|
391
389
|
});
|
|
392
390
|
|