paralith-mcp 0.2.0 → 0.3.0

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,43 @@ 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
+ function tool(server, name, description, inputSchema, run2, annotations = {}, view = null) {
284
517
  if (READ_ONLY && annotations !== READ) return;
285
518
  const schema = view ? { ...inputSchema, full: z.boolean().optional().describe("Return the raw record instead of the compact view.") } : inputSchema;
286
519
  server.registerTool(name, { description, inputSchema: schema, annotations }, async (args) => {
287
520
  try {
288
- const raw = await run(args ?? {});
521
+ const raw = await run2(args ?? {});
289
522
  const value = view && !args?.full ? await view(raw) : raw;
290
523
  const text = typeof value === "string" ? value : JSON.stringify(value, bigints, 2);
291
524
  return { content: [{ type: "text", text: text ?? "null" }] };
@@ -294,7 +527,6 @@ function tool(server, name, description, inputSchema, run, annotations = {}, vie
294
527
  }
295
528
  });
296
529
  }
297
- var bigints = (_k, v) => typeof v === "bigint" ? v.toString() : v;
298
530
  function explain(error) {
299
531
  const name = error?.name ?? "";
300
532
  const message = error?.message ?? String(error);
@@ -305,16 +537,19 @@ The message WAS accepted (slot ${error?.slot ?? "?"}); its result could not be c
305
537
  }
306
538
  return message;
307
539
  }
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";
540
+ var bigints, READ, OWN, WRITE;
541
+ var init_tool = __esm({
542
+ "src/tool.mjs"() {
543
+ "use strict";
544
+ init_runtime();
545
+ bigints = (_k, v) => typeof v === "bigint" ? v.toString() : v;
546
+ READ = { readOnlyHint: true, openWorldHint: true };
547
+ OWN = { readOnlyHint: true, openWorldHint: true };
548
+ WRITE = { readOnlyHint: false, destructiveHint: false, openWorldHint: true };
549
+ }
550
+ });
314
551
 
315
552
  // 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
