openzoo 0.51.4 → 0.51.6

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.
Files changed (55) hide show
  1. package/README.md +12 -1
  2. package/bin/openzoo.js +21 -46
  3. package/lib/chatgpt-install.js +106 -0
  4. package/lib/chatgpt.js +108 -90
  5. package/lib/config.js +34 -0
  6. package/lib/modelcommand.js +443 -0
  7. package/lib/pay.js +60 -4
  8. package/lib/proxy.js +39 -0
  9. package/package.json +4 -7
  10. package/transmute/README.md +0 -385
  11. package/transmute/bin/openzoo-transmute.js +0 -7
  12. package/transmute/docs/OPENZOO_CLI_PATCH.md +0 -139
  13. package/transmute/docs/VERCEL_TO_SOLANA.md +0 -381
  14. package/transmute/lib/build.js +0 -381
  15. package/transmute/lib/cli.js +0 -293
  16. package/transmute/lib/compile/bytecode.js +0 -591
  17. package/transmute/lib/compile/hostids.js +0 -11
  18. package/transmute/lib/compile/index.js +0 -159
  19. package/transmute/lib/compile/ir.js +0 -1634
  20. package/transmute/lib/compile/parse.js +0 -753
  21. package/transmute/lib/compile/rust.js +0 -781
  22. package/transmute/lib/deploy.js +0 -357
  23. package/transmute/lib/eligibility.js +0 -211
  24. package/transmute/lib/gateway.js +0 -710
  25. package/transmute/lib/hub.js +0 -290
  26. package/transmute/lib/solana.js +0 -387
  27. package/transmute/lib/vercel.js +0 -296
  28. package/transmute/lib/wallet.js +0 -41
  29. package/transmute/lib/wire.js +0 -208
  30. package/transmute/prebuilt/zoo_vm.v0.so +0 -0
  31. package/transmute/runtime/zoo-host/Cargo.lock +0 -588
  32. package/transmute/runtime/zoo-host/Cargo.toml +0 -30
  33. package/transmute/runtime/zoo-host/examples/vmrun.rs +0 -43
  34. package/transmute/runtime/zoo-host/src/assets.rs +0 -214
  35. package/transmute/runtime/zoo-host/src/ctx.rs +0 -350
  36. package/transmute/runtime/zoo-host/src/fmt.rs +0 -188
  37. package/transmute/runtime/zoo-host/src/helpers.rs +0 -245
  38. package/transmute/runtime/zoo-host/src/json.rs +0 -263
  39. package/transmute/runtime/zoo-host/src/kv.rs +0 -206
  40. package/transmute/runtime/zoo-host/src/lib.rs +0 -137
  41. package/transmute/runtime/zoo-host/src/site.rs +0 -94
  42. package/transmute/runtime/zoo-host/src/val.rs +0 -1050
  43. package/transmute/runtime/zoo-host/src/vm.rs +0 -902
  44. package/transmute/runtime/zoo-host/src/wire.rs +0 -239
  45. package/transmute/runtime/zoo-vm/Cargo.lock +0 -596
  46. package/transmute/runtime/zoo-vm/Cargo.toml +0 -23
  47. package/transmute/runtime/zoo-vm/src/lib.rs +0 -92
  48. package/transmute/site/.env.production +0 -2
  49. package/transmute/site/api/echo.js +0 -10
  50. package/transmute/site/api/hello.js +0 -12
  51. package/transmute/site/api/hits.js +0 -13
  52. package/transmute/site/api/time.js +0 -5
  53. package/transmute/site/package.json +0 -6
  54. package/transmute/site/public/app.js +0 -16
  55. package/transmute/site/public/index.html +0 -96
