@sherwoodagent/cli 0.3.4 → 0.3.5

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.
@@ -11,7 +11,7 @@ import {
11
11
  import chalk from "chalk";
12
12
  import ora from "ora";
13
13
  async function loadXmtp() {
14
- return import("./xmtp-B34I2M7Q.js");
14
+ return import("./xmtp-AECBOBIJ.js");
15
15
  }
16
16
  function formatTimestamp(date) {
17
17
  return `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}`;
@@ -313,4 +313,4 @@ function registerChatCommands(program) {
313
313
  export {
314
314
  registerChatCommands
315
315
  };
316
- //# sourceMappingURL=chat-WDP74WU2.js.map
316
+ //# sourceMappingURL=chat-R7NCXOW7.js.map
package/dist/index.js CHANGED
@@ -1869,7 +1869,7 @@ try {
1869
1869
  } catch {
1870
1870
  }
1871
1871
  async function loadXmtp() {
1872
- return import("./xmtp-B34I2M7Q.js");
1872
+ return import("./xmtp-AECBOBIJ.js");
1873
1873
  }
1874
1874
  var G = chalk5.green;
1875
1875
  var W = chalk5.white;
@@ -2706,7 +2706,7 @@ ${info.name} (${info.type})`);
2706
2706
  }
2707
2707
  });
2708
2708
  try {
2709
- const { registerChatCommands } = await import("./chat-WDP74WU2.js");
2709
+ const { registerChatCommands } = await import("./chat-R7NCXOW7.js");
2710
2710
  registerChatCommands(program);
2711
2711
  } catch {
2712
2712
  program.command("chat <name> [action] [actionArgs...]").description("Syndicate chat (XMTP) \u2014 requires @xmtp/cli").action(() => {
@@ -42,41 +42,66 @@ function getXmtpBinaryPath() {
42
42
  "XMTP CLI not found. Install with: npm install -g @xmtp/cli"
43
43
  );
44
44
  }
45
- var _synced = false;
46
- function syncXmtpEnv() {
47
- if (_synced) return;
45
+ function getXmtpEnv() {
46
+ return getNetwork() === "base" ? "production" : "dev";
47
+ }
48
+ function getXmtpEnvFile() {
49
+ return path.join(homedir(), ".xmtp", ".env");
50
+ }
51
+ var _envReady = false;
52
+ function ensureXmtpEnv() {
53
+ if (_envReady) return;
48
54
  const config = loadConfig();
49
55
  if (!config.privateKey) {
50
56
  throw new Error(
51
57
  'No private key configured. Run "sherwood config set --private-key 0x..."'
52
58
  );
53
59
  }
54
- const xmtpDir = path.join(homedir(), ".xmtp");
55
- const envFile = path.join(xmtpDir, ".env");
60
+ const envFile = getXmtpEnvFile();
56
61
  const walletKey = config.privateKey.replace(/^0x/, "");
57
62
  if (fs.existsSync(envFile)) {
58
63
  const existing = fs.readFileSync(envFile, "utf8");
59
64
  if (existing.includes(`XMTP_WALLET_KEY=${walletKey}`)) {
60
- _synced = true;
65
+ _envReady = true;
61
66
  return;
62
67
  }
63
68
  const lines = existing.split("\n").filter((l) => !l.startsWith("XMTP_WALLET_KEY="));
64
69
  lines.push(`XMTP_WALLET_KEY=${walletKey}`);
65
70
  fs.writeFileSync(envFile, lines.filter(Boolean).join("\n") + "\n", { mode: 384 });
66
71
  } else {
72
+ const xmtpDir = path.join(homedir(), ".xmtp");
67
73
  fs.mkdirSync(xmtpDir, { recursive: true });
68
- fs.writeFileSync(envFile, `XMTP_WALLET_KEY=${walletKey}
74
+ const bin = getXmtpBinaryPath();
75
+ const initArgs = ["init", "--env", getXmtpEnv()];
76
+ if (bin.endsWith(".js")) {
77
+ execFileSync("node", [bin, ...initArgs], {
78
+ encoding: "utf8",
79
+ timeout: 3e4,
80
+ env: { ...process.env, XMTP_WALLET_KEY: walletKey }
81
+ });
82
+ } else {
83
+ execFileSync(bin, initArgs, {
84
+ encoding: "utf8",
85
+ timeout: 3e4,
86
+ env: { ...process.env, XMTP_WALLET_KEY: walletKey }
87
+ });
88
+ }
89
+ if (fs.existsSync(envFile)) {
90
+ const content = fs.readFileSync(envFile, "utf8");
91
+ const lines = content.split("\n").filter((l) => !l.startsWith("XMTP_WALLET_KEY="));
92
+ lines.push(`XMTP_WALLET_KEY=${walletKey}`);
93
+ fs.writeFileSync(envFile, lines.filter(Boolean).join("\n") + "\n", { mode: 384 });
94
+ } else {
95
+ fs.writeFileSync(envFile, `XMTP_WALLET_KEY=${walletKey}
69
96
  `, { mode: 384 });
97
+ }
70
98
  }
71
- _synced = true;
72
- }
73
- function getXmtpEnv() {
74
- return getNetwork() === "base" ? "production" : "dev";
99
+ _envReady = true;
75
100
  }
76
101
  function execXmtp(args) {
77
- syncXmtpEnv();
102
+ ensureXmtpEnv();
78
103
  const bin = getXmtpBinaryPath();
79
- const fullArgs = [...args, "--env", getXmtpEnv()];
104
+ const fullArgs = [...args, "--env", getXmtpEnv(), "--env-file", getXmtpEnvFile()];
80
105
  if (bin.endsWith(".js")) {
81
106
  return execFileSync("node", [bin, ...fullArgs], {
82
107
  encoding: "utf8",
@@ -98,15 +123,35 @@ function syncConversations() {
98
123
  execXmtp(["conversations", "sync"]);
99
124
  _conversationsSynced = true;
100
125
  }
126
+ function revokeStaleInstallations(inboxId, currentInstallationId) {
127
+ try {
128
+ const inboxStates = execXmtpJson(["inbox-states", inboxId]);
129
+ const state = inboxStates?.[0];
130
+ if (!state?.installations || state.installations.length <= 1) return;
131
+ const staleIds = state.installations.map((i) => i.id).filter((id) => id !== currentInstallationId);
132
+ if (staleIds.length === 0) return;
133
+ execXmtp([
134
+ "revoke-installations",
135
+ inboxId,
136
+ "-i",
137
+ staleIds.join(","),
138
+ "--force"
139
+ ]);
140
+ } catch {
141
+ }
142
+ }
101
143
  async function getXmtpClient() {
102
- syncXmtpEnv();
103
144
  const result = execXmtpJson(["client", "info"]);
145
+ const { inboxId, installationId } = result.properties;
104
146
  const config = loadConfig();
105
- if (!config.xmtpInboxId && result.inboxId) {
106
- config.xmtpInboxId = result.inboxId;
147
+ if (!config.xmtpInboxId && inboxId) {
148
+ config.xmtpInboxId = inboxId;
107
149
  saveConfig(config);
108
150
  }
109
- return result.inboxId;
151
+ if (inboxId && installationId) {
152
+ revokeStaleInstallations(inboxId, installationId);
153
+ }
154
+ return inboxId;
110
155
  }
111
156
  async function createSyndicateGroup(_client, subdomain, publicChat = false) {
112
157
  const creatorAddress = getAccount().address;
@@ -181,7 +226,7 @@ async function sendReaction(groupId, messageId, emoji) {
181
226
  await sendEnvelope(groupId, envelope);
182
227
  }
183
228
  async function streamMessages(groupId, onMessage) {
184
- syncXmtpEnv();
229
+ ensureXmtpEnv();
185
230
  const bin = getXmtpBinaryPath();
186
231
  const args = [
187
232
  "conversations",
@@ -190,7 +235,9 @@ async function streamMessages(groupId, onMessage) {
190
235
  "--log-level",
191
236
  "off",
192
237
  "--env",
193
- getXmtpEnv()
238
+ getXmtpEnv(),
239
+ "--env-file",
240
+ getXmtpEnvFile()
194
241
  ];
195
242
  const proc = bin.endsWith(".js") ? spawn("node", [bin, ...args]) : spawn(bin, args);
196
243
  let buffer = "";
@@ -265,4 +312,4 @@ export {
265
312
  sendReaction,
266
313
  streamMessages
267
314
  };
268
- //# sourceMappingURL=xmtp-B34I2M7Q.js.map
315
+ //# sourceMappingURL=xmtp-AECBOBIJ.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 * 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 { 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 ──\n\nfunction getXmtpEnv(): string {\n return getNetwork() === \"base\" ? \"production\" : \"dev\";\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 * Only runs once per process.\n */\nfunction syncConversations(): void {\n if (_conversationsSynced) return;\n execXmtp([\"conversations\", \"sync\"]);\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 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 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 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.4",
3
+ "version": "0.3.5",
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 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 // Update wallet key while preserving all other env vars (e.g. XMTP_DB_ENCRYPTION_KEY)\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 existing env file — write wallet key only, XMTP CLI will manage its own DB key\n fs.mkdirSync(xmtpDir, { recursive: true });\n fs.writeFileSync(envFile, `XMTP_WALLET_KEY=${walletKey}\\n`, { mode: 0o600 });\n }\n\n _synced = true;\n}\n\nfunction getXmtpEnv(): string {\n return getNetwork() === \"base\" ? \"production\" : \"dev\";\n}\n\n// ── Subprocess runners ──\n\nfunction execXmtp(args: string[]): string {\n syncXmtpEnv();\n const bin = getXmtpBinaryPath();\n const fullArgs = [...args, \"--env\", getXmtpEnv()];\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 ];\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":[]}