553
  function companion(m) {
319
554
  if (!m) return void 0;
320
555
  return {
@@ -332,7 +567,6 @@ function companion(m) {
332
567
  ...m.status?.until_time > Date.now() ? { busyUntil: iso(m.status.until_time) } : {}
333
568
  };
334
569
  }
335
- var EXTRAS = ["rewards", "lootResult", "dailyClaimed", "economyResult", "withdrawal", "arenaLast", "lastHunt", "listing", "error"];
336
570
  function player(p) {
337
571
  if (!p) return p;
338
572
  const companions = { ...p.monsters ?? {}, ...p.collection ?? {} };
@@ -401,19 +635,27 @@ function battle(b, address) {
401
635
  recentTurns: turns
402
636
  };
403
637
  }
404
- function hunt(run) {
405
- if (!run || typeof run !== "object") return run;
406
- const { battle: fight, encounter, ...rest } = run;
638
+ function hunt(run2) {
639
+ if (!run2 || typeof run2 !== "object") return run2;
640
+ const { battle: fight, encounter, ...rest } = run2;
407
641
  return {
408
642
  ...rest,
409
643
  ...encounter ? { encounter: companion(encounter) } : {},
410
- ...fight ? { battle: battle(fight, run.playerId) } : {}
644
+ ...fight ? { battle: battle(fight, run2.playerId) } : {}
411
645
  };
412
646
  }
647
+ var iso, uses, EXTRAS;
648
+ var init_views = __esm({
649
+ "src/views.mjs"() {
650
+ "use strict";
651
+ iso = (ms) => ms ? new Date(Number(ms)).toISOString() : void 0;
652
+ uses = (moves) => Object.fromEntries(Object.entries(moves ?? {}).map(([n, m]) => [n, m?.count ?? m]));
653
+ EXTRAS = ["rewards", "lootResult", "dailyClaimed", "economyResult", "withdrawal", "arenaLast", "lastHunt", "listing", "error"];
654
+ }
655
+ });
413
656
 
414
657
  // 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"];
658
+ import { z as z2 } from "zod";
417
659
  async function me() {
418
660
  const { api, wallets } = await runtime();
419
661
  return { api, address: wallets?.ao.address };
@@ -768,7 +1010,6 @@ function registerGame(server) {
768
1010
  WRITE
769
1011
  );
770
1012
  }
771
- var asPlayer = (r) => r?.address && r?.inventory ? player(r) : r;
772
1013
  async function battleView(b) {
773
1014
  return b?.id ? battle(b, (await me()).address) : b;
774
1015
  }
@@ -784,16 +1025,26 @@ async function attackView(p) {
784
1025
  ...arenaLast ? { arenaLast } : {}
785
1026
  };
786
1027
  }
787
- var huntView = (r) => r?.runId ? hunt(r) : asPlayer(r);
788
1028
  function need(value, name) {
789
1029
  if (value === void 0 || value === null || value === "") throw new Error(`${name} is required for this action`);
790
1030
  return value;
791
1031
  }
1032
+ var FACTIONS, BERRIES, asPlayer, huntView;
1033
+ var init_game = __esm({
1034
+ "src/game.mjs"() {
1035
+ "use strict";
1036
+ init_runtime();
1037
+ init_tool();
1038
+ init_views();
1039
+ FACTIONS = ["Sky Nomads", "Aqua Guardians", "Inferno Blades", "Stone Titans"];
1040
+ BERRIES = ["air_berry", "fire_berry", "rock_berry", "water_berry"];
1041
+ asPlayer = (r) => r?.address && r?.inventory ? player(r) : r;
1042
+ huntView = (r) => r?.runId ? hunt(r) : asPlayer(r);
1043
+ }
1044
+ });
792
1045
 
793
1046
  // src/exchange.mjs
794
1047
  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
1048
  async function terms(venue) {
798
1049
  const { api } = await runtime();
799
1050
  const process2 = venueProcess(venue);
@@ -816,7 +1067,6 @@ function toWire(t, m, price, quantity) {
816
1067
  if (perLot <= 0n) throw new Error("Price is below one quote atom per lot.");
817
1068
  return { lots: (baseAtoms / lot).toString(), perLot: perLot.toString() };
818
1069
  }
819
- var human = (t, free) => Object.fromEntries(Object.entries(free ?? {}).map(([asset, atoms]) => [asset, formatAtoms(atoms, t.den(asset))]));
820
1070
  function registerExchange(server) {
821
1071
  tool(
822
1072
  server,
@@ -971,6 +1221,17 @@ function registerExchange(server) {
971
1221
  WRITE
972
1222
  );
973
1223
  }
1224
+ var VENUE, venueProcess, human;
1225
+ var init_exchange = __esm({
1226
+ "src/exchange.mjs"() {
1227
+ "use strict";
1228
+ init_runtime();
1229
+ init_tool();
1230
+ VENUE = z3.enum(["game", "token"]).describe("game: items vs Gold. token: element tokens vs TEST-RUNE.");
1231
+ venueProcess = (venue) => venue === "game" ? graph.processes.internalVenue : graph.processes.externalVenue;
1232
+ human = (t, free) => Object.fromEntries(Object.entries(free ?? {}).map(([asset, atoms]) => [asset, formatAtoms(atoms, t.den(asset))]));
1233
+ }
1234
+ });
974
1235
 
975
1236
  // src/bridge.mjs
976
1237
  import crypto3 from "node:crypto";
@@ -987,11 +1248,6 @@ import {
987
1248
  } from "@solana/web3.js";
988
1249
  import { SuiGrpcClient } from "@mysten/sui/grpc";
989
1250
  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
1251
  function baseChain(w) {
996
1252
  const cfg = chains.base;
997
1253
  const token = faucetToken(cfg);
@@ -1152,11 +1408,23 @@ function suiChain(w) {
1152
1408
  }
1153
1409
  };
1154
1410
  }
1411
+ async function gasOf(name, address) {
1412
+ if (name === "base") {
1413
+ const rpc = process.env.PARALITH_BASE_RPC || chains.base.rpc;
1414
+ const pub = createPublicClient({ chain: { ...base, id: chains.base.chainId }, transport: http(rpc) });
1415
+ return `${formatEther(await pub.getBalance({ address }))} ETH`;
1416
+ }
1417
+ if (name === "solana") {
1418
+ const conn = new Connection(process.env.PARALITH_SOLANA_RPC || chains.solana.rpc, "confirmed");
1419
+ return `${await conn.getBalance(new PublicKey(address)) / 1e9} SOL`;
1420
+ }
1421
+ const client = new SuiGrpcClient({ network: chains.sui.network, baseUrl: process.env.PARALITH_SUI_RPC || chains.sui.rpc });
1422
+ return `${Number((await client.getBalance({ owner: address })).balance.balance) / 1e9} SUI`;
1423
+ }
1155
1424
  async function chainFor(name) {
1156
1425
  const { wallets } = await runtime();
1157
1426
  return { base: baseChain, solana: solanaChain, sui: suiChain }[name](wallets);
1158
1427
  }
1159
- var pusdBalance = (address) => readAtoms(PUSD, `balance-${address}`);
1160
1428
  function registerBridge(server) {
1161
1429
  tool(
1162
1430
  server,
@@ -1280,28 +1548,32 @@ function registerBridge(server) {
1280
1548
  READ
1281
1549
  );
1282
1550
  }
1551
+ var DAILY_FAUCET, DAY_MS, CHAIN, PUSD, faucetToken, pusdBalance;
1552
+ var init_bridge = __esm({
1553
+ "src/bridge.mjs"() {
1554
+ "use strict";
1555
+ init_runtime();
1556
+ init_tool();
1557
+ DAILY_FAUCET = 100000000n;
1558
+ DAY_MS = 864e5;
1559
+ CHAIN = z4.enum(["base", "solana", "sui"]);
1560
+ PUSD = graph.processes.pusd;
1561
+ faucetToken = (cfg) => (cfg.tokens ?? []).find((t) => t.faucet) ?? cfg.tokens?.[0];
1562
+ pusdBalance = (address) => readAtoms(PUSD, `balance-${address}`);
1563
+ }
1564
+ });
1283
1565
 
1284
1566
  // src/dao.mjs
1285
1567
  import { z as z5 } from "zod";
1286
- var RUNE = graph.processes.rune;
1287
- var DEN = 6;
1288
1568
  function daoProcess() {
1289
1569
  const id = process.env.PARALITH_DAO || graph.processes.dao;
1290
1570
  if (!ID.test(id ?? "")) throw new Error("The DAO is not deployed yet. Read the design at https://docs.paralithmarkets.com/dao/governance");
1291
1571
  return id;
1292
1572
  }
1293
- var tags = (record) => Object.entries(record).filter(([, v]) => v !== void 0 && v !== "").map(([name, value]) => ({ name, value: String(value) }));
1294
1573
  async function stakeOf(address) {
1295
1574
  const row = await readKey(daoProcess(), `stake-${address}`);
1296
1575
  return row ?? { active: "0", cooling: "0", releaseAt: 0, lockedUntil: 0, votes: {} };
1297
1576
  }
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
1577
  async function send(record, options = {}) {
1306
1578
  const { api } = await runtime();
1307
1579
  const reply = await api.rawSend(tags(record), { process: daoProcess(), node: NODE, ...options });
@@ -1446,13 +1718,27 @@ function registerDao(server) {
1446
1718
  WRITE
1447
1719
  );
1448
1720
  }
1721
+ var RUNE, DEN, tags, runeView;
1722
+ var init_dao = __esm({
1723
+ "src/dao.mjs"() {
1724
+ "use strict";
1725
+ init_runtime();
1726
+ init_tool();
1727
+ RUNE = graph.processes.rune;
1728
+ DEN = 6;
1729
+ tags = (record) => Object.entries(record).filter(([, v]) => v !== void 0 && v !== "").map(([name, value]) => ({ name, value: String(value) }));
1730
+ runeView = (row) => row && {
1731
+ ...row,
1732
+ active: formatAtoms(row.active ?? 0, DEN),
1733
+ cooling: formatAtoms(row.cooling ?? 0, DEN),
1734
+ ...row.releaseAt ? { releaseAt: new Date(row.releaseAt).toISOString() } : {},
1735
+ ...row.lockedUntil ? { lockedUntil: new Date(row.lockedUntil).toISOString() } : {}
1736
+ };
1737
+ }
1738
+ });
1449
1739
 
1450
1740
  // src/vaults.mjs
1451
1741
  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
1742
  async function terms2(venue) {
1457
1743
  const { api } = await runtime();
1458
1744
  const process2 = venueProcess2(venue);
@@ -1465,8 +1751,6 @@ function marketFor2(t, item) {
1465
1751
  if (!entry) throw new Error(`No market for "${item}". Markets: ${Object.keys(t.markets).join(", ")}`);
1466
1752
  return { id: entry[0], ...entry[1] };
1467
1753
  }
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
1754
  function vaultView(t, m, v) {
1471
1755
  const b = (atoms) => formatAtoms(atoms ?? 0, t.den(m.base));
1472
1756
  const q = (atoms) => formatAtoms(atoms ?? 0, t.den(m.quote));
@@ -1624,36 +1908,33 @@ function registerVaults(server) {
1624
1908
  WRITE
1625
1909
  );
1626
1910
  }
1911
+ var VENUE2, STRATEGY, venueProcess2, DAY_MS2, pct, iso2;
1912
+ var init_vaults = __esm({
1913
+ "src/vaults.mjs"() {
1914
+ "use strict";
1915
+ init_runtime();
1916
+ init_tool();
1917
+ VENUE2 = z6.enum(["game", "token"]).describe("game: items vs Gold. token: element tokens vs TEST-RUNE.");
1918
+ 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.");
1919
+ venueProcess2 = (venue) => venue === "game" ? graph.processes.internalVenue : graph.processes.externalVenue;
1920
+ DAY_MS2 = 864e5;
1921
+ pct = (part, whole) => BigInt(whole || 0) > 0n ? `${(Number(BigInt(part || 0) * 10000n / BigInt(whole)) / 100).toFixed(2)}%` : "0%";
1922
+ iso2 = (ms) => ms && Number(ms) > 0 ? new Date(Number(ms)).toISOString() : void 0;
1923
+ }
1924
+ });
1627
1925
 
1628
1926
  // src/show.mjs
1629
- import fs3 from "node:fs";
1630
- import path3 from "node:path";
1927
+ import fs5 from "node:fs";
1928
+ import path5 from "node:path";
1631
1929
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1632
1930
  import { z as z7 } from "zod";
1633
1931
  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));
1638
1932
  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");
1933
+ for (const file of [path5.join(HERE2, "view.html"), path5.join(HERE2, "..", "dist", "view.html")]) {
1934
+ if (fs5.existsSync(file)) return fs5.readFileSync(file, "utf8");
1641
1935
  }
1642
1936
  throw new Error("view.html is missing: run npm run build");
1643
1937
  }
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
1938
  function need2(args, key) {
1658
1939
  if (!args[key]) throw new Error(`${key} is required to show this.`);
1659
1940
  return args[key];
@@ -1697,32 +1978,149 @@ function registerShow(server) {
1697
1978
  }
1698
1979
  });
