@sherwoodagent/cli 0.14.1 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/{chat-LFH2SA46.js → chat-X3IKMSNS.js} +11 -8
  2. package/dist/{chat-LFH2SA46.js.map → chat-X3IKMSNS.js.map} +1 -1
  3. package/dist/{chunk-2N7B5N7D.js → chunk-2RYZU7EP.js} +6 -4
  4. package/dist/chunk-2RYZU7EP.js.map +1 -0
  5. package/dist/chunk-4CV4JOE5.js +27 -0
  6. package/dist/{chunk-ADPAZUTB.js → chunk-5NA345RJ.js} +23 -10
  7. package/dist/chunk-5NA345RJ.js.map +1 -0
  8. package/dist/{chunk-IIDZ2TK5.js → chunk-BCOTZTTH.js} +39 -10
  9. package/dist/chunk-BCOTZTTH.js.map +1 -0
  10. package/dist/{chunk-7CN3TSAA.js → chunk-BGEVQQES.js} +5 -3
  11. package/dist/{chunk-7CN3TSAA.js.map → chunk-BGEVQQES.js.map} +1 -1
  12. package/dist/{chunk-FWJBUK57.js → chunk-CGSXIYYZ.js} +5 -3
  13. package/dist/{chunk-FWJBUK57.js.map → chunk-CGSXIYYZ.js.map} +1 -1
  14. package/dist/{chunk-KAZRNDZQ.js → chunk-QIOS7I7I.js} +2 -2
  15. package/dist/{chunk-AC4QMJKC.js → chunk-UBBR7QVJ.js} +2 -2
  16. package/dist/{chunk-LEQLX7XF.js → chunk-YSYX555I.js} +27 -10
  17. package/dist/chunk-YSYX555I.js.map +1 -0
  18. package/dist/{config-U74QT4SC.js → config-5C7QJMPR.js} +5 -2
  19. package/dist/{cron-RG46PYWA.js → cron-IKBNUL3Q.js} +3 -1
  20. package/dist/{cron-RG46PYWA.js.map → cron-IKBNUL3Q.js.map} +1 -1
  21. package/dist/{eas-XWLMRKJU.js → eas-7U7MX24T.js} +7 -6
  22. package/dist/index.js +74 -33
  23. package/dist/index.js.map +1 -1
  24. package/dist/ipfs-AYE4J4OX.js +13 -0
  25. package/dist/{network-ROF3SSAA.js → network-QU2D677V.js} +4 -3
  26. package/dist/research-7RI7VFPK.js +15 -0
  27. package/dist/research-7RI7VFPK.js.map +1 -0
  28. package/dist/{research-QXGYAJUL.js → research-ZDTM73C7.js} +9 -8
  29. package/dist/{research-QXGYAJUL.js.map → research-ZDTM73C7.js.map} +1 -1
  30. package/dist/{session-ECETX4MM.js → session-2VK25CSW.js} +14 -12
  31. package/dist/session-2VK25CSW.js.map +1 -0
  32. package/dist/{xmtp-EKFDS5Y3.js → xmtp-Y3LAZKOC.js} +10 -7
  33. package/dist/{xmtp-EKFDS5Y3.js.map → xmtp-Y3LAZKOC.js.map} +1 -1
  34. package/package.json +1 -1
  35. package/dist/chunk-2N7B5N7D.js.map +0 -1
  36. package/dist/chunk-ADPAZUTB.js.map +0 -1
  37. package/dist/chunk-IIDZ2TK5.js.map +0 -1
  38. package/dist/chunk-LEQLX7XF.js.map +0 -1
  39. package/dist/ipfs-LUJHZGKF.js +0 -11
  40. package/dist/research-HEZP7VPY.js +0 -14
  41. package/dist/session-ECETX4MM.js.map +0 -1
  42. /package/dist/{config-U74QT4SC.js.map → chunk-4CV4JOE5.js.map} +0 -0
  43. /package/dist/{chunk-KAZRNDZQ.js.map → chunk-QIOS7I7I.js.map} +0 -0
  44. /package/dist/{chunk-AC4QMJKC.js.map → chunk-UBBR7QVJ.js.map} +0 -0
  45. /package/dist/{eas-XWLMRKJU.js.map → config-5C7QJMPR.js.map} +0 -0
  46. /package/dist/{ipfs-LUJHZGKF.js.map → eas-7U7MX24T.js.map} +0 -0
  47. /package/dist/{network-ROF3SSAA.js.map → ipfs-AYE4J4OX.js.map} +0 -0
  48. /package/dist/{research-HEZP7VPY.js.map → network-QU2D677V.js.map} +0 -0
@@ -0,0 +1,13 @@
1
+ import {
2
+ fetchMetadata,
3
+ pinJSON,
4
+ uploadMetadata
5
+ } from "./chunk-YSYX555I.js";
6
+ import "./chunk-BCOTZTTH.js";
7
+ import "./chunk-4CV4JOE5.js";
8
+ export {
9
+ fetchMetadata,
10
+ pinJSON,
11
+ uploadMetadata
12
+ };
13
+ //# sourceMappingURL=ipfs-AYE4J4OX.js.map
@@ -9,8 +9,9 @@ import {
9
9
  isTestnet,
10
10
  robinhoodTestnet,
11
11
  setNetwork
12
- } from "./chunk-7CN3TSAA.js";
13
- import "./chunk-IIDZ2TK5.js";
12
+ } from "./chunk-BGEVQQES.js";
13
+ import "./chunk-BCOTZTTH.js";
14
+ import "./chunk-4CV4JOE5.js";
14
15
  export {
15
16
  CHAIN_REGISTRY,
16
17
  VALID_NETWORKS,
@@ -23,4 +24,4 @@ export {
23
24
  robinhoodTestnet,
24
25
  setNetwork
25
26
  };
26
- //# sourceMappingURL=network-ROF3SSAA.js.map
27
+ //# sourceMappingURL=network-QU2D677V.js.map
@@ -0,0 +1,15 @@
1
+ import {
2
+ MessariProvider,
3
+ NansenProvider,
4
+ getResearchProvider
5
+ } from "./chunk-QIOS7I7I.js";
6
+ import "./chunk-CGSXIYYZ.js";
7
+ import "./chunk-BGEVQQES.js";
8
+ import "./chunk-BCOTZTTH.js";
9
+ import "./chunk-4CV4JOE5.js";
10
+ export {
11
+ MessariProvider,
12
+ NansenProvider,
13
+ getResearchProvider
14
+ };
15
+ //# sourceMappingURL=research-7RI7VFPK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -2,12 +2,13 @@ import {
2
2
  MESSARI_COST_ESTIMATE,
3
3
  NANSEN_COST_ESTIMATE,
4
4
  getResearchProvider
5
- } from "./chunk-KAZRNDZQ.js";
5
+ } from "./chunk-QIOS7I7I.js";
6
6
  import {
7
7
  getAccount
8
- } from "./chunk-FWJBUK57.js";
9
- import "./chunk-7CN3TSAA.js";
10
- import "./chunk-IIDZ2TK5.js";
8
+ } from "./chunk-CGSXIYYZ.js";
9
+ import "./chunk-BGEVQQES.js";
10
+ import "./chunk-BCOTZTTH.js";
11
+ import "./chunk-4CV4JOE5.js";
11
12
 
12
13
  // src/commands/research.ts
13
14
  import chalk from "chalk";
@@ -15,7 +16,7 @@ import ora from "ora";
15
16
  import { confirm } from "@inquirer/prompts";
16
17
  import { isAddress } from "viem";
17
18
  async function loadXmtp() {
18
- return import("./xmtp-EKFDS5Y3.js");
19
+ return import("./xmtp-Y3LAZKOC.js");
19
20
  }
