openzoo 0.50.27 → 0.50.29
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/lib/cursorbackend.js +44 -0
- package/lib/moonpayOnramp.js +80 -0
- package/lib/proxy.js +1 -1
- package/lib/stripeOnramp.js +24 -4
- package/package.json +1 -1
package/lib/cursorbackend.js
CHANGED
|
@@ -1136,6 +1136,48 @@ function gatewayEntry(e) {
|
|
|
1136
1136
|
const { seq, pulledRemote, ...rest } = e;
|
|
1137
1137
|
return rest;
|
|
1138
1138
|
}
|
|
1139
|
+
|
|
1140
|
+
function stripSpendFooter(s) {
|
|
1141
|
+
return String(s || '').replace(/\n{2,}this call \$[\d.]+[\s\S]*$/i, '').trimEnd();
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/** Prior turns for zooComplete. sendPrompt used to POST only the latest user
|
|
1145
|
+
* line, so the model said each thread starts blank while the UI still showed
|
|
1146
|
+
* the canvas. Skip the last user echo of `currentPrompt` (already appended). */
|
|
1147
|
+
function historyMessages(agentId, currentPrompt) {
|
|
1148
|
+
const entries = agentTranscript(agentId).entries || [];
|
|
1149
|
+
const cur = String(currentPrompt || '').trim();
|
|
1150
|
+
const out = [];
|
|
1151
|
+
for (const e of entries) {
|
|
1152
|
+
if (!e || typeof e !== 'object') continue;
|
|
1153
|
+
let role = null;
|
|
1154
|
+
let text = '';
|
|
1155
|
+
if (e.kind === 'send-message' || e.role === 'assistant') {
|
|
1156
|
+
const m = e.message;
|
|
1157
|
+
text = typeof m === 'string' ? m : String(m?.content || m?.text || '');
|
|
1158
|
+
role = 'assistant';
|
|
1159
|
+
text = stripSpendFooter(text);
|
|
1160
|
+
} else if (e.kind === 'message' || e.role === 'user') {
|
|
1161
|
+
text = String(e.content || e.message?.content || e.message?.text || '');
|
|
1162
|
+
role = 'user';
|
|
1163
|
+
}
|
|
1164
|
+
text = String(text || '').trim();
|
|
1165
|
+
if (!role || !text) continue;
|
|
1166
|
+
out.push({ role, content: text });
|
|
1167
|
+
}
|
|
1168
|
+
while (out.length && out[out.length - 1].role === 'user' && String(out[out.length - 1].content).trim() === cur) {
|
|
1169
|
+
out.pop();
|
|
1170
|
+
}
|
|
1171
|
+
let chars = 0;
|
|
1172
|
+
const kept = [];
|
|
1173
|
+
for (let i = out.length - 1; i >= 0; i--) {
|
|
1174
|
+
chars += String(out[i].content).length;
|
|
1175
|
+
if (chars > 80_000 && kept.length) break;
|
|
1176
|
+
kept.push(out[i]);
|
|
1177
|
+
}
|
|
1178
|
+
kept.reverse();
|
|
1179
|
+
return kept;
|
|
1180
|
+
}
|
|
1139
1181
|
async function podJson(path0, bodyObj, log) {
|
|
1140
1182
|
if (!realPod?.agent) return null;
|
|
1141
1183
|
let agent;
|
|
@@ -1520,9 +1562,11 @@ async function zooComplete(prompt, log, agentId, parsed = {}) {
|
|
|
1520
1562
|
'Never claim you lack filesystem access or local-exec. If a tool errors, report the error.',
|
|
1521
1563
|
'After using tools you MUST still write a normal chat reply: what you did, file paths written, and what to open. Empty content is a bug.',
|
|
1522
1564
|
'A spend footer is appended after your reply by the host — ignore it.',
|
|
1565
|
+
'Prior turns of THIS Grok Bot chat are in the messages below. Do not claim the thread starts blank or that earlier questions did not arrive.',
|
|
1523
1566
|
].join(' '),
|
|
1524
1567
|
},
|
|
1525
1568
|
];
|
|
1569
|
+
for (const m of historyMessages(agentId, prompt)) messages.push(m);
|
|
1526
1570
|
if (textFiles.length) {
|
|
1527
1571
|
const bits = textFiles.map((a) => (
|
|
1528
1572
|
a.error
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MoonPay-hosted fiat→USDC onramp for the x402 402 path.
|
|
3
|
+
*
|
|
4
|
+
* Unlike Stripe, there is no session POST: we build
|
|
5
|
+
* https://buy.moonpay.com/?apiKey=pk_…¤cyCode=usdc_sol&walletAddress=…
|
|
6
|
+
* and HMAC-SHA256 sign the query string with the secret. The dest is locked
|
|
7
|
+
* because walletAddress + currencyCode + signature are required together —
|
|
8
|
+
* the widget does not prompt for another address.
|
|
9
|
+
*
|
|
10
|
+
* Keys are NEVER in the repo: MOONPAY_PUBLISHABLE_KEY + MOONPAY_SECRET_KEY,
|
|
11
|
+
* else ~/moonpay.json `{publishableKey,secretKey}`, else ~/moonpay.pk +
|
|
12
|
+
* ~/moonpay.key. No keys → caller keeps the send-to-address copy.
|
|
13
|
+
*
|
|
14
|
+
* Do NOT set allowedIpAddress. Live IP matching would bind the URL to the
|
|
15
|
+
* shim/gateway IP; the 402 is opened on the user's machine (or phone).
|
|
16
|
+
*/
|
|
17
|
+
import crypto from 'node:crypto';
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import os from 'node:os';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
|
|
22
|
+
const HOME = os.homedir();
|
|
23
|
+
|
|
24
|
+
function readTrim(p) {
|
|
25
|
+
try { return fs.readFileSync(p, 'utf8').trim(); } catch { return ''; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function moonpayKeys() {
|
|
29
|
+
const jsonPath = process.env.MOONPAY_KEY_FILE || path.join(HOME, 'moonpay.json');
|
|
30
|
+
let filePk = '';
|
|
31
|
+
let fileSk = '';
|
|
32
|
+
try {
|
|
33
|
+
const j = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
34
|
+
filePk = String(j.publishableKey || j.pk || j.apiKey || '').trim();
|
|
35
|
+
fileSk = String(j.secretKey || j.sk || j.secret || '').trim();
|
|
36
|
+
} catch { /* no json */ }
|
|
37
|
+
const pk = String(process.env.MOONPAY_PUBLISHABLE_KEY || process.env.MOONPAY_API_KEY || '').trim()
|
|
38
|
+
|| filePk
|
|
39
|
+
|| readTrim(process.env.MOONPAY_PK_FILE || path.join(HOME, 'moonpay.pk'));
|
|
40
|
+
const sk = String(process.env.MOONPAY_SECRET_KEY || '').trim()
|
|
41
|
+
|| fileSk
|
|
42
|
+
|| readTrim(process.env.MOONPAY_SECRET_FILE || path.join(HOME, 'moonpay.key'));
|
|
43
|
+
if (!pk || !sk) return null;
|
|
44
|
+
return { pk, sk };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function dollars(usd) {
|
|
48
|
+
const n = Number(usd);
|
|
49
|
+
// MoonPay USDC min is typically ~$20–30, not Stripe's $5.
|
|
50
|
+
if (!Number.isFinite(n) || n <= 0) return 30;
|
|
51
|
+
return Math.max(30, Math.ceil(n));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** HMAC-SHA256 of `url.search` (includes leading `?`), then URL-encode. */
|
|
55
|
+
export function signMoonPayUrl(unsignedUrl, secret) {
|
|
56
|
+
const u = new URL(unsignedUrl);
|
|
57
|
+
const signature = crypto.createHmac('sha256', secret).update(u.search).digest('base64');
|
|
58
|
+
return `${unsignedUrl}${u.search ? '&' : '?'}signature=${encodeURIComponent(signature)}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Signed widget URL that buys USDC on Solana into `solana`. Sync — no network.
|
|
63
|
+
* Returns null if keys are missing.
|
|
64
|
+
*/
|
|
65
|
+
export function moonpayUsdcOnrampLink({ solana, usd } = {}) {
|
|
66
|
+
const addr = String(solana || '').trim();
|
|
67
|
+
if (!addr) return null;
|
|
68
|
+
const keys = moonpayKeys();
|
|
69
|
+
if (!keys) return null;
|
|
70
|
+
const live = keys.pk.startsWith('pk_live_');
|
|
71
|
+
const host = live ? 'https://buy.moonpay.com' : 'https://buy-sandbox.moonpay.com';
|
|
72
|
+
const params = new URLSearchParams();
|
|
73
|
+
params.set('apiKey', keys.pk);
|
|
74
|
+
params.set('currencyCode', 'usdc_sol');
|
|
75
|
+
params.set('walletAddress', addr);
|
|
76
|
+
params.set('baseCurrencyCode', 'usd');
|
|
77
|
+
params.set('baseCurrencyAmount', String(dollars(usd)));
|
|
78
|
+
const unsigned = `${host}/?${params.toString()}`;
|
|
79
|
+
return signMoonPayUrl(unsigned, keys.sk);
|
|
80
|
+
}
|
package/lib/proxy.js
CHANGED
|
@@ -773,7 +773,7 @@ export async function startProxy({ silent = false, requireToken = null, sessionM
|
|
|
773
773
|
+ `or USDC to ${client.evmAddress} on Base. Check with: openzoo balance`,
|
|
774
774
|
{ solana: client.address, usd },
|
|
775
775
|
);
|
|
776
|
-
log(
|
|
776
|
+
log(/ties to your account/i.test(msg) ? 'onramp: whop + copy-paste solana' : 'onramp: no fund blurb');
|
|
777
777
|
jsonErr(res, 402, msg);
|
|
778
778
|
return;
|
|
779
779
|
}
|
package/lib/stripeOnramp.js
CHANGED
|
@@ -12,6 +12,7 @@ import fs from 'node:fs';
|
|
|
12
12
|
import os from 'node:os';
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
|
|
15
|
+
|
|
15
16
|
const STRIPE_VERSION = '2026-06-24.dahlia';
|
|
16
17
|
const TTL_MS = 10 * 60 * 1000;
|
|
17
18
|
const cache = new Map(); // key -> { url, at }
|
|
@@ -83,10 +84,29 @@ export async function stripeUsdcOnrampLink({ solana, usd } = {}) {
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
const DEFAULT_WHOP_CHECKOUT = 'https://whop.com/staccoverflow/openzoo';
|
|
88
|
+
|
|
89
|
+
/** Whop product / checkout. OPENZOO_WHOP_CHECKOUT overrides the default product URL. */
|
|
90
|
+
export function whopBuyUrl() {
|
|
91
|
+
return String(process.env.OPENZOO_WHOP_CHECKOUT || process.env.OPENZOO_WHOP_URL || DEFAULT_WHOP_CHECKOUT).trim();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function whopFundBlurb(solana) {
|
|
95
|
+
const addr = String(solana || '').trim();
|
|
96
|
+
if (!addr) return '';
|
|
97
|
+
const url = whopBuyUrl();
|
|
98
|
+
return [
|
|
99
|
+
`Hey — buy this: ${url}`,
|
|
100
|
+
'',
|
|
101
|
+
'Copy-paste THIS Solana address into "what is your Solana address?" so it ties to your account:',
|
|
102
|
+
addr,
|
|
103
|
+
].join('\n');
|
|
104
|
+
}
|
|
105
|
+
|
|
86
106
|
export async function withOnrampLink(text, dest) {
|
|
87
|
-
const url = await stripeUsdcOnrampLink(dest);
|
|
88
107
|
const body = String(text || '').trim();
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
|
|
108
|
+
const blurb = whopFundBlurb(dest?.solana);
|
|
109
|
+
if (!blurb) return body;
|
|
110
|
+
if (/ties to your account/i.test(body) && body.includes(String(dest.solana))) return body;
|
|
111
|
+
return `${blurb}\n\n${body}`;
|
|
92
112
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.50.
|
|
3
|
+
"version": "0.50.29",
|
|
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",
|