1699
1980
  }
1981
+ var GAME, TRADE, URI, HERE2, link, VIEWS;
1982
+ var init_show = __esm({
1983
+ "src/show.mjs"() {
1984
+ "use strict";
1985
+ init_runtime();
1986
+ GAME = (process.env.PARALITH_GAME_URL || "https://game.paralithmarkets.com").replace(/\/$/, "");
1987
+ TRADE = (process.env.PARALITH_TRADE_URL || "https://trade.paralithmarkets.com").replace(/\/$/, "");
1988
+ URI = "ui://paralith/view";
1989
+ HERE2 = path5.dirname(fileURLToPath2(import.meta.url));
1990
+ link = (base2, page, params) => {
1991
+ const query = new URLSearchParams(Object.entries(params).filter(([, v]) => v !== void 0 && v !== ""));
1992
+ return `${base2}/embed/${page}${query.size ? `?${query}` : ""}`;
1993
+ };
1994
+ VIEWS = {
1995
+ 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") }),
1996
+ companion: (a) => link(GAME, "companion", { a: need2(a, "address"), m: a.monster_id }),
1997
+ hunt: (a) => link(GAME, "hunt", { a: need2(a, "address") }),
1998
+ market: (a) => link(TRADE, "market", { venue: a.venue ?? "token", m: need2(a, "market") }),
1999
+ vault: (a) => link(TRADE, "vault", { venue: a.venue ?? "token", m: need2(a, "market"), v: a.vault ?? "amm" }),
2000
+ position: (a) => link(TRADE, "position", { venue: a.venue ?? "token", a: need2(a, "address") }),
2001
+ bridge: (a) => link(TRADE, "bridge", { a: a.address, chain: a.chain, tx: a.tx })
2002
+ };
2003
+ }
2004
+ });
1700
2005
 
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.
2006
+ // src/progress.mjs
2007
+ import { z as z8 } from "zod";
2008
+ async function targets(who) {
2009
+ const { wallets } = await runtime();
2010
+ const agents = readAgents();
2011
+ const self = wallets && { name: process.env.PARALITH_AGENT ?? "you", addresses: publicAddresses(wallets) };
2012
+ if (!who || who === "me") {
2013
+ if (!self) throw new Error("This server has no wallet; pass who: an AO address.");
2014
+ return [self];
2015
+ }
2016
+ if (who === "all") {
2017
+ const list = Object.entries(agents).map(([name, a]) => ({ name, addresses: a.addresses }));
2018
+ if (self && !list.some((t) => t.name === self.name)) list.unshift(self);
2019
+ if (!list.length) throw new Error("No agents are set up here. Run: npx -y paralith-mcp@latest setup");
2020
+ return list;
2021
+ }
2022
+ if (agents[who]) return [{ name: who, addresses: agents[who].addresses }];
2023
+ if (ID.test(who)) return [{ name: who, addresses: { ao: who } }];
2024
+ throw new Error(`"${who}" is not an agent here (${Object.keys(agents).join(", ") || "none"}) or an AO address.`);
2025
+ }
2026
+ async function report({ name, addresses: a }) {
2027
+ const { api } = await runtime();
2028
+ const out = { agent: name, addresses: a };
2029
+ const todo = [];
2030
+ 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")])));
2031
+ if (Object.keys(gas).length) out.gas = gas;
2032
+ if (!a.ao) {
2033
+ out.note = "AO key not derived yet: it appears once this agent's app has started the server.";
2034
+ return out;
2035
+ }
2036
+ const [p, tokens, venues, redemptions] = await Promise.all([
2037
+ api.readPlayer(a.ao).catch(() => null),
2038
+ 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)])),
2039
+ Promise.all(Object.entries(VENUES).map(async ([v, pid]) => [
2040
+ v,
2041
+ await api.readVenuePosition(pid, a.ao).catch(() => null),
2042
+ await api.readVenueVaultPositions(pid, a.ao).catch(() => null)
2043
+ ])),
2044
+ readKey("pusd", "withdrawals").catch(() => null)
2045
+ ]);
2046
+ if (!p) {
2047
+ out.game = { joined: false };
2048
+ todo.push("Join the game (game_join).");
2049
+ } else {
2050
+ const raw = p.monsters?.[p.activeId] ?? p.collection?.[p.activeId];
2051
+ const c = companion(raw);
2052
+ out.game = {
2053
+ faction: p.faction,
2054
+ gold: p.gold,
2055
+ rune: p.inventory?.rune ?? 0,
2056
+ companion: c && { name: c.name, level: c.level, exp: c.exp, energy: c.energy, happiness: c.happiness },
2057
+ arena: { wins: p.wins ?? 0, losses: p.losses ?? 0, rating: p.rating, sessionBattlesLeft: p.battlesRemaining ?? 0 },
2058
+ ...p.battleFleet ? { inBattle: p.battleFleet.battleId } : {},
2059
+ ...p.hunt ? { onHunt: p.hunt.runId } : {}
2060
+ };
2061
+ if (!p.dailyReadyAt || Number(p.dailyReadyAt) <= Date.now()) todo.push("Daily crate is ready (game_daily).");
2062
+ if (count(p.lootboxes)) todo.push(`${count(p.lootboxes)} loot box(es) to open (game_lootbox).`);
2063
+ if (raw && Number(raw.exp ?? 0) >= Number(raw.nextLevelExp ?? Infinity)) todo.push("Companion can level up (game_level_up).");
2064
+ if (c && (c.energy < 25 || c.happiness < 25)) todo.push("Companion is below the arena's 25 energy/happiness: feed or play.");
2065
+ }
2066
+ const held = Object.fromEntries(tokens.filter(([, v]) => v > 0n).map(([t, v]) => [t, formatAtoms(v)]));
2067
+ if (Object.keys(held).length) out.tokens = held;
2068
+ const trading = {};
2069
+ for (const [v, pos, vaults] of venues) {
2070
+ const row = {};
2071
+ if (pos?.orders?.length) row.openOrders = pos.orders.length;
2072
+ if (pos?.fills?.length) row.recentFills = pos.fills.length;
2073
+ const shares = Object.entries(vaults ?? {}).flatMap(([m, r]) => Object.entries(r).filter(([, x]) => BigInt(x?.shares ?? 0) > 0n).map(([s]) => `${m} ${s}`));
2074
+ if (shares.length) row.vaults = shares;
2075
+ if (Object.keys(row).length) trading[v] = row;
2076
+ }
2077
+ if (Object.keys(trading).length) out.trading = trading;
2078
+ const pending = (redemptions ?? []).filter((w) => w.account === a.ao && w.status !== "released");
2079
+ if (pending.length) out.bridge = { pendingRedemptions: pending.length };
2080
+ if (Object.keys(gas).length && Object.values(gas).every((g) => /^0 /.test(g))) {
2081
+ todo.push("No chain gas yet: fund an address above to bridge.");
2082
+ }
2083
+ if (todo.length) out.todo = todo;
2084
+ return out;
2085
+ }
2086
+ function registerProgress(server) {
2087
+ tool(
2088
+ server,
2089
+ "progress",
2090
+ '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.',
2091
+ { who: z8.string().optional() },
2092
+ async ({ who }) => {
2093
+ const list = await Promise.all((await targets(who)).map(report));
2094
+ return list.length === 1 ? list[0] : list;
2095
+ },
2096
+ READ
2097
+ );
2098
+ }
2099
+ var VENUES, count;
2100
+ var init_progress = __esm({
2101
+ "src/progress.mjs"() {
2102
+ "use strict";
2103
+ init_runtime();
2104
+ init_tool();
2105
+ init_agents();
2106
+ init_keys();
2107
+ init_bridge();
2108
+ init_views();
2109
+ VENUES = { game: graph.processes.internalVenue, token: graph.processes.externalVenue };
2110
+ count = (x) => Array.isArray(x) ? x.length : Object.values(x ?? {}).reduce((n, v) => n + Number(v || 0), 0);
2111
+ }
2112
+ });
1718
2113
 
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`;
2114
+ // src/build.mjs
2115
+ var build_exports = {};
2116
+ __export(build_exports, {
2117
+ buildServer: () => buildServer
2118
+ });
2119
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2120
+ import { z as z9 } from "zod";
1723
2121
  function buildServer() {
1724
2122
  const server = new McpServer(
1725
- { name: "paralith", version: "0.2.0" },
2123
+ { name: "paralith", version: "0.3.0" },
1726
2124
  { instructions: READ_ONLY ? `${READ_ONLY_NOTE}