20
21
  var COST_ESTIMATES = {
21
22
  messari: MESSARI_COST_ESTIMATE,
@@ -81,8 +82,8 @@ function formatValue(value) {
81
82
  return String(value);
82
83
  }
83
84
  async function postResearch(syndicateName, result, prompt) {
84
- const { pinJSON } = await import("./ipfs-LUJHZGKF.js");
85
- const { createResearchAttestation, getEasScanUrl } = await import("./eas-XWLMRKJU.js");
85
+ const { pinJSON } = await import("./ipfs-AYE4J4OX.js");
86
+ const { createResearchAttestation, getEasScanUrl } = await import("./eas-7U7MX24T.js");
86
87
  const xmtp = await loadXmtp();
87
88
  const pinSpinner = ora("Pinning research result to IPFS...").start();
88
89
  let resultUri;
@@ -307,4 +308,4 @@ function registerResearchCommands(program) {
307
308
  export {
308
309
  registerResearchCommands
309
310
  };
310
- //# sourceMappingURL=research-QXGYAJUL.js.map
311
+ //# sourceMappingURL=research-ZDTM73C7.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/research.ts"],"sourcesContent":["/**\n * Research commands — sherwood research <subcommand>\n *\n * Query DeFi research providers (Messari, Nansen) using x402 micropayments.\n * Agent pays per-query with USDC from its own wallet — no vault interaction needed.\n *\n * --post <syndicate>: pins result to IPFS, creates EAS attestation, posts\n * lightweight notification to syndicate XMTP chat (not the full result).\n *\n * --yes: skip the cost confirmation prompt (for non-interactive / agent use).\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { confirm } from \"@inquirer/prompts\";\nimport { isAddress } from \"viem\";\nimport { getAccount } from \"../lib/client.js\";\nimport type { MessageType } from \"../lib/types.js\";\nimport { getResearchProvider } from \"../providers/research/index.js\";\nimport type { ResearchResult } from \"../providers/research/index.js\";\nimport { MESSARI_COST_ESTIMATE } from \"../providers/research/messari.js\";\nimport { NANSEN_COST_ESTIMATE } from \"../providers/research/nansen.js\";\n\n// Lazy-load XMTP to avoid breaking non-chat commands when @xmtp/cli is missing\nasync function loadXmtp() {\n return import(\"../lib/xmtp.js\");\n}\n\n// ── Cost estimates ──\n\nconst COST_ESTIMATES: Record<string, Record<string, string>> = {\n messari: MESSARI_COST_ESTIMATE,\n nansen: NANSEN_COST_ESTIMATE,\n};\n\nfunction getEstimatedCost(provider: string, queryType: string): string {\n return COST_ESTIMATES[provider]?.[queryType] ?? \"unknown\";\n}\n\n/**\n * Confirm cost with the user before executing the x402 query.\n * Skipped when --yes is passed (non-interactive / agent mode).\n */\nasync function confirmCost(\n provider: string,\n queryType: string,\n target: string,\n skipConfirm: boolean,\n): Promise<boolean> {\n const estimate = getEstimatedCost(provider, queryType);\n\n console.log();\n console.log(chalk.bold(`Research Query`));\n console.log(chalk.dim(\"─\".repeat(40)));\n console.log(` Provider: ${provider}`);\n console.log(` Type: ${queryType}`);\n console.log(` Target: ${target}`);\n console.log(` Est. cost: ${chalk.yellow(estimate + \" USDC\")} (x402 micropayment)`);\n console.log();\n\n if (skipConfirm) return true;\n\n return confirm({\n message: `Pay ${estimate} USDC to ${provider} for this query?`,\n default: true,\n });\n}\n\n// ── Output formatting ──\n\nfunction formatResearchResult(result: ResearchResult): void {\n console.log();\n console.log(\n chalk.bold(`Research: ${result.queryType} ${result.target} (${result.provider})`),\n );\n console.log(chalk.dim(\"─\".repeat(50)));\n\n // Flatten the top-level data keys into a readable summary\n const data = result.data;\n for (const [key, value] of Object.entries(data)) {\n if (value === null || value === undefined) continue;\n\n if (typeof value === \"object\" && !Array.isArray(value)) {\n // Nested object — show as sub-section\n console.log(chalk.bold(`\\n ${key}`));\n for (const [subKey, subValue] of Object.entries(\n value as Record<string, unknown>,\n )) {\n console.log(` ${subKey}: ${formatValue(subValue)}`);\n }\n } else if (Array.isArray(value)) {\n console.log(` ${key}: ${chalk.dim(`[${value.length} items]`)}`);\n } else {\n console.log(` ${key}: ${formatValue(value)}`);\n }\n }\n\n console.log();\n console.log(\n chalk.dim(\n ` Cost: $${result.costUsdc} USDC • Provider: ${result.provider}`,\n ),\n );\n console.log();\n}\n\nfunction formatValue(value: unknown): string {\n if (typeof value === \"number\") {\n // Format large numbers with commas\n return value.toLocaleString(\"en-US\", { maximumFractionDigits: 4 });\n }\n if (typeof value === \"bigint\") {\n return value.toLocaleString(\"en-US\");\n }\n return String(value);\n}\n\n// ── Post flow: IPFS → EAS → XMTP ──\n\nasync function postResearch(\n syndicateName: string,\n result: ResearchResult,\n prompt: string,\n): Promise<void> {\n const { pinJSON } = await import(\"../lib/ipfs.js\");\n const { createResearchAttestation, getEasScanUrl } = await import(\n \"../lib/eas.js\"\n );\n const xmtp = await loadXmtp();\n\n // 1. Pin full research result to IPFS\n const pinSpinner = ora(\"Pinning research result to IPFS...\").start();\n let resultUri: string;\n try {\n resultUri = await pinJSON(\n {\n schema: \"sherwood/research/v1\",\n provider: result.provider,\n queryType: result.queryType,\n target: result.target,\n prompt,\n costUsdc: result.costUsdc,\n data: result.data,\n timestamp: result.timestamp,\n } as Record<string, unknown>,\n `sherwood-research-${result.provider}-${result.queryType}-${Date.now()}`,\n );\n pinSpinner.succeed(`Pinned to IPFS: ${chalk.dim(resultUri)}`);\n } catch (err) {\n pinSpinner.fail(\"Failed to pin to IPFS\");\n console.error(\n chalk.red(err instanceof Error ? err.message : String(err)),\n );\n return;\n }\n\n // 2. Create EAS attestation with prompt, cost, provider, and IPFS URI\n const easSpinner = ora(\"Creating EAS attestation...\").start();\n let attestationUid: string;\n try {\n const { uid } = await createResearchAttestation(\n result.provider,\n result.queryType,\n prompt,\n result.costUsdc,\n resultUri,\n );\n attestationUid = uid;\n easSpinner.succeed(\n `Attested: ${chalk.dim(getEasScanUrl(uid))}`,\n );\n } catch (err) {\n easSpinner.fail(\"Failed to create EAS attestation\");\n console.error(\n chalk.red(err instanceof Error ? err.message : String(err)),\n );\n return;\n }\n\n // 3. Post lightweight notification to XMTP chat\n const chatSpinner = ora(\"Posting to syndicate chat...\").start();\n try {\n const group = await xmtp.getGroup(\"\", syndicateName);\n await xmtp.sendEnvelope(group, {\n type: \"X402_RESEARCH\" as MessageType,\n from: getAccount().address,\n text: `Ran ${result.provider} ${result.queryType} on ${result.target} ($${result.costUsdc} USDC)`,\n data: {\n provider: result.provider,\n queryType: result.queryType,\n target: result.target,\n costUsdc: result.costUsdc,\n resultUri,\n attestationUid,\n },\n timestamp: result.timestamp,\n });\n chatSpinner.succeed(\"Posted to syndicate chat\");\n } catch (err) {\n chatSpinner.fail(\"Failed to post to chat\");\n console.error(\n chalk.red(err instanceof Error ? err.message : String(err)),\n );\n }\n}\n\n// ── Command registration ──\n\nexport function registerResearchCommands(program: Command): void {\n const research = program\n .command(\"research\")\n .description(\n \"Query DeFi research providers via x402 micropayments (USDC on Base)\",\n );\n\n // ── research token <target> ──\n\n research\n .command(\"token <target>\")\n .description(\"Token report — profile, market data, on-chain metrics\")\n .requiredOption(\n \"--provider <name>\",\n \"Research provider (messari, nansen)\",\n )\n .option(\n \"--post <syndicate>\",\n \"Post result to syndicate chat (pin to IPFS + attest to EAS)\",\n )\n .option(\"--yes\", \"Skip cost confirmation prompt\", false)\n .action(async (target: string, opts: { provider: string; post?: string; yes: boolean }) => {\n const ok = await confirmCost(opts.provider, \"token\", target, opts.yes);\n if (!ok) {\n console.log(chalk.dim(\"Cancelled.\"));\n return;\n }\n\n const spinner = ora(\n `Querying ${opts.provider} for token ${target}...`,\n ).start();\n\n try {\n const provider = getResearchProvider(opts.provider);\n const result = await provider.query({ type: \"token\", target });\n spinner.succeed(`Token data received from ${opts.provider}`);\n formatResearchResult(result);\n\n if (opts.post) {\n await postResearch(opts.post, result, `token ${target}`);\n }\n } catch (err) {\n spinner.fail(`Token query failed`);\n console.error(\n chalk.red(err instanceof Error ? err.message : String(err)),\n );\n process.exit(1);\n }\n });\n\n // ── research market <asset> ──\n\n research\n .command(\"market <asset>\")\n .description(\"Market overview — price, volume, market cap, ROI, ATH\")\n .requiredOption(\n \"--provider <name>\",\n \"Research provider (messari, nansen)\",\n )\n .option(\n \"--post <syndicate>\",\n \"Post result to syndicate chat (pin to IPFS + attest to EAS)\",\n )\n .option(\"--yes\", \"Skip cost confirmation prompt\", false)\n .action(async (asset: string, opts: { provider: string; post?: string; yes: boolean }) => {\n const ok = await confirmCost(opts.provider, \"market\", asset, opts.yes);\n if (!ok) {\n console.log(chalk.dim(\"Cancelled.\"));\n return;\n }\n\n const spinner = ora(\n `Querying ${opts.provider} for ${asset} market data...`,\n ).start();\n\n try {\n const provider = getResearchProvider(opts.provider);\n const result = await provider.query({ type: \"market\", target: asset });\n spinner.succeed(`Market data received from ${opts.provider}`);\n formatResearchResult(result);\n\n if (opts.post) {\n await postResearch(opts.post, result, `market ${asset}`);\n }\n } catch (err) {\n spinner.fail(`Market query failed`);\n console.error(\n chalk.red(err instanceof Error ? err.message : String(err)),\n );\n process.exit(1);\n }\n });\n\n // ── research smart-money ──\n\n research\n .command(\"smart-money\")\n .description(\n \"Smart money flows — net flow, DEX trades, holdings from labeled wallets\",\n )\n .requiredOption(\"--token <symbol>\", \"Token symbol to analyze (e.g. WETH)\")\n .requiredOption(\n \"--provider <name>\",\n \"Research provider (messari, nansen)\",\n )\n .option(\n \"--post <syndicate>\",\n \"Post result to syndicate chat (pin to IPFS + attest to EAS)\",\n )\n .option(\"--yes\", \"Skip cost confirmation prompt\", false)\n .action(async (opts: { token: string; provider: string; post?: string; yes: boolean }) => {\n const ok = await confirmCost(opts.provider, \"smart-money\", opts.token, opts.yes);\n if (!ok) {\n console.log(chalk.dim(\"Cancelled.\"));\n return;\n }\n\n const spinner = ora(\n `Querying ${opts.provider} for ${opts.token} smart money flows...`,\n ).start();\n\n try {\n const provider = getResearchProvider(opts.provider);\n const result = await provider.query({\n type: \"smart-money\",\n target: opts.token,\n options: { token: opts.token },\n });\n spinner.succeed(\n `Smart money data received from ${opts.provider}`,\n );\n formatResearchResult(result);\n\n if (opts.post) {\n await postResearch(\n opts.post,\n result,\n `smart-money --token ${opts.token}`,\n );\n }\n } catch (err) {\n spinner.fail(`Smart money query failed`);\n console.error(\n chalk.red(err instanceof Error ? err.message : String(err)),\n );\n process.exit(1);\n }\n });\n\n // ── research wallet <address> ──\n\n research\n .command(\"wallet <address>\")\n .description(\n \"Wallet due diligence — PnL history, transaction patterns, counterparties\",\n )\n .requiredOption(\n \"--provider <name>\",\n \"Research provider (messari, nansen)\",\n )\n .option(\n \"--post <syndicate>\",\n \"Post result to syndicate chat (pin to IPFS + attest to EAS)\",\n )\n .option(\"--yes\", \"Skip cost confirmation prompt\", false)\n .action(async (address: string, opts: { provider: string; post?: string; yes: boolean }) => {\n if (!isAddress(address)) {\n console.error(chalk.red(`Invalid wallet address: ${address}`));\n process.exit(1);\n }\n\n const ok = await confirmCost(opts.provider, \"wallet\", address, opts.yes);\n if (!ok) {\n console.log(chalk.dim(\"Cancelled.\"));\n return;\n }\n\n const spinner = ora(\n `Querying ${opts.provider} for wallet ${address.slice(0, 8)}...`,\n ).start();\n\n try {\n const provider = getResearchProvider(opts.provider);\n const result = await provider.query({\n type: \"wallet\",\n target: address,\n });\n spinner.succeed(`Wallet data received from ${opts.provider}`);\n formatResearchResult(result);\n\n if (opts.post) {\n await postResearch(opts.post, result, `wallet ${address}`);\n }\n } catch (err) {\n spinner.fail(`Wallet query failed`);\n console.error(\n chalk.red(err instanceof Error ? err.message : String(err)),\n );\n process.exit(1);\n }\n });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/commands/research.ts"],"sourcesContent":["/**\n * Research commands — sherwood research <subcommand>\n *\n * Query DeFi research providers (Messari, Nansen) using x402 micropayments.\n * Agent pays per-query with USDC from its own wallet — no vault interaction needed.\n *\n * --post <syndicate>: pins result to IPFS, creates EAS attestation, posts\n * lightweight notification to syndicate XMTP chat (not the full result).\n *\n * --yes: skip the cost confirmation prompt (for non-interactive / agent use).\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { confirm } from \"@inquirer/prompts\";\nimport { isAddress } from \"viem\";\nimport { getAccount } from \"../lib/client.js\";\nimport type { MessageType } from \"../lib/types.js\";\nimport { getResearchProvider } from \"../providers/research/index.js\";\nimport type { ResearchResult } from \"../providers/research/index.js\";\nimport { MESSARI_COST_ESTIMATE } from \"../providers/research/messari.js\";\nimport { NANSEN_COST_ESTIMATE } from \"../providers/research/nansen.js\";\n\n// Lazy-load XMTP to avoid breaking non-chat commands when @xmtp/cli is missing\nasync function loadXmtp() {\n return import(\"../lib/xmtp.js\");\n}\n\n// ── Cost estimates ──\n\nconst COST_ESTIMATES: Record<string, Record<string, string>> = {\n messari: MESSARI_COST_ESTIMATE,\n nansen: NANSEN_COST_ESTIMATE,\n};\n\nfunction getEstimatedCost(provider: string, queryType: string): string {\n return COST_ESTIMATES[provider]?.[queryType] ?? \"unknown\";\n}\n\n/**\n * Confirm cost with the user before executing the x402 query.\n * Skipped when --yes is passed (non-interactive / agent mode).\n */\nasync function confirmCost(\n provider: string,\n queryType: string,\n target: string,\n skipConfirm: boolean,\n): Promise<boolean> {\n const estimate = getEstimatedCost(provider, queryType);\n\n console.log();\n console.log(chalk.bold(`Research Query`));\n console.log(chalk.dim(\"─\".repeat(40)));\n console.log(` Provider: ${provider}`);\n console.log(` Type: ${queryType}`);\n console.log(` Target: ${target}`);\n console.log(` Est. cost: ${chalk.yellow(estimate + \" USDC\")} (x402 micropayment)`);\n console.log();\n\n if (skipConfirm) return true;\n\n return confirm({\n message: `Pay ${estimate} USDC to ${provider} for this query?`,\n default: true,\n });\n}\n\n// ── Output formatting ──\n\nfunction formatResearchResult(result: ResearchResult): void {\n console.log();\n console.log(\n chalk.bold(`Research: ${result.queryType} ${result.target} (${result.provider})`),\n );\n console.log(chalk.dim(\"─\".repeat(50)));\n\n // Flatten the top-level data keys into a readable summary\n const data = result.data;\n for (const [key, value] of Object.entries(data)) {\n if (value === null || value === undefined) continue;\n\n if (typeof value === \"object\" && !Array.isArray(value)) {\n // Nested object — show as sub-section\n console.log(chalk.bold(`\\n ${key}`));\n for (const [subKey, subValue] of Object.entries(\n value as Record<string, unknown>,\n )) {\n console.log(` ${subKey}: ${formatValue(subValue)}`);\n }\n } else if (Array.isArray(value)) {\n console.log(` ${key}: ${chalk.dim(`[${value.length} items]`)}`);\n } else {\n console.log(` ${key}: ${formatValue(value)}`);\n }\n }\n\n console.log();\n console.log(\n chalk.dim(\n ` Cost: $${result.costUsdc} USDC • Provider: ${result.provider}`,\n ),\n );\n console.log();\n}\n\nfunction formatValue(value: unknown): string {\n if (typeof value === \"number\") {\n // Format large numbers with commas\n return value.toLocaleString(\"en-US\", { maximumFractionDigits: 4 });\n }\n if (typeof value === \"bigint\") {\n return value.toLocaleString(\"en-US\");\n }\n return String(value);\n}\n\n// ── Post flow: IPFS → EAS → XMTP ──\n\nasync function postResearch(\n syndicateName: string,\n result: ResearchResult,\n prompt: string,\n): Promise<void> {\n const { pinJSON } = await import(\"../lib/ipfs.js\");\n const { createResearchAttestation, getEasScanUrl } = await import(\n \"../lib/eas.js\"\n );\n const xmtp = await loadXmtp();\n\n // 1. Pin full research result to IPFS\n const pinSpinner = ora(\"Pinning research result to IPFS...\").start();\n let resultUri: string;\n try {\n resultUri = await pinJSON(\n {\n schema: \"sherwood/research/v1\",\n provider: result.provider,\n queryType: result.queryType,\n target: result.target,\n prompt,\n costUsdc: result.costUsdc,\n data: result.data,\n timestamp: result.timestamp,\n } as Record<string, unknown>,\n `sherwood-research-${result.provider}-${result.queryType}-${Date.now()}`,\n );\n pinSpinner.succeed(`Pinned to IPFS: ${chalk.dim(resultUri)}`);\n } catch (err) {\n pinSpinner.fail(\"Failed to pin to IPFS\");\n console.error(\n chalk.red(err instanceof Error ? err.message : String(err)),\n );\n return;\n }\n\n // 2. Create EAS attestation with prompt, cost, provider, and IPFS URI\n const easSpinner = ora(\"Creating EAS attestation...\").start();\n let attestationUid: string;\n try {\n const { uid } = await createResearchAttestation(\n result.provider,\n result.queryType,\n prompt,\n result.costUsdc,\n resultUri,\n );\n attestationUid = uid;\n easSpinner.succeed(\n `Attested: ${chalk.dim(getEasScanUrl(uid))}`,\n );\n } catch (err) {\n easSpinner.fail(\"Failed to create EAS attestation\");\n console.error(\n chalk.red(err instanceof Error ? err.message : String(err)),\n );\n return;\n }\n\n // 3. Post lightweight notification to XMTP chat\n const chatSpinner = ora(\"Posting to syndicate chat...\").start();\n try {\n const group = await xmtp.getGroup(\"\", syndicateName);\n await xmtp.sendEnvelope(group, {\n type: \"X402_RESEARCH\" as MessageType,\n from: getAccount().address,\n text: `Ran ${result.provider} ${result.queryType} on ${result.target} ($${result.costUsdc} USDC)`,\n data: {\n provider: result.provider,\n queryType: result.queryType,\n target: result.target,\n costUsdc: result.costUsdc,\n resultUri,\n attestationUid,\n },\n timestamp: result.timestamp,\n });\n chatSpinner.succeed(\"Posted to syndicate chat\");\n } catch (err) {\n chatSpinner.fail(\"Failed to post to chat\");\n console.error(\n chalk.red(err instanceof Error ? err.message : String(err)),\n );\n }\n}\n\n// ── Command registration ──\n\nexport function registerResearchCommands(program: Command): void {\n const research = program\n .command(\"research\")\n .description(\n \"Query DeFi research providers via x402 micropayments (USDC on Base)\",\n );\n\n // ── research token <target> ──\n\n research\n .command(\"token <target>\")\n .description(\"Token report — profile, market data, on-chain metrics\")\n .requiredOption(\n \"--provider <name>\",\n \"Research provider (messari, nansen)\",\n )\n .option(\n \"--post <syndicate>\",\n \"Post result to syndicate chat (pin to IPFS + attest to EAS)\",\n )\n .option(\"--yes\", \"Skip cost confirmation prompt\", false)\n .action(async (target: string, opts: { provider: string; post?: string; yes: boolean }) => {\n const ok = await confirmCost(opts.provider, \"token\", target, opts.yes);\n if (!ok) {\n console.log(chalk.dim(\"Cancelled.\"));\n return;\n }\n\n const spinner = ora(\n `Querying ${opts.provider} for token ${target}...`,\n ).start();\n\n try {\n const provider = getResearchProvider(opts.provider);\n const result = await provider.query({ type: \"token\", target });\n spinner.succeed(`Token data received from ${opts.provider}`);\n formatResearchResult(result);\n\n if (opts.post) {\n await postResearch(opts.post, result, `token ${target}`);\n }\n } catch (err) {\n spinner.fail(`Token query failed`);\n console.error(\n chalk.red(err instanceof Error ? err.message : String(err)),\n );\n process.exit(1);\n }\n });\n\n // ── research market <asset> ──\n\n research\n .command(\"market <asset>\")\n .description(\"Market overview — price, volume, market cap, ROI, ATH\")\n .requiredOption(\n \"--provider <name>\",\n \"Research provider (messari, nansen)\",\n )\n .option(\n \"--post <syndicate>\",\n \"Post result to syndicate chat (pin to IPFS + attest to EAS)\",\n )\n .option(\"--yes\", \"Skip cost confirmation prompt\", false)\n .action(async (asset: string, opts: { provider: string; post?: string; yes: boolean }) => {\n const ok = await confirmCost(opts.provider, \"market\", asset, opts.yes);\n if (!ok) {\n console.log(chalk.dim(\"Cancelled.\"));\n return;\n }\n\n const spinner = ora(\n `Querying ${opts.provider} for ${asset} market data...`,\n ).start();\n\n try {\n const provider = getResearchProvider(opts.provider);\n const result = await provider.query({ type: \"market\", target: asset });\n spinner.succeed(`Market data received from ${opts.provider}`);\n formatResearchResult(result);\n\n if (opts.post) {\n await postResearch(opts.post, result, `market ${asset}`);\n }\n } catch (err) {\n spinner.fail(`Market query failed`);\n console.error(\n chalk.red(err instanceof Error ? err.message : String(err)),\n );\n process.exit(1);\n }\n });\n\n // ── research smart-money ──\n\n research\n .command(\"smart-money\")\n .description(\n \"Smart money flows — net flow, DEX trades, holdings from labeled wallets\",\n )\n .requiredOption(\"--token <symbol>\", \"Token symbol to analyze (e.g. WETH)\")\n .requiredOption(\n \"--provider <name>\",\n \"Research provider (messari, nansen)\",\n )\n .option(\n \"--post <syndicate>\",\n \"Post result to syndicate chat (pin to IPFS + attest to EAS)\",\n )\n .option(\"--yes\", \"Skip cost confirmation prompt\", false)\n .action(async (opts: { token: string; provider: string; post?: string; yes: boolean }) => {\n const ok = await confirmCost(opts.provider, \"smart-money\", opts.token, opts.yes);\n if (!ok) {\n console.log(chalk.dim(\"Cancelled.\"));\n return;\n }\n\n const spinner = ora(\n `Querying ${opts.provider} for ${opts.token} smart money flows...`,\n ).start();\n\n try {\n const provider = getResearchProvider(opts.provider);\n const result = await provider.query({\n type: \"smart-money\",\n target: opts.token,\n options: { token: opts.token },\n });\n spinner.succeed(\n `Smart money data received from ${opts.provider}`,\n );\n formatResearchResult(result);\n\n if (opts.post) {\n await postResearch(\n opts.post,\n result,\n `smart-money --token ${opts.token}`,\n );\n }\n } catch (err) {\n spinner.fail(`Smart money query failed`);\n console.error(\n chalk.red(err instanceof Error ? err.message : String(err)),\n );\n process.exit(1);\n }\n });\n\n // ── research wallet <address> ──\n\n research\n .command(\"wallet <address>\")\n .description(\n \"Wallet due diligence — PnL history, transaction patterns, counterparties\",\n )\n .requiredOption(\n \"--provider <name>\",\n \"Research provider (messari, nansen)\",\n )\n .option(\n \"--post <syndicate>\",\n \"Post result to syndicate chat (pin to IPFS + attest to EAS)\",\n )\n .option(\"--yes\", \"Skip cost confirmation prompt\", false)\n .action(async (address: string, opts: { provider: string; post?: string; yes: boolean }) => {\n if (!isAddress(address)) {\n console.error(chalk.red(`Invalid wallet address: ${address}`));\n process.exit(1);\n }\n\n const ok = await confirmCost(opts.provider, \"wallet\", address, opts.yes);\n if (!ok) {\n console.log(chalk.dim(\"Cancelled.\"));\n return;\n }\n\n const spinner = ora(\n `Querying ${opts.provider} for wallet ${address.slice(0, 8)}...`,\n ).start();\n\n try {\n const provider = getResearchProvider(opts.provider);\n const result = await provider.query({\n type: \"wallet\",\n target: address,\n });\n spinner.succeed(`Wallet data received from ${opts.provider}`);\n formatResearchResult(result);\n\n if (opts.post) {\n await postResearch(opts.post, result, `wallet ${address}`);\n }\n } catch (err) {\n spinner.fail(`Wallet query failed`);\n console.error(\n chalk.red(err instanceof Error ? err.message : String(err)),\n );\n process.exit(1);\n }\n });\n}\n"],"mappings":"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","names":[]}
@@ -1,23 +1,25 @@
1
1
  import {
2
2
  resolveSyndicate
3
- } from "./chunk-ADPAZUTB.js";
3
+ } from "./chunk-5NA345RJ.js";
4
4
  import {
5
5
  SHERWOOD,
6
6
  SYNDICATE_VAULT_ABI
7
- } from "./chunk-AC4QMJKC.js";
7
+ } from "./chunk-UBBR7QVJ.js";
8
8
  import {
9
9
  getPublicClient
10
- } from "./chunk-FWJBUK57.js";
11
- import "./chunk-7CN3TSAA.js";
12
- import "./chunk-IIDZ2TK5.js";
10
+ } from "./chunk-CGSXIYYZ.js";
11
+ import "./chunk-BGEVQQES.js";
12
+ import "./chunk-BCOTZTTH.js";
13
+ import "./chunk-4CV4JOE5.js";
13
14
 
