@sherwoodagent/cli 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ import {
6
6
  getTextRecord,
7
7
  loadConfig,
8
8
  saveConfig
9
- } from "./chunk-CR3ZNOB7.js";
9
+ } from "./chunk-I2G5OWKA.js";
10
10
 
11
11
  // src/lib/xmtp.ts
12
12
  import { execFileSync, spawn, execSync } from "child_process";
@@ -95,6 +95,12 @@ function execXmtpJson(args) {
95
95
  const stdout = execXmtp([...args, "--json", "--log-level", "off"]);
96
96
  return JSON.parse(stdout);
97
97
  }
98
+ var _conversationsSynced = false;
99
+ function syncConversations() {
100
+ if (_conversationsSynced) return;
101
+ execXmtp(["conversations", "sync"]);
102
+ _conversationsSynced = true;
103
+ }
98
104
  async function getXmtpClient() {
99
105
  syncXmtpEnv();
100
106
  const result = execXmtpJson(["client", "info"]);
@@ -146,12 +152,15 @@ async function getGroup(_client, subdomain) {
146
152
  return groupId;
147
153
  }
148
154
  async function addMember(groupId, address) {
155
+ syncConversations();
149
156
  execXmtp(["conversation", "add-members", groupId, address]);
150
157
  }
151
158
  async function removeMember(groupId, address) {
159
+ syncConversations();
152
160
  execXmtp(["conversation", "remove-members", groupId, address]);
153
161
  }
154
162
  async function sendEnvelope(groupId, envelope) {
163
+ syncConversations();
155
164
  const text = JSON.stringify(envelope);
156
165
  execXmtp(["conversation", "send-text", groupId, text]);
157
166
  }
@@ -217,6 +226,7 @@ async function streamMessages(groupId, onMessage) {
217
226
  };
218
227
  }
