paralith-mcp 0.2.0 → 0.3.1

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/dist/server.mjs CHANGED
@@ -1,20 +1,21 @@
1
1
  #!/usr/bin/env node
2
-
3
- // src/server.mjs
4
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
-
6
- // src/runtime.mjs
7
- import fs2 from "node:fs";
8
- import path2 from "node:path";
9
- import { fileURLToPath, pathToFileURL } from "node:url";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res, err) => function __init() {
5
+ if (err) throw err[0];
6
+ try {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ } catch (e) {
9
+ throw err = [e], e;
10
+ }
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
10
16
 
11
17
  // ../../backend/native/ans104.mjs
12
18
  import crypto from "node:crypto";
13
- var enc = new TextEncoder();
14
- var sha384 = (b) => crypto.createHash("sha384").update(b).digest();
15
- var sha256 = (b) => crypto.createHash("sha256").update(b).digest();
16
- var b64url = (b) => Buffer.from(b).toString("base64url");
17
- var b64urlDec = (s) => Buffer.from(s, "base64url");
18
19
  function deepHash(value) {
19
20
  if (Array.isArray(value)) {
20
21
  const tag2 = enc.encode(`list${value.length}`);
@@ -102,7 +103,6 @@ async function signDataItem(jwk, item) {
102
103
  data
103
104
  ]);
104
105
  }
105
- var jwkToAddress = (jwk) => b64url(sha256(b64urlDec(jwk.n)));
106
106
  function walletShim(jwk) {
107
107
  const address = jwkToAddress(jwk);
108
108
  return {
@@ -122,6 +122,18 @@ function walletShim(jwk) {
122
122
  }
123
123
  };
124
124
  }
125
+ var enc, sha384, sha256, b64url, b64urlDec, jwkToAddress;
126
+ var init_ans104 = __esm({
127
+ "../../backend/native/ans104.mjs"() {
128
+ "use strict";
129
+ enc = new TextEncoder();
130
+ sha384 = (b) => crypto.createHash("sha384").update(b).digest();
131
+ sha256 = (b) => crypto.createHash("sha256").update(b).digest();
132
+ b64url = (b) => Buffer.from(b).toString("base64url");
133
+ b64urlDec = (s) => Buffer.from(s, "base64url");
134
+ jwkToAddress = (jwk) => b64url(sha256(b64urlDec(jwk.n)));
135
+ }
136
+ });
125
137
 
126
138
  // src/keys.mjs
127
139
  import crypto2 from "node:crypto";
@@ -134,7 +146,6 @@ import { mnemonicToAccount } from "viem/accounts";
134
146
  import { Keypair } from "@solana/web3.js";
135
147
  import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
136
148
  import { getKeyFromMnemonic } from "arweave-mnemonic-keys";
137
- var CACHE_DIR = process.env.PARALITH_MCP_HOME || path.join(os.homedir(), ".paralith", "mcp");
138
149
  function readPhrase(env = process.env) {
139
150
  const raw = env.PARALITH_MNEMONIC ?? (env.PARALITH_MNEMONIC_FILE ? fs.readFileSync(env.PARALITH_MNEMONIC_FILE, "utf8") : null);
140
151
  if (!raw) {
@@ -161,60 +172,260 @@ function slip10(seed, derivation) {
161
172
  async function aoJwk(phrase, env) {
162
173
  if (env.PARALITH_AO_JWK) return JSON.parse(fs.readFileSync(env.PARALITH_AO_JWK, "utf8"));
163
174
  const id = crypto2.createHash("sha256").update(phrase).digest("hex").slice(0, 24);
164
- const file = path.join(CACHE_DIR, `ao-${id}.json`);
175
+ const file = path.join(HOME, `ao-${id}.json`);
165
176
  if (fs.existsSync(file)) return JSON.parse(fs.readFileSync(file, "utf8"));
166
177
  const jwk = await getKeyFromMnemonic(phrase);
167
- fs.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
178
+ fs.mkdirSync(HOME, { recursive: true, mode: 448 });
168
179
  fs.writeFileSync(file, JSON.stringify(jwk), { mode: 384 });
169
180
  return jwk;
170
181
  }
171
- async function deriveWallets(phrase, env = process.env) {
182
+ function chainWallets(phrase) {
172
183
  const evm = mnemonicToAccount(phrase, { path: "m/44'/60'/0'/0/0" });
173
184
  const solana = Keypair.fromSeed(slip10(mnemonicToSeedSync(phrase), "m/44'/501'/0'/0'"));
174
185
  const sui = Ed25519Keypair.deriveKeypair(phrase, "m/44'/784'/0'/0'/0'");
175
- const jwk = await aoJwk(phrase, env);
176
186
  return {
177
- ao: { jwk, address: jwkToAddress(jwk) },
178
187
  base: { account: evm, address: evm.address },
179
188
  solana: { keypair: solana, address: solana.publicKey.toBase58() },
180
189
  sui: { keypair: sui, address: sui.getPublicKey().toSuiAddress() }
181
190
  };
182
191
  }
183
- var publicAddresses = (w) => ({
184
- ao: w.ao.address,
185
- base: w.base.address,
186
- solana: w.solana.address,
187
- sui: w.sui.address
192
+ async function deriveWallets(phrase, env = process.env) {
193
+ const jwk = await aoJwk(phrase, env);
194
+ return { ao: { jwk, address: jwkToAddress(jwk) }, ...chainWallets(phrase) };
195
+ }
196
+ var HOME, publicAddresses;
197
+ var init_keys = __esm({
198
+ "src/keys.mjs"() {
199
+ "use strict";
200
+ init_ans104();
201
+ HOME = process.env.PARALITH_MCP_HOME || path.join(os.homedir(), ".paralith", "mcp");
202
+ publicAddresses = (w) => ({
203
+ ao: w.ao.address,
204
+ base: w.base.address,
205
+ solana: w.solana.address,
206
+ sui: w.sui.address
207
+ });
208
+ }
209
+ });
210
+
211
+ // src/agents.mjs
212
+ import fs2 from "node:fs";
213
+ import path2 from "node:path";
214
+ function readAgents() {
215
+ try {
216
+ return JSON.parse(fs2.readFileSync(FILE, "utf8"));
217
+ } catch {
218
+ return {};
219
+ }
220
+ }
221
+ function saveAgent(name, entry) {
222
+ const all = readAgents();
223
+ all[name] = { ...all[name], ...entry, addresses: { ...all[name]?.addresses, ...entry.addresses } };
224
+ fs2.mkdirSync(HOME, { recursive: true, mode: 448 });
225
+ fs2.writeFileSync(FILE, `${JSON.stringify(all, null, 2)}
226
+ `);
227
+ return all[name];
228
+ }
229
+ var FILE;
230
+ var init_agents = __esm({
231
+ "src/agents.mjs"() {
232
+ "use strict";
233
+ init_keys();
234
+ FILE = path2.join(HOME, "agents.json");
235
+ }
236
+ });
237
+
238
+ // src/setup.mjs
239
+ var setup_exports = {};
240
+ __export(setup_exports, {
241
+ setup: () => setup,
242
+ upsertToml: () => upsertToml
243
+ });
244
+ import fs3 from "node:fs";
245
+ import os2 from "node:os";
246
+ import path3 from "node:path";
247
+ import { spawnSync } from "node:child_process";
248
+ import { generateMnemonic } from "@scure/bip39";
249
+ import { wordlist as wordlist2 } from "@scure/bip39/wordlists/english.js";
250
+ function run(cli, args) {
251
+ const r = WIN ? spawnSync([cli, ...args].map((a) => /[\s"&|<>^]/.test(a) ? `"${a}"` : a).join(" "), { shell: true, encoding: "utf8" }) : spawnSync(cli, args, { encoding: "utf8" });
252
+ return { ok: r.status === 0, out: `${r.stdout ?? ""}${r.stderr ?? ""}`.trim() };
253
+ }
254
+ function desktopConfig() {
255
+ const dir = WIN ? path3.join(process.env.APPDATA ?? path3.join(os2.homedir(), "AppData", "Roaming"), "Claude") : process.platform === "darwin" ? path3.join(os2.homedir(), "Library", "Application Support", "Claude") : path3.join(os2.homedir(), ".config", "Claude");
256
+ return path3.join(dir, "claude_desktop_config.json");
257
+ }
258
+ function upsertToml(text, env) {
259
+ const kept = [];
260
+ let skipping = false;
261
+ for (const line of text.split(/\r?\n/)) {
262
+ const header = /^\s*\[/.test(line);
263
+ if (header) skipping = /^\s*\[\s*mcp_servers\.(paralith|"paralith")\s*[\].]/.test(line);
264
+ if (!skipping) kept.push(line);
265
+ }
266
+ const s = JSON.stringify;
267
+ const block = [
268
+ "[mcp_servers.paralith]",
269
+ `command = ${s(LAUNCH.command)}`,
270
+ `args = [${LAUNCH.args.map(s).join(", ")}]`,
271
+ "startup_timeout_sec = 120",
272
+ // first run downloads the package
273
+ "tool_timeout_sec = 1200",
274
+ // a bridge deposit waits for the relayer
275
+ "",
276
+ "[mcp_servers.paralith.env]",
277
+ ...Object.entries(env).map(([k, v]) => `${k} = ${s(v)}`)
278
+ ];
279
+ const body = kept.join("\n").replace(/\s+$/, "");
280
+ return `${body ? `${body}
281
+
282
+ ` : ""}${block.join("\n")}
283
+ `;
284
+ }
285
+ function writeToml(file, env) {
286
+ fs3.mkdirSync(path3.dirname(file), { recursive: true });
287
+ fs3.writeFileSync(file, upsertToml(fs3.existsSync(file) ? fs3.readFileSync(file, "utf8") : "", env));
288
+ }
289
+ function phraseFile(agent) {
290
+ const file = path3.join(HOME, "agents", `${agent}.txt`);
291
+ if (!fs3.existsSync(file)) {
292
+ fs3.mkdirSync(path3.dirname(file), { recursive: true, mode: 448 });
293
+ fs3.writeFileSync(file, `${generateMnemonic(wordlist2, 256)}
294
+ `, { mode: 384 });
295
+ }
296
+ return file;
297
+ }
298
+ async function setup(argv) {
299
+ const only = argv.find((a) => a.startsWith("--only"))?.split("=")[1]?.split(",") ?? (argv.includes("--only") ? argv[argv.indexOf("--only") + 1]?.split(",") : null);
300
+ const unknown = (only ?? []).filter((c) => !CLIENTS[c]);
301
+ if (unknown.length) throw new Error(`Unknown app: ${unknown.join(", ")}. Choose from ${Object.keys(CLIENTS).join(", ")}.`);
302
+ const done = [];
303
+ for (const [id, client] of Object.entries(CLIENTS)) {
304
+ if (only ? !only.includes(id) : !client.found()) continue;
305
+ const file = phraseFile(client.wallet);
306
+ const chain = chainWallets(readPhrase({ PARALITH_MNEMONIC_FILE: file }));
307
+ saveAgent(client.wallet, {
308
+ phraseFile: slash(file),
309
+ addresses: { base: chain.base.address, solana: chain.solana.address, sui: chain.sui.address }
310
+ });
311
+ try {
312
+ client.connect({ PARALITH_MNEMONIC_FILE: slash(file), PARALITH_AGENT: client.wallet });
313
+ done.push(client.label);
314
+ console.log(`\u2713 ${client.label} connected (wallet: ${client.wallet})`);
315
+ } catch (error) {
316
+ console.log(`\u2717 ${client.label}: ${error.message.split("\n")[0]}`);
317
+ }
318
+ }
319
+ if (!done.length) {
320
+ console.log("No agent app found. Install Claude Code, Claude Desktop, Codex or Grok, or name one with --only.");
321
+ return;
322
+ }
323
+ const agents = readAgents();
324
+ console.log("\nFund these to bridge (gas only; playing and trading need nothing):");
325
+ for (const name of new Set(Object.values(CLIENTS).filter((c) => done.includes(c.label)).map((c) => c.wallet))) {
326
+ const a = agents[name].addresses;
327
+ console.log(`
328
+ ${name}
329
+ Base (ETH) ${a.base}
330
+ Solana (SOL) ${a.solana}
331
+ Sui (SUI) ${a.sui}`);
332
+ }
333
+ console.log(`
334
+ Phrases: ${slash(path3.join(HOME, "agents"))} (back them up; anything funded is spendable by the agent).`);
335
+ console.log(`Restart ${done.join(", ")}, then say: "Play Rune Realm and show me." Ask any of them "how are my agents doing?"`);
336
+ }
337
+ var WIN, NPX, LAUNCH, slash, installed, CLIENTS;
338
+ var init_setup = __esm({
339
+ "src/setup.mjs"() {
340
+ "use strict";
341
+ init_keys();
342
+ init_agents();
343
+ WIN = process.platform === "win32";
344
+ NPX = ["npx", "-y", "paralith-mcp@latest"];
345
+ LAUNCH = WIN ? { command: "cmd", args: ["/c", ...NPX] } : { command: NPX[0], args: NPX.slice(1) };
346
+ slash = (p) => p.replace(/\\/g, "/");
347
+ installed = (cli) => run(cli, ["--version"]).ok;
348
+ CLIENTS = {
349
+ claude: {
350
+ label: "Claude Code",
351
+ wallet: "claude",
352
+ found: () => installed("claude"),
353
+ connect(env) {
354
+ run("claude", ["mcp", "remove", "paralith", "--scope", "user"]);
355
+ const r = run("claude", [
356
+ "mcp",
357
+ "add",
358
+ "paralith",
359
+ "--scope",
360
+ "user",
361
+ ...Object.entries(env).flatMap(([k, v]) => ["-e", `${k}=${v}`]),
362
+ "--",
363
+ LAUNCH.command,
364
+ ...LAUNCH.args
365
+ ]);
366
+ if (!r.ok) throw new Error(r.out);
367
+ }
368
+ },
369
+ "claude-desktop": {
370
+ label: "Claude Desktop",
371
+ wallet: "claude",
372
+ found: () => fs3.existsSync(path3.dirname(desktopConfig())),
373
+ connect(env) {
374
+ const file = desktopConfig();
375
+ const config = fs3.existsSync(file) ? JSON.parse(fs3.readFileSync(file, "utf8")) : {};
376
+ config.mcpServers = { ...config.mcpServers, paralith: { ...LAUNCH, env } };
377
+ fs3.writeFileSync(file, `${JSON.stringify(config, null, 2)}
378
+ `);
379
+ }
380
+ },
381
+ codex: {
382
+ label: "Codex",
383
+ wallet: "codex",
384
+ found: () => installed("codex") || fs3.existsSync(path3.join(os2.homedir(), ".codex")),
385
+ connect: (env) => writeToml(path3.join(os2.homedir(), ".codex", "config.toml"), env)
386
+ },
387
+ grok: {
388
+ label: "Grok",
389
+ wallet: "grok",
390
+ found: () => installed("grok") || fs3.existsSync(path3.join(os2.homedir(), ".grok")),
391
+ connect: (env) => writeToml(path3.join(os2.homedir(), ".grok", "config.toml"), env)
392
+ }
393
+ };
394
+ }
188
395
  });
189
396
 
190
397
  // src/runtime.mjs
191
- var HERE = path2.dirname(fileURLToPath(import.meta.url));
192
- var DIST = fs2.existsSync(path2.join(HERE, "graph.json")) ? HERE : path2.join(HERE, "..", "dist");
193
- var graph = JSON.parse(fs2.readFileSync(path2.join(DIST, "graph.json"), "utf8"));
194
- var NODE = graph.node.replace(/\/$/, "");
195
- var chains = JSON.parse(fs2.readFileSync(path2.join(DIST, "chains.json"), "utf8"));
196
- var PROCESS_ALIASES = {
197
- game: graph.processes.game,
198
- rune: graph.processes.rune,
199
- pusd: graph.processes.pusd,
200
- ...graph.processes.dao ? { dao: graph.processes.dao } : {},
201
- "game-venue": graph.processes.internalVenue,
202
- "token-venue": graph.processes.externalVenue,
203
- ...Object.fromEntries(Object.entries(graph.processes.elements ?? {}).map(([k, v]) => [`${k}-token`, v]))
204
- };
205
- var ID = /^[A-Za-z0-9_-]{43}$/;
398
+ var runtime_exports = {};
399
+ __export(runtime_exports, {
400
+ ID: () => ID,
401
+ NODE: () => NODE,
402
+ PROCESS_ALIASES: () => PROCESS_ALIASES,
403
+ READ_ONLY: () => READ_ONLY,
404
+ chains: () => chains,
405
+ formatAtoms: () => formatAtoms,
406
+ graph: () => graph,
407
+ parseAmount: () => parseAmount,
408
+ processId: () => processId,
409
+ readAtoms: () => readAtoms,
410
+ readKey: () => readKey,
411
+ runtime: () => runtime,
412
+ sleep: () => sleep,
413
+ until: () => until
414
+ });
415
+ import fs4 from "node:fs";
416
+ import path4 from "node:path";
417
+ import { fileURLToPath, pathToFileURL } from "node:url";
206
418
  function processId(nameOrId) {
207
419
  if (ID.test(nameOrId)) return nameOrId;
208
420
  const id = PROCESS_ALIASES[nameOrId];
209
421
  if (!id) throw new Error(`Unknown process "${nameOrId}". Use a 43-character id or one of: ${Object.keys(PROCESS_ALIASES).join(", ")}`);
210
422
  return id;
211
423
  }
212
- var READ_ONLY = !process.env.PARALITH_MNEMONIC && !process.env.PARALITH_MNEMONIC_FILE;
213
- var loaded = null;
214
424
  function runtime() {
215
425
  loaded ??= (async () => {
216
426
  const wallets = READ_ONLY ? null : await deriveWallets(readPhrase());
217
- const api = await import(pathToFileURL(path2.join(DIST, "client.mjs")).href);
427
+ if (wallets && process.env.PARALITH_AGENT) saveAgent(process.env.PARALITH_AGENT, { addresses: publicAddresses(wallets) });
428
+ const api = await import(pathToFileURL(path4.join(DIST, "client.mjs")).href);
218
429
  if (wallets) api.swarmWalletSlot.wallet = walletShim(wallets.ao.jwk);
219
430
  return { api, wallets };
220
431
  })();
@@ -223,7 +434,6 @@ function runtime() {
223
434
  });
224
435
  return loaded;
225
436
  }
226
- var HTML = /^\s*(?:<!doctype\s+html|<html|<head|<body)/i;
227
437
  async function readKey(process2, key, node = NODE) {
228
438
  const res = await fetch(
229
439
  `${node}/${processId(process2)}~process@1.0/now/${key}`,
@@ -263,7 +473,6 @@ function parseAmount(text, decimals = 6) {
263
473
  if (atoms <= 0n) throw new Error("Amount must be positive");
264
474
  return atoms;
265
475
  }
266
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
267
476
  async function until(what, read, done, timeoutMs = 6e5, everyMs = 3e3) {
268
477
  const deadline = Date.now() + timeoutMs;
269
478
  for (; ; ) {
@@ -273,19 +482,58 @@ async function until(what, read, done, timeoutMs = 6e5, everyMs = 3e3) {
273
482
  await sleep(everyMs);
274
483
  }
275
484
  }
276
-
277
- // src/build.mjs
278
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
279
- import { z as z8 } from "zod";
485
+ var HERE, DIST, graph, NODE, chains, PROCESS_ALIASES, ID, READ_ONLY, loaded, HTML, sleep;
486
+ var init_runtime = __esm({
487
+ "src/runtime.mjs"() {
488
+ "use strict";
489
+ init_ans104();
490
+ init_keys();
491
+ init_agents();
492
+ HERE = path4.dirname(fileURLToPath(import.meta.url));
493
+ DIST = fs4.existsSync(path4.join(HERE, "graph.json")) ? HERE : path4.join(HERE, "..", "dist");
494
+ graph = JSON.parse(fs4.readFileSync(path4.join(DIST, "graph.json"), "utf8"));
495
+ NODE = graph.node.replace(/\/$/, "");
496
+ chains = JSON.parse(fs4.readFileSync(path4.join(DIST, "chains.json"), "utf8"));
497
+ PROCESS_ALIASES = {
498
+ game: graph.processes.game,
499
+ rune: graph.processes.rune,
500
+ pusd: graph.processes.pusd,
501
+ ...graph.processes.dao ? { dao: graph.processes.dao } : {},
502
+ "game-venue": graph.processes.internalVenue,
503
+ "token-venue": graph.processes.externalVenue,
504
+ ...Object.fromEntries(Object.entries(graph.processes.elements ?? {}).map(([k, v]) => [`${k}-token`, v]))
505
+ };
506
+ ID = /^[A-Za-z0-9_-]{43}$/;
507
+ READ_ONLY = !process.env.PARALITH_MNEMONIC && !process.env.PARALITH_MNEMONIC_FILE;
508
+ loaded = null;
509
+ HTML = /^\s*(?:<!doctype\s+html|<html|<head|<body)/i;
510
+ sleep = (ms) => new Promise((r) => setTimeout(r, ms));
511
+ }
512
+ });
280
513
 
281
514
  // src/tool.mjs
282
515
  import { z } from "zod";
283
- function tool(server, name, description, inputSchema, run, annotations = {}, view = null) {
516
+ async function migrationRefusal(read = readKey, now = Date.now()) {
517
+ if (!migrationSeen || now - migrationSeen.at > 15e3) {
518
+ migrationSeen = { at: now, value: await read("game", "migration").catch(() => null) };
519
+ }
520
+ const m = migrationSeen.value;
521
+ if (!m || typeof m !== "object" || m.phase === "live") return null;
522
+ if (m.phase === "retired") {
523
+ return `Paralith has moved to new processes (the game is now ${m.successor}). Nothing was sent. Update paralith-mcp to its latest version to use them.`;
524
+ }
525
+ return "A migration is in progress: Paralith is paused while every balance moves to new processes. Nothing was sent, and nothing is lost. Reads still work; try writes again later.";
526
+ }
527
+ function tool(server, name, description, inputSchema, run2, annotations = {}, view = null) {
284
528
  if (READ_ONLY && annotations !== READ) return;
285
529
  const schema = view ? { ...inputSchema, full: z.boolean().optional().describe("Return the raw record instead of the compact view.") } : inputSchema;
286
530
  server.registerTool(name, { description, inputSchema: schema, annotations }, async (args) => {
287
531
  try {
288
- const raw = await run(args ?? {});
532
+ if (annotations === WRITE) {
533
+ const refusal = await migrationRefusal();
534
+ if (refusal) return { isError: true, content: [{ type: "text", text: refusal }] };
535
+ }
536
+ const raw = await run2(args ?? {});
289
537
  const value = view && !args?.full ? await view(raw) : raw;
290
538
  const text = typeof value === "string" ? value : JSON.stringify(value, bigints, 2);
291
539
  return { content: [{ type: "text", text: text ?? "null" }] };
@@ -294,7 +542,6 @@ function tool(server, name, description, inputSchema, run, annotations = {}, vie
294
542
  }
295
543
  });
296
544
  }
297
- var bigints = (_k, v) => typeof v === "bigint" ? v.toString() : v;
298
545
  function explain(error) {
299
546
  const name = error?.name ?? "";
300
547
  const message = error?.message ?? String(error);
@@ -305,16 +552,20 @@ The message WAS accepted (slot ${error?.slot ?? "?"}); its result could not be c
305
552
  }
306
553
  return message;
307
554
  }
308
- var READ = { readOnlyHint: true, openWorldHint: true };
309
- var OWN = { readOnlyHint: true, openWorldHint: true };
310
- var WRITE = { readOnlyHint: false, destructiveHint: false, openWorldHint: true };
311
-
312
- // src/game.mjs
313
- import { z as z2 } from "zod";
555
+ var migrationSeen, bigints, READ, OWN, WRITE;
556
+ var init_tool = __esm({
557
+ "src/tool.mjs"() {
558
+ "use strict";
559
+ init_runtime();
560
+ migrationSeen = null;
561
+ bigints = (_k, v) => typeof v === "bigint" ? v.toString() : v;
562
+ READ = { readOnlyHint: true, openWorldHint: true };
563
+ OWN = { readOnlyHint: true, openWorldHint: true };
564
+ WRITE = { readOnlyHint: false, destructiveHint: false, openWorldHint: true };
565
+ }
566
+ });
314
567
 
315
568
  // src/views.mjs
316
- var iso = (ms) => ms ? new Date(Number(ms)).toISOString() : void 0;
317
- var uses = (moves) => Object.fromEntries(Object.entries(moves ?? {}).map(([n, m]) => [n, m?.count ?? m]));
318
569
  function companion(m) {
319
570
  if (!m) return void 0;
320
571
  return {
@@ -332,7 +583,6 @@ function companion(m) {
332
583
  ...m.status?.until_time > Date.now() ? { busyUntil: iso(m.status.until_time) } : {}
333
584
  };
334
585
  }
335
- var EXTRAS = ["rewards", "lootResult", "dailyClaimed", "economyResult", "withdrawal", "arenaLast", "lastHunt", "listing", "error"];
336
586
  function player(p) {
337
587
  if (!p) return p;
338
588
  const companions = { ...p.monsters ?? {}, ...p.collection ?? {} };
@@ -401,19 +651,27 @@ function battle(b, address) {
401
651
  recentTurns: turns
402
652
  };
403
653
  }
404
- function hunt(run) {
405
- if (!run || typeof run !== "object") return run;
406
- const { battle: fight, encounter, ...rest } = run;
654
+ function hunt(run2) {
655
+ if (!run2 || typeof run2 !== "object") return run2;
656
+ const { battle: fight, encounter, ...rest } = run2;
407
657
  return {
408
658
  ...rest,
409
659
  ...encounter ? { encounter: companion(encounter) } : {},
410
- ...fight ? { battle: battle(fight, run.playerId) } : {}
660
+ ...fight ? { battle: battle(fight, run2.playerId) } : {}
411
661
  };
412
662
  }
663
+ var iso, uses, EXTRAS;
664
+ var init_views = __esm({
665
+ "src/views.mjs"() {
666
+ "use strict";
667
+ iso = (ms) => ms ? new Date(Number(ms)).toISOString() : void 0;
668
+ uses = (moves) => Object.fromEntries(Object.entries(moves ?? {}).map(([n, m]) => [n, m?.count ?? m]));
669
+ EXTRAS = ["rewards", "lootResult", "dailyClaimed", "economyResult", "withdrawal", "arenaLast", "lastHunt", "listing", "error"];
670
+ }
671
+ });
413
672
 
414
673
  // src/game.mjs
415
- var FACTIONS = ["Sky Nomads", "Aqua Guardians", "Inferno Blades", "Stone Titans"];
416
- var BERRIES = ["air_berry", "fire_berry", "rock_berry", "water_berry"];
674
+ import { z as z2 } from "zod";
417
675
  async function me() {
418
676
  const { api, wallets } = await runtime();
419
677
  return { api, address: wallets?.ao.address };
@@ -538,7 +796,7 @@ function registerGame(server) {
538
796
  tool(
539
797
  server,
540
798
  "game_daily",
541
- "Claim the daily crate and Rune. Once per UTC day.",
799
+ "Claim the daily crate (and accrued Rune, pass holders only). Once per UTC day.",
542
800
  {},
543
801
  async () => (await me()).api.claimDaily(),
544
802
  WRITE,
@@ -768,7 +1026,6 @@ function registerGame(server) {
768
1026
  WRITE
769
1027
  );
770
1028
  }
771
- var asPlayer = (r) => r?.address && r?.inventory ? player(r) : r;
772
1029
  async function battleView(b) {
773
1030
  return b?.id ? battle(b, (await me()).address) : b;
774
1031
  }
@@ -784,16 +1041,26 @@ async function attackView(p) {
784
1041
  ...arenaLast ? { arenaLast } : {}
785
1042
  };
786
1043
  }
787
- var huntView = (r) => r?.runId ? hunt(r) : asPlayer(r);
788
1044
  function need(value, name) {
789
1045
  if (value === void 0 || value === null || value === "") throw new Error(`${name} is required for this action`);
790
1046
  return value;
791
1047
  }
1048
+ var FACTIONS, BERRIES, asPlayer, huntView;
1049
+ var init_game = __esm({
1050
+ "src/game.mjs"() {
1051
+ "use strict";
1052
+ init_runtime();
1053
+ init_tool();
1054
+ init_views();
1055
+ FACTIONS = ["Sky Nomads", "Aqua Guardians", "Inferno Blades", "Stone Titans"];
1056
+ BERRIES = ["air_berry", "fire_berry", "rock_berry", "water_berry"];
1057
+ asPlayer = (r) => r?.address && r?.inventory ? player(r) : r;
1058
+ huntView = (r) => r?.runId ? hunt(r) : asPlayer(r);
1059
+ }
1060
+ });
792
1061
 
793
1062
  // src/exchange.mjs
794
1063
  import { z as z3 } from "zod";
795
- var VENUE = z3.enum(["game", "token"]).describe("game: items vs Gold. token: element tokens vs TEST-RUNE.");
796
- var venueProcess = (venue) => venue === "game" ? graph.processes.internalVenue : graph.processes.externalVenue;
797
1064
  async function terms(venue) {
798
1065
  const { api } = await runtime();
799
1066
  const process2 = venueProcess(venue);
@@ -816,7 +1083,6 @@ function toWire(t, m, price, quantity) {
816
1083
  if (perLot <= 0n) throw new Error("Price is below one quote atom per lot.");
817
1084
  return { lots: (baseAtoms / lot).toString(), perLot: perLot.toString() };
818
1085
  }
819
- var human = (t, free) => Object.fromEntries(Object.entries(free ?? {}).map(([asset, atoms]) => [asset, formatAtoms(atoms, t.den(asset))]));
820
1086
  function registerExchange(server) {
821
1087
  tool(
822
1088
  server,
@@ -971,6 +1237,17 @@ function registerExchange(server) {
971
1237
  WRITE
972
1238
  );
973
1239
  }
1240
+ var VENUE, venueProcess, human;
1241
+ var init_exchange = __esm({
1242
+ "src/exchange.mjs"() {
1243
+ "use strict";
1244
+ init_runtime();
1245
+ init_tool();
1246
+ VENUE = z3.enum(["game", "token"]).describe("game: items vs Gold. token: element tokens vs TEST-RUNE.");
1247
+ venueProcess = (venue) => venue === "game" ? graph.processes.internalVenue : graph.processes.externalVenue;
1248
+ human = (t, free) => Object.fromEntries(Object.entries(free ?? {}).map(([asset, atoms]) => [asset, formatAtoms(atoms, t.den(asset))]));
1249
+ }
1250
+ });
974
1251
 
975
1252
  // src/bridge.mjs
976
1253
  import crypto3 from "node:crypto";
@@ -987,11 +1264,6 @@ import {
987
1264
  } from "@solana/web3.js";
988
1265
  import { SuiGrpcClient } from "@mysten/sui/grpc";
989
1266
  import { Transaction as SuiTransaction, coinWithBalance } from "@mysten/sui/transactions";
990
- var DAILY_FAUCET = 100000000n;
991
- var DAY_MS = 864e5;
992
- var CHAIN = z4.enum(["base", "solana", "sui"]);
993
- var PUSD = graph.processes.pusd;
994
- var faucetToken = (cfg) => (cfg.tokens ?? []).find((t) => t.faucet) ?? cfg.tokens?.[0];
995
1267
  function baseChain(w) {
996
1268
  const cfg = chains.base;
997
1269
  const token = faucetToken(cfg);
@@ -1152,11 +1424,23 @@ function suiChain(w) {
1152
1424
  }
1153
1425
  };
1154
1426
  }
1427
+ async function gasOf(name, address) {
1428
+ if (name === "base") {
1429
+ const rpc = process.env.PARALITH_BASE_RPC || chains.base.rpc;
1430
+ const pub = createPublicClient({ chain: { ...base, id: chains.base.chainId }, transport: http(rpc) });
1431
+ return `${formatEther(await pub.getBalance({ address }))} ETH`;
1432
+ }
1433
+ if (name === "solana") {
1434
+ const conn = new Connection(process.env.PARALITH_SOLANA_RPC || chains.solana.rpc, "confirmed");
1435
+ return `${await conn.getBalance(new PublicKey(address)) / 1e9} SOL`;
1436
+ }
1437
+ const client = new SuiGrpcClient({ network: chains.sui.network, baseUrl: process.env.PARALITH_SUI_RPC || chains.sui.rpc });
1438
+ return `${Number((await client.getBalance({ owner: address })).balance.balance) / 1e9} SUI`;
1439
+ }
1155
1440
  async function chainFor(name) {
1156
1441
  const { wallets } = await runtime();
1157
1442
  return { base: baseChain, solana: solanaChain, sui: suiChain }[name](wallets);
1158
1443
  }
1159
- var pusdBalance = (address) => readAtoms(PUSD, `balance-${address}`);
1160
1444
  function registerBridge(server) {
1161
1445
  tool(
1162
1446
  server,
@@ -1280,28 +1564,32 @@ function registerBridge(server) {
1280
1564
  READ
1281
1565
  );
1282
1566
  }
1567
+ var DAILY_FAUCET, DAY_MS, CHAIN, PUSD, faucetToken, pusdBalance;
1568
+ var init_bridge = __esm({
1569
+ "src/bridge.mjs"() {
1570
+ "use strict";
1571
+ init_runtime();
1572
+ init_tool();
1573
+ DAILY_FAUCET = 100000000n;
1574
+ DAY_MS = 864e5;
1575
+ CHAIN = z4.enum(["base", "solana", "sui"]);
1576
+ PUSD = graph.processes.pusd;
1577
+ faucetToken = (cfg) => (cfg.tokens ?? []).find((t) => t.faucet) ?? cfg.tokens?.[0];
1578
+ pusdBalance = (address) => readAtoms(PUSD, `balance-${address}`);
1579
+ }
1580
+ });
1283
1581
 
1284
1582
  // src/dao.mjs
1285
1583
  import { z as z5 } from "zod";
1286
- var RUNE = graph.processes.rune;
1287
- var DEN = 6;
1288
1584
  function daoProcess() {
1289
1585
  const id = process.env.PARALITH_DAO || graph.processes.dao;
1290
1586
  if (!ID.test(id ?? "")) throw new Error("The DAO is not deployed yet. Read the design at https://docs.paralithmarkets.com/dao/governance");
1291
1587
  return id;
1292
1588
  }
1293
- var tags = (record) => Object.entries(record).filter(([, v]) => v !== void 0 && v !== "").map(([name, value]) => ({ name, value: String(value) }));
1294
1589
  async function stakeOf(address) {
1295
1590
  const row = await readKey(daoProcess(), `stake-${address}`);
1296
1591
  return row ?? { active: "0", cooling: "0", releaseAt: 0, lockedUntil: 0, votes: {} };
1297
1592
  }
1298
- var runeView = (row) => row && {
1299
- ...row,
1300
- active: formatAtoms(row.active ?? 0, DEN),
1301
- cooling: formatAtoms(row.cooling ?? 0, DEN),
1302
- ...row.releaseAt ? { releaseAt: new Date(row.releaseAt).toISOString() } : {},
1303
- ...row.lockedUntil ? { lockedUntil: new Date(row.lockedUntil).toISOString() } : {}
1304
- };
1305
1593
  async function send(record, options = {}) {
1306
1594
  const { api } = await runtime();
1307
1595
  const reply = await api.rawSend(tags(record), { process: daoProcess(), node: NODE, ...options });
@@ -1446,13 +1734,27 @@ function registerDao(server) {
1446
1734
  WRITE
1447
1735
  );
1448
1736
  }
1737
+ var RUNE, DEN, tags, runeView;
1738
+ var init_dao = __esm({
1739
+ "src/dao.mjs"() {
1740
+ "use strict";
1741
+ init_runtime();
1742
+ init_tool();
1743
+ RUNE = graph.processes.rune;
1744
+ DEN = 6;
1745
+ tags = (record) => Object.entries(record).filter(([, v]) => v !== void 0 && v !== "").map(([name, value]) => ({ name, value: String(value) }));
1746
+ runeView = (row) => row && {
1747
+ ...row,
1748
+ active: formatAtoms(row.active ?? 0, DEN),
1749
+ cooling: formatAtoms(row.cooling ?? 0, DEN),
1750
+ ...row.releaseAt ? { releaseAt: new Date(row.releaseAt).toISOString() } : {},
1751
+ ...row.lockedUntil ? { lockedUntil: new Date(row.lockedUntil).toISOString() } : {}
1752
+ };
1753
+ }
1754
+ });
1449
1755
 
1450
1756
  // src/vaults.mjs
1451
1757
  import { z as z6 } from "zod";
1452
- var VENUE2 = z6.enum(["game", "token"]).describe("game: items vs Gold. token: element tokens vs TEST-RUNE.");
1453
- var STRATEGY = z6.enum(["amm", "active", "grid"]).describe("amm: passive, steady, close to holding. active: concentrated, best sideways, loses most in trends. grid: fixed steps, best range-bound, loses in trends.");
1454
- var venueProcess2 = (venue) => venue === "game" ? graph.processes.internalVenue : graph.processes.externalVenue;
1455
- var DAY_MS2 = 864e5;
1456
1758
  async function terms2(venue) {
1457
1759
  const { api } = await runtime();
1458
1760
  const process2 = venueProcess2(venue);
@@ -1465,8 +1767,6 @@ function marketFor2(t, item) {
1465
1767
  if (!entry) throw new Error(`No market for "${item}". Markets: ${Object.keys(t.markets).join(", ")}`);
1466
1768
  return { id: entry[0], ...entry[1] };
1467
1769
  }
1468
- var pct = (part, whole) => BigInt(whole || 0) > 0n ? `${(Number(BigInt(part || 0) * 10000n / BigInt(whole)) / 100).toFixed(2)}%` : "0%";
1469
- var iso2 = (ms) => ms && Number(ms) > 0 ? new Date(Number(ms)).toISOString() : void 0;
1470
1770
  function vaultView(t, m, v) {
1471
1771
  const b = (atoms) => formatAtoms(atoms ?? 0, t.den(m.base));
1472
1772
  const q = (atoms) => formatAtoms(atoms ?? 0, t.den(m.quote));
@@ -1624,36 +1924,34 @@ function registerVaults(server) {
1624
1924
  WRITE
1625
1925
  );
1626
1926
  }
1927
+ var VENUE2, STRATEGY, venueProcess2, DAY_MS2, pct, iso2;
1928
+ var init_vaults = __esm({
1929
+ "src/vaults.mjs"() {
1930
+ "use strict";
1931
+ init_runtime();
1932
+ init_tool();
1933
+ VENUE2 = z6.enum(["game", "token"]).describe("game: items vs Gold. token: element tokens vs TEST-RUNE.");
1934
+ STRATEGY = z6.enum(["amm", "active", "grid"]).describe("amm: passive, steady, close to holding. active: concentrated, best sideways, loses most in trends. grid: fixed steps, best range-bound, loses in trends.");
1935
+ venueProcess2 = (venue) => venue === "game" ? graph.processes.internalVenue : graph.processes.externalVenue;
1936
+ DAY_MS2 = 864e5;
1937
+ pct = (part, whole) => BigInt(whole || 0) > 0n ? `${(Number(BigInt(part || 0) * 10000n / BigInt(whole)) / 100).toFixed(2)}%` : "0%";
1938
+ iso2 = (ms) => ms && Number(ms) > 0 ? new Date(Number(ms)).toISOString() : void 0;
1939
+ }
1940
+ });
1627
1941
 
1628
1942
  // src/show.mjs
1629
- import fs3 from "node:fs";
1630
- import path3 from "node:path";
1943
+ import fs5 from "node:fs";
1944
+ import path5 from "node:path";
1631
1945
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1632
1946
  import { z as z7 } from "zod";
1633
- import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
1634
- var GAME = (process.env.PARALITH_GAME_URL || "https://game.paralithmarkets.com").replace(/\/$/, "");
1635
- var TRADE = (process.env.PARALITH_TRADE_URL || "https://trade.paralithmarkets.com").replace(/\/$/, "");
1636
- var URI = "ui://paralith/view";
1637
- var HERE2 = path3.dirname(fileURLToPath2(import.meta.url));
1947
+ import { spawn } from "node:child_process";
1948
+ import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE, getUiCapability } from "@modelcontextprotocol/ext-apps/server";
1638
1949
  function viewHtml() {
1639
- for (const file of [path3.join(HERE2, "view.html"), path3.join(HERE2, "..", "dist", "view.html")]) {
1640
- if (fs3.existsSync(file)) return fs3.readFileSync(file, "utf8");
1950
+ for (const file of [path5.join(HERE2, "view.html"), path5.join(HERE2, "..", "dist", "view.html")]) {
1951
+ if (fs5.existsSync(file)) return fs5.readFileSync(file, "utf8");
1641
1952
  }
1642
1953
  throw new Error("view.html is missing: run npm run build");
1643
1954
  }
1644
- var link = (base2, page, params) => {
1645
- const query = new URLSearchParams(Object.entries(params).filter(([, v]) => v !== void 0 && v !== ""));
1646
- return `${base2}/embed/${page}${query.size ? `?${query}` : ""}`;
1647
- };
1648
- var VIEWS = {
1649
- battle: (a) => a.battle_id && a.worker ? link(GAME, "battle", { w: a.worker, b: a.battle_id, a: a.address }) : link(GAME, "battle", { a: need2(a, "address") }),
1650
- companion: (a) => link(GAME, "companion", { a: need2(a, "address"), m: a.monster_id }),
1651
- hunt: (a) => link(GAME, "hunt", { a: need2(a, "address") }),
1652
- market: (a) => link(TRADE, "market", { venue: a.venue ?? "token", m: need2(a, "market") }),
1653
- vault: (a) => link(TRADE, "vault", { venue: a.venue ?? "token", m: need2(a, "market"), v: a.vault ?? "amm" }),
1654
- position: (a) => link(TRADE, "position", { venue: a.venue ?? "token", a: need2(a, "address") }),
1655
- bridge: (a) => link(TRADE, "bridge", { a: a.address, chain: a.chain, tx: a.tx })
1656
- };
1657
1955
  function need2(args, key) {
1658
1956
  if (!args[key]) throw new Error(`${key} is required to show this.`);
1659
1957
  return args[key];
@@ -1663,6 +1961,16 @@ async function ownAddress() {
1663
1961
  const { wallets } = await runtime();
1664
1962
  return wallets?.ao.address;
1665
1963
  }
1964
+ function openInBrowser(url) {
1965
+ const [cmd, args] = process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : process.platform === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
1966
+ try {
1967
+ spawn(cmd, args, { detached: true, stdio: "ignore" }).on("error", () => {
1968
+ }).unref();
1969
+ return true;
1970
+ } catch {
1971
+ return false;
1972
+ }
1973
+ }
1666
1974
  function registerShow(server) {
1667
1975
  const csp = { frameDomains: [GAME, TRADE] };
1668
1976
  registerAppResource(
@@ -1673,7 +1981,7 @@ function registerShow(server) {
1673
1981
  async () => ({ contents: [{ uri: URI, mimeType: RESOURCE_MIME_TYPE, text: viewHtml(), _meta: { ui: { csp } } }] })
1674
1982
  );
1675
1983
  registerAppTool(server, "show", {
1676
- description: "Show something with the game's and exchange's own visuals, live: battle (follows a player's arena fights, animated), companion (card), hunt, market (order book and chart), vault, position, bridge (transfers). Returns a link; clients that support MCP Apps also display it inline. Address defaults to yours.",
1984
+ description: "Show something with the game's and exchange's own visuals, live: battle (follows a player's arena fights, animated), companion (card), hunt, market (order book and chart), vault, position, bridge (transfers). Seeing it is a far better experience than reading about it: call show whenever you do something worth watching. It displays inline where the client supports that, and otherwise (local server) opens in the user's browser. Address defaults to yours.",
1677
1985
  inputSchema: {
1678
1986
  what: z7.enum(Object.keys(VIEWS)),
1679
1987
  address: z7.string().optional().describe("AO address; omit for yours."),
@@ -1684,45 +1992,167 @@ function registerShow(server) {
1684
1992
  market: z7.string().optional().describe("market/vault: the market, as exchange_markets lists it."),
1685
1993
  vault: z7.enum(["amm", "active", "grid"]).optional(),
1686
1994
  chain: z7.enum(["base", "solana", "sui"]).optional().describe("bridge: with tx, one transfer."),
1687
- tx: z7.string().optional()
1995
+ tx: z7.string().optional(),
1996
+ open: z7.boolean().optional().describe("Open in the user's browser. Default: only when this client cannot display it inline.")
1688
1997
  },
1689
1998
  annotations: { readOnlyHint: true, openWorldHint: true },
1690
1999
  _meta: { ui: { resourceUri: URI } }
1691
2000
  }, async (args) => {
1692
2001
  try {
1693
2002
  const view = VIEWS[args.what]({ ...args, address: args.address ?? await ownAddress() });
1694
- return { structuredContent: { what: args.what, view }, content: [{ type: "text", text: `${args.what}: ${view}` }] };
2003
+ const inline = !!getUiCapability(server.server.getClientCapabilities());
2004
+ const opened = !READ_ONLY && (args.open ?? !inline) && openInBrowser(view);
2005
+ const where = inline ? "Displayed inline." : opened ? "Opened in the user's browser." : "Give the user this link.";
2006
+ return { structuredContent: { what: args.what, view }, content: [{ type: "text", text: `${args.what}: ${view}
2007
+ ${where}` }] };
1695
2008
  } catch (error) {
1696
2009
  return { isError: true, content: [{ type: "text", text: error.message }] };
1697
2010
  }
1698
2011
  });
1699
2012
  }
2013
+ var GAME, TRADE, URI, HERE2, link, VIEWS;
2014
+ var init_show = __esm({
2015
+ "src/show.mjs"() {
2016
+ "use strict";
2017
+ init_runtime();
2018
+ GAME = (process.env.PARALITH_GAME_URL || "https://game.paralithmarkets.com").replace(/\/$/, "");
2019
+ TRADE = (process.env.PARALITH_TRADE_URL || "https://trade.paralithmarkets.com").replace(/\/$/, "");
2020
+ URI = "ui://paralith/view";
2021
+ HERE2 = path5.dirname(fileURLToPath2(import.meta.url));
2022
+ link = (base2, page, params) => {
2023
+ const query = new URLSearchParams(Object.entries(params).filter(([, v]) => v !== void 0 && v !== ""));
2024
+ return `${base2}/embed/${page}${query.size ? `?${query}` : ""}`;
2025
+ };
2026
+ VIEWS = {
2027
+ battle: (a) => a.battle_id && a.worker ? link(GAME, "battle", { w: a.worker, b: a.battle_id, a: a.address }) : link(GAME, "battle", { a: need2(a, "address") }),
2028
+ companion: (a) => link(GAME, "companion", { a: need2(a, "address"), m: a.monster_id }),
2029
+ hunt: (a) => link(GAME, "hunt", { a: need2(a, "address") }),
2030
+ market: (a) => link(TRADE, "market", { venue: a.venue ?? "token", m: need2(a, "market") }),
2031
+ vault: (a) => link(TRADE, "vault", { venue: a.venue ?? "token", m: need2(a, "market"), v: a.vault ?? "amm" }),
2032
+ position: (a) => link(TRADE, "position", { venue: a.venue ?? "token", a: need2(a, "address") }),
2033
+ bridge: (a) => link(TRADE, "bridge", { a: a.address, chain: a.chain, tx: a.tx })
2034
+ };
2035
+ }
2036
+ });
1700
2037
 
1701
- // src/build.mjs
1702
- var DOCS = process.env.PARALITH_DOCS_URL || "https://docs.paralithmarkets.com";
1703
- var INSTRUCTIONS = `Paralith on AO (HyperBEAM): Rune Realm (a companion game), two order books, and the PUSD bridge.
1704
- Everything is TEST-prefixed while unreleased. Reads are free; every write is a signed message (a few seconds round trip, longer under load).
1705
-
1706
- Start: wallet -> game_player. No account? game_join (pick a faction).
1707
- Core loop: game_daily once a day; game_companion feed/play/quest/claim to keep energy and happiness up and earn exp and Gold;
1708
- game_level_up when exp allows; game_learn_move when an offer is pending.
1709
- Arena: arena_enter (25 energy + 25 happiness, 4 fights), arena_fight mode=bot, then arena_attack each round
1710
- (read arena_battle for moves and uses left; Rally and Mend are free once each), arena_end leave when done.
1711
- Hunt: hunt_begin -> hunt_act search -> attack until defeated -> capture (1-3 Rune + a Scroll) or decline -> end.
1712
- Markets: exchange_markets / exchange_book / exchange_order on venue "game" (items vs Gold) or "token" (elements vs TEST-RUNE).
1713
- Vaults (pooled liquidity, one amm/active/grid per market): vault_list, vault_position, vault_deposit, vault_shares (withdraw/lock/burn), vault_fund, vault_crank.
1714
- Rune: rune_move withdraw (game -> token) / deposit (token -> game). Bridge: bridge_balances, bridge_faucet, bridge_deposit, bridge_redeem.
1715
- DAO (stake TEST-RUNE; a passed proposal may send one attached message): dao_info, dao_proposals, dao_stake stake/unstake/withdraw, dao_propose, dao_vote.
1716
-
1717
- To let the user SEE something (an arena fight as it happens, a companion, a hunt, a market's book and chart, a vault, a position, bridge transfers), call show: it returns a live link and displays inline where the client supports it.
2038
+ // src/progress.mjs
2039
+ import { z as z8 } from "zod";
2040
+ async function targets(who) {
2041
+ const { wallets } = await runtime();
2042
+ const agents = readAgents();
2043
+ const self = wallets && { name: process.env.PARALITH_AGENT ?? "you", addresses: publicAddresses(wallets) };
2044
+ if (!who || who === "me") {
2045
+ if (!self) throw new Error("This server has no wallet; pass who: an AO address.");
2046
+ return [self];
2047
+ }
2048
+ if (who === "all") {
2049
+ const list = Object.entries(agents).map(([name, a]) => ({ name, addresses: a.addresses }));
2050
+ if (self && !list.some((t) => t.name === self.name)) list.unshift(self);
2051
+ if (!list.length) throw new Error("No agents are set up here. Run: npx -y paralith-mcp@latest setup");
2052
+ return list;
2053
+ }
2054
+ if (agents[who]) return [{ name: who, addresses: agents[who].addresses }];
2055
+ if (ID.test(who)) return [{ name: who, addresses: { ao: who } }];
2056
+ throw new Error(`"${who}" is not an agent here (${Object.keys(agents).join(", ") || "none"}) or an AO address.`);
2057
+ }
2058
+ async function report({ name, addresses: a }) {
2059
+ const { api } = await runtime();
2060
+ const out = { agent: name, addresses: a };
2061
+ const todo = [];
2062
+ const gas = Object.fromEntries(await Promise.all(["base", "solana", "sui"].filter((c) => a[c]).map(async (c) => [c, await gasOf(c, a[c]).catch(() => "unreadable")])));
2063
+ if (Object.keys(gas).length) out.gas = gas;
2064
+ if (!a.ao) {
2065
+ out.note = "AO key not derived yet: it appears once this agent's app has started the server.";
2066
+ return out;
2067
+ }
2068
+ const [p, tokens, venues, redemptions] = await Promise.all([
2069
+ api.readPlayer(a.ao).catch(() => null),
2070
+ Promise.all(Object.entries({ rune: graph.processes.rune, pusd: graph.processes.pusd, ...graph.processes.elements }).map(async ([t, id]) => [t, await readAtoms(id, `balance-${a.ao}`).catch(() => 0n)])),
2071
+ Promise.all(Object.entries(VENUES).map(async ([v, pid]) => [
2072
+ v,
2073
+ await api.readVenuePosition(pid, a.ao).catch(() => null),
2074
+ await api.readVenueVaultPositions(pid, a.ao).catch(() => null)
2075
+ ])),
2076
+ readKey("pusd", "withdrawals").catch(() => null)
2077
+ ]);
2078
+ if (!p) {
2079
+ out.game = { joined: false };
2080
+ todo.push("Join the game (game_join).");
2081
+ } else {
2082
+ const raw = p.monsters?.[p.activeId] ?? p.collection?.[p.activeId];
2083
+ const c = companion(raw);
2084
+ out.game = {
2085
+ faction: p.faction,
2086
+ gold: p.gold,
2087
+ rune: p.inventory?.rune ?? 0,
2088
+ companion: c && { name: c.name, level: c.level, exp: c.exp, energy: c.energy, happiness: c.happiness },
2089
+ arena: { wins: p.wins ?? 0, losses: p.losses ?? 0, rating: p.rating, sessionBattlesLeft: p.battlesRemaining ?? 0 },
2090
+ ...p.battleFleet ? { inBattle: p.battleFleet.battleId } : {},
2091
+ ...p.hunt ? { onHunt: p.hunt.runId } : {}
2092
+ };
2093
+ if (!p.dailyReadyAt || Number(p.dailyReadyAt) <= Date.now()) todo.push("Daily crate is ready (game_daily).");
2094
+ if (count(p.lootboxes)) todo.push(`${count(p.lootboxes)} loot box(es) to open (game_lootbox).`);
2095
+ if (raw && Number(raw.exp ?? 0) >= Number(raw.nextLevelExp ?? Infinity)) todo.push("Companion can level up (game_level_up).");
2096
+ if (c && (c.energy < 25 || c.happiness < 25)) todo.push("Companion is below the arena's 25 energy/happiness: feed or play.");
2097
+ }
2098
+ const held = Object.fromEntries(tokens.filter(([, v]) => v > 0n).map(([t, v]) => [t, formatAtoms(v)]));
2099
+ if (Object.keys(held).length) out.tokens = held;
2100
+ const trading = {};
2101
+ for (const [v, pos, vaults] of venues) {
2102
+ const row = {};
2103
+ if (pos?.orders?.length) row.openOrders = pos.orders.length;
2104
+ if (pos?.fills?.length) row.recentFills = pos.fills.length;
2105
+ const shares = Object.entries(vaults ?? {}).flatMap(([m, r]) => Object.entries(r).filter(([, x]) => BigInt(x?.shares ?? 0) > 0n).map(([s]) => `${m} ${s}`));
2106
+ if (shares.length) row.vaults = shares;
2107
+ if (Object.keys(row).length) trading[v] = row;
2108
+ }
2109
+ if (Object.keys(trading).length) out.trading = trading;
2110
+ const pending = (redemptions ?? []).filter((w) => w.account === a.ao && w.status !== "released");
2111
+ if (pending.length) out.bridge = { pendingRedemptions: pending.length };
2112
+ if (Object.keys(gas).length && Object.values(gas).every((g) => /^0 /.test(g))) {
2113
+ todo.push("No chain gas yet: fund an address above to bridge.");
2114
+ }
2115
+ if (todo.length) out.todo = todo;
2116
+ return out;
2117
+ }
2118
+ function registerProgress(server) {
2119
+ tool(
2120
+ server,
2121
+ "progress",
2122
+ 'How an agent is doing, in one free read: game (companion, Gold, Rune, arena record, live battle or hunt), tokens, open orders and vault shares on both venues, pending bridge redemptions, chain gas, and what needs doing next. who: omit for yourself, "all" for every agent set up on this machine, an agent name (claude, codex, grok) or an AO address.',
2123
+ { who: z8.string().optional() },
2124
+ async ({ who }) => {
2125
+ const list = await Promise.all((await targets(who)).map(report));
2126
+ return list.length === 1 ? list[0] : list;
2127
+ },
2128
+ READ
2129
+ );
2130
+ }
2131
+ var VENUES, count;
2132
+ var init_progress = __esm({
2133
+ "src/progress.mjs"() {
2134
+ "use strict";
2135
+ init_runtime();
2136
+ init_tool();
2137
+ init_agents();
2138
+ init_keys();
2139
+ init_bridge();
2140
+ init_views();
2141
+ VENUES = { game: graph.processes.internalVenue, token: graph.processes.externalVenue };
2142
+ count = (x) => Array.isArray(x) ? x.length : Object.values(x ?? {}).reduce((n, v) => n + Number(v || 0), 0);
2143
+ }
2144
+ });
1718
2145
 
1719
- Rules: if a tool says a write WAS accepted but unconfirmed, never resend it; re-read state instead.
1720
- A refusal ({"error": ...}) costs nothing. Use game_catalog for exact costs and move stats; docs at ${DOCS}/llms-full.txt.`;
1721
- var READ_ONLY_NOTE = `This endpoint is READ-ONLY: it explains the protocol and reads live state for any address.
1722
- To play, trade or bridge, run the local server with a wallet phrase: ${DOCS}/ai/mcp`;
2146
+ // src/build.mjs
2147
+ var build_exports = {};
2148
+ __export(build_exports, {
2149
+ buildServer: () => buildServer
2150
+ });
2151
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2152
+ import { z as z9 } from "zod";
1723
2153
  function buildServer() {
1724
2154
  const server = new McpServer(
1725
- { name: "paralith", version: "0.2.0" },
2155
+ { name: "paralith", version: "0.3.1" },
1726
2156
  { instructions: READ_ONLY ? `${READ_ONLY_NOTE}
1727
2157
 
1728
2158
  ${INSTRUCTIONS}` : INSTRUCTIONS }
@@ -1753,7 +2183,7 @@ ${INSTRUCTIONS}` : INSTRUCTIONS }
1753
2183
  server,
1754
2184
  "read_state",
1755
2185
  `Read any published key of any process, free: /<process>~process@1.0/now/<key>. process is an id or an alias (${Object.keys(PROCESS_ALIASES).join(", ")}). Returns null when absent.`,
1756
- { process: z8.string(), key: z8.string() },
2186
+ { process: z9.string(), key: z9.string() },
1757
2187
  async ({ process: process2, key }) => readKey(process2, key),
1758
2188
  READ
1759
2189
  );
@@ -1762,10 +2192,10 @@ ${INSTRUCTIONS}` : INSTRUCTIONS }
1762
2192
  "send_message",
1763
2193
  "Escape hatch: sign and send a raw message to any process and return its reply. Prefer the specific tools; use this only for an action they do not cover. Tag values must be strings; never use a tag named Target.",
1764
2194
  {
1765
- process: z8.string(),
1766
- tags: z8.record(z8.string(), z8.string()).describe('e.g. {"Action": "Pass.Info"}'),
1767
- data: z8.string().optional(),
1768
- push_outbox: z8.boolean().optional().describe("Deliver the messages this one emits to other processes (e.g. a Transfer to a process).")
2195
+ process: z9.string(),
2196
+ tags: z9.record(z9.string(), z9.string()).describe('e.g. {"Action": "Pass.Info"}'),
2197
+ data: z9.string().optional(),
2198
+ push_outbox: z9.boolean().optional().describe("Deliver the messages this one emits to other processes (e.g. a Transfer to a process).")
1769
2199
  },
1770
2200
  async ({ process: process2, tags: tags2, data, push_outbox }) => {
1771
2201
  if (Object.keys(tags2).some((k) => k.toLowerCase() === "target")) throw new Error("Target is not a usable tag name.");
@@ -1795,9 +2225,74 @@ ${INSTRUCTIONS}` : INSTRUCTIONS }
1795
2225
  registerDao(server);
1796
2226
  registerVaults(server);
1797
2227
  registerShow(server);
2228
+ registerProgress(server);
1798
2229
  return server;
1799
2230
  }
2231
+ var DOCS, INSTRUCTIONS, READ_ONLY_NOTE;
2232
+ var init_build = __esm({
2233
+ "src/build.mjs"() {
2234
+ "use strict";
2235
+ init_runtime();
2236
+ init_keys();
2237
+ init_tool();
2238
+ init_game();
2239
+ init_exchange();
2240
+ init_bridge();
2241
+ init_dao();
2242
+ init_vaults();
2243
+ init_show();
2244
+ init_progress();
2245
+ DOCS = process.env.PARALITH_DOCS_URL || "https://docs.paralithmarkets.com";
2246
+ INSTRUCTIONS = `Paralith on AO (HyperBEAM): Rune Realm (a companion game), two order books, and the PUSD bridge.
2247
+ Everything is TEST-prefixed while unreleased. Reads are free; every write is a signed message (a few seconds round trip, longer under load).
2248
+
2249
+ Start: wallet -> game_player. No account? game_join (pick a faction).
2250
+ Core loop: game_daily once a day; game_companion feed/play/quest/claim to keep energy and happiness up and earn exp and Gold;
2251
+ game_level_up when exp allows; game_learn_move when an offer is pending.
2252
+ Arena: arena_enter (25 energy + 25 happiness, 4 fights), arena_fight mode=bot, then arena_attack each round
2253
+ (read arena_battle for moves and uses left; Rally and Mend are free once each), arena_end leave when done.
2254
+ Hunt: hunt_begin -> hunt_act search -> attack until defeated -> capture (1-3 Rune + a Scroll) or decline -> end.
2255
+ Markets: exchange_markets / exchange_book / exchange_order on venue "game" (items vs Gold) or "token" (elements vs TEST-RUNE).
2256
+ Vaults (pooled liquidity, one amm/active/grid per market): vault_list, vault_position, vault_deposit, vault_shares (withdraw/lock/burn), vault_fund, vault_crank.
2257
+ Rune: rune_move withdraw (game -> token) / deposit (token -> game). Bridge: bridge_balances, bridge_faucet, bridge_deposit, bridge_redeem.
2258
+ DAO (stake TEST-RUNE; a passed proposal may send one attached message): dao_info, dao_proposals, dao_stake stake/unstake/withdraw, dao_propose, dao_vote.
2259
+
2260
+ HOW RESOURCES FLOW. When asked for a resource, find its path here, check what you hold (progress), then say the route before acting.
2261
+ - Gold (in-game only, never leaves the game). Earn: companion play/quest/claim (capped at 60 Gold per 20 h), arena bot wins,
2262
+ selling berries/Scrolls/Rune to the shop (game_shop sell) or on the game venue. Spend: arena stakes, shop buys, game-venue buys.
2263
+ - Berries and Scrolls. Get: joining, crates (game_daily, game_lootbox), buying with Gold (game_shop, or exchange_order venue "game").
2264
+ Use: feed the companion, hunt escrow (2 of each berry), arena boosts; a Scroll plus 1-3 Rune for a hunt capture.
2265
+ - Rune exists twice: in-game Rune and the TEST-RUNE wallet token; rune_move converts 1:1 either way.
2266
+ Get Rune: (1) the shop, only if game_shop quote shows an ask (it often only bids); (2) sell a companion on companion_market;
2267
+ (3) the token route, which always works: element_claim (free daily, 1-10 of one element) -> exchange_order venue "token"
2268
+ sell that element for TEST-RUNE -> rune_move deposit. Public TEST accounts accrue no Rune from game_daily (pass holders only).
2269
+ Spend in-game Rune: game_level_up, hunt capture, companion_market buys. TEST-RUNE: token-venue buys, vaults, dao_stake.
2270
+ - Element tokens (TEST-EARTH/AIR/FIRE/WATER): element_claim daily; trade against TEST-RUNE on the token venue; vault liquidity.
2271
+ - PUSD: bridge in TEST-USDC from Base, Solana or Sui (needs that chain's gas, which the user funds; bridge_faucet gives TEST-USDC).
2272
+ No PUSD market is listed yet. Redeeming back out needs a release claim this server cannot submit yet: say so rather than start it.
2273
+ - Vaults: deposit both sides of a market to earn from its strategy; withdraw with vault_shares.
2274
+
2275
+ To report how you (or every agent set up on this machine) are doing, call progress (who: "all" for every agent); it also lists what needs doing next.
2276
+
2277
+ SHOW IT. Watching beats reading: call show for anything worth seeing (a fight as it happens, the companion after a level-up, the book
2278
+ you are trading, a vault, a bridge transfer). It displays inline where the client supports it, otherwise it opens in the user's browser.
2279
+ Show once per thing, not once per round.
2280
+
2281
+ Rules: if a tool says a write WAS accepted but unconfirmed, never resend it; re-read state instead.
2282
+ A refusal ({"error": ...}) costs nothing. Use game_catalog for exact costs and move stats; docs at ${DOCS}/llms-full.txt.`;
2283
+ READ_ONLY_NOTE = `This endpoint is READ-ONLY: it explains the protocol and reads live state for any address.
2284
+ To play, trade or bridge, run the local server with a wallet phrase: ${DOCS}/ai/mcp`;
2285
+ }
2286
+ });
1800
2287
 
1801
2288
  // src/server.mjs
1802
- await buildServer().connect(new StdioServerTransport());
1803
- runtime().catch((error) => console.error(`paralith-mcp: ${error.message}`));
2289
+ if (process.argv[2] === "setup") {
2290
+ const { setup: setup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
2291
+ await setup2(process.argv.slice(3));
2292
+ } else {
2293
+ const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
2294
+ const { runtime: runtime2 } = await Promise.resolve().then(() => (init_runtime(), runtime_exports));
2295
+ const { buildServer: buildServer2 } = await Promise.resolve().then(() => (init_build(), build_exports));
2296
+ await buildServer2().connect(new StdioServerTransport());
2297
+ runtime2().catch((error) => console.error(`paralith-mcp: ${error.message}`));
2298
+ }