1727
2125
 
1728
2126
  ${INSTRUCTIONS}` : INSTRUCTIONS }
@@ -1753,7 +2151,7 @@ ${INSTRUCTIONS}` : INSTRUCTIONS }
1753
2151
  server,
1754
2152
  "read_state",
1755
2153
  `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() },
2154
+ { process: z9.string(), key: z9.string() },
1757
2155
  async ({ process: process2, key }) => readKey(process2, key),
1758
2156
  READ
1759
2157
  );
@@ -1762,10 +2160,10 @@ ${INSTRUCTIONS}` : INSTRUCTIONS }
1762
2160
  "send_message",
1763
2161
  "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
2162
  {
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).")
2163
+ process: z9.string(),
2164
+ tags: z9.record(z9.string(), z9.string()).describe('e.g. {"Action": "Pass.Info"}'),
2165
+ data: z9.string().optional(),
2166
+ push_outbox: z9.boolean().optional().describe("Deliver the messages this one emits to other processes (e.g. a Transfer to a process).")
1769
2167
  },
1770
2168
  async ({ process: process2, tags: tags2, data, push_outbox }) => {
1771
2169
  if (Object.keys(tags2).some((k) => k.toLowerCase() === "target")) throw new Error("Target is not a usable tag name.");
@@ -1795,9 +2193,57 @@ ${INSTRUCTIONS}` : INSTRUCTIONS }
1795
2193
  registerDao(server);
1796
2194
  registerVaults(server);
1797
2195
  registerShow(server);
2196
+ registerProgress(server);
1798
2197
  return server;
1799
2198
  }
2199
+ var DOCS, INSTRUCTIONS, READ_ONLY_NOTE;
2200
+ var init_build = __esm({
2201
+ "src/build.mjs"() {
2202
+ "use strict";
2203
+ init_runtime();
2204
+ init_keys();
2205
+ init_tool();
2206
+ init_game();
2207
+ init_exchange();
2208
+ init_bridge();
2209
+ init_dao();
2210
+ init_vaults();
2211
+ init_show();
2212
+ init_progress();
2213
+ DOCS = process.env.PARALITH_DOCS_URL || "https://docs.paralithmarkets.com";
2214
+ INSTRUCTIONS = `Paralith on AO (HyperBEAM): Rune Realm (a companion game), two order books, and the PUSD bridge.
2215
+ Everything is TEST-prefixed while unreleased. Reads are free; every write is a signed message (a few seconds round trip, longer under load).
2216
+
2217
+ Start: wallet -> game_player. No account? game_join (pick a faction).
2218
+ Core loop: game_daily once a day; game_companion feed/play/quest/claim to keep energy and happiness up and earn exp and Gold;
2219
+ game_level_up when exp allows; game_learn_move when an offer is pending.
2220
+ Arena: arena_enter (25 energy + 25 happiness, 4 fights), arena_fight mode=bot, then arena_attack each round
2221
+ (read arena_battle for moves and uses left; Rally and Mend are free once each), arena_end leave when done.
2222
+ Hunt: hunt_begin -> hunt_act search -> attack until defeated -> capture (1-3 Rune + a Scroll) or decline -> end.
2223
+ Markets: exchange_markets / exchange_book / exchange_order on venue "game" (items vs Gold) or "token" (elements vs TEST-RUNE).
2224
+ Vaults (pooled liquidity, one amm/active/grid per market): vault_list, vault_position, vault_deposit, vault_shares (withdraw/lock/burn), vault_fund, vault_crank.
2225
+ Rune: rune_move withdraw (game -> token) / deposit (token -> game). Bridge: bridge_balances, bridge_faucet, bridge_deposit, bridge_redeem.
2226
+ 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.
2227
+
2228
+ 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.
2229
+
2230
+ 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.
2231
+
2232
+ Rules: if a tool says a write WAS accepted but unconfirmed, never resend it; re-read state instead.
2233
+ A refusal ({"error": ...}) costs nothing. Use game_catalog for exact costs and move stats; docs at ${DOCS}/llms-full.txt.`;
2234
+ READ_ONLY_NOTE = `This endpoint is READ-ONLY: it explains the protocol and reads live state for any address.
2235
+ To play, trade or bridge, run the local server with a wallet phrase: ${DOCS}/ai/mcp`;
2236
+ }
2237
+ });
1800
2238
 
1801
2239
  // src/server.mjs
1802
- await buildServer().connect(new StdioServerTransport());
1803
- runtime().catch((error) => console.error(`paralith-mcp: ${error.message}`));
2240
+ if (process.argv[2] === "setup") {
2241
+ const { setup: setup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
2242
+ await setup2(process.argv.slice(3));
2243
+ } else {
2244
+ const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
2245
+ const { runtime: runtime2 } = await Promise.resolve().then(() => (init_runtime(), runtime_exports));
2246
+ const { buildServer: buildServer2 } = await Promise.resolve().then(() => (init_build(), build_exports));
2247
+ await buildServer2().connect(new StdioServerTransport());
2248
+ runtime2().catch((error) => console.error(`paralith-mcp: ${error.message}`));
2249
+ }