219
228
  async function getRecentMessages(groupId, limit = 20) {
229
+ syncConversations();
220
230
  const raw = execXmtpJson([
221
231
  "conversation",
222
232
  "messages",
@@ -235,6 +245,7 @@ async function getRecentMessages(groupId, limit = 20) {
235
245
  return messages.slice(-limit);
236
246
  }
237
247
  async function getMembers(groupId) {
248
+ syncConversations();
238
249
  const raw = execXmtpJson([
239
250
  "conversation",
240
251
  "members",
@@ -259,4 +270,4 @@ export {
259
270
  sendReaction,
260
271
  streamMessages
261
272
  };
262
- //# sourceMappingURL=xmtp-4G7BTQZJ.js.map
273
+ //# sourceMappingURL=xmtp-6TFVTFOO.js.map
@@ -0,0 +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\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 { getNetwork } 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 sync ──\n\nlet _synced = false;\n\nfunction syncXmtpEnv(): void {\n if (_synced) 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 xmtpDir = path.join(homedir(), \".xmtp\");\n const envFile = path.join(xmtpDir, \".env\");\n const walletKey = config.privateKey.replace(/^0x/, \"\");\n\n // Check if already synced\n if (fs.existsSync(envFile)) {\n const existing = fs.readFileSync(envFile, \"utf8\");\n if (existing.includes(`XMTP_WALLET_KEY=${walletKey}`)) {\n _synced = true;\n return;\n }\n }\n\n // Write env file\n fs.mkdirSync(xmtpDir, { recursive: true });\n const content = [\n `XMTP_WALLET_KEY=${walletKey}`,\n `XMTP_DB_ENCRYPTION_KEY=${config.dbEncryptionKey}`,\n `XMTP_ENV=${getXmtpEnv()}`,\n ].join(\"\\n\");\n fs.writeFileSync(envFile, content, { mode: 0o600 });\n\n _synced = true;\n}\n\nfunction getXmtpEnv(): string {\n return getNetwork() === \"base\" ? \"production\" : \"dev\";\n}\n\n// ── Subprocess runners ──\n\nfunction getXmtpEnvFile(): string {\n return path.join(homedir(), \".xmtp\", \".env\");\n}\n\nfunction execXmtp(args: string[]): string {\n syncXmtpEnv();\n const bin = getXmtpBinaryPath();\n const fullArgs = [...args, \"--env\", getXmtpEnv(), \"--env-file\", getXmtpEnvFile()];\n\n // Use node to run the bin/run.js if it's a .js file\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 * Only runs once per process.\n */\nfunction syncConversations(): void {\n if (_conversationsSynced) return;\n execXmtp([\"conversations\", \"sync\"]);\n _conversationsSynced = true;\n}\n\n// ── Client ──\n\nexport async function getXmtpClient(): Promise<string> {\n syncXmtpEnv();\n\n const result = execXmtpJson<{ inboxId: string }>([\"client\", \"info\"]);\n\n // Cache inbox ID\n const config = loadConfig();\n if (!config.xmtpInboxId && result.inboxId) {\n config.xmtpInboxId = result.inboxId;\n saveConfig(config);\n }\n\n return result.inboxId;\n}\n\n// ── Group Creation ──\n\nexport async function createSyndicateGroup(\n _client: string,\n subdomain: string,\n publicChat: 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\n if (publicChat && process.env.DASHBOARD_SPECTATOR_ADDRESS) {\n await addMember(groupId, process.env.DASHBOARD_SPECTATOR_ADDRESS);\n }\n\n // Cache locally\n cacheGroupId(subdomain, groupId);\n\n return groupId;\n}\n\n// ── Group Lookup ──\n\nexport async function getGroup(\n _client: string,\n subdomain: string,\n): Promise<string> {\n // Try local cache first\n let groupId = getCachedGroupId(subdomain);\n\n // Fall back to on-chain ENS text record\n if (!groupId) {\n groupId = await getTextRecord(subdomain, \"xmtpGroupId\");\n if (groupId) {\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 syncConversations();\n execXmtp([\"conversation\", \"add-members\", groupId, address]);\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 syncConversations();\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 syncXmtpEnv();\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 syncConversations();\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 syncConversations();\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.3.0",
3
+ "version": "0.3.1",
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/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\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 { getNetwork } 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 sync ──\n\nlet _synced = false;\n\nfunction syncXmtpEnv(): void {\n if (_synced) 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 xmtpDir = path.join(homedir(), \".xmtp\");\n const envFile = path.join(xmtpDir, \".env\");\n const walletKey = config.privateKey.replace(/^0x/, \"\");\n\n // Check if already synced\n if (fs.existsSync(envFile)) {\n const existing = fs.readFileSync(envFile, \"utf8\");\n if (existing.includes(`XMTP_WALLET_KEY=${walletKey}`)) {\n _synced = true;\n return;\n }\n }\n\n // Write env file\n fs.mkdirSync(xmtpDir, { recursive: true });\n const content = [\n `XMTP_WALLET_KEY=${walletKey}`,\n `XMTP_DB_ENCRYPTION_KEY=${config.dbEncryptionKey}`,\n `XMTP_ENV=${getXmtpEnv()}`,\n ].join(\"\\n\");\n fs.writeFileSync(envFile, content, { mode: 0o600 });\n\n _synced = true;\n}\n\nfunction getXmtpEnv(): string {\n return getNetwork() === \"base\" ? \"production\" : \"dev\";\n}\n\n// ── Subprocess runners ──\n\nfunction getXmtpEnvFile(): string {\n return path.join(homedir(), \".xmtp\", \".env\");\n}\n\nfunction execXmtp(args: string[]): string {\n syncXmtpEnv();\n const bin = getXmtpBinaryPath();\n const fullArgs = [...args, \"--env\", getXmtpEnv(), \"--env-file\", getXmtpEnvFile()];\n\n // Use node to run the bin/run.js if it's a .js file\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// ── Client ──\n\nexport async function getXmtpClient(): Promise<string> {\n syncXmtpEnv();\n\n const result = execXmtpJson<{ inboxId: string }>([\"client\", \"info\"]);\n\n // Cache inbox ID\n const config = loadConfig();\n if (!config.xmtpInboxId && result.inboxId) {\n config.xmtpInboxId = result.inboxId;\n saveConfig(config);\n }\n\n return result.inboxId;\n}\n\n// ── Group Creation ──\n\nexport async function createSyndicateGroup(\n _client: string,\n subdomain: string,\n publicChat: 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\n if (publicChat && process.env.DASHBOARD_SPECTATOR_ADDRESS) {\n await addMember(groupId, process.env.DASHBOARD_SPECTATOR_ADDRESS);\n }\n\n // Cache locally\n cacheGroupId(subdomain, groupId);\n\n return groupId;\n}\n\n// ── Group Lookup ──\n\nexport async function getGroup(\n _client: string,\n subdomain: string,\n): Promise<string> {\n // Try local cache first\n let groupId = getCachedGroupId(subdomain);\n\n // Fall back to on-chain ENS text record\n if (!groupId) {\n groupId = await getTextRecord(subdomain, \"xmtpGroupId\");\n if (groupId) {\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 execXmtp([\"conversation\", \"add-members\", groupId, address]);\n}\n\nexport async function removeMember(\n groupId: string,\n address: string,\n): Promise<void> {\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 syncXmtpEnv();\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":[]}