@@ -1,290 +0,0 @@
1
- // The hosted explorer: one process, any number of transmuted sites.
2
- //
3
- // `openzoo-transmute serve` fronts ONE program on localhost. The hub fronts
4
- // EVERY program on a cluster from one public host, read-only:
5
- //
6
- // GET /s/<programId> pin that site (cookie) and go to its /
7
- // GET /s/<programId>/<path> one page of it, absolute
8
- // GET /<path> the pinned site's <path> (so a site's own
9
- // root-relative links — /app.js, /api/x — work)
10
- // GET /.hub the landing page (paste a program id)
11
- // GET /.hub/leave unpin
12
- // GET /.hub/sites.json what this hub has served lately
13
- //
14
- // Reads are free simulations, exactly like the local gateway. Writes need a
15
- // signer, and a public host must never sign with a shared wallet, so they
16
- // answer 402 with the same message the local gateway gives — the seam where
17
- // x402 pays per request later. Everything a site is comes from the chain:
18
- // the manifest at /.zoo/manifest.json, the assets, the program; the hub only
19
- // caches.
20
- import http from 'node:http';
21
- import { PublicKey } from '@solana/web3.js';
22
- import { makeState, normalizeManifest, handleRequest, safeResponseHeaders, PACKET_DATA_SIZE, BODY_LIMIT } from './gateway.js';
23
- import { connect, readManifest, getSiteInfo } from './solana.js';
24
- import { rpcUrl } from './wallet.js';
25
- import { MANIFEST_PATH } from './wire.js';
26
-
27
- export const HUB_PREFIX = '/.hub';
28
- export const SITE_PREFIX = '/s/';
29
- export const COOKIE = 'zoo_site';
30
- export const DEFAULT_HUB_PORT = 8080;
31
-
32
- const JSON_CT = 'application/json; charset=utf-8';
33
- const HTML_CT = 'text/html; charset=utf-8';
34
-
35
- function isProgramId(s) {
36
- if (typeof s !== 'string' || !/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(s)) return false;
37
- try { new PublicKey(s); return true; } catch { return false; }
38
- }
39
-
40
- function cookieOf(req) {
41
- const raw = req.headers?.cookie || '';
42
- for (const part of String(raw).split(';')) {
43
- const [k, ...v] = part.trim().split('=');
44
- if (k === COOKIE) { const id = decodeURIComponent(v.join('=')); return isProgramId(id) ? id : null; }
45
- }
46
- return null;
47
- }
48
-
49
- const setCookie = (id) => `${COOKIE}=${id}; Path=/; Max-Age=2592000; SameSite=Lax`;
50
- const clearCookie = () => `${COOKIE}=; Path=/; Max-Age=0; SameSite=Lax`;
51
-
52
- function json(status, body, headers = {}) {
53
- return { status, headers: { 'content-type': JSON_CT, ...headers }, body: Buffer.from(JSON.stringify(body, null, 2)) };
54
- }
55
- function html(status, body, headers = {}) {
56
- return { status, headers: { 'content-type': HTML_CT, ...headers }, body: Buffer.from(body) };
57
- }
58
- function redirect(location, headers = {}) {
59
- return { status: 302, headers: { location, 'content-type': 'text/plain; charset=utf-8', ...headers }, body: Buffer.from(`→ ${location}\n`) };
60
- }
61
- const escape = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
62
-
63
- /**
64
- * Hub state: the cluster connection plus an LRU of per-site gateway states.
65
- * `o.makeSite(programId)` can be injected for tests.
66
- */
67
- export function makeHub(o = {}) {
68
- const cluster = o.cluster || process.env.OPENZOO_CLUSTER || 'mainnet';
69
- const rpc = o.connection?.rpcEndpoint || rpcUrl(cluster);
70
- const hub = {
71
- cluster,
72
- rpc,
73
- connection: o.connection ?? connect(rpc),
74
- keypair: o.keypair ?? null,
75
- sites: new Map(), // programId -> { state, at, lastSeen, hits }
76
- maxSites: o.maxSites ?? 64,
77
- manifestTtlMs: o.manifestTtlMs ?? 60_000,
78
- log: o.log ?? (() => {}),
79
- makeSite: o.makeSite ?? null,
80
- startedAt: Date.now(),
81
- stats: { requests: 0, sitesLoaded: 0, writes: 0, writesRefused: 0, spentLamports: 0 },
82
- publicUrl: o.publicUrl || null,
83
- // The shared runtime program on this cluster: ids that are site accounts
84
- // under it are served as shared sites; anything else as a compiled program.
85
- runtime: o.runtime || process.env.OPENZOO_VM_PROGRAM || null,
86
- // write governor: per-IP token bucket + a daily lamport budget for the signer
87
- writesPerMin: o.writesPerMin ?? Number(process.env.OPENZOO_HUB_WRITES_PER_MIN || 3),
88
- budgetLamports: Math.round((o.writeBudgetSol ?? Number(process.env.OPENZOO_HUB_WRITE_BUDGET_SOL || 0.05)) * 1e9),
89
- ipHits: new Map(),
90
- dayStart: Date.now(),
91
- };
92
- return hub;
93
- }
94
-
95
- const READ_METHODS = ['GET', 'HEAD', 'OPTIONS'];
96
-
97
- /** Allow a signed write? Refills per minute per IP; budget resets daily. */
98
- export function allowWrite(hub, ip, now = Date.now()) {
99
- if (!hub.keypair) return { ok: false, reason: 'no signer' };
100
- if (now - hub.dayStart > 86_400_000) { hub.dayStart = now; hub.stats.spentLamports = 0; }
101
- if (hub.stats.spentLamports >= hub.budgetLamports) return { ok: false, reason: 'daily write budget spent' };
102
- const key = ip || 'unknown';
103
- const win = hub.ipHits.get(key) || { t: now, n: 0 };
104
- if (now - win.t > 60_000) { win.t = now; win.n = 0; }
105
- if (win.n >= hub.writesPerMin) return { ok: false, reason: `rate limit: ${hub.writesPerMin} writes/min` };
106
- win.n++; hub.ipHits.set(key, win);
107
- if (hub.ipHits.size > 10_000) hub.ipHits.clear();
108
- return { ok: true };
109
- }
110
-
111
- async function siteFor(hub, programId) {
112
- const hit = hub.sites.get(programId);
113
- if (hit) {
114
- hit.lastSeen = Date.now(); hit.hits++;
115
- hub.sites.delete(programId); hub.sites.set(programId, hit); // LRU order
116
- return hit;
117
- }
118
- let state;
119
- if (hub.makeSite) state = await hub.makeSite(programId);
120
- else {
121
- let shared = false;
122
- if (hub.runtime) {
123
- try { shared = (await getSiteInfo(hub.connection, hub.runtime, programId)).exists; } catch { /* fall through to program mode */ }
124
- }
125
- state = shared
126
- ? makeState({ programId: hub.runtime, site: programId, cluster: hub.cluster, connection: hub.connection, keypair: hub.keypair, log: hub.log, cacheTtlMs: hub.manifestTtlMs })
127
- : makeState({ programId, cluster: hub.cluster, connection: hub.connection, keypair: hub.keypair, log: hub.log, cacheTtlMs: hub.manifestTtlMs });
128
- try {
129
- const m = await readManifest(hub.connection, state.programId, state.site);
130
- if (m) { state.manifest = normalizeManifest(m); state.manifestAt = Date.now(); }
131
- else state.noManifest = true;
132
- } catch (e) { state.manifestError = String(e?.message || e); }
133
- }
134
- const entry = { state, at: Date.now(), lastSeen: Date.now(), hits: 1 };
135
- // An RPC failure is not a fact about the site: answer it, but do not cache it.
136
- if (state.manifestError) return entry;
137
- hub.sites.set(programId, entry);
138
- hub.stats.sitesLoaded++;
139
- while (hub.sites.size > hub.maxSites) hub.sites.delete(hub.sites.keys().next().value);
140
- return entry;
141
- }
142
-
143
- function siteSummary(id, entry) {
144
- const m = entry.state.manifest || {};
145
- return {
146
- programId: id,
147
- mode: entry.state.site ? 'shared' : 'program',
148
- name: m.name || null,
149
- framework: m.framework || null,
150
- routes: (m.routes || []).length,
151
- assets: (m.static || []).length,
152
- deployedAt: m.deployedAt || null,
153
- manifest: entry.state.noManifest ? 'missing' : entry.state.manifestError ? `error: ${entry.state.manifestError}` : 'ok',
154
- hits: entry.hits,
155
- lastSeen: new Date(entry.lastSeen).toISOString(),
156
- };
157
- }
158
-
159
- export function landingHtml(hub, pinned = null) {
160
- const rows = [...hub.sites.entries()].reverse().map(([id, e]) => {
161
- const s = siteSummary(id, e);
162
- return `<tr><td><a href="/s/${escape(id)}">${escape(s.name || id.slice(0, 8) + '…')}</a></td><td><code>${escape(id)}</code></td><td>${s.routes}</td><td>${s.assets}</td><td>${escape(s.manifest)}</td></tr>`;
163
- }).join('\n');
164
- return `<!doctype html>
165
- <meta charset="utf-8">
166
- <meta name="viewport" content="width=device-width,initial-scale=1">
167
- <title>openzoo sites · ${escape(hub.cluster)}</title>
168
- <style>
169
- :root{color-scheme:light dark;font-family:ui-sans-serif,system-ui,sans-serif}
170
- body{max-width:56rem;margin:2rem auto;padding:0 1rem;line-height:1.45}
171
- h1{font-size:1.5rem;margin:.2rem 0} .muted{opacity:.7} code{font-size:.9em}
172
- form{display:flex;gap:.5rem;margin:1rem 0} input{flex:1;padding:.55rem .7rem;font:inherit;border:1px solid #8884;border-radius:.4rem;background:transparent;color:inherit}
173
- button{padding:.55rem 1rem;font:inherit;border:1px solid #8886;border-radius:.4rem;background:#3b82f6;color:#fff;cursor:pointer}
174
- table{border-collapse:collapse;width:100%;font-size:.92rem} td,th{padding:.35rem .5rem;border-bottom:1px solid #8883;text-align:left;vertical-align:top}
175
- pre{background:#8881;padding:.7rem;border-radius:.4rem;overflow:auto;font-size:.85rem}
176
- .pin{padding:.6rem .8rem;border:1px solid #3b82f6;border-radius:.4rem;margin:1rem 0}
177
- </style>
178
- <h1>openzoo sites</h1>
179
- <p class="muted">Vercel apps transmuted onto Solana <b>${escape(hub.cluster)}</b>: the frontend lives in asset accounts, the <code>/api/*</code> Lambdas run as a Pinocchio program. This host reads them straight off the chain. Reads are free simulations; writes need a signer, so they answer 402 here — run <code>npx openzoo serve &lt;programId&gt;</code> locally to write with your own wallet.</p>
180
- ${pinned ? `<div class="pin">Pinned site: <code>${escape(pinned)}</code> — <a href="/">open</a> · <a href="/.zoo/">explorer</a> · <a href="/.hub/leave">unpin</a></div>` : ''}
181
- <form action="/.hub/go" method="get"><input name="program" placeholder="program id (base58)" autocomplete="off" required pattern="[1-9A-HJ-NP-Za-km-z]{32,44}"><button type="submit">open</button></form>
182
- <h2 style="font-size:1.1rem">recently served</h2>
183
- ${rows ? `<table><tr><th>site</th><th>program</th><th>routes</th><th>assets</th><th>manifest</th></tr>${rows}</table>` : '<p class="muted">nothing yet — paste a program id above.</p>'}
184
- <h2 style="font-size:1.1rem">put your app here</h2>
185
- <pre>npx openzoo inspect . # your Next.js / Vite app, in Vercel terms + eligibility
186
- npx openzoo build . # → Pinocchio Rust program + asset plan (cargo build-sbf)
187
- npx openzoo deploy . --cluster ${escape(hub.cluster)} --yes # rent is quoted first; paid by ~/.openzoo/wallet.json
188
- # then: ${escape(hub.publicUrl || 'https://<this host>')}/s/&lt;programId&gt;</pre>
189
- <p class="muted">Each site's <code>/.zoo/manifest.json</code> and <code>/.zoo/status</code> are public; <code>/.hub/sites.json</code> lists what this hub has served.</p>
190
- `;
191
- }
192
-
193
- async function readBody(req, max) {
194
- return new Promise((resolve) => {
195
- const chunks = []; let length = 0, truncated = false;
196
- req.on('data', (c) => { length += c.length; if (length > max) { truncated = true; req.destroy?.(); return; } chunks.push(c); });
197
- req.on('end', () => resolve({ body: Buffer.concat(chunks), truncated, length }));
198
- req.on('error', () => resolve({ body: Buffer.concat(chunks), truncated, length }));
199
- req.on('close', () => resolve({ body: Buffer.concat(chunks), truncated, length }));
200
- });
201
- }
202
-
203
- /** Route one request. `req` = { method, url, headers, body, remoteAddress }. */
204
- export async function handleHub(hub, req) {
205
- hub.stats.requests++;
206
- const method = String(req.method || 'GET').toUpperCase();
207
- const url = new URL(req.url || '/', 'http://hub.local');
208
- const pathname = url.pathname;
209
- const pinned = cookieOf(req);
210
-
211
- // ---- hub pages
212
- if (pathname === HUB_PREFIX || pathname === `${HUB_PREFIX}/`) return html(200, landingHtml(hub, pinned));
213
- if (pathname === `${HUB_PREFIX}/go`) {
214
- const id = (url.searchParams.get('program') || '').trim();
215
- if (!isProgramId(id)) return json(400, { error: 'not a program id', program: id });
216
- return redirect(`${SITE_PREFIX}${id}`);
217
- }
218
- if (pathname === `${HUB_PREFIX}/leave`) return redirect(HUB_PREFIX, { 'set-cookie': clearCookie() });
219
- if (pathname === `${HUB_PREFIX}/sites.json`) {
220
- return json(200, { cluster: hub.cluster, rpc: hub.rpc.replace(/\?.*$/, ''), sites: [...hub.sites.entries()].map(([id, e]) => siteSummary(id, e)), uptimeS: Math.round((Date.now() - hub.startedAt) / 1000), stats: hub.stats });
221
- }
222
- if (pathname === `${HUB_PREFIX}/health`) return json(200, { ok: true, sites: hub.sites.size, cluster: hub.cluster });
223
-
224
- // ---- /s/<programId>[/<path>]
225
- let target = null, rest = null, pin = false;
226
- if (pathname.startsWith(SITE_PREFIX)) {
227
- const after = pathname.slice(SITE_PREFIX.length);
228
- const slash = after.indexOf('/');
229
- const id = slash < 0 ? after : after.slice(0, slash);
230
- if (!isProgramId(id)) return json(400, { error: 'not a program id', program: id });
231
- target = id; pin = true;
232
- rest = slash < 0 ? null : after.slice(slash) || '/';
233
- if (rest === null) return redirect('/', { 'set-cookie': setCookie(id) }); // pin, then the site's own /
234
- } else if (pinned) {
235
- target = pinned; rest = pathname;
236
- } else {
237
- if (pathname === '/') return html(200, landingHtml(hub, null));
238
- return json(404, { error: 'no site pinned', hint: `open /s/<programId> first, or /.hub` });
239
- }
240
-
241
- const entry = await siteFor(hub, target);
242
- const state = entry.state;
243
- if (state.manifestError) {
244
- return json(502, { error: 'rpc error', message: state.manifestError, cluster: hub.cluster, rpc: hub.rpc.replace(/\?.*$/, ''), program: target }, pin ? { 'set-cookie': setCookie(target) } : {});
245
- }
246
- if (state.noManifest && (rest === '/' || rest === MANIFEST_PATH)) {
247
- return json(404, { error: 'no site at this program id', program: target, hint: `no manifest at ${MANIFEST_PATH}; is this an openzoo-transmute deployment on ${hub.cluster}?` }, pin ? { 'set-cookie': setCookie(target) } : {});
248
- }
249
- if (!READ_METHODS.includes(method) && hub.keypair) {
250
- const gate = allowWrite(hub, req.remoteAddress || req.headers?.['fly-client-ip'] || req.headers?.['x-forwarded-for']);
251
- if (!gate.ok) { hub.stats.writesRefused++; return json(429, { error: 'write refused', reason: gate.reason, hint: 'run `npx openzoo serve <programId>` locally to write with your own wallet' }, { 'x-zoo-site': target }); }
252
- }
253
- const before = hub.keypair && !READ_METHODS.includes(method) ? await hub.connection.getBalance(hub.keypair.publicKey).catch(() => null) : null;
254
- const r = await handleRequest(state, { ...req, method, url: rest + (url.search || '') });
255
- if (before != null) {
256
- hub.stats.writes++;
257
- const after = await hub.connection.getBalance(hub.keypair.publicKey).catch(() => before);
258
- hub.stats.spentLamports += Math.max(0, before - after);
259
- }
260
- const headers = { ...(r.headers || {}), 'x-zoo-site': target };
261
- if (pin) headers['set-cookie'] = setCookie(target);
262
- return { ...r, headers };
263
- }
264
-
265
- export async function startHub({ cluster, port = DEFAULT_HUB_PORT, host = '0.0.0.0', keypair = null, connection, log = console.log, quiet = false, publicUrl, maxSites } = {}) {
266
- const hub = makeHub({ cluster, connection, keypair, log: quiet ? () => {} : log, publicUrl, maxSites });
267
- const server = http.createServer(async (req, res) => {
268
- const t0 = Date.now();
269
- try {
270
- const { body, truncated, length } = await readBody(req, PACKET_DATA_SIZE);
271
- const r = truncated
272
- ? json(413, { error: 'payload too large', limit: BODY_LIMIT, received: length })
273
- : await handleHub(hub, { method: req.method, url: req.url, headers: req.headers, body, remoteAddress: req.socket?.remoteAddress });
274
- const out = Buffer.isBuffer(r.body) ? r.body : Buffer.from(String(r.body ?? ''));
275
- const headers = safeResponseHeaders(r.headers);
276
- headers['content-length'] = String(out.length);
277
- res.writeHead(r.status, headers);
278
- res.end(req.method === 'HEAD' ? undefined : out);
279
- if (!quiet) log(`${req.method} ${req.url} → ${r.status} ${out.length}B ${Date.now() - t0}ms${headers['x-zoo-site'] ? ' site=' + headers['x-zoo-site'].slice(0, 8) : ''}`);
280
- } catch (e) {
281
- if (!quiet) log(`${req.method} ${req.url} → 500 ${e?.stack || e}`);
282
- if (!res.headersSent) res.writeHead(500, { 'content-type': JSON_CT });
283
- res.end(JSON.stringify({ error: 'hub error', message: String(e?.message || e) }));
284
- }
285
- });
286
- await new Promise((resolve, reject) => { server.once('error', reject); server.listen(port, host, () => { server.off('error', reject); resolve(); }); });
287
- const actualPort = server.address().port;
288
- const url = `http://${host === '0.0.0.0' ? '127.0.0.1' : host}:${actualPort}`;
289
- return { hub, server, port: actualPort, url, close: () => new Promise((r) => server.close(() => r())) };
290
- }