14
15
  // src/commands/session.ts
15
16
  import chalk from "chalk";
16
17
 
17
18
  // src/lib/session.ts
18
19
  import fs from "fs";
20
+ import os from "os";
19
21
  import path from "path";
20
- var SESSION_DIR = path.join(process.env.HOME || "~", ".sherwood");
22
+ var SESSION_DIR = path.join(os.homedir(), ".sherwood");
21
23
  var SESSION_PATH = path.join(SESSION_DIR, "session.json");
22
24
  function loadSessionState() {
23
25
  try {
@@ -81,7 +83,7 @@ var VAULT_EVENTS = [
81
83
  ];
82
84
  var GOVERNOR_EVENTS = [
83
85
  parseAbiItem("event ProposalCreated(uint256 indexed proposalId, address indexed proposer, address indexed vault, uint256 performanceFeeBps, uint256 strategyDuration, uint256 executeCallCount, uint256 settlementCallCount, string metadataURI)"),
84
- parseAbiItem("event VoteCast(uint256 indexed proposalId, address indexed voter, bool support, uint256 weight)"),
86
+ parseAbiItem("event VoteCast(uint256 indexed proposalId, address indexed voter, uint8 support, uint256 weight)"),
85
87
  parseAbiItem("event ProposalExecuted(uint256 indexed proposalId, address indexed vault, uint256 capitalSnapshot)"),
86
88
  parseAbiItem("event ProposalSettled(uint256 indexed proposalId, address indexed vault, int256 pnl, uint256 performanceFee, uint256 duration)"),
87
89
  parseAbiItem("event ProposalCancelled(uint256 indexed proposalId, address indexed cancelledBy)")
@@ -161,7 +163,7 @@ async function getCurrentBlock() {
161
163
 
162
164
  // src/commands/session.ts
163
165
  async function loadXmtp() {
164
- return import("./xmtp-EKFDS5Y3.js");
166
+ return import("./xmtp-Y3LAZKOC.js");
165
167
  }
166
168
  async function resolveGovernor(vaultAddress) {
167
169
  const client = getPublicClient();
@@ -385,9 +387,9 @@ function registerSessionCommands(program) {
385
387
  }
386
388
  );
387
389
  session.command("cron <name>").description("Manage participation crons (OpenClaw agents)").option("--remove", "Remove participation crons", false).option("--status", "Show cron status", false).action(async (name, opts) => {
388
- const { isOpenClaw, registerSyndicateCrons, unregisterSyndicateCrons, getSyndicateCronStatus } = await import("./cron-RG46PYWA.js");
389
- const { isTestnet } = await import("./network-ROF3SSAA.js");
390
- const { getNotifyTo } = await import("./config-U74QT4SC.js");
390
+ const { isOpenClaw, registerSyndicateCrons, unregisterSyndicateCrons, getSyndicateCronStatus } = await import("./cron-IKBNUL3Q.js");
391
+ const { isTestnet } = await import("./network-QU2D677V.js");
392
+ const { getNotifyTo } = await import("./config-5C7QJMPR.js");
391
393
  if (!isOpenClaw()) {
392
394
  console.log(chalk.yellow("Not running on OpenClaw \u2014 cron commands unavailable"));
393
395
  console.log(chalk.dim(` Set up your own scheduler: sherwood session check ${name} --stream`));
@@ -428,4 +430,4 @@ function registerSessionCommands(program) {
428
430
  export {
429
431
  registerSessionCommands
430
432
  };
431
- //# sourceMappingURL=session-ECETX4MM.js.map
433
+ //# sourceMappingURL=session-2VK25CSW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/session.ts","../src/lib/session.ts","../src/lib/events.ts"],"sourcesContent":["/**\n * Session commands — sherwood session check|status|reset\n *\n * Provides agent awareness: catch up on XMTP messages and on-chain events\n * since the last session. Supports one-shot polling (default) and persistent\n * streaming (--stream).\n *\n * Usage:\n * sherwood session check <name> — one-shot catch-up (JSON to stdout)\n * sherwood session check <name> --stream — persistent stream (JSON lines to stdout)\n * sherwood session status [name] — show session cursor positions\n * sherwood session reset <name> [--full] — reset session cursors\n */\n\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport type { Address } from \"viem\";\nimport { resolveSyndicate } from \"../lib/ens.js\";\nimport { SHERWOOD } from \"../lib/addresses.js\";\nimport { getPublicClient } from \"../lib/client.js\";\nimport { SYNDICATE_VAULT_ABI } from \"../lib/abis.js\";\nimport {\n getSession,\n updateSession,\n resetSession,\n getAllSessions,\n} from \"../lib/session.js\";\nimport {\n getVaultEvents,\n getGovernorEvents,\n getCurrentBlock,\n type ChainEvent,\n} from \"../lib/events.js\";\nimport type { ChatEnvelope } from \"../lib/types.js\";\n\n// Lazy-load XMTP to avoid breaking session commands when @xmtp/cli is missing\nasync function loadXmtp() {\n return import(\"../lib/xmtp.js\");\n}\n\n// ── Output types ──\n\ninterface SessionMessage {\n source: \"xmtp\";\n id: string;\n from: string;\n type: string;\n text: string;\n sentAt: string; // ISO 8601\n}\n\ninterface SessionCheckResult {\n syndicate: string;\n messages: SessionMessage[];\n events: ChainEvent[];\n meta: {\n newMessages: number;\n newEvents: number;\n blocksScanned: number;\n lastCheckAt: string; // ISO 8601\n };\n}\n\n// ── Helpers ──\n\n/** Resolve the governor address from the vault contract. */\nasync function resolveGovernor(vaultAddress: Address): Promise<Address> {\n const client = getPublicClient();\n try {\n const governor = await client.readContract({\n address: vaultAddress,\n abi: SYNDICATE_VAULT_ABI,\n functionName: \"governor\",\n });\n return governor as Address;\n } catch {\n // Vault might not have a governor set\n return SHERWOOD().GOVERNOR;\n }\n}\n\n/** Convert an XMTP message to session output format. */\nfunction toSessionMessage(msg: {\n id: string;\n senderInboxId: string;\n content: string;\n sentAt: Date;\n}): SessionMessage {\n let type = \"MESSAGE\";\n let text = msg.content;\n let from = msg.senderInboxId;\n\n try {\n const envelope: ChatEnvelope = JSON.parse(msg.content);\n type = envelope.type;\n text = envelope.text || envelope.type;\n from = envelope.from || msg.senderInboxId;\n } catch {\n // Plain text message\n }\n\n return {\n source: \"xmtp\",\n id: msg.id,\n from,\n type,\n text,\n sentAt: msg.sentAt.toISOString(),\n };\n}\n\n// ── Command handlers ──\n\nasync function handleCheck(name: string, stream: boolean): Promise<void> {\n // Resolve syndicate\n const syndicate = await resolveSyndicate(name);\n const vaultAddress = syndicate.vault;\n const governorAddress = await resolveGovernor(vaultAddress);\n\n // Load or initialize session\n const session = getSession(name);\n const currentBlock = await getCurrentBlock();\n\n // On first run: look back ~1000 blocks (~8 min on Base)\n const fromBlock = session?.lastBlockNumber\n ? BigInt(session.lastBlockNumber) + 1n\n : currentBlock > 1000n\n ? currentBlock - 1000n\n : 0n;\n\n const lastMessageTimestamp = session?.lastMessageTimestamp || 0;\n\n // ── Fetch XMTP messages ──\n let messages: SessionMessage[] = [];\n try {\n const xmtp = await loadXmtp();\n const groupId = await xmtp.getGroup(\"\", name);\n const recent = await xmtp.getRecentMessages(groupId, 100);\n\n // Filter to messages after our cursor (compare in ms for precision)\n const cursorMs = lastMessageTimestamp * 1000;\n const newMessages = recent.filter(\n (m) => m.sentAt.getTime() > cursorMs,\n );\n messages = newMessages.map(toSessionMessage);\n } catch {\n // XMTP not available or group not found — skip messages\n }\n\n // ── Fetch on-chain events ──\n let events: ChainEvent[] = [];\n if (fromBlock <= currentBlock) {\n const vaultEvents = await getVaultEvents(\n vaultAddress,\n fromBlock,\n currentBlock,\n );\n const govEvents = await getGovernorEvents(\n governorAddress,\n vaultAddress,\n fromBlock,\n currentBlock,\n );\n events = [...vaultEvents, ...govEvents].sort((a, b) => a.block - b.block);\n }\n\n // ── Output initial catch-up result ──\n const result: SessionCheckResult = {\n syndicate: name,\n messages,\n events,\n meta: {\n newMessages: messages.length,\n newEvents: events.length,\n blocksScanned: Number(currentBlock - fromBlock),\n lastCheckAt: session?.lastCheckAt\n ? new Date(session.lastCheckAt * 1000).toISOString()\n : \"never\",\n },\n };\n\n process.stdout.write(JSON.stringify(result) + \"\\n\");\n\n // ── Update session state ──\n // Find the newest message timestamp (messages may not be sorted)\n // Use ceil to ensure we don't re-fetch the same message due to sub-second precision\n let newestTimestamp = lastMessageTimestamp;\n let newestMessageId = session?.lastMessageId || \"\";\n for (const msg of messages) {\n const ts = Math.ceil(new Date(msg.sentAt).getTime() / 1000);\n if (ts > newestTimestamp) {\n newestTimestamp = ts;\n newestMessageId = msg.id;\n }\n }\n\n updateSession(name, {\n vault: vaultAddress,\n governor: governorAddress,\n lastBlockNumber: Number(currentBlock),\n lastCheckAt: Math.floor(Date.now() / 1000),\n lastMessageId: newestMessageId,\n lastMessageTimestamp: newestTimestamp,\n totalMessagesProcessed:\n (session?.totalMessagesProcessed || 0) + messages.length,\n totalEventsProcessed:\n (session?.totalEventsProcessed || 0) + events.length,\n });\n\n // ── If --stream, stay alive ──\n if (stream) {\n await startStream(name, vaultAddress, governorAddress);\n }\n}\n\nasync function startStream(\n name: string,\n vaultAddress: Address,\n governorAddress: Address,\n): Promise<void> {\n // Start XMTP message stream\n let xmtpCleanup: (() => void) | undefined;\n try {\n const xmtp = await loadXmtp();\n const groupId = await xmtp.getGroup(\"\", name);\n\n xmtpCleanup = await xmtp.streamMessages(groupId, (msg) => {\n const sessionMsg = toSessionMessage(msg);\n process.stdout.write(JSON.stringify(sessionMsg) + \"\\n\");\n\n // Update session state incrementally\n updateSession(name, {\n lastMessageId: msg.id,\n lastMessageTimestamp: Math.floor(msg.sentAt.getTime() / 1000),\n lastCheckAt: Math.floor(Date.now() / 1000),\n totalMessagesProcessed:\n (getSession(name)?.totalMessagesProcessed || 0) + 1,\n });\n });\n } catch {\n // XMTP not available — continue with event polling only\n }\n\n // Start on-chain event polling (~30s interval)\n const pollInterval = setInterval(async () => {\n try {\n const session = getSession(name);\n const fromBlock = BigInt(session?.lastBlockNumber || 0) + 1n;\n const toBlock = await getCurrentBlock();\n\n if (fromBlock > toBlock) return; // No new blocks\n\n const vaultEvents = await getVaultEvents(\n vaultAddress,\n fromBlock,\n toBlock,\n );\n const govEvents = await getGovernorEvents(\n governorAddress,\n vaultAddress,\n fromBlock,\n toBlock,\n );\n\n const events = [...vaultEvents, ...govEvents].sort(\n (a, b) => a.block - b.block,\n );\n\n for (const event of events) {\n process.stdout.write(JSON.stringify(event) + \"\\n\");\n }\n\n updateSession(name, {\n lastBlockNumber: Number(toBlock),\n lastCheckAt: Math.floor(Date.now() / 1000),\n totalEventsProcessed:\n (getSession(name)?.totalEventsProcessed || 0) + events.length,\n });\n } catch {\n // RPC error — skip this poll cycle\n }\n }, 30_000);\n\n // Clean up on exit\n const cleanup = () => {\n clearInterval(pollInterval);\n xmtpCleanup?.();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n\n // Keep alive\n await new Promise(() => {});\n}\n\nasync function handleStatus(name?: string): Promise<void> {\n const sessions = getAllSessions();\n\n if (name) {\n const session = sessions[name];\n if (!session) {\n console.log(\n chalk.dim(`No session found for \"${name}\". Run \"sherwood session check ${name}\" first.`),\n );\n return;\n }\n console.log(JSON.stringify({ [name]: formatSessionStatus(session) }, null, 2));\n return;\n }\n\n // Show all sessions\n if (Object.keys(sessions).length === 0) {\n console.log(chalk.dim(\"No sessions found. Run \\\"sherwood session check <name>\\\" to start.\"));\n return;\n }\n\n const output: Record<string, ReturnType<typeof formatSessionStatus>> = {};\n for (const [subdomain, session] of Object.entries(sessions)) {\n output[subdomain] = formatSessionStatus(session);\n }\n console.log(JSON.stringify(output, null, 2));\n}\n\nfunction formatSessionStatus(session: {\n lastCheckAt: number;\n lastBlockNumber: number;\n totalMessagesProcessed: number;\n totalEventsProcessed: number;\n}) {\n return {\n lastCheckAt: session.lastCheckAt\n ? new Date(session.lastCheckAt * 1000).toISOString()\n : \"never\",\n lastBlockNumber: session.lastBlockNumber,\n totalMessagesProcessed: session.totalMessagesProcessed,\n totalEventsProcessed: session.totalEventsProcessed,\n };\n}\n\nasync function handleReset(\n name: string,\n sinceBlock?: string,\n full?: boolean,\n): Promise<void> {\n if (full || !sinceBlock) {\n resetSession(name);\n console.log(chalk.green(`Session for \"${name}\" has been reset.`));\n } else {\n const block = parseInt(sinceBlock, 10);\n if (isNaN(block)) {\n console.error(chalk.red(\"--since-block must be a number\"));\n process.exit(1);\n }\n resetSession(name, block);\n console.log(\n chalk.green(`Block cursor for \"${name}\" reset to ${block}.`),\n );\n }\n}\n\n// ── Command Registration ──\n\nexport function registerSessionCommands(program: Command): void {\n const session = program\n .command(\"session\")\n .description(\"Agent session — catch up on messages + on-chain events\");\n\n session\n .command(\"check <name>\")\n .description(\"Fetch new XMTP messages and on-chain events since last check\")\n .option(\"--stream\", \"Stay alive streaming messages and polling events\", false)\n .action(async (name: string, opts: { stream: boolean }) => {\n await handleCheck(name, opts.stream);\n });\n\n session\n .command(\"status [name]\")\n .description(\"Show session cursor positions\")\n .action(async (name?: string) => {\n await handleStatus(name);\n });\n\n session\n .command(\"reset <name>\")\n .description(\"Reset session cursors\")\n .option(\"--since-block <n>\", \"Reset block cursor to a specific block\")\n .option(\"--full\", \"Reset everything (messages + events)\", false)\n .action(\n async (\n name: string,\n opts: { sinceBlock?: string; full: boolean },\n ) => {\n await handleReset(name, opts.sinceBlock, opts.full);\n },\n );\n\n session\n .command(\"cron <name>\")\n .description(\"Manage participation crons (OpenClaw agents)\")\n .option(\"--remove\", \"Remove participation crons\", false)\n .option(\"--status\", \"Show cron status\", false)\n .action(async (name: string, opts: { remove: boolean; status: boolean }) => {\n const { isOpenClaw, registerSyndicateCrons, unregisterSyndicateCrons, getSyndicateCronStatus } =\n await import(\"../lib/cron.js\");\n const { isTestnet } = await import(\"../lib/network.js\");\n const { getNotifyTo } = await import(\"../lib/config.js\");\n\n if (!isOpenClaw()) {\n console.log(chalk.yellow(\"Not running on OpenClaw — cron commands unavailable\"));\n console.log(chalk.dim(` Set up your own scheduler: sherwood session check ${name} --stream`));\n return;\n }\n\n if (opts.status) {\n const status = getSyndicateCronStatus(name, isTestnet());\n if (status.crons.length === 0) {\n console.log(chalk.dim(\"No participation crons found for \" + name));\n return;\n }\n console.log();\n console.log(chalk.bold(`Participation Crons — ${name}`));\n console.log(chalk.dim(\"─\".repeat(50)));\n for (const cron of status.crons) {\n console.log(` ${chalk.green(cron.name)} every ${cron.every}${cron.lastRun ? ` last: ${cron.lastRun}` : \"\"}`);\n }\n console.log();\n return;\n }\n\n if (opts.remove) {\n const result = unregisterSyndicateCrons(name, isTestnet());\n if (result.removed) {\n console.log(chalk.green(\"Participation crons removed\"));\n } else {\n console.log(chalk.dim(\"No crons found to remove\"));\n }\n return;\n }\n\n // Register/update\n const result = registerSyndicateCrons(name, isTestnet(), getNotifyTo());\n if (result.registered) {\n console.log(chalk.green(\"Participation crons registered: \" + result.cronNames.join(\", \")));\n } else {\n console.log(chalk.dim(\"Crons already registered\"));\n }\n });\n}\n","/**\n * Session state management — ~/.sherwood/session.json\n *\n * Tracks per-syndicate cursors for XMTP messages and on-chain events.\n * Enables agents to catch up on what they missed between sessions.\n */\n\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nconst SESSION_DIR = path.join(os.homedir(), \".sherwood\");\nconst SESSION_PATH = path.join(SESSION_DIR, \"session.json\");\n\nexport interface SyndicateSession {\n subdomain: string;\n vault: string; // vault address for event filtering\n governor: string; // governor address for proposal events\n // XMTP cursors\n lastMessageId: string;\n lastMessageTimestamp: number; // unix seconds\n // On-chain cursors\n lastBlockNumber: number;\n // Session metadata\n lastCheckAt: number; // unix seconds\n totalMessagesProcessed: number;\n totalEventsProcessed: number;\n}\n\ninterface SessionState {\n version: 1;\n syndicates: Record<string, SyndicateSession>;\n}\n\nexport function loadSessionState(): SessionState {\n try {\n if (fs.existsSync(SESSION_PATH)) {\n return JSON.parse(fs.readFileSync(SESSION_PATH, \"utf-8\"));\n }\n } catch {\n // Corrupted file — start fresh\n }\n return { version: 1, syndicates: {} };\n}\n\nexport function saveSessionState(state: SessionState): void {\n fs.mkdirSync(SESSION_DIR, { recursive: true });\n fs.writeFileSync(SESSION_PATH, JSON.stringify(state, null, 2));\n}\n\nexport function getSession(subdomain: string): SyndicateSession | undefined {\n const state = loadSessionState();\n return state.syndicates[subdomain];\n}\n\nexport function updateSession(\n subdomain: string,\n update: Partial<SyndicateSession>,\n): void {\n const state = loadSessionState();\n const existing = state.syndicates[subdomain] || {\n subdomain,\n vault: \"\",\n governor: \"\",\n lastMessageId: \"\",\n lastMessageTimestamp: 0,\n lastBlockNumber: 0,\n lastCheckAt: 0,\n totalMessagesProcessed: 0,\n totalEventsProcessed: 0,\n };\n state.syndicates[subdomain] = { ...existing, ...update };\n saveSessionState(state);\n}\n\nexport function resetSession(\n subdomain: string,\n sinceBlock?: number,\n): void {\n const state = loadSessionState();\n if (sinceBlock !== undefined) {\n // Partial reset — just move the block cursor\n if (state.syndicates[subdomain]) {\n state.syndicates[subdomain].lastBlockNumber = sinceBlock;\n saveSessionState(state);\n }\n } else {\n // Full reset\n delete state.syndicates[subdomain];\n saveSessionState(state);\n }\n}\n\nexport function getAllSessions(): Record<string, SyndicateSession> {\n return loadSessionState().syndicates;\n}\n","/**\n * On-chain event reader — fetches vault and governor events via viem getLogs.\n *\n * Uses HTTP RPC (no WebSocket needed). Block ranges are capped at 10,000\n * per call to avoid RPC timeouts (~83 minutes on Base at 2 blocks/sec).\n */\n\nimport type { Address, Log } from \"viem\";\nimport { parseAbiItem } from \"viem\";\nimport { getPublicClient } from \"./client.js\";\n\nconst MAX_BLOCK_RANGE = 10_000n;\n\n// ── Event signatures (parseAbiItem format for getLogs) ──\n\nconst VAULT_EVENTS = [\n parseAbiItem(\"event AgentRegistered(uint256 indexed agentId, address indexed agentAddress)\"),\n parseAbiItem(\"event AgentRemoved(address indexed agentAddress)\"),\n parseAbiItem(\"event DepositorApproved(address indexed depositor)\"),\n parseAbiItem(\"event DepositorRemoved(address indexed depositor)\"),\n parseAbiItem(\"event RedemptionsLockedEvent()\"),\n parseAbiItem(\"event RedemptionsUnlockedEvent()\"),\n] as const;\n\nconst GOVERNOR_EVENTS = [\n parseAbiItem(\"event ProposalCreated(uint256 indexed proposalId, address indexed proposer, address indexed vault, uint256 performanceFeeBps, uint256 strategyDuration, uint256 executeCallCount, uint256 settlementCallCount, string metadataURI)\"),\n parseAbiItem(\"event VoteCast(uint256 indexed proposalId, address indexed voter, uint8 support, uint256 weight)\"),\n parseAbiItem(\"event ProposalExecuted(uint256 indexed proposalId, address indexed vault, uint256 capitalSnapshot)\"),\n parseAbiItem(\"event ProposalSettled(uint256 indexed proposalId, address indexed vault, int256 pnl, uint256 performanceFee, uint256 duration)\"),\n parseAbiItem(\"event ProposalCancelled(uint256 indexed proposalId, address indexed cancelledBy)\"),\n] as const;\n\n/** Normalized event returned by the session check. */\nexport interface ChainEvent {\n source: \"chain\";\n type: string;\n block: number;\n tx: string;\n args: Record<string, string>;\n}\n\nfunction logToChainEvent(log: Log, eventName: string): ChainEvent {\n const args: Record<string, string> = {};\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const decoded = (log as any).args;\n if (decoded) {\n for (const [key, value] of Object.entries(decoded)) {\n args[key] = String(value);\n }\n }\n\n return {\n source: \"chain\",\n type: eventName,\n block: Number(log.blockNumber),\n tx: log.transactionHash || \"\",\n args,\n };\n}\n\n/**\n * Fetch events in chunks to stay within RPC limits.\n * Returns all logs from `fromBlock` to `toBlock` inclusive.\n */\nasync function getLogsChunked(params: {\n address: Address;\n events: readonly ReturnType<typeof parseAbiItem>[];\n fromBlock: bigint;\n toBlock: bigint;\n}): Promise<Log[]> {\n const client = getPublicClient();\n const { address, events, fromBlock, toBlock } = params;\n\n const allLogs: Log[] = [];\n let cursor = fromBlock;\n\n while (cursor <= toBlock) {\n const end =\n cursor + MAX_BLOCK_RANGE - 1n > toBlock\n ? toBlock\n : cursor + MAX_BLOCK_RANGE - 1n;\n\n const logs = await client.getLogs({\n address,\n events: events as never,\n fromBlock: cursor,\n toBlock: end,\n });\n\n allLogs.push(...logs);\n cursor = end + 1n;\n }\n\n return allLogs;\n}\n\n/** Fetch vault events (AgentRegistered, Ragequit, etc.) */\nexport async function getVaultEvents(\n vaultAddress: Address,\n fromBlock: bigint,\n toBlock: bigint,\n): Promise<ChainEvent[]> {\n const logs = await getLogsChunked({\n address: vaultAddress,\n events: VAULT_EVENTS,\n fromBlock,\n toBlock,\n });\n\n return logs.map((log) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const eventName = (log as any).eventName || \"UnknownVaultEvent\";\n return logToChainEvent(log, eventName);\n });\n}\n\n/** Fetch governor events filtered to a specific vault. */\nexport async function getGovernorEvents(\n governorAddress: Address,\n vaultAddress: Address,\n fromBlock: bigint,\n toBlock: bigint,\n): Promise<ChainEvent[]> {\n if (governorAddress === \"0x0000000000000000000000000000000000000000\") {\n return []; // Governor not deployed yet\n }\n\n const logs = await getLogsChunked({\n address: governorAddress,\n events: GOVERNOR_EVENTS,\n fromBlock,\n toBlock,\n });\n\n // Filter governor events to only those involving our vault\n return logs\n .filter((log) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const args = (log as any).args;\n if (!args) return true;\n // ProposalCreated, ProposalExecuted, ProposalSettled have `vault` in args\n if (args.vault) {\n return (args.vault as string).toLowerCase() === vaultAddress.toLowerCase();\n }\n // VoteCast, ProposalCancelled don't have vault — include them\n // (agents care about all votes/cancellations on their governor)\n return true;\n })\n .map((log) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const eventName = (log as any).eventName || \"UnknownGovernorEvent\";\n return logToChainEvent(log, eventName);\n });\n}\n\n/** Get current block number from the RPC. */\nexport async function getCurrentBlock(): Promise<bigint> {\n const client = getPublicClient();\n return client.getBlockNumber();\n}\n"],"mappings":"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","names":["result"]}
@@ -1,21 +1,24 @@
1
1
  import {
2
2
  getTextRecord
3
- } from "./chunk-ADPAZUTB.js";
4
- import "./chunk-AC4QMJKC.js";
3
+ } from "./chunk-5NA345RJ.js";
4
+ import "./chunk-UBBR7QVJ.js";
5
5
  import {
6
6
  getAccount
7
- } from "./chunk-FWJBUK57.js";
7
+ } from "./chunk-CGSXIYYZ.js";
8
8
  import {
9
9
  getChainConfig
10
- } from "./chunk-7CN3TSAA.js";
10
+ } from "./chunk-BGEVQQES.js";
11
11
  import {
12
12
  cacheGroupId,
13
13
  getCachedGroupId,
14
+ init_config,
14
15
  loadConfig,
15
16
  saveConfig
16
- } from "./chunk-IIDZ2TK5.js";
17
+ } from "./chunk-BCOTZTTH.js";
18
+ import "./chunk-4CV4JOE5.js";
17
19
 
18
20
  // src/lib/xmtp.ts
21
+ init_config();
19
22
  import { execFileSync, spawn, execSync } from "child_process";
20
23
  import fs from "fs";
21
24
  import path from "path";
@@ -179,7 +182,7 @@ async function createSyndicateGroup(_client, subdomain, isPublic = false) {
179
182
  if (!groupId) {
180
183
  throw new Error("Failed to parse group ID from xmtp CLI output");
181
184
  }
182
- const SPECTATOR_ADDRESS = process.env.DASHBOARD_SPECTATOR_ADDRESS || "0xec00089b73fbd0733cc11ee39f81404cbc9c9786";
185
+ const SPECTATOR_ADDRESS = process.env.DASHBOARD_SPECTATOR_ADDRESS || "0x9f6518e69a62c526ead155ad2661f5957b6b2fc3";
183
186
  if (isPublic) {
184
187
  await addMember(groupId, SPECTATOR_ADDRESS);
185
188
  }
@@ -364,4 +367,4 @@ export {
364
367
  sendReaction,
365
368
  streamMessages
366
369
  };
367
- //# sourceMappingURL=xmtp-EKFDS5Y3.js.map
370
+ //# sourceMappingURL=xmtp-Y3LAZKOC.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/xmtp.ts"],"sourcesContent":["/**\n * XMTP client and group operations for syndicate chat.\n *\n * Shells out to the @xmtp/cli binary instead of using @xmtp/node-sdk directly.\n * This avoids native binding (GLIBC) issues on Linux.\n *\n * Credentials: the sherwood private key is passed to each subprocess via the\n * XMTP_WALLET_KEY env var. We never write to ~/.xmtp/.env — the XMTP CLI\n * manages its own DB encryption key and env file. This avoids destroying\n * existing XMTP setups when agents already have the CLI configured.\n */\n\nimport { execFileSync, spawn, execSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { homedir } from \"node:os\";\nimport {\n loadConfig,\n saveConfig,\n cacheGroupId,\n getCachedGroupId,\n} from \"./config.js\";\nimport { getTextRecord } from \"./ens.js\";\nimport { getChainConfig } from \"./network.js\";\nimport type { ChatEnvelope } from \"./types.js\";\nimport { getAccount } from \"./client.js\";\n\n// ── Types ──\n\nexport interface XmtpMessage {\n id: string;\n conversationId: string;\n senderInboxId: string;\n contentType: string;\n content: string;\n sentAt: Date;\n}\n\nexport interface XmtpMember {\n inboxId: string;\n permissionLevel: string;\n}\n\n// ── Binary resolution ──\n\nlet _binaryPath: string | null = null;\n\nfunction getXmtpBinaryPath(): string {\n if (_binaryPath) return _binaryPath;\n\n // Try local node_modules/@xmtp/cli/bin/run.js relative to this file\n const searchPaths = [\n // From dist/ after build\n path.resolve(import.meta.dirname, \"..\", \"node_modules\", \"@xmtp\", \"cli\", \"bin\", \"run.js\"),\n // From src/ during dev\n path.resolve(import.meta.dirname, \"..\", \"..\", \"node_modules\", \"@xmtp\", \"cli\", \"bin\", \"run.js\"),\n // From cwd\n path.resolve(process.cwd(), \"node_modules\", \"@xmtp\", \"cli\", \"bin\", \"run.js\"),\n ];\n\n for (const p of searchPaths) {\n if (fs.existsSync(p)) {\n _binaryPath = p;\n return _binaryPath;\n }\n }\n\n // Fall back to system PATH\n try {\n const which = execSync(\"which xmtp\", { encoding: \"utf8\" }).trim();\n if (which) {\n _binaryPath = which;\n return _binaryPath;\n }\n } catch {\n // Not on PATH\n }\n\n throw new Error(\n \"XMTP CLI not found. Install with: npm install -g @xmtp/cli\",\n );\n}\n\n// ── Environment ──\n\nfunction getXmtpEnv(): string {\n return getChainConfig().xmtpEnv;\n}\n\nfunction getXmtpEnvFile(): string {\n return path.join(homedir(), \".xmtp\", \".env\");\n}\n\n/**\n * Ensure ~/.xmtp/.env exists with at least the wallet key and a DB encryption key.\n * If the file already exists, only update the wallet key line — preserve everything\n * else (especially XMTP_DB_ENCRYPTION_KEY). If no file exists, run `xmtp init` to\n * let the CLI generate its own keys, then patch in our wallet key.\n */\nlet _envReady = false;\n\nfunction ensureXmtpEnv(): void {\n if (_envReady) return;\n\n const config = loadConfig();\n if (!config.privateKey) {\n throw new Error(\n 'No private key configured. Run \"sherwood config set --private-key 0x...\"',\n );\n }\n\n const envFile = getXmtpEnvFile();\n const walletKey = config.privateKey.replace(/^0x/, \"\");\n\n if (fs.existsSync(envFile)) {\n const existing = fs.readFileSync(envFile, \"utf8\");\n if (existing.includes(`XMTP_WALLET_KEY=${walletKey}`)) {\n _envReady = true;\n return;\n }\n\n // Update wallet key while preserving all other vars (DB encryption key, etc.)\n const lines = existing.split(\"\\n\").filter((l) => !l.startsWith(\"XMTP_WALLET_KEY=\"));\n lines.push(`XMTP_WALLET_KEY=${walletKey}`);\n fs.writeFileSync(envFile, lines.filter(Boolean).join(\"\\n\") + \"\\n\", { mode: 0o600 });\n } else {\n // No env file — let XMTP CLI generate keys via `init`, then patch wallet key\n const xmtpDir = path.join(homedir(), \".xmtp\");\n fs.mkdirSync(xmtpDir, { recursive: true });\n\n const bin = getXmtpBinaryPath();\n const initArgs = [\"init\", \"--env\", getXmtpEnv()];\n if (bin.endsWith(\".js\")) {\n execFileSync(\"node\", [bin, ...initArgs], {\n encoding: \"utf8\",\n timeout: 30_000,\n env: { ...process.env, XMTP_WALLET_KEY: walletKey },\n });\n } else {\n execFileSync(bin, initArgs, {\n encoding: \"utf8\",\n timeout: 30_000,\n env: { ...process.env, XMTP_WALLET_KEY: walletKey },\n });\n }\n\n // Patch in our wallet key (init may have generated a different one)\n if (fs.existsSync(envFile)) {\n const content = fs.readFileSync(envFile, \"utf8\");\n const lines = content.split(\"\\n\").filter((l) => !l.startsWith(\"XMTP_WALLET_KEY=\"));\n lines.push(`XMTP_WALLET_KEY=${walletKey}`);\n fs.writeFileSync(envFile, lines.filter(Boolean).join(\"\\n\") + \"\\n\", { mode: 0o600 });\n } else {\n // init didn't create the file — write a minimal one\n fs.writeFileSync(envFile, `XMTP_WALLET_KEY=${walletKey}\\n`, { mode: 0o600 });\n }\n }\n\n _envReady = true;\n}\n\n// ── Subprocess runners ──\n\nfunction execXmtp(args: string[]): string {\n ensureXmtpEnv();\n const bin = getXmtpBinaryPath();\n const fullArgs = [...args, \"--env\", getXmtpEnv(), \"--env-file\", getXmtpEnvFile()];\n\n if (bin.endsWith(\".js\")) {\n return execFileSync(\"node\", [bin, ...fullArgs], {\n encoding: \"utf8\",\n timeout: 30_000,\n }).trim();\n }\n\n return execFileSync(bin, fullArgs, {\n encoding: \"utf8\",\n timeout: 30_000,\n }).trim();\n}\n\nfunction execXmtpJson<T>(args: string[]): T {\n const stdout = execXmtp([...args, \"--json\", \"--log-level\", \"off\"]);\n return JSON.parse(stdout) as T;\n}\n\n// ── Conversation sync ──\n\nlet _conversationsSynced = false;\n\n/**\n * Sync conversations from the network into the local XMTP DB.\n * One-shot commands (send, messages, members) spawn a fresh process\n * that may not have the group locally — this ensures it's available.\n *\n * Uses `sync-all` instead of `sync` because `sync` only refreshes\n * already-known conversations. `sync-all` also processes MLS welcome\n * messages, which is required for agents that were added to a group\n * by someone else. Only runs once per process.\n */\nfunction syncConversations(): void {\n if (_conversationsSynced) return;\n execXmtp([\"conversations\", \"sync-all\"]);\n _conversationsSynced = true;\n}\n\n// ── Stale installation cleanup ──\n\n/**\n * Revoke stale XMTP installations for the current wallet.\n *\n * When an agent wipes ~/.xmtp/ and re-initializes, a new MLS installation is\n * created but old ones remain registered on the network. When add-members runs,\n * the MLS welcome may target a stale installation's KeyPackage — the current\n * installation can never sync that group.\n *\n * This function detects and revokes all installations except the current one,\n * ensuring add-members targets the right installation.\n */\nfunction revokeStaleInstallations(inboxId: string, currentInstallationId: string): void {\n try {\n // Get all installations for this inbox from the network\n const inboxStates = execXmtpJson<Array<{\n inboxId: string;\n installations: Array<{ id: string }>;\n }>>([\"inbox-states\", inboxId]);\n\n const state = inboxStates?.[0];\n if (!state?.installations || state.installations.length <= 1) return;\n\n const staleIds = state.installations\n .map((i) => i.id)\n .filter((id) => id !== currentInstallationId);\n\n if (staleIds.length === 0) return;\n\n // Revoke stale installations — only the wallet owner can do this\n execXmtp([\n \"revoke-installations\",\n inboxId,\n \"-i\",\n staleIds.join(\",\"),\n \"--force\",\n ]);\n } catch {\n // Non-fatal — stale installations are a UX issue, not a blocker\n }\n}\n\n// ── Client ──\n\nexport async function getXmtpClient(): Promise<string> {\n // client info returns { properties: { inboxId, installationId, ... }, options: { ... } }\n const result = execXmtpJson<{\n properties: {\n inboxId: string;\n installationId: string;\n };\n }>([\"client\", \"info\"]);\n\n const { inboxId, installationId } = result.properties;\n\n // Cache inbox ID\n const config = loadConfig();\n if (!config.xmtpInboxId && inboxId) {\n config.xmtpInboxId = inboxId;\n saveConfig(config);\n }\n\n // Clean up stale installations so add-members targets the current one\n if (inboxId && installationId) {\n revokeStaleInstallations(inboxId, installationId);\n }\n\n return inboxId;\n}\n\n// ── Group Creation ──\n\nexport async function createSyndicateGroup(\n _client: string,\n subdomain: string,\n isPublic: boolean = false,\n): Promise<string> {\n // CLI requires at least one member address; use creator's own address\n // (creator is auto-added as super admin regardless)\n const creatorAddress = getAccount().address;\n const result = execXmtpJson<{ id?: string; conversationId?: string; groupId?: string }>(\n [\n \"conversations\",\n \"create-group\",\n creatorAddress,\n \"--name\",\n subdomain,\n \"--description\",\n `Sherwood syndicate: ${subdomain}.sherwoodagent.eth`,\n \"--permissions\",\n \"admin-only\",\n ],\n );\n\n const groupId = result.id || result.conversationId || result.groupId;\n if (!groupId) {\n throw new Error(\"Failed to parse group ID from xmtp CLI output\");\n }\n\n // Add spectator if requested — enables dashboard live feed\n const SPECTATOR_ADDRESS = process.env.DASHBOARD_SPECTATOR_ADDRESS || \"0xec00089b73fbd0733cc11ee39f81404cbc9c9786\";\n if (isPublic) {\n await addMember(groupId, SPECTATOR_ADDRESS);\n }\n\n // Cache locally\n cacheGroupId(subdomain, groupId);\n\n return groupId;\n}\n\n// ── Group Lookup ──\n\n/**\n * Check if a conversation exists in the local XMTP DB.\n * Returns true if `conversations get <id>` succeeds.\n */\nfunction conversationExists(groupId: string): boolean {\n try {\n execXmtp([\"conversations\", \"get\", groupId]);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Search synced conversations for a group matching the syndicate name.\n * Falls back to this when the cached/ENS group ID is stale.\n */\nfunction findGroupByName(subdomain: string): string | null {\n try {\n const conversations = execXmtpJson<Array<{ id?: string; name?: string; description?: string }>>(\n [\"conversations\", \"list\", \"--type\", \"group\"],\n );\n if (!Array.isArray(conversations)) return null;\n\n const match = conversations.find(\n (c) => c.name === subdomain || c.description?.includes(`${subdomain}.sherwoodagent.eth`),\n );\n return match?.id || null;\n } catch {\n return null;\n }\n}\n\nexport async function getGroup(\n _client: string,\n subdomain: string,\n): Promise<string> {\n // Sync first so we have the latest conversations locally\n syncConversations();\n\n // Try local cache\n let groupId: string | undefined = getCachedGroupId(subdomain);\n\n // Validate cached ID actually exists in the local DB\n if (groupId && !conversationExists(groupId)) {\n cacheGroupId(subdomain, \"\"); // invalidate stale entry\n groupId = undefined;\n }\n\n // Fall back to on-chain ENS text record\n if (!groupId) {\n const ensId = await getTextRecord(subdomain, \"xmtpGroupId\");\n if (ensId && conversationExists(ensId)) {\n groupId = ensId;\n cacheGroupId(subdomain, groupId);\n }\n }\n\n // Last resort: search synced conversations by name\n if (!groupId) {\n const found = findGroupByName(subdomain);\n if (found) {\n groupId = found;\n cacheGroupId(subdomain, groupId);\n }\n }\n\n if (!groupId) {\n throw new Error(\n `No XMTP group found for syndicate \"${subdomain}\". Run \"sherwood chat ${subdomain} init\" to create one.`,\n );\n }\n\n return groupId;\n}\n\n// ── Member Management ──\n\nexport async function addMember(\n groupId: string,\n address: string,\n): Promise<void> {\n // Verify the target has an active XMTP identity before adding\n const reachable = execXmtpJson<Array<{ identifier: string; reachable: boolean }>>(\n [\"can-message\", address],\n );\n if (!reachable?.[0]?.reachable) {\n throw new Error(\n `${address} is not reachable on XMTP. They need to initialize their client first (run: xmtp client info --env ${getXmtpEnv()}).`,\n );\n }\n\n syncConversations();\n execXmtp([\"conversation\", \"add-members\", groupId, address]);\n\n // Set consent to \"allowed\" on the adder's side so subsequent operations\n // (send, messages) don't skip this conversation due to unknown consent state\n try {\n execXmtp([\"conversation\", \"update-consent\", groupId, \"--state\", \"allowed\"]);\n } catch {\n // Non-fatal — consent state is a filtering concern, not a blocker\n }\n}\n\nexport async function removeMember(\n groupId: string,\n address: string,\n): Promise<void> {\n syncConversations();\n execXmtp([\"conversation\", \"remove-members\", groupId, address]);\n}\n\n// ── Messaging ──\n\nexport async function sendEnvelope(\n groupId: string,\n envelope: ChatEnvelope,\n): Promise<void> {\n const text = JSON.stringify(envelope);\n execXmtp([\"conversation\", \"send-text\", groupId, text]);\n}\n\nexport async function sendMarkdown(\n groupId: string,\n markdown: string,\n): Promise<void> {\n const envelope: ChatEnvelope = {\n type: \"MESSAGE\",\n from: getAccount().address,\n text: markdown,\n data: { format: \"markdown\" },\n timestamp: Math.floor(Date.now() / 1000),\n };\n await sendEnvelope(groupId, envelope);\n}\n\nexport async function sendReaction(\n groupId: string,\n messageId: string,\n emoji: string,\n): Promise<void> {\n const envelope: ChatEnvelope = {\n type: \"REACTION\",\n from: getAccount().address,\n data: { reference: messageId, emoji },\n timestamp: Math.floor(Date.now() / 1000),\n };\n await sendEnvelope(groupId, envelope);\n}\n\n// ── Streaming ──\n\nexport async function streamMessages(\n groupId: string,\n onMessage: (msg: XmtpMessage) => void,\n): Promise<() => void> {\n ensureXmtpEnv();\n const bin = getXmtpBinaryPath();\n\n const args = [\n \"conversations\",\n \"stream-all-messages\",\n \"--json\",\n \"--log-level\",\n \"off\",\n \"--env\",\n getXmtpEnv(),\n \"--env-file\",\n getXmtpEnvFile(),\n ];\n\n const proc = bin.endsWith(\".js\")\n ? spawn(\"node\", [bin, ...args])\n : spawn(bin, args);\n\n let buffer = \"\";\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString();\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const msg = JSON.parse(line);\n // Filter to our group\n if (msg.conversationId === groupId) {\n onMessage({\n id: msg.id || \"\",\n conversationId: msg.conversationId || \"\",\n senderInboxId: msg.senderInboxId || \"\",\n contentType: msg.contentType?.typeId || \"text\",\n content: typeof msg.content === \"string\" ? msg.content : JSON.stringify(msg.content),\n sentAt: new Date(msg.sentAt || Date.now()),\n });\n }\n } catch {\n // Skip unparseable lines\n }\n }\n });\n\n // Return cleanup function\n return () => {\n proc.kill(\"SIGTERM\");\n };\n}\n\n// ── Message History ──\n\nexport async function getRecentMessages(\n groupId: string,\n limit: number = 20,\n): Promise<XmtpMessage[]> {\n const raw = execXmtpJson<Array<Record<string, unknown>>>([\n \"conversation\",\n \"messages\",\n groupId,\n ]);\n\n const messages: XmtpMessage[] = (Array.isArray(raw) ? raw : []).map((m) => ({\n id: String(m.id || \"\"),\n conversationId: String(m.conversationId || \"\"),\n senderInboxId: String(m.senderInboxId || \"\"),\n contentType: String(\n (m.contentType as Record<string, unknown>)?.typeId || \"text\",\n ),\n content:\n typeof m.content === \"string\" ? m.content : JSON.stringify(m.content),\n sentAt: new Date((m.sentAt as string) || Date.now()),\n }));\n\n return messages.slice(-limit);\n}\n\n// ── Members ──\n\nexport async function getMembers(\n groupId: string,\n): Promise<XmtpMember[]> {\n const raw = execXmtpJson<Array<Record<string, unknown>>>([\n \"conversation\",\n \"members\",\n groupId,\n ]);\n\n // permissionLevel from CLI: 0 = member, 1 = admin, 2 = super_admin\n const levelMap: Record<number, string> = { 0: \"member\", 1: \"admin\", 2: \"super_admin\" };\n return (Array.isArray(raw) ? raw : []).map((m) => ({\n inboxId: String(m.inboxId || \"\"),\n permissionLevel: levelMap[Number(m.permissionLevel)] || \"member\",\n }));\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/lib/xmtp.ts"],"sourcesContent":["/**\n * XMTP client and group operations for syndicate chat.\n *\n * Shells out to the @xmtp/cli binary instead of using @xmtp/node-sdk directly.\n * This avoids native binding (GLIBC) issues on Linux.\n *\n * Credentials: the sherwood private key is passed to each subprocess via the\n * XMTP_WALLET_KEY env var. We never write to ~/.xmtp/.env — the XMTP CLI\n * manages its own DB encryption key and env file. This avoids destroying\n * existing XMTP setups when agents already have the CLI configured.\n */\n\nimport { execFileSync, spawn, execSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { homedir } from \"node:os\";\nimport {\n loadConfig,\n saveConfig,\n cacheGroupId,\n getCachedGroupId,\n} from \"./config.js\";\nimport { getTextRecord } from \"./ens.js\";\nimport { getChainConfig } from \"./network.js\";\nimport type { ChatEnvelope } from \"./types.js\";\nimport { getAccount } from \"./client.js\";\n\n// ── Types ──\n\nexport interface XmtpMessage {\n id: string;\n conversationId: string;\n senderInboxId: string;\n contentType: string;\n content: string;\n sentAt: Date;\n}\n\nexport interface XmtpMember {\n inboxId: string;\n permissionLevel: string;\n}\n\n// ── Binary resolution ──\n\nlet _binaryPath: string | null = null;\n\nfunction getXmtpBinaryPath(): string {\n if (_binaryPath) return _binaryPath;\n\n // Try local node_modules/@xmtp/cli/bin/run.js relative to this file\n const searchPaths = [\n // From dist/ after build\n path.resolve(import.meta.dirname, \"..\", \"node_modules\", \"@xmtp\", \"cli\", \"bin\", \"run.js\"),\n // From src/ during dev\n path.resolve(import.meta.dirname, \"..\", \"..\", \"node_modules\", \"@xmtp\", \"cli\", \"bin\", \"run.js\"),\n // From cwd\n path.resolve(process.cwd(), \"node_modules\", \"@xmtp\", \"cli\", \"bin\", \"run.js\"),\n ];\n\n for (const p of searchPaths) {\n if (fs.existsSync(p)) {\n _binaryPath = p;\n return _binaryPath;\n }\n }\n\n // Fall back to system PATH\n try {\n const which = execSync(\"which xmtp\", { encoding: \"utf8\" }).trim();\n if (which) {\n _binaryPath = which;\n return _binaryPath;\n }\n } catch {\n // Not on PATH\n }\n\n throw new Error(\n \"XMTP CLI not found. Install with: npm install -g @xmtp/cli\",\n );\n}\n\n// ── Environment ──\n\nfunction getXmtpEnv(): string {\n return getChainConfig().xmtpEnv;\n}\n\nfunction getXmtpEnvFile(): string {\n return path.join(homedir(), \".xmtp\", \".env\");\n}\n\n/**\n * Ensure ~/.xmtp/.env exists with at least the wallet key and a DB encryption key.\n * If the file already exists, only update the wallet key line — preserve everything\n * else (especially XMTP_DB_ENCRYPTION_KEY). If no file exists, run `xmtp init` to\n * let the CLI generate its own keys, then patch in our wallet key.\n */\nlet _envReady = false;\n\nfunction ensureXmtpEnv(): void {\n if (_envReady) return;\n\n const config = loadConfig();\n if (!config.privateKey) {\n throw new Error(\n 'No private key configured. Run \"sherwood config set --private-key 0x...\"',\n );\n }\n\n const envFile = getXmtpEnvFile();\n const walletKey = config.privateKey.replace(/^0x/, \"\");\n\n if (fs.existsSync(envFile)) {\n const existing = fs.readFileSync(envFile, \"utf8\");\n if (existing.includes(`XMTP_WALLET_KEY=${walletKey}`)) {\n _envReady = true;\n return;\n }\n\n // Update wallet key while preserving all other vars (DB encryption key, etc.)\n const lines = existing.split(\"\\n\").filter((l) => !l.startsWith(\"XMTP_WALLET_KEY=\"));\n lines.push(`XMTP_WALLET_KEY=${walletKey}`);\n fs.writeFileSync(envFile, lines.filter(Boolean).join(\"\\n\") + \"\\n\", { mode: 0o600 });\n } else {\n // No env file — let XMTP CLI generate keys via `init`, then patch wallet key\n const xmtpDir = path.join(homedir(), \".xmtp\");\n fs.mkdirSync(xmtpDir, { recursive: true });\n\n const bin = getXmtpBinaryPath();\n const initArgs = [\"init\", \"--env\", getXmtpEnv()];\n if (bin.endsWith(\".js\")) {\n execFileSync(\"node\", [bin, ...initArgs], {\n encoding: \"utf8\",\n timeout: 30_000,\n env: { ...process.env, XMTP_WALLET_KEY: walletKey },\n });\n } else {\n execFileSync(bin, initArgs, {\n encoding: \"utf8\",\n timeout: 30_000,\n env: { ...process.env, XMTP_WALLET_KEY: walletKey },\n });\n }\n\n // Patch in our wallet key (init may have generated a different one)\n if (fs.existsSync(envFile)) {\n const content = fs.readFileSync(envFile, \"utf8\");\n const lines = content.split(\"\\n\").filter((l) => !l.startsWith(\"XMTP_WALLET_KEY=\"));\n lines.push(`XMTP_WALLET_KEY=${walletKey}`);\n fs.writeFileSync(envFile, lines.filter(Boolean).join(\"\\n\") + \"\\n\", { mode: 0o600 });\n } else {\n // init didn't create the file — write a minimal one\n fs.writeFileSync(envFile, `XMTP_WALLET_KEY=${walletKey}\\n`, { mode: 0o600 });\n }\n }\n\n _envReady = true;\n}\n\n// ── Subprocess runners ──\n\nfunction execXmtp(args: string[]): string {\n ensureXmtpEnv();\n const bin = getXmtpBinaryPath();\n const fullArgs = [...args, \"--env\", getXmtpEnv(), \"--env-file\", getXmtpEnvFile()];\n\n if (bin.endsWith(\".js\")) {\n return execFileSync(\"node\", [bin, ...fullArgs], {\n encoding: \"utf8\",\n timeout: 30_000,\n }).trim();\n }\n\n return execFileSync(bin, fullArgs, {\n encoding: \"utf8\",\n timeout: 30_000,\n }).trim();\n}\n\nfunction execXmtpJson<T>(args: string[]): T {\n const stdout = execXmtp([...args, \"--json\", \"--log-level\", \"off\"]);\n return JSON.parse(stdout) as T;\n}\n\n// ── Conversation sync ──\n\nlet _conversationsSynced = false;\n\n/**\n * Sync conversations from the network into the local XMTP DB.\n * One-shot commands (send, messages, members) spawn a fresh process\n * that may not have the group locally — this ensures it's available.\n *\n * Uses `sync-all` instead of `sync` because `sync` only refreshes\n * already-known conversations. `sync-all` also processes MLS welcome\n * messages, which is required for agents that were added to a group\n * by someone else. Only runs once per process.\n */\nfunction syncConversations(): void {\n if (_conversationsSynced) return;\n execXmtp([\"conversations\", \"sync-all\"]);\n _conversationsSynced = true;\n}\n\n// ── Stale installation cleanup ──\n\n/**\n * Revoke stale XMTP installations for the current wallet.\n *\n * When an agent wipes ~/.xmtp/ and re-initializes, a new MLS installation is\n * created but old ones remain registered on the network. When add-members runs,\n * the MLS welcome may target a stale installation's KeyPackage — the current\n * installation can never sync that group.\n *\n * This function detects and revokes all installations except the current one,\n * ensuring add-members targets the right installation.\n */\nfunction revokeStaleInstallations(inboxId: string, currentInstallationId: string): void {\n try {\n // Get all installations for this inbox from the network\n const inboxStates = execXmtpJson<Array<{\n inboxId: string;\n installations: Array<{ id: string }>;\n }>>([\"inbox-states\", inboxId]);\n\n const state = inboxStates?.[0];\n if (!state?.installations || state.installations.length <= 1) return;\n\n const staleIds = state.installations\n .map((i) => i.id)\n .filter((id) => id !== currentInstallationId);\n\n if (staleIds.length === 0) return;\n\n // Revoke stale installations — only the wallet owner can do this\n execXmtp([\n \"revoke-installations\",\n inboxId,\n \"-i\",\n staleIds.join(\",\"),\n \"--force\",\n ]);\n } catch {\n // Non-fatal — stale installations are a UX issue, not a blocker\n }\n}\n\n// ── Client ──\n\nexport async function getXmtpClient(): Promise<string> {\n // client info returns { properties: { inboxId, installationId, ... }, options: { ... } }\n const result = execXmtpJson<{\n properties: {\n inboxId: string;\n installationId: string;\n };\n }>([\"client\", \"info\"]);\n\n const { inboxId, installationId } = result.properties;\n\n // Cache inbox ID\n const config = loadConfig();\n if (!config.xmtpInboxId && inboxId) {\n config.xmtpInboxId = inboxId;\n saveConfig(config);\n }\n\n // Clean up stale installations so add-members targets the current one\n if (inboxId && installationId) {\n revokeStaleInstallations(inboxId, installationId);\n }\n\n return inboxId;\n}\n\n// ── Group Creation ──\n\nexport async function createSyndicateGroup(\n _client: string,\n subdomain: string,\n isPublic: boolean = false,\n): Promise<string> {\n // CLI requires at least one member address; use creator's own address\n // (creator is auto-added as super admin regardless)\n const creatorAddress = getAccount().address;\n const result = execXmtpJson<{ id?: string; conversationId?: string; groupId?: string }>(\n [\n \"conversations\",\n \"create-group\",\n creatorAddress,\n \"--name\",\n subdomain,\n \"--description\",\n `Sherwood syndicate: ${subdomain}.sherwoodagent.eth`,\n \"--permissions\",\n \"admin-only\",\n ],\n );\n\n const groupId = result.id || result.conversationId || result.groupId;\n if (!groupId) {\n throw new Error(\"Failed to parse group ID from xmtp CLI output\");\n }\n\n // Add spectator if requested — enables dashboard live feed\n const SPECTATOR_ADDRESS = process.env.DASHBOARD_SPECTATOR_ADDRESS || \"0x9f6518e69a62c526ead155ad2661f5957b6b2fc3\";\n if (isPublic) {\n await addMember(groupId, SPECTATOR_ADDRESS);\n }\n\n // Cache locally\n cacheGroupId(subdomain, groupId);\n\n return groupId;\n}\n\n// ── Group Lookup ──\n\n/**\n * Check if a conversation exists in the local XMTP DB.\n * Returns true if `conversations get <id>` succeeds.\n */\nfunction conversationExists(groupId: string): boolean {\n try {\n execXmtp([\"conversations\", \"get\", groupId]);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Search synced conversations for a group matching the syndicate name.\n * Falls back to this when the cached/ENS group ID is stale.\n */\nfunction findGroupByName(subdomain: string): string | null {\n try {\n const conversations = execXmtpJson<Array<{ id?: string; name?: string; description?: string }>>(\n [\"conversations\", \"list\", \"--type\", \"group\"],\n );\n if (!Array.isArray(conversations)) return null;\n\n const match = conversations.find(\n (c) => c.name === subdomain || c.description?.includes(`${subdomain}.sherwoodagent.eth`),\n );\n return match?.id || null;\n } catch {\n return null;\n }\n}\n\nexport async function getGroup(\n _client: string,\n subdomain: string,\n): Promise<string> {\n // Sync first so we have the latest conversations locally\n syncConversations();\n\n // Try local cache\n let groupId: string | undefined = getCachedGroupId(subdomain);\n\n // Validate cached ID actually exists in the local DB\n if (groupId && !conversationExists(groupId)) {\n cacheGroupId(subdomain, \"\"); // invalidate stale entry\n groupId = undefined;\n }\n\n // Fall back to on-chain ENS text record\n if (!groupId) {\n const ensId = await getTextRecord(subdomain, \"xmtpGroupId\");\n if (ensId && conversationExists(ensId)) {\n groupId = ensId;\n cacheGroupId(subdomain, groupId);\n }\n }\n\n // Last resort: search synced conversations by name\n if (!groupId) {\n const found = findGroupByName(subdomain);\n if (found) {\n groupId = found;\n cacheGroupId(subdomain, groupId);\n }\n }\n\n if (!groupId) {\n throw new Error(\n `No XMTP group found for syndicate \"${subdomain}\". Run \"sherwood chat ${subdomain} init\" to create one.`,\n );\n }\n\n return groupId;\n}\n\n// ── Member Management ──\n\nexport async function addMember(\n groupId: string,\n address: string,\n): Promise<void> {\n // Verify the target has an active XMTP identity before adding\n const reachable = execXmtpJson<Array<{ identifier: string; reachable: boolean }>>(\n [\"can-message\", address],\n );\n if (!reachable?.[0]?.reachable) {\n throw new Error(\n `${address} is not reachable on XMTP. They need to initialize their client first (run: xmtp client info --env ${getXmtpEnv()}).`,\n );\n }\n\n syncConversations();\n execXmtp([\"conversation\", \"add-members\", groupId, address]);\n\n // Set consent to \"allowed\" on the adder's side so subsequent operations\n // (send, messages) don't skip this conversation due to unknown consent state\n try {\n execXmtp([\"conversation\", \"update-consent\", groupId, \"--state\", \"allowed\"]);\n } catch {\n // Non-fatal — consent state is a filtering concern, not a blocker\n }\n}\n\nexport async function removeMember(\n groupId: string,\n address: string,\n): Promise<void> {\n syncConversations();\n execXmtp([\"conversation\", \"remove-members\", groupId, address]);\n}\n\n// ── Messaging ──\n\nexport async function sendEnvelope(\n groupId: string,\n envelope: ChatEnvelope,\n): Promise<void> {\n const text = JSON.stringify(envelope);\n execXmtp([\"conversation\", \"send-text\", groupId, text]);\n}\n\nexport async function sendMarkdown(\n groupId: string,\n markdown: string,\n): Promise<void> {\n const envelope: ChatEnvelope = {\n type: \"MESSAGE\",\n from: getAccount().address,\n text: markdown,\n data: { format: \"markdown\" },\n timestamp: Math.floor(Date.now() / 1000),\n };\n await sendEnvelope(groupId, envelope);\n}\n\nexport async function sendReaction(\n groupId: string,\n messageId: string,\n emoji: string,\n): Promise<void> {\n const envelope: ChatEnvelope = {\n type: \"REACTION\",\n from: getAccount().address,\n data: { reference: messageId, emoji },\n timestamp: Math.floor(Date.now() / 1000),\n };\n await sendEnvelope(groupId, envelope);\n}\n\n// ── Streaming ──\n\nexport async function streamMessages(\n groupId: string,\n onMessage: (msg: XmtpMessage) => void,\n): Promise<() => void> {\n ensureXmtpEnv();\n const bin = getXmtpBinaryPath();\n\n const args = [\n \"conversations\",\n \"stream-all-messages\",\n \"--json\",\n \"--log-level\",\n \"off\",\n \"--env\",\n getXmtpEnv(),\n \"--env-file\",\n getXmtpEnvFile(),\n ];\n\n const proc = bin.endsWith(\".js\")\n ? spawn(\"node\", [bin, ...args])\n : spawn(bin, args);\n\n let buffer = \"\";\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString();\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const msg = JSON.parse(line);\n // Filter to our group\n if (msg.conversationId === groupId) {\n onMessage({\n id: msg.id || \"\",\n conversationId: msg.conversationId || \"\",\n senderInboxId: msg.senderInboxId || \"\",\n contentType: msg.contentType?.typeId || \"text\",\n content: typeof msg.content === \"string\" ? msg.content : JSON.stringify(msg.content),\n sentAt: new Date(msg.sentAt || Date.now()),\n });\n }\n } catch {\n // Skip unparseable lines\n }\n }\n });\n\n // Return cleanup function\n return () => {\n proc.kill(\"SIGTERM\");\n };\n}\n\n// ── Message History ──\n\nexport async function getRecentMessages(\n groupId: string,\n limit: number = 20,\n): Promise<XmtpMessage[]> {\n const raw = execXmtpJson<Array<Record<string, unknown>>>([\n \"conversation\",\n \"messages\",\n groupId,\n ]);\n\n const messages: XmtpMessage[] = (Array.isArray(raw) ? raw : []).map((m) => ({\n id: String(m.id || \"\"),\n conversationId: String(m.conversationId || \"\"),\n senderInboxId: String(m.senderInboxId || \"\"),\n contentType: String(\n (m.contentType as Record<string, unknown>)?.typeId || \"text\",\n ),\n content:\n typeof m.content === \"string\" ? m.content : JSON.stringify(m.content),\n sentAt: new Date((m.sentAt as string) || Date.now()),\n }));\n\n return messages.slice(-limit);\n}\n\n// ── Members ──\n\nexport async function getMembers(\n groupId: string,\n): Promise<XmtpMember[]> {\n const raw = execXmtpJson<Array<Record<string, unknown>>>([\n \"conversation\",\n \"members\",\n groupId,\n ]);\n\n // permissionLevel from CLI: 0 = member, 1 = admin, 2 = super_admin\n const levelMap: Record<number, string> = { 0: \"member\", 1: \"admin\", 2: \"super_admin\" };\n return (Array.isArray(raw) ? raw : []).map((m) => ({\n inboxId: String(m.inboxId || \"\"),\n permissionLevel: levelMap[Number(m.permissionLevel)] || \"member\",\n }));\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sherwoodagent/cli",
3
- "version": "0.14.1",
3
+ "version": "0.14.2",
4
4
  "description": "CLI for agent-managed investment syndicates — onchain DeFi syndicates with XMTP chat",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/eas.ts"],"sourcesContent":["/**\n * EAS (Ethereum Attestation Service) wrapper for syndicate join requests and approvals.\n *\n * Uses viem for on-chain writes and the EAS GraphQL API for queries.\n * No ethers dependency — attestation data is encoded with viem's encodeAbiParameters.\n */\n\nimport type { Address, Hex } from \"viem\";\nimport { encodeAbiParameters, parseAbiParameters, decodeAbiParameters } from \"viem\";\nimport { getPublicClient, getWalletClient, getAccount } from \"./client.js\";\nimport { getChain, getNetwork, getChainConfig } from \"./network.js\";\nimport { EAS_CONTRACTS, EAS_SCHEMAS } from \"./addresses.js\";\nimport { EAS_ABI } from \"./abis.js\";\n\n// ── Schema definitions ──\n\nconst ZERO_BYTES32 = \"0x0000000000000000000000000000000000000000000000000000000000000000\" as Hex;\n\nconst JOIN_REQUEST_PARAMS = parseAbiParameters(\"uint256, uint256, address, string\");\nconst AGENT_APPROVED_PARAMS = parseAbiParameters(\"uint256, uint256, address\");\nconst X402_RESEARCH_PARAMS = parseAbiParameters(\"string, string, string, string, string\");\n\nfunction assertSchemasRegistered() {\n const schemas = EAS_SCHEMAS();\n if (schemas.SYNDICATE_JOIN_REQUEST === ZERO_BYTES32 || schemas.AGENT_APPROVED === ZERO_BYTES32) {\n throw new Error(\n \"EAS schemas not registered. Run: npx tsx scripts/register-eas-schemas.ts --testnet\",\n );\n }\n}\n\n// ── GraphQL ──\n\nfunction getEasGraphqlUrl(): string {\n const url = getChainConfig().easGraphqlUrl;\n if (!url) {\n throw new Error(\n `EAS is not available on ${getNetwork()}. Attestation operations require a chain with EAS (e.g. base, base-sepolia).`,\n );\n }\n return url;\n}\n\nexport function getEasScanUrl(uid: Hex): string {\n const host = getChainConfig().easScanHost;\n if (!host) {\n throw new Error(`EAS scan is not available on ${getNetwork()}.`);\n }\n return `https://${host}/attestation/view/${uid}`;\n}\n\n// ── Attestation Creation ──\n\n/**\n * Extract the attestation UID from a transaction receipt.\n * The EAS contract emits: event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID)\n * uid is a non-indexed parameter in the event data.\n */\nfunction extractAttestationUid(receipt: { logs: readonly { topics: readonly Hex[]; data: Hex }[] }): Hex {\n for (const log of receipt.logs) {\n // Attested event has 4 topics (sig + 3 indexed) and data contains the uid (bytes32)\n if (log.topics.length === 4 && log.data.length >= 66) {\n return (\"0x\" + log.data.slice(2, 66)) as Hex;\n }\n }\n throw new Error(\"Could not extract attestation UID from transaction receipt\");\n}\n\n/**\n * Create a SYNDICATE_JOIN_REQUEST attestation.\n * Attester: the calling agent. Recipient: the syndicate creator.\n */\nexport async function createJoinRequest(\n syndicateId: bigint,\n agentId: bigint,\n vault: Address,\n creatorAddress: Address,\n message: string,\n): Promise<{ uid: Hex; hash: Hex }> {\n assertSchemasRegistered();\n const wallet = getWalletClient();\n const client = getPublicClient();\n\n const data = encodeAbiParameters(JOIN_REQUEST_PARAMS, [\n syndicateId, agentId, vault, message,\n ]);\n\n const hash = await wallet.writeContract({\n account: getAccount(),\n chain: getChain(),\n address: EAS_CONTRACTS().EAS,\n abi: EAS_ABI,\n functionName: \"attest\",\n args: [{\n schema: EAS_SCHEMAS().SYNDICATE_JOIN_REQUEST,\n data: {\n recipient: creatorAddress,\n expirationTime: 0n,\n revocable: true,\n refUID: ZERO_BYTES32,\n data,\n value: 0n,\n },\n }],\n value: 0n,\n });\n\n const receipt = await client.waitForTransactionReceipt({ hash });\n const uid = extractAttestationUid(receipt);\n\n return { uid, hash };\n}\n\n/**\n * Create an AGENT_APPROVED attestation.\n * Attester: the syndicate creator. Recipient: the agent wallet.\n */\nexport async function createApproval(\n syndicateId: bigint,\n agentId: bigint,\n vault: Address,\n agentAddress: Address,\n): Promise<{ uid: Hex; hash: Hex }> {\n assertSchemasRegistered();\n const wallet = getWalletClient();\n const client = getPublicClient();\n\n const data = encodeAbiParameters(AGENT_APPROVED_PARAMS, [\n syndicateId, agentId, vault,\n ]);\n\n const hash = await wallet.writeContract({\n account: getAccount(),\n chain: getChain(),\n address: EAS_CONTRACTS().EAS,\n abi: EAS_ABI,\n functionName: \"attest\",\n args: [{\n schema: EAS_SCHEMAS().AGENT_APPROVED,\n data: {\n recipient: agentAddress,\n expirationTime: 0n,\n revocable: true,\n refUID: ZERO_BYTES32,\n data,\n value: 0n,\n },\n }],\n value: 0n,\n });\n\n const receipt = await client.waitForTransactionReceipt({ hash });\n const uid = extractAttestationUid(receipt);\n\n return { uid, hash };\n}\n\n/**\n * Revoke an attestation. Only the original attester can revoke.\n */\nexport async function revokeAttestation(\n schemaUid: Hex,\n attestationUid: Hex,\n): Promise<Hex> {\n const wallet = getWalletClient();\n\n return wallet.writeContract({\n account: getAccount(),\n chain: getChain(),\n address: EAS_CONTRACTS().EAS,\n abi: EAS_ABI,\n functionName: \"revoke\",\n args: [{\n schema: schemaUid,\n data: {\n uid: attestationUid,\n value: 0n,\n },\n }],\n value: 0n,\n });\n}\n\n/**\n * Create an X402_RESEARCH attestation — records a research query on-chain.\n * Attester: the agent. Recipient: the agent itself (self-attestation for audit trail).\n * Schema: \"string provider, string queryType, string prompt, string costUsdc, string resultUri\"\n */\nexport async function createResearchAttestation(\n provider: string,\n queryType: string,\n prompt: string,\n costUsdc: string,\n resultUri: string,\n): Promise<{ uid: Hex; hash: Hex }> {\n const schemas = EAS_SCHEMAS();\n if (schemas.X402_RESEARCH === ZERO_BYTES32) {\n throw new Error(\n \"X402_RESEARCH schema not registered. Run: npx tsx scripts/register-eas-schemas.ts --testnet\",\n );\n }\n\n const wallet = getWalletClient();\n const client = getPublicClient();\n const account = getAccount();\n\n const data = encodeAbiParameters(X402_RESEARCH_PARAMS, [\n provider, queryType, prompt, costUsdc, resultUri,\n ]);\n\n const hash = await wallet.writeContract({\n account,\n chain: getChain(),\n address: EAS_CONTRACTS().EAS,\n abi: EAS_ABI,\n functionName: \"attest\",\n args: [{\n schema: schemas.X402_RESEARCH,\n data: {\n recipient: account.address,\n expirationTime: 0n,\n revocable: false,\n refUID: ZERO_BYTES32,\n data,\n value: 0n,\n },\n }],\n value: 0n,\n });\n\n const receipt = await client.waitForTransactionReceipt({ hash });\n const uid = extractAttestationUid(receipt);\n\n return { uid, hash };\n}\n\n// ── Attestation Queries ──\n\nexport interface JoinRequestAttestation {\n uid: Hex;\n attester: Address;\n recipient: Address;\n time: number;\n decoded: {\n syndicateId: bigint;\n agentId: bigint;\n vault: Address;\n message: string;\n };\n}\n\n/**\n * Query pending (non-revoked) join requests for a given recipient (creator address).\n * Uses the EAS GraphQL API.\n */\nexport interface ApprovalAttestation {\n uid: Hex;\n attester: Address;\n recipient: Address;\n time: number;\n decoded: {\n syndicateId: bigint;\n agentId: bigint;\n vault: Address;\n };\n}\n\n/**\n * Query existing (non-revoked) AGENT_APPROVED attestations created by a given attester (creator).\n * Used to check for duplicates before creating a new approval and to filter already-approved agents from requests.\n */\nexport async function queryApprovals(\n attester: Address,\n): Promise<ApprovalAttestation[]> {\n assertSchemasRegistered();\n const schemaUid = EAS_SCHEMAS().AGENT_APPROVED;\n const url = getEasGraphqlUrl();\n\n const query = `\n query Approvals($schemaId: String!, $attester: String!) {\n attestations(\n where: {\n schemaId: { equals: $schemaId }\n attester: { equals: $attester }\n revoked: { equals: false }\n }\n orderBy: [{ time: desc }]\n ) {\n id\n attester\n recipient\n time\n data\n }\n }\n `;\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n query,\n variables: { schemaId: schemaUid, attester },\n }),\n });\n\n if (!response.ok) {\n throw new Error(`EAS GraphQL query failed: ${response.statusText}`);\n }\n\n const json = await response.json() as {\n data?: {\n attestations: Array<{\n id: string;\n attester: string;\n recipient: string;\n time: number;\n data: string;\n }>;\n };\n };\n\n if (!json.data?.attestations) return [];\n\n return json.data.attestations.map((a) => {\n const decoded = decodeAbiParameters(AGENT_APPROVED_PARAMS, a.data as Hex);\n return {\n uid: a.id as Hex,\n attester: a.attester as Address,\n recipient: a.recipient as Address,\n time: a.time,\n decoded: {\n syndicateId: decoded[0],\n agentId: decoded[1],\n vault: decoded[2],\n },\n };\n });\n}\n\nexport async function queryJoinRequests(\n recipient: Address,\n): Promise<JoinRequestAttestation[]> {\n assertSchemasRegistered();\n const schemaUid = EAS_SCHEMAS().SYNDICATE_JOIN_REQUEST;\n const url = getEasGraphqlUrl();\n\n const query = `\n query JoinRequests($schemaId: String!, $recipient: String!) {\n attestations(\n where: {\n schemaId: { equals: $schemaId }\n recipient: { equals: $recipient }\n revoked: { equals: false }\n }\n orderBy: [{ time: desc }]\n ) {\n id\n attester\n recipient\n time\n data\n }\n }\n `;\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n query,\n variables: { schemaId: schemaUid, recipient },\n }),\n });\n\n if (!response.ok) {\n throw new Error(`EAS GraphQL query failed: ${response.statusText}`);\n }\n\n const json = await response.json() as {\n data?: {\n attestations: Array<{\n id: string;\n attester: string;\n recipient: string;\n time: number;\n data: string;\n }>;\n };\n };\n\n if (!json.data?.attestations) return [];\n\n return json.data.attestations.map((a) => {\n const decoded = decodeAbiParameters(JOIN_REQUEST_PARAMS, a.data as Hex);\n return {\n uid: a.id as Hex,\n attester: a.attester as Address,\n recipient: a.recipient as Address,\n time: a.time,\n decoded: {\n syndicateId: decoded[0],\n agentId: decoded[1],\n vault: decoded[2],\n message: decoded[3],\n },\n };\n });\n}\n"],"mappings":"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","names":[]}